mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2026-04-17 21:44:48 -04:00
Compare commits
1 Commits
v1.3.1.279
...
instance-n
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0be2aee9c3 |
@@ -117,6 +117,7 @@ dotnet_diagnostic.CA1003.severity = suggestion
|
|||||||
dotnet_diagnostic.CA1008.severity = suggestion
|
dotnet_diagnostic.CA1008.severity = suggestion
|
||||||
dotnet_diagnostic.CA1010.severity = suggestion
|
dotnet_diagnostic.CA1010.severity = suggestion
|
||||||
dotnet_diagnostic.CA1012.severity = suggestion
|
dotnet_diagnostic.CA1012.severity = suggestion
|
||||||
|
dotnet_diagnostic.CA1014.severity = suggestion
|
||||||
dotnet_diagnostic.CA1016.severity = suggestion
|
dotnet_diagnostic.CA1016.severity = suggestion
|
||||||
dotnet_diagnostic.CA1017.severity = suggestion
|
dotnet_diagnostic.CA1017.severity = suggestion
|
||||||
dotnet_diagnostic.CA1018.severity = suggestion
|
dotnet_diagnostic.CA1018.severity = suggestion
|
||||||
@@ -162,7 +163,6 @@ dotnet_diagnostic.CA1309.severity = suggestion
|
|||||||
dotnet_diagnostic.CA1310.severity = suggestion
|
dotnet_diagnostic.CA1310.severity = suggestion
|
||||||
dotnet_diagnostic.CA1401.severity = suggestion
|
dotnet_diagnostic.CA1401.severity = suggestion
|
||||||
dotnet_diagnostic.CA1416.severity = suggestion
|
dotnet_diagnostic.CA1416.severity = suggestion
|
||||||
dotnet_diagnostic.CA1419.severity = suggestion
|
|
||||||
dotnet_diagnostic.CA1507.severity = suggestion
|
dotnet_diagnostic.CA1507.severity = suggestion
|
||||||
dotnet_diagnostic.CA1508.severity = suggestion
|
dotnet_diagnostic.CA1508.severity = suggestion
|
||||||
dotnet_diagnostic.CA1707.severity = suggestion
|
dotnet_diagnostic.CA1707.severity = suggestion
|
||||||
@@ -178,6 +178,9 @@ dotnet_diagnostic.CA1720.severity = suggestion
|
|||||||
dotnet_diagnostic.CA1721.severity = suggestion
|
dotnet_diagnostic.CA1721.severity = suggestion
|
||||||
dotnet_diagnostic.CA1724.severity = suggestion
|
dotnet_diagnostic.CA1724.severity = suggestion
|
||||||
dotnet_diagnostic.CA1725.severity = suggestion
|
dotnet_diagnostic.CA1725.severity = suggestion
|
||||||
|
dotnet_diagnostic.CA1801.severity = suggestion
|
||||||
|
dotnet_diagnostic.CA1802.severity = suggestion
|
||||||
|
dotnet_diagnostic.CA1805.severity = suggestion
|
||||||
dotnet_diagnostic.CA1806.severity = suggestion
|
dotnet_diagnostic.CA1806.severity = suggestion
|
||||||
dotnet_diagnostic.CA1810.severity = suggestion
|
dotnet_diagnostic.CA1810.severity = suggestion
|
||||||
dotnet_diagnostic.CA1812.severity = suggestion
|
dotnet_diagnostic.CA1812.severity = suggestion
|
||||||
@@ -189,14 +192,13 @@ dotnet_diagnostic.CA1819.severity = suggestion
|
|||||||
dotnet_diagnostic.CA1822.severity = suggestion
|
dotnet_diagnostic.CA1822.severity = suggestion
|
||||||
dotnet_diagnostic.CA1823.severity = suggestion
|
dotnet_diagnostic.CA1823.severity = suggestion
|
||||||
dotnet_diagnostic.CA1824.severity = suggestion
|
dotnet_diagnostic.CA1824.severity = suggestion
|
||||||
dotnet_diagnostic.CA1835.severity = suggestion
|
|
||||||
dotnet_diagnostic.CA1845.severity = suggestion
|
|
||||||
dotnet_diagnostic.CA1848.severity = suggestion
|
|
||||||
dotnet_diagnostic.CA1849.severity = suggestion
|
|
||||||
dotnet_diagnostic.CA2000.severity = suggestion
|
dotnet_diagnostic.CA2000.severity = suggestion
|
||||||
dotnet_diagnostic.CA2002.severity = suggestion
|
dotnet_diagnostic.CA2002.severity = suggestion
|
||||||
dotnet_diagnostic.CA2007.severity = suggestion
|
dotnet_diagnostic.CA2007.severity = suggestion
|
||||||
dotnet_diagnostic.CA2008.severity = suggestion
|
dotnet_diagnostic.CA2008.severity = suggestion
|
||||||
|
dotnet_diagnostic.CA2009.severity = suggestion
|
||||||
|
dotnet_diagnostic.CA2010.severity = suggestion
|
||||||
|
dotnet_diagnostic.CA2011.severity = suggestion
|
||||||
dotnet_diagnostic.CA2012.severity = suggestion
|
dotnet_diagnostic.CA2012.severity = suggestion
|
||||||
dotnet_diagnostic.CA2013.severity = suggestion
|
dotnet_diagnostic.CA2013.severity = suggestion
|
||||||
dotnet_diagnostic.CA2100.severity = suggestion
|
dotnet_diagnostic.CA2100.severity = suggestion
|
||||||
@@ -227,7 +229,6 @@ dotnet_diagnostic.CA2243.severity = suggestion
|
|||||||
dotnet_diagnostic.CA2244.severity = suggestion
|
dotnet_diagnostic.CA2244.severity = suggestion
|
||||||
dotnet_diagnostic.CA2245.severity = suggestion
|
dotnet_diagnostic.CA2245.severity = suggestion
|
||||||
dotnet_diagnostic.CA2246.severity = suggestion
|
dotnet_diagnostic.CA2246.severity = suggestion
|
||||||
dotnet_diagnostic.CA2254.severity = suggestion
|
|
||||||
dotnet_diagnostic.CA3061.severity = suggestion
|
dotnet_diagnostic.CA3061.severity = suggestion
|
||||||
dotnet_diagnostic.CA3075.severity = suggestion
|
dotnet_diagnostic.CA3075.severity = suggestion
|
||||||
dotnet_diagnostic.CA3076.severity = suggestion
|
dotnet_diagnostic.CA3076.severity = suggestion
|
||||||
@@ -254,7 +255,6 @@ dotnet_diagnostic.CA5385.severity = suggestion
|
|||||||
dotnet_diagnostic.CA5392.severity = suggestion
|
dotnet_diagnostic.CA5392.severity = suggestion
|
||||||
dotnet_diagnostic.CA5394.severity = suggestion
|
dotnet_diagnostic.CA5394.severity = suggestion
|
||||||
dotnet_diagnostic.CA5397.severity = suggestion
|
dotnet_diagnostic.CA5397.severity = suggestion
|
||||||
dotnet_diagnostic.CA5401.severity = suggestion
|
|
||||||
|
|
||||||
dotnet_diagnostic.SYSLIB0014.severity = none
|
dotnet_diagnostic.SYSLIB0014.severity = none
|
||||||
|
|
||||||
|
|||||||
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -5,9 +5,9 @@ body:
|
|||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
label: Is there an existing issue for this?
|
label: Is there an existing issue for this?
|
||||||
description: Please search to see if an open or closed issue already exists for the bug you encountered. If a bug exists and is closed note that it may only be fixed in an unstable branch.
|
description: Please search to see if an issue already exists for the bug you encountered.
|
||||||
options:
|
options:
|
||||||
- label: I have searched the existing open and closed issues
|
- label: I have searched the existing issues
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
4
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
4
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -5,9 +5,9 @@ body:
|
|||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
label: Is there an existing issue for this?
|
label: Is there an existing issue for this?
|
||||||
description: Please search to see if an open or closed issue already exists for the feature you are requesting. If a request exists and is closed note that it may only be fixed in an unstable branch.
|
description: Please search to see if an issue already exists for the feature you are requesting.
|
||||||
options:
|
options:
|
||||||
- label: I have searched the existing open and closed issues
|
- label: I have searched the existing issues
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
41
.github/workflows/azuresync.yml
vendored
Normal file
41
.github/workflows/azuresync.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
name: Sync issue to Azure DevOps work item
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types:
|
||||||
|
[opened, edited, deleted, closed, reopened, labeled, unlabeled, assigned]
|
||||||
|
|
||||||
|
concurrency: azuresync-${{ github.event.issue.number }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
alert:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: danhellem/github-actions-issue-to-work-item@master
|
||||||
|
if: "${{ contains(github.event.issue.labels.*.name, 'Type: Bug') == true }}"
|
||||||
|
env:
|
||||||
|
ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}"
|
||||||
|
github_token: "${{ github.token }}"
|
||||||
|
ado_organization: "Servarr"
|
||||||
|
ado_project: "Servarr"
|
||||||
|
ado_area_path: "Servarr\\Prowlarr"
|
||||||
|
ado_wit: "Bug"
|
||||||
|
ado_new_state: "New"
|
||||||
|
ado_active_state: "Active"
|
||||||
|
ado_close_state: "Closed"
|
||||||
|
ado_bypassrules: true
|
||||||
|
log_level: 100
|
||||||
|
- uses: danhellem/github-actions-issue-to-work-item@master
|
||||||
|
if: "${{ contains(github.event.issue.labels.*.name, 'Type: Bug') == false }}"
|
||||||
|
env:
|
||||||
|
ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}"
|
||||||
|
github_token: "${{ github.token }}"
|
||||||
|
ado_organization: "Servarr"
|
||||||
|
ado_project: "Servarr"
|
||||||
|
ado_area_path: "Servarr\\Prowlarr"
|
||||||
|
ado_wit: "User Story"
|
||||||
|
ado_new_state: "New"
|
||||||
|
ado_active_state: "Active"
|
||||||
|
ado_close_state: "Closed"
|
||||||
|
ado_bypassrules: true
|
||||||
|
log_level: 100
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
# Contributor Covenant Code of Conduct
|
|
||||||
|
|
||||||
## Our Pledge
|
|
||||||
|
|
||||||
We as members, contributors, and leaders pledge to make participation in our
|
|
||||||
community a harassment-free experience for everyone, regardless of age, body
|
|
||||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
||||||
identity and expression, level of experience, education, socio-economic status,
|
|
||||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
|
||||||
identity and orientation.
|
|
||||||
|
|
||||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
||||||
diverse, inclusive, and healthy community.
|
|
||||||
|
|
||||||
## Our Standards
|
|
||||||
|
|
||||||
Examples of behavior that contributes to a positive environment for our
|
|
||||||
community include:
|
|
||||||
|
|
||||||
* Demonstrating empathy and kindness toward other people
|
|
||||||
* Being respectful of differing opinions, viewpoints, and experiences
|
|
||||||
* Giving and gracefully accepting constructive feedback
|
|
||||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
|
||||||
and learning from the experience
|
|
||||||
* Focusing on what is best not just for us as individuals, but for the overall
|
|
||||||
community
|
|
||||||
|
|
||||||
Examples of unacceptable behavior include:
|
|
||||||
|
|
||||||
* The use of sexualized language or imagery, and sexual attention or advances of
|
|
||||||
any kind
|
|
||||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
||||||
* Public or private harassment
|
|
||||||
* Publishing others' private information, such as a physical or email address,
|
|
||||||
without their explicit permission
|
|
||||||
* Other conduct which could reasonably be considered inappropriate in a
|
|
||||||
professional setting
|
|
||||||
|
|
||||||
## Enforcement Responsibilities
|
|
||||||
|
|
||||||
Community leaders are responsible for clarifying and enforcing our standards of
|
|
||||||
acceptable behavior and will take appropriate and fair corrective action in
|
|
||||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
|
||||||
or harmful.
|
|
||||||
|
|
||||||
Community leaders have the right and responsibility to remove, edit, or reject
|
|
||||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
|
||||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
|
||||||
decisions when appropriate.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This Code of Conduct applies within all community spaces, and also applies when
|
|
||||||
an individual is officially representing the community in public spaces.
|
|
||||||
Examples of representing our community include using an official e-mail address,
|
|
||||||
posting via an official social media account, or acting as an appointed
|
|
||||||
representative at an online or offline event.
|
|
||||||
|
|
||||||
## Enforcement
|
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
||||||
reported to the community leaders responsible for enforcement at
|
|
||||||
<development@prowlarr.com>.
|
|
||||||
All complaints will be reviewed and investigated promptly and fairly.
|
|
||||||
|
|
||||||
All community leaders are obligated to respect the privacy and security of the
|
|
||||||
reporter of any incident.
|
|
||||||
|
|
||||||
## Enforcement Guidelines
|
|
||||||
|
|
||||||
Community leaders will follow these Community Impact Guidelines in determining
|
|
||||||
the consequences for any action they deem in violation of this Code of Conduct:
|
|
||||||
|
|
||||||
### 1. Correction
|
|
||||||
|
|
||||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
|
||||||
unprofessional or unwelcome in the community.
|
|
||||||
|
|
||||||
**Consequence**: A private, written warning from community leaders, providing
|
|
||||||
clarity around the nature of the violation and an explanation of why the
|
|
||||||
behavior was inappropriate. A public apology may be requested.
|
|
||||||
|
|
||||||
### 2. Warning
|
|
||||||
|
|
||||||
**Community Impact**: A violation through a single incident or series of
|
|
||||||
actions.
|
|
||||||
|
|
||||||
**Consequence**: A warning with consequences for continued behavior. No
|
|
||||||
interaction with the people involved, including unsolicited interaction with
|
|
||||||
those enforcing the Code of Conduct, for a specified period of time. This
|
|
||||||
includes avoiding interactions in community spaces as well as external channels
|
|
||||||
like social media. Violating these terms may lead to a temporary or permanent
|
|
||||||
ban.
|
|
||||||
|
|
||||||
### 3. Temporary Ban
|
|
||||||
|
|
||||||
**Community Impact**: A serious violation of community standards, including
|
|
||||||
sustained inappropriate behavior.
|
|
||||||
|
|
||||||
**Consequence**: A temporary ban from any sort of interaction or public
|
|
||||||
communication with the community for a specified period of time. No public or
|
|
||||||
private interaction with the people involved, including unsolicited interaction
|
|
||||||
with those enforcing the Code of Conduct, is allowed during this period.
|
|
||||||
Violating these terms may lead to a permanent ban.
|
|
||||||
|
|
||||||
### 4. Permanent Ban
|
|
||||||
|
|
||||||
**Community Impact**: Demonstrating a pattern of violation of community
|
|
||||||
standards, including sustained inappropriate behavior, harassment of an
|
|
||||||
individual, or aggression toward or disparagement of classes of individuals.
|
|
||||||
|
|
||||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
|
||||||
community.
|
|
||||||
|
|
||||||
## Attribution
|
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|
||||||
version 2.1, available at
|
|
||||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
|
||||||
|
|
||||||
Community Impact Guidelines were inspired by
|
|
||||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
|
||||||
|
|
||||||
For answers to common questions about this code of conduct, see the FAQ at
|
|
||||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
|
||||||
[https://www.contributor-covenant.org/translations][translations].
|
|
||||||
|
|
||||||
[homepage]: https://www.contributor-covenant.org
|
|
||||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
|
||||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
|
||||||
[FAQ]: https://www.contributor-covenant.org/faq
|
|
||||||
[translations]: https://www.contributor-covenant.org/translations
|
|
||||||
@@ -27,7 +27,10 @@ Prowlarr is an indexer manager/proxy built on the popular \*arr .net/reactjs bas
|
|||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
|
Note: Prowlarr is currently early in life, thus bugs should be expected
|
||||||
|
|
||||||
[](https://wiki.servarr.com/prowlarr)
|
[](https://wiki.servarr.com/prowlarr)
|
||||||
|
|
||||||
[](https://prowlarr.com/discord)
|
[](https://prowlarr.com/discord)
|
||||||
[](https://www.reddit.com/r/Prowlarr)
|
[](https://www.reddit.com/r/Prowlarr)
|
||||||
|
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ variables:
|
|||||||
testsFolder: './_tests'
|
testsFolder: './_tests'
|
||||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||||
majorVersion: '1.3.1'
|
majorVersion: '0.4.0'
|
||||||
minorVersion: $[counter('minorVersion', 1)]
|
minorVersion: $[counter('minorVersion', 1)]
|
||||||
prowlarrVersion: '$(majorVersion).$(minorVersion)'
|
prowlarrVersion: '$(majorVersion).$(minorVersion)'
|
||||||
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
|
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
|
||||||
sentryOrg: 'servarr'
|
sentryOrg: 'servarr'
|
||||||
sentryUrl: 'https://sentry.servarr.com'
|
sentryUrl: 'https://sentry.servarr.com'
|
||||||
dotnetVersion: '6.0.405'
|
dotnetVersion: '6.0.201'
|
||||||
innoVersion: '6.2.0'
|
innoVersion: '6.2.0'
|
||||||
nodeVersion: '16.x'
|
nodeVersion: '16.x'
|
||||||
windowsImage: 'windows-2022'
|
windowsImage: 'windows-2022'
|
||||||
@@ -97,14 +97,15 @@ stages:
|
|||||||
- bash: |
|
- bash: |
|
||||||
BUNDLEDVERSIONS=${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}/Microsoft.NETCoreSdk.BundledVersions.props
|
BUNDLEDVERSIONS=${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}/Microsoft.NETCoreSdk.BundledVersions.props
|
||||||
echo $BUNDLEDVERSIONS
|
echo $BUNDLEDVERSIONS
|
||||||
|
grep osx-x64 $BUNDLEDVERSIONS
|
||||||
if grep -q freebsd-x64 $BUNDLEDVERSIONS; then
|
if grep -q freebsd-x64 $BUNDLEDVERSIONS; then
|
||||||
echo "Extra platforms already enabled"
|
echo "BSD already enabled"
|
||||||
else
|
else
|
||||||
echo "Enabling extra platform support"
|
echo "Enabling BSD support"
|
||||||
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS
|
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64/' $BUNDLEDVERSIONS
|
||||||
fi
|
fi
|
||||||
displayName: Enable Extra Platform Support
|
displayName: Enable FreeBSD Support
|
||||||
- bash: ./build.sh --backend --enable-extra-platforms
|
- bash: ./build.sh --backend --enable-bsd
|
||||||
displayName: Build Prowlarr Backend
|
displayName: Build Prowlarr Backend
|
||||||
- bash: |
|
- bash: |
|
||||||
find ${OUTPUTFOLDER} -type f ! -path "*/publish/*" -exec rm -rf {} \;
|
find ${OUTPUTFOLDER} -type f ! -path "*/publish/*" -exec rm -rf {} \;
|
||||||
@@ -118,28 +119,24 @@ stages:
|
|||||||
displayName: Publish Backend
|
displayName: Publish Backend
|
||||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
- publish: '$(testsFolder)/net6.0/win-x64/publish'
|
- publish: '$(testsFolder)/net6.0/win-x64/publish'
|
||||||
artifact: win-x64-tests
|
artifact: WindowsCoreTests
|
||||||
displayName: Publish win-x64 Test Package
|
displayName: Publish Windows Test Package
|
||||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
- publish: '$(testsFolder)/net6.0/linux-x64/publish'
|
- publish: '$(testsFolder)/net6.0/linux-x64/publish'
|
||||||
artifact: linux-x64-tests
|
artifact: LinuxCoreTests
|
||||||
displayName: Publish linux-x64 Test Package
|
displayName: Publish Linux Test Package
|
||||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
|
||||||
- publish: '$(testsFolder)/net6.0/linux-x86/publish'
|
|
||||||
artifact: linux-x86-tests
|
|
||||||
displayName: Publish linux-x86 Test Package
|
|
||||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
- publish: '$(testsFolder)/net6.0/linux-musl-x64/publish'
|
- publish: '$(testsFolder)/net6.0/linux-musl-x64/publish'
|
||||||
artifact: linux-musl-x64-tests
|
artifact: LinuxMuslCoreTests
|
||||||
displayName: Publish linux-musl-x64 Test Package
|
displayName: Publish Linux Musl Test Package
|
||||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
- publish: '$(testsFolder)/net6.0/freebsd-x64/publish'
|
- publish: '$(testsFolder)/net6.0/freebsd-x64/publish'
|
||||||
artifact: freebsd-x64-tests
|
artifact: FreebsdCoreTests
|
||||||
displayName: Publish freebsd-x64 Test Package
|
displayName: Publish FreeBSD Test Package
|
||||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
- publish: '$(testsFolder)/net6.0/osx-x64/publish'
|
- publish: '$(testsFolder)/net6.0/osx-x64/publish'
|
||||||
artifact: osx-x64-tests
|
artifact: MacCoreTests
|
||||||
displayName: Publish osx-x64 Test Package
|
displayName: Publish MacOS Test Package
|
||||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
|
|
||||||
- stage: Build_Frontend
|
- stage: Build_Frontend
|
||||||
@@ -242,35 +239,35 @@ stages:
|
|||||||
artifactName: WindowsFrontend
|
artifactName: WindowsFrontend
|
||||||
targetPath: _output
|
targetPath: _output
|
||||||
displayName: Fetch Frontend
|
displayName: Fetch Frontend
|
||||||
- bash: ./build.sh --packages --enable-extra-platforms
|
- bash: ./build.sh --packages --enable-bsd
|
||||||
displayName: Create Packages
|
displayName: Create Packages
|
||||||
- bash: |
|
- bash: |
|
||||||
find . -name "Prowlarr" -exec chmod a+x {} \;
|
find . -name "Prowlarr" -exec chmod a+x {} \;
|
||||||
find . -name "Prowlarr.Update" -exec chmod a+x {} \;
|
find . -name "Prowlarr.Update" -exec chmod a+x {} \;
|
||||||
displayName: Set executable bits
|
displayName: Set executable bits
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create win-x64 zip
|
displayName: Create Windows Core zip
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).windows-core-x64.zip'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).windows-core-x64.zip'
|
||||||
archiveType: 'zip'
|
archiveType: 'zip'
|
||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/win-x64/net6.0
|
rootFolderOrFile: $(artifactsFolder)/win-x64/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create win-x86 zip
|
displayName: Create Windows x86 Core zip
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).windows-core-x86.zip'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).windows-core-x86.zip'
|
||||||
archiveType: 'zip'
|
archiveType: 'zip'
|
||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/win-x86/net6.0
|
rootFolderOrFile: $(artifactsFolder)/win-x86/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create osx-x64 app
|
displayName: Create MacOS x64 Core app
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).osx-app-core-x64.zip'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).osx-app-core-x64.zip'
|
||||||
archiveType: 'zip'
|
archiveType: 'zip'
|
||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/osx-x64-app/net6.0
|
rootFolderOrFile: $(artifactsFolder)/osx-x64-app/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create osx-x64 tar
|
displayName: Create MacOS x64 Core tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).osx-core-x64.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).osx-core-x64.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -278,14 +275,14 @@ stages:
|
|||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/osx-x64/net6.0
|
rootFolderOrFile: $(artifactsFolder)/osx-x64/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create osx-arm64 app
|
displayName: Create MacOS arm64 Core app
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).osx-app-core-arm64.zip'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).osx-app-core-arm64.zip'
|
||||||
archiveType: 'zip'
|
archiveType: 'zip'
|
||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/osx-arm64-app/net6.0
|
rootFolderOrFile: $(artifactsFolder)/osx-arm64-app/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create osx-arm64 tar
|
displayName: Create MacOS arm64 Core tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).osx-core-arm64.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).osx-core-arm64.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -293,7 +290,7 @@ stages:
|
|||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/osx-arm64/net6.0
|
rootFolderOrFile: $(artifactsFolder)/osx-arm64/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create linux-x64 tar
|
displayName: Create Linux Core tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-core-x64.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-core-x64.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -301,7 +298,7 @@ stages:
|
|||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/linux-x64/net6.0
|
rootFolderOrFile: $(artifactsFolder)/linux-x64/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create linux-musl-x64 tar
|
displayName: Create Linux Musl Core tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-musl-core-x64.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-musl-core-x64.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -309,15 +306,7 @@ stages:
|
|||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/net6.0
|
rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create linux-x86 tar
|
displayName: Create ARM32 Linux Core tar
|
||||||
inputs:
|
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-core-x86.tar.gz'
|
|
||||||
archiveType: 'tar'
|
|
||||||
tarCompression: 'gz'
|
|
||||||
includeRootFolder: false
|
|
||||||
rootFolderOrFile: $(artifactsFolder)/linux-x86/net6.0
|
|
||||||
- task: ArchiveFiles@2
|
|
||||||
displayName: Create linux-arm tar
|
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-core-arm.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-core-arm.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -325,7 +314,7 @@ stages:
|
|||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/linux-arm/net6.0
|
rootFolderOrFile: $(artifactsFolder)/linux-arm/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create linux-musl-arm tar
|
displayName: Create ARM32 Linux Musl Core tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-musl-core-arm.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-musl-core-arm.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -333,7 +322,7 @@ stages:
|
|||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm/net6.0
|
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create linux-arm64 tar
|
displayName: Create ARM64 Linux Core tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-core-arm64.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-core-arm64.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -341,7 +330,7 @@ stages:
|
|||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/linux-arm64/net6.0
|
rootFolderOrFile: $(artifactsFolder)/linux-arm64/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create linux-musl-arm64 tar
|
displayName: Create ARM64 Linux Musl Core tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-musl-core-arm64.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-musl-core-arm64.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -416,22 +405,22 @@ stages:
|
|||||||
matrix:
|
matrix:
|
||||||
MacCore:
|
MacCore:
|
||||||
osName: 'Mac'
|
osName: 'Mac'
|
||||||
testName: 'osx-x64'
|
testName: 'MacCore'
|
||||||
poolName: 'Azure Pipelines'
|
poolName: 'Azure Pipelines'
|
||||||
imageName: ${{ variables.macImage }}
|
imageName: ${{ variables.macImage }}
|
||||||
WindowsCore:
|
WindowsCore:
|
||||||
osName: 'Windows'
|
osName: 'Windows'
|
||||||
testName: 'win-x64'
|
testName: 'WindowsCore'
|
||||||
poolName: 'Azure Pipelines'
|
poolName: 'Azure Pipelines'
|
||||||
imageName: ${{ variables.windowsImage }}
|
imageName: ${{ variables.windowsImage }}
|
||||||
LinuxCore:
|
LinuxCore:
|
||||||
osName: 'Linux'
|
osName: 'Linux'
|
||||||
testName: 'linux-x64'
|
testName: 'LinuxCore'
|
||||||
poolName: 'Azure Pipelines'
|
poolName: 'Azure Pipelines'
|
||||||
imageName: ${{ variables.linuxImage }}
|
imageName: ${{ variables.linuxImage }}
|
||||||
FreebsdCore:
|
FreebsdCore:
|
||||||
osName: 'Linux'
|
osName: 'Linux'
|
||||||
testName: 'freebsd-x64'
|
testName: 'FreebsdCore'
|
||||||
poolName: 'FreeBSD'
|
poolName: 'FreeBSD'
|
||||||
imageName:
|
imageName:
|
||||||
|
|
||||||
@@ -450,7 +439,7 @@ stages:
|
|||||||
displayName: Download Test Artifact
|
displayName: Download Test Artifact
|
||||||
inputs:
|
inputs:
|
||||||
buildType: 'current'
|
buildType: 'current'
|
||||||
artifactName: '$(testName)-tests'
|
artifactName: '$(testName)Tests'
|
||||||
targetPath: $(testsFolder)
|
targetPath: $(testsFolder)
|
||||||
- powershell: Set-Service SCardSvr -StartupType Manual
|
- powershell: Set-Service SCardSvr -StartupType Manual
|
||||||
displayName: Enable Windows Test Service
|
displayName: Enable Windows Test Service
|
||||||
@@ -480,12 +469,8 @@ stages:
|
|||||||
matrix:
|
matrix:
|
||||||
alpine:
|
alpine:
|
||||||
testName: 'Musl Net Core'
|
testName: 'Musl Net Core'
|
||||||
artifactName: linux-musl-x64-tests
|
artifactName: LinuxMuslCoreTests
|
||||||
containerImage: ghcr.io/servarr/testimages:alpine
|
containerImage: ghcr.io/servarr/testimages:alpine
|
||||||
linux-x86:
|
|
||||||
testName: 'linux-x86'
|
|
||||||
artifactName: linux-x86-tests
|
|
||||||
containerImage: ghcr.io/servarr/testimages:linux-x86
|
|
||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: ${{ variables.linuxImage }}
|
vmImage: ${{ variables.linuxImage }}
|
||||||
@@ -496,15 +481,9 @@ stages:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
displayName: 'Install .NET'
|
displayName: 'Install .net core'
|
||||||
inputs:
|
inputs:
|
||||||
version: $(dotnetVersion)
|
version: $(dotnetVersion)
|
||||||
condition: and(succeeded(), ne(variables['testName'], 'linux-x86'))
|
|
||||||
- bash: |
|
|
||||||
SDKURL=$(curl -s https://api.github.com/repos/Servarr/dotnet-linux-x86/releases | jq -rc '.[].assets[].browser_download_url' | grep sdk-${DOTNETVERSION}.*gz$)
|
|
||||||
curl -fsSL $SDKURL | tar xzf - -C /opt/dotnet
|
|
||||||
displayName: 'Install .NET'
|
|
||||||
condition: and(succeeded(), eq(variables['testName'], 'linux-x86'))
|
|
||||||
- checkout: none
|
- checkout: none
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: Download Test Artifact
|
displayName: Download Test Artifact
|
||||||
@@ -527,58 +506,6 @@ stages:
|
|||||||
testResultsFiles: '**/TestResult.xml'
|
testResultsFiles: '**/TestResult.xml'
|
||||||
testRunTitle: '$(testName) Unit Tests'
|
testRunTitle: '$(testName) Unit Tests'
|
||||||
failTaskOnFailedTests: true
|
failTaskOnFailedTests: true
|
||||||
|
|
||||||
- job: Unit_LinuxCore_Postgres
|
|
||||||
displayName: Unit Native LinuxCore with Postgres Database
|
|
||||||
dependsOn: Prepare
|
|
||||||
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
|
||||||
variables:
|
|
||||||
pattern: 'Prowlarr.*.linux-core-x64.tar.gz'
|
|
||||||
artifactName: linux-x64-tests
|
|
||||||
Prowlarr__Postgres__Host: 'localhost'
|
|
||||||
Prowlarr__Postgres__Port: '5432'
|
|
||||||
Prowlarr__Postgres__User: 'prowlarr'
|
|
||||||
Prowlarr__Postgres__Password: 'prowlarr'
|
|
||||||
|
|
||||||
pool:
|
|
||||||
vmImage: ${{ variables.linuxImage }}
|
|
||||||
|
|
||||||
timeoutInMinutes: 10
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- task: UseDotNet@2
|
|
||||||
displayName: 'Install .net core'
|
|
||||||
inputs:
|
|
||||||
version: $(dotnetVersion)
|
|
||||||
- checkout: none
|
|
||||||
- task: DownloadPipelineArtifact@2
|
|
||||||
displayName: Download Test Artifact
|
|
||||||
inputs:
|
|
||||||
buildType: 'current'
|
|
||||||
artifactName: $(artifactName)
|
|
||||||
targetPath: $(testsFolder)
|
|
||||||
- bash: find ${TESTSFOLDER} -name "Prowlarr.Test.Dummy" -exec chmod a+x {} \;
|
|
||||||
displayName: Make Test Dummy Executable
|
|
||||||
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
|
|
||||||
- bash: |
|
|
||||||
docker run -d --name=postgres14 \
|
|
||||||
-e POSTGRES_PASSWORD=prowlarr \
|
|
||||||
-e POSTGRES_USER=prowlarr \
|
|
||||||
-p 5432:5432/tcp \
|
|
||||||
postgres:14
|
|
||||||
displayName: Start postgres
|
|
||||||
- bash: |
|
|
||||||
chmod a+x ${TESTSFOLDER}/test.sh
|
|
||||||
ls -lR ${TESTSFOLDER}
|
|
||||||
${TESTSFOLDER}/test.sh Linux Unit Test
|
|
||||||
displayName: Run Tests
|
|
||||||
- task: PublishTestResults@2
|
|
||||||
displayName: Publish Test Results
|
|
||||||
inputs:
|
|
||||||
testResultsFormat: 'NUnit'
|
|
||||||
testResultsFiles: '**/TestResult.xml'
|
|
||||||
testRunTitle: 'LinuxCore Postgres Unit Tests'
|
|
||||||
failTaskOnFailedTests: true
|
|
||||||
|
|
||||||
- stage: Integration
|
- stage: Integration
|
||||||
displayName: Integration
|
displayName: Integration
|
||||||
@@ -606,17 +533,17 @@ stages:
|
|||||||
matrix:
|
matrix:
|
||||||
MacCore:
|
MacCore:
|
||||||
osName: 'Mac'
|
osName: 'Mac'
|
||||||
testName: 'osx-x64'
|
testName: 'MacCore'
|
||||||
imageName: ${{ variables.macImage }}
|
imageName: ${{ variables.macImage }}
|
||||||
pattern: 'Prowlarr.*.osx-core-x64.tar.gz'
|
pattern: 'Prowlarr.*.osx-core-x64.tar.gz'
|
||||||
WindowsCore:
|
WindowsCore:
|
||||||
osName: 'Windows'
|
osName: 'Windows'
|
||||||
testName: 'win-x64'
|
testName: 'WindowsCore'
|
||||||
imageName: ${{ variables.windowsImage }}
|
imageName: ${{ variables.windowsImage }}
|
||||||
pattern: 'Prowlarr.*.windows-core-x64.zip'
|
pattern: 'Prowlarr.*.windows-core-x64.zip'
|
||||||
LinuxCore:
|
LinuxCore:
|
||||||
osName: 'Linux'
|
osName: 'Linux'
|
||||||
testName: 'linux-x64'
|
testName: 'LinuxCore'
|
||||||
imageName: ${{ variables.linuxImage }}
|
imageName: ${{ variables.linuxImage }}
|
||||||
pattern: 'Prowlarr.*.linux-core-x64.tar.gz'
|
pattern: 'Prowlarr.*.linux-core-x64.tar.gz'
|
||||||
|
|
||||||
@@ -633,7 +560,7 @@ stages:
|
|||||||
displayName: Download Test Artifact
|
displayName: Download Test Artifact
|
||||||
inputs:
|
inputs:
|
||||||
buildType: 'current'
|
buildType: 'current'
|
||||||
artifactName: '$(testName)-tests'
|
artifactName: '$(testName)Tests'
|
||||||
targetPath: $(testsFolder)
|
targetPath: $(testsFolder)
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: Download Build Artifact
|
displayName: Download Build Artifact
|
||||||
@@ -663,67 +590,6 @@ stages:
|
|||||||
failTaskOnFailedTests: true
|
failTaskOnFailedTests: true
|
||||||
displayName: Publish Test Results
|
displayName: Publish Test Results
|
||||||
|
|
||||||
- job: Integration_LinuxCore_Postgres
|
|
||||||
displayName: Integration Native LinuxCore with Postgres Database
|
|
||||||
dependsOn: Prepare
|
|
||||||
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
|
||||||
variables:
|
|
||||||
pattern: 'Prowlarr.*.linux-core-x64.tar.gz'
|
|
||||||
Prowlarr__Postgres__Host: 'localhost'
|
|
||||||
Prowlarr__Postgres__Port: '5432'
|
|
||||||
Prowlarr__Postgres__User: 'prowlarr'
|
|
||||||
Prowlarr__Postgres__Password: 'prowlarr'
|
|
||||||
|
|
||||||
pool:
|
|
||||||
vmImage: ${{ variables.linuxImage }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- task: UseDotNet@2
|
|
||||||
displayName: 'Install .net core'
|
|
||||||
inputs:
|
|
||||||
version: $(dotnetVersion)
|
|
||||||
- checkout: none
|
|
||||||
- task: DownloadPipelineArtifact@2
|
|
||||||
displayName: Download Test Artifact
|
|
||||||
inputs:
|
|
||||||
buildType: 'current'
|
|
||||||
artifactName: 'linux-x64-tests'
|
|
||||||
targetPath: $(testsFolder)
|
|
||||||
- task: DownloadPipelineArtifact@2
|
|
||||||
displayName: Download Build Artifact
|
|
||||||
inputs:
|
|
||||||
buildType: 'current'
|
|
||||||
artifactName: Packages
|
|
||||||
itemPattern: '**/$(pattern)'
|
|
||||||
targetPath: $(Build.ArtifactStagingDirectory)
|
|
||||||
- task: ExtractFiles@1
|
|
||||||
inputs:
|
|
||||||
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
|
||||||
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
|
||||||
displayName: Extract Package
|
|
||||||
- bash: |
|
|
||||||
mkdir -p ./bin/
|
|
||||||
cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Prowlarr/. ./bin/
|
|
||||||
displayName: Move Package Contents
|
|
||||||
- bash: |
|
|
||||||
docker run -d --name=postgres14 \
|
|
||||||
-e POSTGRES_PASSWORD=prowlarr \
|
|
||||||
-e POSTGRES_USER=prowlarr \
|
|
||||||
-p 5432:5432/tcp \
|
|
||||||
postgres:14
|
|
||||||
displayName: Start postgres
|
|
||||||
- bash: |
|
|
||||||
chmod a+x ${TESTSFOLDER}/test.sh
|
|
||||||
${TESTSFOLDER}/test.sh Linux Integration Test
|
|
||||||
displayName: Run Integration Tests
|
|
||||||
- task: PublishTestResults@2
|
|
||||||
inputs:
|
|
||||||
testResultsFormat: 'NUnit'
|
|
||||||
testResultsFiles: '**/TestResult.xml'
|
|
||||||
testRunTitle: 'Integration LinuxCore Postgres Database Integration Tests'
|
|
||||||
failTaskOnFailedTests: true
|
|
||||||
displayName: Publish Test Results
|
|
||||||
|
|
||||||
- job: Integration_FreeBSD
|
- job: Integration_FreeBSD
|
||||||
displayName: Integration Native FreeBSD
|
displayName: Integration Native FreeBSD
|
||||||
dependsOn: Prepare
|
dependsOn: Prepare
|
||||||
@@ -741,14 +607,14 @@ stages:
|
|||||||
displayName: Download Test Artifact
|
displayName: Download Test Artifact
|
||||||
inputs:
|
inputs:
|
||||||
buildType: 'current'
|
buildType: 'current'
|
||||||
artifactName: 'freebsd-x64-tests'
|
artifactName: 'FreebsdCoreTests'
|
||||||
targetPath: $(testsFolder)
|
targetPath: $(testsFolder)
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: Download Build Artifact
|
displayName: Download Build Artifact
|
||||||
inputs:
|
inputs:
|
||||||
buildType: 'current'
|
buildType: 'current'
|
||||||
artifactName: Packages
|
artifactName: Packages
|
||||||
itemPattern: '**/$(pattern)'
|
itemPattern: '/$(pattern)'
|
||||||
targetPath: $(Build.ArtifactStagingDirectory)
|
targetPath: $(Build.ArtifactStagingDirectory)
|
||||||
- bash: |
|
- bash: |
|
||||||
mkdir -p ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin
|
mkdir -p ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin
|
||||||
@@ -777,15 +643,11 @@ stages:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
alpine:
|
alpine:
|
||||||
testName: 'linux-musl-x64'
|
testName: 'Musl Net Core'
|
||||||
artifactName: linux-musl-x64-tests
|
artifactName: LinuxMuslCoreTests
|
||||||
containerImage: ghcr.io/servarr/testimages:alpine
|
containerImage: ghcr.io/servarr/testimages:alpine
|
||||||
pattern: 'Prowlarr.*.linux-musl-core-x64.tar.gz'
|
pattern: 'Prowlarr.*.linux-musl-core-x64.tar.gz'
|
||||||
linux-x86:
|
|
||||||
testName: 'linux-x86'
|
|
||||||
artifactName: linux-x86-tests
|
|
||||||
containerImage: ghcr.io/servarr/testimages:linux-x86
|
|
||||||
pattern: 'Prowlarr.*.linux-core-x86.tar.gz'
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: ${{ variables.linuxImage }}
|
vmImage: ${{ variables.linuxImage }}
|
||||||
|
|
||||||
@@ -795,15 +657,9 @@ stages:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
displayName: 'Install .NET'
|
displayName: 'Install .net core'
|
||||||
inputs:
|
inputs:
|
||||||
version: $(dotnetVersion)
|
version: $(dotnetVersion)
|
||||||
condition: and(succeeded(), ne(variables['testName'], 'linux-x86'))
|
|
||||||
- bash: |
|
|
||||||
SDKURL=$(curl -s https://api.github.com/repos/Servarr/dotnet-linux-x86/releases | jq -rc '.[].assets[].browser_download_url' | grep sdk-${DOTNETVERSION}.*gz$)
|
|
||||||
curl -fsSL $SDKURL | tar xzf - -C /opt/dotnet
|
|
||||||
displayName: 'Install .NET'
|
|
||||||
condition: and(succeeded(), eq(variables['testName'], 'linux-x86'))
|
|
||||||
- checkout: none
|
- checkout: none
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: Download Test Artifact
|
displayName: Download Test Artifact
|
||||||
@@ -849,19 +705,16 @@ stages:
|
|||||||
matrix:
|
matrix:
|
||||||
Linux:
|
Linux:
|
||||||
osName: 'Linux'
|
osName: 'Linux'
|
||||||
artifactName: 'linux-x64'
|
|
||||||
imageName: ${{ variables.linuxImage }}
|
imageName: ${{ variables.linuxImage }}
|
||||||
pattern: 'Prowlarr.*.linux-core-x64.tar.gz'
|
pattern: 'Prowlarr.*.linux-core-x64.tar.gz'
|
||||||
failBuild: true
|
failBuild: true
|
||||||
Mac:
|
Mac:
|
||||||
osName: 'Mac'
|
osName: 'Mac'
|
||||||
artifactName: 'osx-x64'
|
|
||||||
imageName: ${{ variables.macImage }}
|
imageName: ${{ variables.macImage }}
|
||||||
pattern: 'Prowlarr.*.osx-core-x64.tar.gz'
|
pattern: 'Prowlarr.*.osx-core-x64.tar.gz'
|
||||||
failBuild: true
|
failBuild: true
|
||||||
Windows:
|
Windows:
|
||||||
osName: 'Windows'
|
osName: 'Windows'
|
||||||
artifactName: 'win-x64'
|
|
||||||
imageName: ${{ variables.windowsImage }}
|
imageName: ${{ variables.windowsImage }}
|
||||||
pattern: 'Prowlarr.*.windows-core-x64.zip'
|
pattern: 'Prowlarr.*.windows-core-x64.zip'
|
||||||
failBuild: true
|
failBuild: true
|
||||||
@@ -879,7 +732,7 @@ stages:
|
|||||||
displayName: Download Test Artifact
|
displayName: Download Test Artifact
|
||||||
inputs:
|
inputs:
|
||||||
buildType: 'current'
|
buildType: 'current'
|
||||||
artifactName: '$(artifactName)-tests'
|
artifactName: '$(osName)CoreTests'
|
||||||
targetPath: $(testsFolder)
|
targetPath: $(testsFolder)
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: Download Build Artifact
|
displayName: Download Build Artifact
|
||||||
@@ -1108,5 +961,4 @@ stages:
|
|||||||
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
||||||
DISCORDCHANNELID: $(discordChannelId)
|
DISCORDCHANNELID: $(discordChannelId)
|
||||||
DISCORDWEBHOOKKEY: $(discordWebhookKey)
|
DISCORDWEBHOOKKEY: $(discordWebhookKey)
|
||||||
DISCORDTHREADID: $(discordThreadId)
|
|
||||||
|
|
||||||
|
|||||||
45
build.sh
45
build.sh
@@ -25,22 +25,15 @@ UpdateVersionNumber()
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
EnableExtraPlatformsInSDK()
|
EnableBsdSupport()
|
||||||
{
|
{
|
||||||
SDK_PATH=$(dotnet --list-sdks | grep -P '6\.\d\.\d+' | head -1 | sed 's/\(6\.[0-9]*\.[0-9]*\).*\[\(.*\)\]/\2\/\1/g')
|
#todo enable sdk with
|
||||||
BUNDLEDVERSIONS="${SDK_PATH}/Microsoft.NETCoreSdk.BundledVersions.props"
|
#SDK_PATH=$(dotnet --list-sdks | grep -P '5\.\d\.\d+' | head -1 | sed 's/\(5\.[0-9]*\.[0-9]*\).*\[\(.*\)\]/\2\/\1/g')
|
||||||
if grep -q freebsd-x64 $BUNDLEDVERSIONS; then
|
# BUNDLED_VERSIONS="${SDK_PATH}/Microsoft.NETCoreSdk.BundledVersions.props"
|
||||||
echo "Extra platforms already enabled"
|
|
||||||
else
|
|
||||||
echo "Enabling extra platform support"
|
|
||||||
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
EnableExtraPlatforms()
|
|
||||||
{
|
|
||||||
if grep -qv freebsd-x64 src/Directory.Build.props; then
|
if grep -qv freebsd-x64 src/Directory.Build.props; then
|
||||||
sed -i'' -e "s^<RuntimeIdentifiers>\(.*\)</RuntimeIdentifiers>^<RuntimeIdentifiers>\1;freebsd-x64;linux-x86</RuntimeIdentifiers>^g" src/Directory.Build.props
|
sed -i'' -e "s^<RuntimeIdentifiers>\(.*\)</RuntimeIdentifiers>^<RuntimeIdentifiers>\1;freebsd-x64</RuntimeIdentifiers>^g" src/Directory.Build.props
|
||||||
|
sed -i'' -e "s^<ExcludedRuntimeFrameworkPairs>\(.*\)</ExcludedRuntimeFrameworkPairs>^<ExcludedRuntimeFrameworkPairs>\1;freebsd-x64:net472</ExcludedRuntimeFrameworkPairs>^g" src/Directory.Build.props
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,8 +293,7 @@ if [ $# -eq 0 ]; then
|
|||||||
PACKAGES=YES
|
PACKAGES=YES
|
||||||
INSTALLER=NO
|
INSTALLER=NO
|
||||||
LINT=YES
|
LINT=YES
|
||||||
ENABLE_EXTRA_PLATFORMS=NO
|
ENABLE_BSD=NO
|
||||||
ENABLE_EXTRA_PLATFORMS_IN_SDK=NO
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]
|
while [[ $# -gt 0 ]]
|
||||||
@@ -313,12 +305,8 @@ case $key in
|
|||||||
BACKEND=YES
|
BACKEND=YES
|
||||||
shift # past argument
|
shift # past argument
|
||||||
;;
|
;;
|
||||||
--enable-bsd|--enable-extra-platforms)
|
--enable-bsd)
|
||||||
ENABLE_EXTRA_PLATFORMS=YES
|
ENABLE_BSD=YES
|
||||||
shift # past argument
|
|
||||||
;;
|
|
||||||
--enable-extra-platforms-in-sdk)
|
|
||||||
ENABLE_EXTRA_PLATFORMS_IN_SDK=YES
|
|
||||||
shift # past argument
|
shift # past argument
|
||||||
;;
|
;;
|
||||||
-r|--runtime)
|
-r|--runtime)
|
||||||
@@ -362,17 +350,12 @@ esac
|
|||||||
done
|
done
|
||||||
set -- "${POSITIONAL[@]}" # restore positional parameters
|
set -- "${POSITIONAL[@]}" # restore positional parameters
|
||||||
|
|
||||||
if [ "$ENABLE_EXTRA_PLATFORMS_IN_SDK" = "YES" ];
|
|
||||||
then
|
|
||||||
EnableExtraPlatformsInSDK
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$BACKEND" = "YES" ];
|
if [ "$BACKEND" = "YES" ];
|
||||||
then
|
then
|
||||||
UpdateVersionNumber
|
UpdateVersionNumber
|
||||||
if [ "$ENABLE_EXTRA_PLATFORMS" = "YES" ];
|
if [ "$ENABLE_BSD" = "YES" ];
|
||||||
then
|
then
|
||||||
EnableExtraPlatforms
|
EnableBsdSupport
|
||||||
fi
|
fi
|
||||||
Build
|
Build
|
||||||
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
|
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
|
||||||
@@ -382,10 +365,9 @@ then
|
|||||||
PackageTests "net6.0" "linux-x64"
|
PackageTests "net6.0" "linux-x64"
|
||||||
PackageTests "net6.0" "linux-musl-x64"
|
PackageTests "net6.0" "linux-musl-x64"
|
||||||
PackageTests "net6.0" "osx-x64"
|
PackageTests "net6.0" "osx-x64"
|
||||||
if [ "$ENABLE_EXTRA_PLATFORMS" = "YES" ];
|
if [ "$ENABLE_BSD" = "YES" ];
|
||||||
then
|
then
|
||||||
PackageTests "net6.0" "freebsd-x64"
|
PackageTests "net6.0" "freebsd-x64"
|
||||||
PackageTests "net6.0" "linux-x86"
|
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
PackageTests "$FRAMEWORK" "$RID"
|
PackageTests "$FRAMEWORK" "$RID"
|
||||||
@@ -424,10 +406,9 @@ then
|
|||||||
Package "net6.0" "linux-musl-arm"
|
Package "net6.0" "linux-musl-arm"
|
||||||
Package "net6.0" "osx-x64"
|
Package "net6.0" "osx-x64"
|
||||||
Package "net6.0" "osx-arm64"
|
Package "net6.0" "osx-arm64"
|
||||||
if [ "$ENABLE_EXTRA_PLATFORMS" = "YES" ];
|
if [ "$ENABLE_BSD" = "YES" ];
|
||||||
then
|
then
|
||||||
Package "net6.0" "freebsd-x64"
|
Package "net6.0" "freebsd-x64"
|
||||||
Package "net6.0" "linux-x86"
|
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
Package "$FRAMEWORK" "$RID"
|
Package "$FRAMEWORK" "$RID"
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ module.exports = {
|
|||||||
plugins: [
|
plugins: [
|
||||||
'filenames',
|
'filenames',
|
||||||
'react',
|
'react',
|
||||||
'react-hooks',
|
|
||||||
'simple-import-sort',
|
'simple-import-sort',
|
||||||
'import'
|
'import'
|
||||||
],
|
],
|
||||||
@@ -309,9 +308,7 @@ module.exports = {
|
|||||||
'react/react-in-jsx-scope': 2,
|
'react/react-in-jsx-scope': 2,
|
||||||
'react/self-closing-comp': 2,
|
'react/self-closing-comp': 2,
|
||||||
'react/sort-comp': 2,
|
'react/sort-comp': 2,
|
||||||
'react/jsx-wrap-multilines': 2,
|
'react/jsx-wrap-multilines': 2
|
||||||
'react-hooks/rules-of-hooks': 'error',
|
|
||||||
'react-hooks/exhaustive-deps': 'error'
|
|
||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -142,8 +142,8 @@ module.exports = (env) => {
|
|||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.jsx?$/,
|
test: /\.js?$/,
|
||||||
exclude: /[\\/]node_modules[\\/](?!(@sentry\/browser|@sentry\/integrations|chart.js|filesize|normalize.css)[\\/])/,
|
exclude: /(node_modules|JsLibraries)/,
|
||||||
use: [
|
use: [
|
||||||
{
|
{
|
||||||
loader: 'babel-loader',
|
loader: 'babel-loader',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Fragment, useCallback, useEffect } from 'react';
|
import React, { Fragment, useEffect } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import themes from 'Styles/Themes';
|
import themes from 'Styles/Themes';
|
||||||
@@ -19,8 +19,7 @@ function createMapStateToProps() {
|
|||||||
|
|
||||||
function ApplyTheme({ theme, children }) {
|
function ApplyTheme({ theme, children }) {
|
||||||
// Update the CSS Variables
|
// Update the CSS Variables
|
||||||
|
function updateCSSVariables() {
|
||||||
const updateCSSVariables = useCallback(() => {
|
|
||||||
const arrayOfVariableKeys = Object.keys(themes[theme]);
|
const arrayOfVariableKeys = Object.keys(themes[theme]);
|
||||||
const arrayOfVariableValues = Object.values(themes[theme]);
|
const arrayOfVariableValues = Object.values(themes[theme]);
|
||||||
|
|
||||||
@@ -32,12 +31,12 @@ function ApplyTheme({ theme, children }) {
|
|||||||
arrayOfVariableValues[index]
|
arrayOfVariableValues[index]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}, [theme]);
|
}
|
||||||
|
|
||||||
// On Component Mount and Component Update
|
// On Component Mount and Component Update
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateCSSVariables(theme);
|
updateCSSVariables(theme);
|
||||||
}, [updateCSSVariables, theme]);
|
}, [theme]);
|
||||||
|
|
||||||
return <Fragment>{children}</Fragment>;
|
return <Fragment>{children}</Fragment>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ function CustomFiltersModalContent(props) {
|
|||||||
|
|
||||||
<div className={styles.addButtonContainer}>
|
<div className={styles.addButtonContainer}>
|
||||||
<Button onPress={onAddCustomFilter}>
|
<Button onPress={onAddCustomFilter}>
|
||||||
{translate('AddCustomFilter')}
|
Add Custom Filter
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
|||||||
@@ -78,7 +78,7 @@
|
|||||||
|
|
||||||
border: 1px solid var(--inputBorderColor);
|
border: 1px solid var(--inputBorderColor);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-color: var(--inputBackgroundColor);
|
background-color: var(--white);
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading {
|
.loading {
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import FormInputHelpText from './FormInputHelpText';
|
|||||||
import IndexerFlagsSelectInputConnector from './IndexerFlagsSelectInputConnector';
|
import IndexerFlagsSelectInputConnector from './IndexerFlagsSelectInputConnector';
|
||||||
import InfoInput from './InfoInput';
|
import InfoInput from './InfoInput';
|
||||||
import KeyValueListInput from './KeyValueListInput';
|
import KeyValueListInput from './KeyValueListInput';
|
||||||
import NewznabCategorySelectInputConnector from './NewznabCategorySelectInputConnector';
|
|
||||||
import NumberInput from './NumberInput';
|
import NumberInput from './NumberInput';
|
||||||
import OAuthInputConnector from './OAuthInputConnector';
|
import OAuthInputConnector from './OAuthInputConnector';
|
||||||
import PasswordInput from './PasswordInput';
|
import PasswordInput from './PasswordInput';
|
||||||
@@ -69,9 +68,6 @@ function getComponent(type) {
|
|||||||
case inputTypes.PATH:
|
case inputTypes.PATH:
|
||||||
return PathInputConnector;
|
return PathInputConnector;
|
||||||
|
|
||||||
case inputTypes.CATEGORY_SELECT:
|
|
||||||
return NewznabCategorySelectInputConnector;
|
|
||||||
|
|
||||||
case inputTypes.INDEXER_FLAGS_SELECT:
|
case inputTypes.INDEXER_FLAGS_SELECT:
|
||||||
return IndexerFlagsSelectInputConnector;
|
return IndexerFlagsSelectInputConnector;
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ function createMapStateToProps() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
value: value || [],
|
value,
|
||||||
values
|
values
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ class TagInputInput extends Component {
|
|||||||
<div
|
<div
|
||||||
ref={forwardedRef}
|
ref={forwardedRef}
|
||||||
className={className}
|
className={className}
|
||||||
|
component="div"
|
||||||
onMouseDown={this.onMouseDown}
|
onMouseDown={this.onMouseDown}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -36,5 +36,5 @@
|
|||||||
/** Outline **/
|
/** Outline **/
|
||||||
|
|
||||||
.outline {
|
.outline {
|
||||||
background-color: var(--cardBackgroundColor);
|
background-color: var(--white);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,5 +108,5 @@
|
|||||||
/** Outline **/
|
/** Outline **/
|
||||||
|
|
||||||
.outline {
|
.outline {
|
||||||
background-color: var(--cardBackgroundColor);
|
background-color: var(--white);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,10 +30,10 @@ function ConfirmModal(props) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
bindShortcut('enter', onConfirm);
|
bindShortcut('enter', onConfirm);
|
||||||
|
} else {
|
||||||
return () => unbindShortcut('enter', onConfirm);
|
unbindShortcut('enter', onConfirm);
|
||||||
}
|
}
|
||||||
}, [bindShortcut, unbindShortcut, isOpen, onConfirm]);
|
}, [onConfirm]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ class PageHeader extends Component {
|
|||||||
<IconButton
|
<IconButton
|
||||||
className={styles.donate}
|
className={styles.donate}
|
||||||
name={icons.HEART}
|
name={icons.HEART}
|
||||||
to="https://prowlarr.com/donate"
|
to="https://opencollective.com/prowlarr"
|
||||||
size={14}
|
size={14}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: #515253;
|
color: var(--toobarButtonHoverColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ 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';
|
||||||
@@ -76,7 +75,6 @@ class Page extends Component {
|
|||||||
isSmallScreen,
|
isSmallScreen,
|
||||||
isSidebarVisible,
|
isSidebarVisible,
|
||||||
enableColorImpairedMode,
|
enableColorImpairedMode,
|
||||||
authenticationEnabled,
|
|
||||||
onSidebarToggle,
|
onSidebarToggle,
|
||||||
onSidebarVisibleChange
|
onSidebarVisibleChange
|
||||||
} = this.props;
|
} = this.props;
|
||||||
@@ -111,10 +109,6 @@ 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>
|
||||||
);
|
);
|
||||||
@@ -130,7 +124,6 @@ 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
|
||||||
|
|||||||
@@ -7,11 +7,10 @@ import { saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions';
|
|||||||
import { fetchCustomFilters } from 'Store/Actions/customFilterActions';
|
import { fetchCustomFilters } from 'Store/Actions/customFilterActions';
|
||||||
import { fetchIndexers } from 'Store/Actions/indexerActions';
|
import { fetchIndexers } from 'Store/Actions/indexerActions';
|
||||||
import { fetchIndexerStatus } from 'Store/Actions/indexerStatusActions';
|
import { fetchIndexerStatus } from 'Store/Actions/indexerStatusActions';
|
||||||
import { fetchAppProfiles, fetchGeneralSettings, fetchIndexerCategories, fetchUISettings } from 'Store/Actions/settingsActions';
|
import { fetchAppProfiles, fetchGeneralSettings, fetchIndexerCategories, fetchLanguages, fetchUISettings } from 'Store/Actions/settingsActions';
|
||||||
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';
|
||||||
@@ -49,6 +48,7 @@ const selectIsPopulated = createSelector(
|
|||||||
(state) => state.tags.isPopulated,
|
(state) => state.tags.isPopulated,
|
||||||
(state) => state.settings.ui.isPopulated,
|
(state) => state.settings.ui.isPopulated,
|
||||||
(state) => state.settings.general.isPopulated,
|
(state) => state.settings.general.isPopulated,
|
||||||
|
(state) => state.settings.languages.isPopulated,
|
||||||
(state) => state.settings.appProfiles.isPopulated,
|
(state) => state.settings.appProfiles.isPopulated,
|
||||||
(state) => state.indexers.isPopulated,
|
(state) => state.indexers.isPopulated,
|
||||||
(state) => state.indexerStatus.isPopulated,
|
(state) => state.indexerStatus.isPopulated,
|
||||||
@@ -59,6 +59,7 @@ const selectIsPopulated = createSelector(
|
|||||||
tagsIsPopulated,
|
tagsIsPopulated,
|
||||||
uiSettingsIsPopulated,
|
uiSettingsIsPopulated,
|
||||||
generalSettingsIsPopulated,
|
generalSettingsIsPopulated,
|
||||||
|
languagesIsPopulated,
|
||||||
appProfilesIsPopulated,
|
appProfilesIsPopulated,
|
||||||
indexersIsPopulated,
|
indexersIsPopulated,
|
||||||
indexerStatusIsPopulated,
|
indexerStatusIsPopulated,
|
||||||
@@ -70,6 +71,7 @@ const selectIsPopulated = createSelector(
|
|||||||
tagsIsPopulated &&
|
tagsIsPopulated &&
|
||||||
uiSettingsIsPopulated &&
|
uiSettingsIsPopulated &&
|
||||||
generalSettingsIsPopulated &&
|
generalSettingsIsPopulated &&
|
||||||
|
languagesIsPopulated &&
|
||||||
appProfilesIsPopulated &&
|
appProfilesIsPopulated &&
|
||||||
indexersIsPopulated &&
|
indexersIsPopulated &&
|
||||||
indexerStatusIsPopulated &&
|
indexerStatusIsPopulated &&
|
||||||
@@ -84,6 +86,7 @@ const selectErrors = createSelector(
|
|||||||
(state) => state.tags.error,
|
(state) => state.tags.error,
|
||||||
(state) => state.settings.ui.error,
|
(state) => state.settings.ui.error,
|
||||||
(state) => state.settings.general.error,
|
(state) => state.settings.general.error,
|
||||||
|
(state) => state.settings.languages.error,
|
||||||
(state) => state.settings.appProfiles.error,
|
(state) => state.settings.appProfiles.error,
|
||||||
(state) => state.indexers.error,
|
(state) => state.indexers.error,
|
||||||
(state) => state.indexerStatus.error,
|
(state) => state.indexerStatus.error,
|
||||||
@@ -94,6 +97,7 @@ const selectErrors = createSelector(
|
|||||||
tagsError,
|
tagsError,
|
||||||
uiSettingsError,
|
uiSettingsError,
|
||||||
generalSettingsError,
|
generalSettingsError,
|
||||||
|
languagesError,
|
||||||
appProfilesError,
|
appProfilesError,
|
||||||
indexersError,
|
indexersError,
|
||||||
indexerStatusError,
|
indexerStatusError,
|
||||||
@@ -105,6 +109,7 @@ const selectErrors = createSelector(
|
|||||||
tagsError ||
|
tagsError ||
|
||||||
uiSettingsError ||
|
uiSettingsError ||
|
||||||
generalSettingsError ||
|
generalSettingsError ||
|
||||||
|
languagesError ||
|
||||||
appProfilesError ||
|
appProfilesError ||
|
||||||
indexersError ||
|
indexersError ||
|
||||||
indexerStatusError ||
|
indexerStatusError ||
|
||||||
@@ -118,6 +123,7 @@ const selectErrors = createSelector(
|
|||||||
tagsError,
|
tagsError,
|
||||||
uiSettingsError,
|
uiSettingsError,
|
||||||
generalSettingsError,
|
generalSettingsError,
|
||||||
|
languagesError,
|
||||||
appProfilesError,
|
appProfilesError,
|
||||||
indexersError,
|
indexersError,
|
||||||
indexerStatusError,
|
indexerStatusError,
|
||||||
@@ -134,21 +140,18 @@ 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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -163,6 +166,9 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
dispatchFetchTags() {
|
dispatchFetchTags() {
|
||||||
dispatch(fetchTags());
|
dispatch(fetchTags());
|
||||||
},
|
},
|
||||||
|
dispatchFetchLanguages() {
|
||||||
|
dispatch(fetchLanguages());
|
||||||
|
},
|
||||||
dispatchFetchIndexers() {
|
dispatchFetchIndexers() {
|
||||||
dispatch(fetchIndexers());
|
dispatch(fetchIndexers());
|
||||||
},
|
},
|
||||||
@@ -210,6 +216,7 @@ class PageConnector extends Component {
|
|||||||
if (!this.props.isPopulated) {
|
if (!this.props.isPopulated) {
|
||||||
this.props.dispatchFetchCustomFilters();
|
this.props.dispatchFetchCustomFilters();
|
||||||
this.props.dispatchFetchTags();
|
this.props.dispatchFetchTags();
|
||||||
|
this.props.dispatchFetchLanguages();
|
||||||
this.props.dispatchFetchAppProfiles();
|
this.props.dispatchFetchAppProfiles();
|
||||||
this.props.dispatchFetchIndexers();
|
this.props.dispatchFetchIndexers();
|
||||||
this.props.dispatchFetchIndexerStatus();
|
this.props.dispatchFetchIndexerStatus();
|
||||||
@@ -235,6 +242,7 @@ class PageConnector extends Component {
|
|||||||
isPopulated,
|
isPopulated,
|
||||||
hasError,
|
hasError,
|
||||||
dispatchFetchTags,
|
dispatchFetchTags,
|
||||||
|
dispatchFetchLanguages,
|
||||||
dispatchFetchAppProfiles,
|
dispatchFetchAppProfiles,
|
||||||
dispatchFetchIndexers,
|
dispatchFetchIndexers,
|
||||||
dispatchFetchIndexerStatus,
|
dispatchFetchIndexerStatus,
|
||||||
@@ -275,6 +283,7 @@ PageConnector.propTypes = {
|
|||||||
isSidebarVisible: PropTypes.bool.isRequired,
|
isSidebarVisible: PropTypes.bool.isRequired,
|
||||||
dispatchFetchCustomFilters: PropTypes.func.isRequired,
|
dispatchFetchCustomFilters: PropTypes.func.isRequired,
|
||||||
dispatchFetchTags: PropTypes.func.isRequired,
|
dispatchFetchTags: PropTypes.func.isRequired,
|
||||||
|
dispatchFetchLanguages: PropTypes.func.isRequired,
|
||||||
dispatchFetchAppProfiles: PropTypes.func.isRequired,
|
dispatchFetchAppProfiles: PropTypes.func.isRequired,
|
||||||
dispatchFetchIndexers: PropTypes.func.isRequired,
|
dispatchFetchIndexers: PropTypes.func.isRequired,
|
||||||
dispatchFetchIndexerStatus: PropTypes.func.isRequired,
|
dispatchFetchIndexerStatus: PropTypes.func.isRequired,
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ const SIDEBAR_WIDTH = parseInt(dimensions.sidebarWidth);
|
|||||||
const links = [
|
const links = [
|
||||||
{
|
{
|
||||||
iconName: icons.MOVIE_CONTINUING,
|
iconName: icons.MOVIE_CONTINUING,
|
||||||
title: translate('Indexers'),
|
title: 'Indexers',
|
||||||
to: '/',
|
to: '/',
|
||||||
alias: '/indexers',
|
alias: '/movies',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
title: translate('Stats'),
|
title: translate('Stats'),
|
||||||
@@ -33,13 +33,13 @@ const links = [
|
|||||||
|
|
||||||
{
|
{
|
||||||
iconName: icons.SEARCH,
|
iconName: icons.SEARCH,
|
||||||
title: translate('Search'),
|
title: 'Search',
|
||||||
to: '/search'
|
to: '/search'
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
iconName: icons.ACTIVITY,
|
iconName: icons.ACTIVITY,
|
||||||
title: translate('History'),
|
title: 'History',
|
||||||
to: '/history'
|
to: '/history'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&.default {
|
&.default {
|
||||||
background-color: var(--popoverBodyBackgroundColor);
|
background-color: var(--white);
|
||||||
box-shadow: 0 5px 10px var(--popoverShadowColor);
|
box-shadow: 0 5px 10px var(--popoverShadowColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
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;
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
.authRequiredAlert {
|
|
||||||
composes: alert from '~Components/Alert.css';
|
|
||||||
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
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, authenticationRequiredWarning } 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
|
|
||||||
} = 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}
|
|
||||||
>
|
|
||||||
{authenticationRequiredWarning}
|
|
||||||
</Alert>
|
|
||||||
|
|
||||||
{
|
|
||||||
isPopulated && !error ?
|
|
||||||
<div>
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>{translate('Authentication')}</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.SELECT}
|
|
||||||
name="authenticationMethod"
|
|
||||||
values={authenticationMethodOptions}
|
|
||||||
helpText={translate('AuthenticationMethodHelpText')}
|
|
||||||
onChange={onInputChange}
|
|
||||||
{...authenticationMethod}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
{
|
|
||||||
authenticationEnabled ?
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>{translate('AuthenticationRequired')}</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.SELECT}
|
|
||||||
name="authenticationRequired"
|
|
||||||
values={authenticationRequiredOptions}
|
|
||||||
helpText={translate('AuthenticationRequiredHelpText')}
|
|
||||||
onChange={onInputChange}
|
|
||||||
{...authenticationRequired}
|
|
||||||
/>
|
|
||||||
</FormGroup> :
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
authenticationEnabled ?
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>{translate('Username')}</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.TEXT}
|
|
||||||
name="username"
|
|
||||||
onChange={onInputChange}
|
|
||||||
{...username}
|
|
||||||
/>
|
|
||||||
</FormGroup> :
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
authenticationEnabled ?
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>{translate('Password')}</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.PASSWORD}
|
|
||||||
name="password"
|
|
||||||
onChange={onInputChange}
|
|
||||||
{...password}
|
|
||||||
/>
|
|
||||||
</FormGroup> :
|
|
||||||
null
|
|
||||||
}
|
|
||||||
</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;
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
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);
|
|
||||||
@@ -8,7 +8,6 @@ export const DEVICE = 'device';
|
|||||||
export const KEY_VALUE_LIST = 'keyValueList';
|
export const KEY_VALUE_LIST = 'keyValueList';
|
||||||
export const INFO = 'info';
|
export const INFO = 'info';
|
||||||
export const MOVIE_MONITORED_SELECT = 'movieMonitoredSelect';
|
export const MOVIE_MONITORED_SELECT = 'movieMonitoredSelect';
|
||||||
export const CATEGORY_SELECT = 'newznabCategorySelect';
|
|
||||||
export const NUMBER = 'number';
|
export const NUMBER = 'number';
|
||||||
export const OAUTH = 'oauth';
|
export const OAUTH = 'oauth';
|
||||||
export const PASSWORD = 'password';
|
export const PASSWORD = 'password';
|
||||||
@@ -33,7 +32,6 @@ export const all = [
|
|||||||
KEY_VALUE_LIST,
|
KEY_VALUE_LIST,
|
||||||
INFO,
|
INFO,
|
||||||
MOVIE_MONITORED_SELECT,
|
MOVIE_MONITORED_SELECT,
|
||||||
CATEGORY_SELECT,
|
|
||||||
NUMBER,
|
NUMBER,
|
||||||
OAUTH,
|
OAUTH,
|
||||||
PASSWORD,
|
PASSWORD,
|
||||||
|
|||||||
@@ -226,42 +226,6 @@ class HistoryRow extends Component {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
data.label ?
|
|
||||||
<HistoryRowParameter
|
|
||||||
title='Label'
|
|
||||||
value={data.label}
|
|
||||||
/> :
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
data.track ?
|
|
||||||
<HistoryRowParameter
|
|
||||||
title='Track'
|
|
||||||
value={data.track}
|
|
||||||
/> :
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
data.year ?
|
|
||||||
<HistoryRowParameter
|
|
||||||
title='Year'
|
|
||||||
value={data.year}
|
|
||||||
/> :
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
data.genre ?
|
|
||||||
<HistoryRowParameter
|
|
||||||
title='Genre'
|
|
||||||
value={data.genre}
|
|
||||||
/> :
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
data.author ?
|
data.author ?
|
||||||
<HistoryRowParameter
|
<HistoryRowParameter
|
||||||
@@ -279,15 +243,6 @@ class HistoryRow extends Component {
|
|||||||
/> :
|
/> :
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
data.publisher ?
|
|
||||||
<HistoryRowParameter
|
|
||||||
title='Publisher'
|
|
||||||
value={data.publisher}
|
|
||||||
/> :
|
|
||||||
null
|
|
||||||
}
|
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ class AddIndexerModalContent extends Component {
|
|||||||
const filteredIndexers = indexers.filter((indexer) => {
|
const filteredIndexers = indexers.filter((indexer) => {
|
||||||
const { filter, filterProtocols, filterLanguages, filterPrivacyLevels } = this.state;
|
const { filter, filterProtocols, filterLanguages, filterPrivacyLevels } = this.state;
|
||||||
|
|
||||||
if (!indexer.name.toLowerCase().includes(filter.toLocaleLowerCase()) && !indexer.description.toLowerCase().includes(filter.toLocaleLowerCase())) {
|
if (!indexer.name.toLowerCase().includes(filter.toLocaleLowerCase())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,15 +61,15 @@ class TagsModalContent extends Component {
|
|||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const applyTagsOptions = [
|
const applyTagsOptions = [
|
||||||
{ key: 'add', value: translate('Add') },
|
{ key: 'add', value: 'Add' },
|
||||||
{ key: 'remove', value: translate('Remove') },
|
{ key: 'remove', value: 'Remove' },
|
||||||
{ key: 'replace', value: translate('Replace') }
|
{ key: 'replace', value: 'Replace' }
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
{translate('Tags')}
|
Tags
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ class IndexerIndex extends Component {
|
|||||||
|
|
||||||
onKeyUp = (event) => {
|
onKeyUp = (event) => {
|
||||||
const jumpBarItems = this.state.jumpBarItems.order;
|
const jumpBarItems = this.state.jumpBarItems.order;
|
||||||
if (event.composedPath && event.composedPath().length === 4) {
|
if (event.path.length === 4) {
|
||||||
if (event.keyCode === keyCodes.HOME && event.ctrlKey) {
|
if (event.keyCode === keyCodes.HOME && event.ctrlKey) {
|
||||||
this.setState({ jumpToCharacter: jumpBarItems[0] });
|
this.setState({ jumpToCharacter: jumpBarItems[0] });
|
||||||
}
|
}
|
||||||
@@ -272,7 +272,6 @@ class IndexerIndex extends Component {
|
|||||||
saveError,
|
saveError,
|
||||||
isDeleting,
|
isDeleting,
|
||||||
isTestingAll,
|
isTestingAll,
|
||||||
isSyncingIndexers,
|
|
||||||
deleteError,
|
deleteError,
|
||||||
onScroll,
|
onScroll,
|
||||||
onSortSelect,
|
onSortSelect,
|
||||||
@@ -310,15 +309,6 @@ class IndexerIndex extends Component {
|
|||||||
onPress={this.onAddIndexerPress}
|
onPress={this.onAddIndexerPress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PageToolbarSeparator />
|
|
||||||
|
|
||||||
<PageToolbarButton
|
|
||||||
label={translate('SyncAppIndexers')}
|
|
||||||
iconName={icons.REFRESH}
|
|
||||||
isSpinning={isSyncingIndexers}
|
|
||||||
onPress={this.props.onAppIndexerSyncPress}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<PageToolbarButton
|
<PageToolbarButton
|
||||||
label={translate('TestAllIndexers')}
|
label={translate('TestAllIndexers')}
|
||||||
iconName={icons.TEST}
|
iconName={icons.TEST}
|
||||||
@@ -503,12 +493,10 @@ IndexerIndex.propTypes = {
|
|||||||
saveError: PropTypes.object,
|
saveError: PropTypes.object,
|
||||||
isDeleting: PropTypes.bool.isRequired,
|
isDeleting: PropTypes.bool.isRequired,
|
||||||
isTestingAll: PropTypes.bool.isRequired,
|
isTestingAll: PropTypes.bool.isRequired,
|
||||||
isSyncingIndexers: PropTypes.bool.isRequired,
|
|
||||||
deleteError: PropTypes.object,
|
deleteError: PropTypes.object,
|
||||||
onSortSelect: PropTypes.func.isRequired,
|
onSortSelect: PropTypes.func.isRequired,
|
||||||
onFilterSelect: PropTypes.func.isRequired,
|
onFilterSelect: PropTypes.func.isRequired,
|
||||||
onTestAllPress: PropTypes.func.isRequired,
|
onTestAllPress: PropTypes.func.isRequired,
|
||||||
onAppIndexerSyncPress: PropTypes.func.isRequired,
|
|
||||||
onScroll: PropTypes.func.isRequired,
|
onScroll: PropTypes.func.isRequired,
|
||||||
onSaveSelected: PropTypes.func.isRequired
|
onSaveSelected: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,13 +2,10 @@ 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';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import * as commandNames from 'Commands/commandNames';
|
|
||||||
import withScrollPosition from 'Components/withScrollPosition';
|
import withScrollPosition from 'Components/withScrollPosition';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
|
||||||
import { testAllIndexers } from 'Store/Actions/indexerActions';
|
import { testAllIndexers } from 'Store/Actions/indexerActions';
|
||||||
import { saveIndexerEditor, setMovieFilter, setMovieSort, setMovieTableOption } from 'Store/Actions/indexerIndexActions';
|
import { saveIndexerEditor, setMovieFilter, setMovieSort, setMovieTableOption } from 'Store/Actions/indexerIndexActions';
|
||||||
import scrollPositions from 'Store/scrollPositions';
|
import scrollPositions from 'Store/scrollPositions';
|
||||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
|
||||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||||
import createIndexerClientSideCollectionItemsSelector from 'Store/Selectors/createIndexerClientSideCollectionItemsSelector';
|
import createIndexerClientSideCollectionItemsSelector from 'Store/Selectors/createIndexerClientSideCollectionItemsSelector';
|
||||||
import IndexerIndex from './IndexerIndex';
|
import IndexerIndex from './IndexerIndex';
|
||||||
@@ -16,16 +13,13 @@ import IndexerIndex from './IndexerIndex';
|
|||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createIndexerClientSideCollectionItemsSelector('indexerIndex'),
|
createIndexerClientSideCollectionItemsSelector('indexerIndex'),
|
||||||
createCommandExecutingSelector(commandNames.APP_INDEXER_SYNC),
|
|
||||||
createDimensionsSelector(),
|
createDimensionsSelector(),
|
||||||
(
|
(
|
||||||
indexers,
|
indexers,
|
||||||
isSyncingIndexers,
|
|
||||||
dimensionsState
|
dimensionsState
|
||||||
) => {
|
) => {
|
||||||
return {
|
return {
|
||||||
...indexers,
|
...indexers,
|
||||||
isSyncingIndexers,
|
|
||||||
isSmallScreen: dimensionsState.isSmallScreen
|
isSmallScreen: dimensionsState.isSmallScreen
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -52,12 +46,6 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
|
|
||||||
onTestAllPress() {
|
onTestAllPress() {
|
||||||
dispatch(testAllIndexers());
|
dispatch(testAllIndexers());
|
||||||
},
|
|
||||||
|
|
||||||
onAppIndexerSyncPress() {
|
|
||||||
dispatch(executeCommand({
|
|
||||||
name: commandNames.APP_INDEXER_SYNC
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,21 +35,21 @@ class IndexerIndexFooter extends PureComponent {
|
|||||||
<div className={styles.legendItem}>
|
<div className={styles.legendItem}>
|
||||||
<div className={styles.enabled} />
|
<div className={styles.enabled} />
|
||||||
<div>
|
<div>
|
||||||
{translate('Enabled')}
|
Enabled
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.legendItem}>
|
<div className={styles.legendItem}>
|
||||||
<div className={styles.redirected} />
|
<div className={styles.redirected} />
|
||||||
<div>
|
<div>
|
||||||
{translate('EnabledRedirected')}
|
Enabled, Redirected
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.legendItem}>
|
<div className={styles.legendItem}>
|
||||||
<div className={styles.disabled} />
|
<div className={styles.disabled} />
|
||||||
<div>
|
<div>
|
||||||
{translate('Disabled')}
|
Disabled
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ class IndexerIndexFooter extends PureComponent {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
{translate('Error')}
|
Error
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ function IndexerIndexSortMenu(props) {
|
|||||||
sortDirection={sortDirection}
|
sortDirection={sortDirection}
|
||||||
onPress={onSortSelect}
|
onPress={onSortSelect}
|
||||||
>
|
>
|
||||||
{translate('Status')}
|
Status
|
||||||
</SortMenuItem>
|
</SortMenuItem>
|
||||||
|
|
||||||
<SortMenuItem
|
<SortMenuItem
|
||||||
@@ -62,7 +62,7 @@ function IndexerIndexSortMenu(props) {
|
|||||||
sortDirection={sortDirection}
|
sortDirection={sortDirection}
|
||||||
onPress={onSortSelect}
|
onPress={onSortSelect}
|
||||||
>
|
>
|
||||||
{translate('Priority')}
|
{'Priority'}
|
||||||
</SortMenuItem>
|
</SortMenuItem>
|
||||||
|
|
||||||
<SortMenuItem
|
<SortMenuItem
|
||||||
@@ -71,7 +71,7 @@ function IndexerIndexSortMenu(props) {
|
|||||||
sortDirection={sortDirection}
|
sortDirection={sortDirection}
|
||||||
onPress={onSortSelect}
|
onPress={onSortSelect}
|
||||||
>
|
>
|
||||||
{translate('Protocol')}
|
{'Protocol'}
|
||||||
</SortMenuItem>
|
</SortMenuItem>
|
||||||
|
|
||||||
<SortMenuItem
|
<SortMenuItem
|
||||||
@@ -80,7 +80,7 @@ function IndexerIndexSortMenu(props) {
|
|||||||
sortDirection={sortDirection}
|
sortDirection={sortDirection}
|
||||||
onPress={onSortSelect}
|
onPress={onSortSelect}
|
||||||
>
|
>
|
||||||
{translate('Privacy')}
|
{'Privacy'}
|
||||||
</SortMenuItem>
|
</SortMenuItem>
|
||||||
</MenuContent>
|
</MenuContent>
|
||||||
</SortMenu>
|
</SortMenu>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ function CapabilitiesLabel(props) {
|
|||||||
let filteredList = categories.filter((item) => item.id < 100000);
|
let filteredList = categories.filter((item) => item.id < 100000);
|
||||||
|
|
||||||
if (categoryFilter.length > 0) {
|
if (categoryFilter.length > 0) {
|
||||||
filteredList = filteredList.filter((item) => categoryFilter.includes(item.id) || (item.subCategories && item.subCategories.some((r) => categoryFilter.includes(r.id))));
|
filteredList = filteredList.filter((item) => categoryFilter.includes(item.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
const nameList = filteredList.map((item) => item.name).sort();
|
const nameList = filteredList.map((item) => item.name).sort();
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ class IndexerIndexRow extends Component {
|
|||||||
privacy,
|
privacy,
|
||||||
priority,
|
priority,
|
||||||
status,
|
status,
|
||||||
fields,
|
|
||||||
appProfile,
|
appProfile,
|
||||||
added,
|
added,
|
||||||
capabilities,
|
capabilities,
|
||||||
@@ -97,8 +96,6 @@ class IndexerIndexRow extends Component {
|
|||||||
isIndexerInfoModalOpen
|
isIndexerInfoModalOpen
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const baseUrl = fields.find((field) => field.name === 'baseUrl')?.value ?? (Array.isArray(indexerUrls) ? indexerUrls[0] : undefined);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{
|
{
|
||||||
@@ -248,12 +245,12 @@ class IndexerIndexRow extends Component {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{
|
{
|
||||||
baseUrl ?
|
indexerUrls ?
|
||||||
<IconButton
|
<IconButton
|
||||||
className={styles.externalLink}
|
className={styles.externalLink}
|
||||||
name={icons.EXTERNAL_LINK}
|
name={icons.EXTERNAL_LINK}
|
||||||
title={translate('Website')}
|
title={translate('Website')}
|
||||||
to={baseUrl.replace(/(:\/\/)api\./, '$1')}
|
to={indexerUrls[0].replace('api.', '')}
|
||||||
/> : null
|
/> : null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,7 +299,6 @@ IndexerIndexRow.propTypes = {
|
|||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
enable: PropTypes.bool.isRequired,
|
enable: PropTypes.bool.isRequired,
|
||||||
redirect: PropTypes.bool.isRequired,
|
redirect: PropTypes.bool.isRequired,
|
||||||
fields: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
appProfile: PropTypes.object.isRequired,
|
appProfile: PropTypes.object.isRequired,
|
||||||
status: PropTypes.object,
|
status: PropTypes.object,
|
||||||
capabilities: PropTypes.object,
|
capabilities: PropTypes.object,
|
||||||
|
|||||||
@@ -20,14 +20,11 @@ function IndexerInfoModalContent(props) {
|
|||||||
encoding,
|
encoding,
|
||||||
language,
|
language,
|
||||||
indexerUrls,
|
indexerUrls,
|
||||||
fields,
|
|
||||||
protocol,
|
protocol,
|
||||||
capabilities,
|
capabilities,
|
||||||
onModalClose
|
onModalClose
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const baseUrl = fields.find((field) => field.name === 'baseUrl')?.value ?? indexerUrls[0];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
@@ -60,7 +57,7 @@ function IndexerInfoModalContent(props) {
|
|||||||
/>
|
/>
|
||||||
<DescriptionListItemTitle>{translate('IndexerSite')}</DescriptionListItemTitle>
|
<DescriptionListItemTitle>{translate('IndexerSite')}</DescriptionListItemTitle>
|
||||||
<DescriptionListItemDescription>
|
<DescriptionListItemDescription>
|
||||||
<Link to={baseUrl}>{baseUrl}</Link>
|
<Link to={indexerUrls[0]}>{indexerUrls[0]}</Link>
|
||||||
</DescriptionListItemDescription>
|
</DescriptionListItemDescription>
|
||||||
<DescriptionListItemTitle>{`${protocol === 'usenet' ? 'Newznab' : 'Torznab'} Url`}</DescriptionListItemTitle>
|
<DescriptionListItemTitle>{`${protocol === 'usenet' ? 'Newznab' : 'Torznab'} Url`}</DescriptionListItemTitle>
|
||||||
<DescriptionListItemDescription>
|
<DescriptionListItemDescription>
|
||||||
@@ -117,7 +114,6 @@ IndexerInfoModalContent.propTypes = {
|
|||||||
encoding: PropTypes.string.isRequired,
|
encoding: PropTypes.string.isRequired,
|
||||||
language: PropTypes.string.isRequired,
|
language: PropTypes.string.isRequired,
|
||||||
indexerUrls: PropTypes.arrayOf(PropTypes.string).isRequired,
|
indexerUrls: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||||
fields: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
protocol: PropTypes.string.isRequired,
|
protocol: PropTypes.string.isRequired,
|
||||||
capabilities: PropTypes.object.isRequired,
|
capabilities: PropTypes.object.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
$hoverScale: 1.05;
|
|
||||||
|
|
||||||
.content {
|
|
||||||
display: flex;
|
|
||||||
flex-grow: 0;
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
border-radius: 4px;
|
|
||||||
background-color: var(--cardBackgroundColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.info {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex-grow: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.titleRow {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
height: 38px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.indexerRow {
|
|
||||||
color: var(--disabledColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.infoRow {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
overflow: hidden;
|
|
||||||
width: 85%;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 14px;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
@@ -1,202 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import TextTruncate from 'react-text-truncate';
|
|
||||||
import Label from 'Components/Label';
|
|
||||||
import IconButton from 'Components/Link/IconButton';
|
|
||||||
import Link from 'Components/Link/Link';
|
|
||||||
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
|
||||||
import { icons, kinds } from 'Helpers/Props';
|
|
||||||
import CategoryLabel from 'Search/Table/CategoryLabel';
|
|
||||||
import Peers from 'Search/Table/Peers';
|
|
||||||
import ProtocolLabel from 'Search/Table/ProtocolLabel';
|
|
||||||
import dimensions from 'Styles/Variables/dimensions';
|
|
||||||
import formatAge from 'Utilities/Number/formatAge';
|
|
||||||
import formatBytes from 'Utilities/Number/formatBytes';
|
|
||||||
import translate from 'Utilities/String/translate';
|
|
||||||
import styles from './SearchIndexOverview.css';
|
|
||||||
|
|
||||||
const columnPadding = parseInt(dimensions.movieIndexColumnPadding);
|
|
||||||
const columnPaddingSmallScreen = parseInt(dimensions.movieIndexColumnPaddingSmallScreen);
|
|
||||||
|
|
||||||
function getContentHeight(rowHeight, isSmallScreen) {
|
|
||||||
const padding = isSmallScreen ? columnPaddingSmallScreen : columnPadding;
|
|
||||||
|
|
||||||
return rowHeight - padding;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDownloadIcon(isGrabbing, isGrabbed, grabError) {
|
|
||||||
if (isGrabbing) {
|
|
||||||
return icons.SPINNER;
|
|
||||||
} else if (isGrabbed) {
|
|
||||||
return icons.DOWNLOADING;
|
|
||||||
} else if (grabError) {
|
|
||||||
return icons.DOWNLOADING;
|
|
||||||
}
|
|
||||||
|
|
||||||
return icons.DOWNLOAD;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDownloadTooltip(isGrabbing, isGrabbed, grabError) {
|
|
||||||
if (isGrabbing) {
|
|
||||||
return '';
|
|
||||||
} else if (isGrabbed) {
|
|
||||||
return translate('AddedToDownloadClient');
|
|
||||||
} else if (grabError) {
|
|
||||||
return grabError;
|
|
||||||
}
|
|
||||||
|
|
||||||
return translate('AddToDownloadClient');
|
|
||||||
}
|
|
||||||
|
|
||||||
class SearchIndexOverview extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onGrabPress = () => {
|
|
||||||
const {
|
|
||||||
guid,
|
|
||||||
indexerId,
|
|
||||||
onGrabPress
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
onGrabPress({
|
|
||||||
guid,
|
|
||||||
indexerId
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
title,
|
|
||||||
infoUrl,
|
|
||||||
protocol,
|
|
||||||
downloadUrl,
|
|
||||||
categories,
|
|
||||||
seeders,
|
|
||||||
leechers,
|
|
||||||
size,
|
|
||||||
age,
|
|
||||||
ageHours,
|
|
||||||
ageMinutes,
|
|
||||||
indexer,
|
|
||||||
rowHeight,
|
|
||||||
isSmallScreen,
|
|
||||||
isGrabbed,
|
|
||||||
isGrabbing,
|
|
||||||
grabError
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const contentHeight = getContentHeight(rowHeight, isSmallScreen);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.container}>
|
|
||||||
<div className={styles.content}>
|
|
||||||
<div className={styles.info} style={{ height: contentHeight }}>
|
|
||||||
<div className={styles.titleRow}>
|
|
||||||
<div className={styles.title}>
|
|
||||||
<Link
|
|
||||||
to={infoUrl}
|
|
||||||
title={title}
|
|
||||||
>
|
|
||||||
<TextTruncate
|
|
||||||
line={2}
|
|
||||||
text={title}
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.actions}>
|
|
||||||
<SpinnerIconButton
|
|
||||||
name={getDownloadIcon(isGrabbing, isGrabbed, grabError)}
|
|
||||||
kind={grabError ? kinds.DANGER : kinds.DEFAULT}
|
|
||||||
title={getDownloadTooltip(isGrabbing, isGrabbed, grabError)}
|
|
||||||
isDisabled={isGrabbed}
|
|
||||||
isSpinning={isGrabbing}
|
|
||||||
onPress={this.onGrabPress}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<IconButton
|
|
||||||
className={styles.downloadLink}
|
|
||||||
name={icons.SAVE}
|
|
||||||
title={translate('Save')}
|
|
||||||
to={downloadUrl}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={styles.indexerRow}>
|
|
||||||
{indexer}
|
|
||||||
</div>
|
|
||||||
<div className={styles.infoRow}>
|
|
||||||
<ProtocolLabel
|
|
||||||
protocol={protocol}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{
|
|
||||||
protocol === 'torrent' &&
|
|
||||||
<Peers
|
|
||||||
seeders={seeders}
|
|
||||||
leechers={leechers}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
|
|
||||||
<Label>
|
|
||||||
{formatBytes(size)}
|
|
||||||
</Label>
|
|
||||||
|
|
||||||
<Label>
|
|
||||||
{formatAge(age, ageHours, ageMinutes)}
|
|
||||||
</Label>
|
|
||||||
|
|
||||||
<CategoryLabel
|
|
||||||
categories={categories}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SearchIndexOverview.propTypes = {
|
|
||||||
guid: PropTypes.string.isRequired,
|
|
||||||
categories: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
protocol: PropTypes.string.isRequired,
|
|
||||||
age: PropTypes.number.isRequired,
|
|
||||||
ageHours: PropTypes.number.isRequired,
|
|
||||||
ageMinutes: PropTypes.number.isRequired,
|
|
||||||
publishDate: PropTypes.string.isRequired,
|
|
||||||
title: PropTypes.string.isRequired,
|
|
||||||
infoUrl: PropTypes.string.isRequired,
|
|
||||||
downloadUrl: PropTypes.string.isRequired,
|
|
||||||
indexerId: PropTypes.number.isRequired,
|
|
||||||
indexer: PropTypes.string.isRequired,
|
|
||||||
size: PropTypes.number.isRequired,
|
|
||||||
files: PropTypes.number,
|
|
||||||
grabs: PropTypes.number,
|
|
||||||
seeders: PropTypes.number,
|
|
||||||
leechers: PropTypes.number,
|
|
||||||
indexerFlags: PropTypes.arrayOf(PropTypes.string).isRequired,
|
|
||||||
rowHeight: PropTypes.number.isRequired,
|
|
||||||
showRelativeDates: PropTypes.bool.isRequired,
|
|
||||||
shortDateFormat: PropTypes.string.isRequired,
|
|
||||||
longDateFormat: PropTypes.string.isRequired,
|
|
||||||
timeFormat: PropTypes.string.isRequired,
|
|
||||||
isSmallScreen: PropTypes.bool.isRequired,
|
|
||||||
onGrabPress: PropTypes.func.isRequired,
|
|
||||||
isGrabbing: PropTypes.bool.isRequired,
|
|
||||||
isGrabbed: PropTypes.bool.isRequired,
|
|
||||||
grabError: PropTypes.string
|
|
||||||
};
|
|
||||||
|
|
||||||
SearchIndexOverview.defaultProps = {
|
|
||||||
isGrabbing: false,
|
|
||||||
isGrabbed: false
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SearchIndexOverview;
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
.grid {
|
|
||||||
flex: 1 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
&:hover {
|
|
||||||
.content {
|
|
||||||
background-color: var(--tableRowHoverBackgroundColor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,209 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { Grid, WindowScroller } from 'react-virtualized';
|
|
||||||
import Measure from 'Components/Measure';
|
|
||||||
import SearchIndexItemConnector from 'Search/Table/SearchIndexItemConnector';
|
|
||||||
import getIndexOfFirstCharacter from 'Utilities/Array/getIndexOfFirstCharacter';
|
|
||||||
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
|
|
||||||
import SearchIndexOverview from './SearchIndexOverview';
|
|
||||||
import styles from './SearchIndexOverviews.css';
|
|
||||||
|
|
||||||
class SearchIndexOverviews extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Lifecycle
|
|
||||||
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
width: 0,
|
|
||||||
columnCount: 1,
|
|
||||||
rowHeight: 100,
|
|
||||||
scrollRestored: false
|
|
||||||
};
|
|
||||||
|
|
||||||
this._grid = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
|
||||||
const {
|
|
||||||
items,
|
|
||||||
sortKey,
|
|
||||||
jumpToCharacter,
|
|
||||||
scrollTop,
|
|
||||||
isSmallScreen
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const {
|
|
||||||
width,
|
|
||||||
rowHeight,
|
|
||||||
scrollRestored
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
if (prevProps.sortKey !== sortKey) {
|
|
||||||
this.calculateGrid(this.state.width, isSmallScreen);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
this._grid &&
|
|
||||||
(prevState.width !== width ||
|
|
||||||
prevState.rowHeight !== rowHeight ||
|
|
||||||
hasDifferentItemsOrOrder(prevProps.items, items, 'guid')
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
// recomputeGridSize also forces Grid to discard its cache of rendered cells
|
|
||||||
this._grid.recomputeGridSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._grid && scrollTop !== 0 && !scrollRestored) {
|
|
||||||
this.setState({ scrollRestored: true });
|
|
||||||
this._grid.scrollToPosition({ scrollTop });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
|
|
||||||
const index = getIndexOfFirstCharacter(items, jumpToCharacter);
|
|
||||||
|
|
||||||
if (this._grid && index != null) {
|
|
||||||
|
|
||||||
this._grid.scrollToCell({
|
|
||||||
rowIndex: index,
|
|
||||||
columnIndex: 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Control
|
|
||||||
|
|
||||||
setGridRef = (ref) => {
|
|
||||||
this._grid = ref;
|
|
||||||
};
|
|
||||||
|
|
||||||
calculateGrid = (width = this.state.width, isSmallScreen) => {
|
|
||||||
const rowHeight = 100;
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
width,
|
|
||||||
rowHeight
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
cellRenderer = ({ key, rowIndex, style }) => {
|
|
||||||
const {
|
|
||||||
items,
|
|
||||||
showRelativeDates,
|
|
||||||
shortDateFormat,
|
|
||||||
longDateFormat,
|
|
||||||
timeFormat,
|
|
||||||
isSmallScreen,
|
|
||||||
onGrabPress
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const {
|
|
||||||
rowHeight
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
const release = items[rowIndex];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={styles.container}
|
|
||||||
key={key}
|
|
||||||
style={style}
|
|
||||||
>
|
|
||||||
<SearchIndexItemConnector
|
|
||||||
key={release.guid}
|
|
||||||
component={SearchIndexOverview}
|
|
||||||
rowHeight={rowHeight}
|
|
||||||
showRelativeDates={showRelativeDates}
|
|
||||||
shortDateFormat={shortDateFormat}
|
|
||||||
longDateFormat={longDateFormat}
|
|
||||||
timeFormat={timeFormat}
|
|
||||||
isSmallScreen={isSmallScreen}
|
|
||||||
style={style}
|
|
||||||
guid={release.guid}
|
|
||||||
onGrabPress={onGrabPress}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onMeasure = ({ width }) => {
|
|
||||||
this.calculateGrid(width, this.props.isSmallScreen);
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
items
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const {
|
|
||||||
width,
|
|
||||||
rowHeight
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Measure
|
|
||||||
whitelist={['width']}
|
|
||||||
onMeasure={this.onMeasure}
|
|
||||||
>
|
|
||||||
<WindowScroller
|
|
||||||
scrollElement={undefined}
|
|
||||||
>
|
|
||||||
{({ height, registerChild, onChildScroll, scrollTop }) => {
|
|
||||||
if (!height) {
|
|
||||||
return <div />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div ref={registerChild}>
|
|
||||||
<Grid
|
|
||||||
ref={this.setGridRef}
|
|
||||||
className={styles.grid}
|
|
||||||
autoHeight={true}
|
|
||||||
height={height}
|
|
||||||
columnCount={1}
|
|
||||||
columnWidth={width}
|
|
||||||
rowCount={items.length}
|
|
||||||
rowHeight={rowHeight}
|
|
||||||
width={width}
|
|
||||||
onScroll={onChildScroll}
|
|
||||||
scrollTop={scrollTop}
|
|
||||||
overscanRowCount={2}
|
|
||||||
cellRenderer={this.cellRenderer}
|
|
||||||
scrollToAlignment={'start'}
|
|
||||||
isScrollingOptOut={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</WindowScroller>
|
|
||||||
</Measure>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SearchIndexOverviews.propTypes = {
|
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
sortKey: PropTypes.string,
|
|
||||||
scrollTop: PropTypes.number.isRequired,
|
|
||||||
jumpToCharacter: PropTypes.string,
|
|
||||||
scroller: PropTypes.instanceOf(Element).isRequired,
|
|
||||||
showRelativeDates: PropTypes.bool.isRequired,
|
|
||||||
shortDateFormat: PropTypes.string.isRequired,
|
|
||||||
longDateFormat: PropTypes.string.isRequired,
|
|
||||||
isSmallScreen: PropTypes.bool.isRequired,
|
|
||||||
timeFormat: PropTypes.string.isRequired,
|
|
||||||
onGrabPress: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SearchIndexOverviews;
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import { connect } from 'react-redux';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import { grabRelease } from 'Store/Actions/releaseActions';
|
|
||||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
|
||||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
|
||||||
import SearchIndexOverviews from './SearchIndexOverviews';
|
|
||||||
|
|
||||||
function createMapStateToProps() {
|
|
||||||
return createSelector(
|
|
||||||
createUISettingsSelector(),
|
|
||||||
createDimensionsSelector(),
|
|
||||||
(uiSettings, dimensions) => {
|
|
||||||
return {
|
|
||||||
showRelativeDates: uiSettings.showRelativeDates,
|
|
||||||
shortDateFormat: uiSettings.shortDateFormat,
|
|
||||||
longDateFormat: uiSettings.longDateFormat,
|
|
||||||
timeFormat: uiSettings.timeFormat,
|
|
||||||
isSmallScreen: dimensions.isSmallScreen
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createMapDispatchToProps(dispatch, props) {
|
|
||||||
return {
|
|
||||||
onGrabPress(payload) {
|
|
||||||
dispatch(grabRelease(payload));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(createMapStateToProps, createMapDispatchToProps)(SearchIndexOverviews);
|
|
||||||
@@ -24,7 +24,6 @@ const searchOptions = [
|
|||||||
const seriesTokens = [
|
const seriesTokens = [
|
||||||
{ token: '{ImdbId:tt1234567}', example: 'tt12345' },
|
{ token: '{ImdbId:tt1234567}', example: 'tt12345' },
|
||||||
{ token: '{TvdbId:12345}', example: '12345' },
|
{ token: '{TvdbId:12345}', example: '12345' },
|
||||||
{ token: '{TmdbId:12345}', example: '12345' },
|
|
||||||
{ token: '{TvMazeId:12345}', example: '54321' },
|
{ token: '{TvMazeId:12345}', example: '54321' },
|
||||||
{ token: '{Season:00}', example: '01' },
|
{ token: '{Season:00}', example: '01' },
|
||||||
{ token: '{Episode:00}', example: '01' }
|
{ token: '{Episode:00}', example: '01' }
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
|||||||
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||||
import { align, icons, sortDirections } from 'Helpers/Props';
|
import { align, icons, sortDirections } from 'Helpers/Props';
|
||||||
|
import AddIndexerModal from 'Indexer/Add/AddIndexerModal';
|
||||||
|
import EditIndexerModalConnector from 'Indexer/Edit/EditIndexerModalConnector';
|
||||||
import NoIndexer from 'Indexer/NoIndexer';
|
import NoIndexer from 'Indexer/NoIndexer';
|
||||||
import * as keyCodes from 'Utilities/Constants/keyCodes';
|
import * as keyCodes from 'Utilities/Constants/keyCodes';
|
||||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||||
@@ -21,17 +23,12 @@ import selectAll from 'Utilities/Table/selectAll';
|
|||||||
import toggleSelected from 'Utilities/Table/toggleSelected';
|
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||||
import SearchIndexFilterMenu from './Menus/SearchIndexFilterMenu';
|
import SearchIndexFilterMenu from './Menus/SearchIndexFilterMenu';
|
||||||
import SearchIndexSortMenu from './Menus/SearchIndexSortMenu';
|
import SearchIndexSortMenu from './Menus/SearchIndexSortMenu';
|
||||||
import SearchIndexOverviewsConnector from './Mobile/SearchIndexOverviewsConnector';
|
|
||||||
import NoSearchResults from './NoSearchResults';
|
import NoSearchResults from './NoSearchResults';
|
||||||
import SearchFooterConnector from './SearchFooterConnector';
|
import SearchFooterConnector from './SearchFooterConnector';
|
||||||
import SearchIndexTableConnector from './Table/SearchIndexTableConnector';
|
import SearchIndexTableConnector from './Table/SearchIndexTableConnector';
|
||||||
import styles from './SearchIndex.css';
|
import styles from './SearchIndex.css';
|
||||||
|
|
||||||
function getViewComponent(isSmallScreen) {
|
function getViewComponent() {
|
||||||
if (isSmallScreen) {
|
|
||||||
return SearchIndexOverviewsConnector;
|
|
||||||
}
|
|
||||||
|
|
||||||
return SearchIndexTableConnector;
|
return SearchIndexTableConnector;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,6 +44,8 @@ class SearchIndex extends Component {
|
|||||||
scroller: null,
|
scroller: null,
|
||||||
jumpBarItems: { order: [] },
|
jumpBarItems: { order: [] },
|
||||||
jumpToCharacter: null,
|
jumpToCharacter: null,
|
||||||
|
isAddIndexerModalOpen: false,
|
||||||
|
isEditIndexerModalOpen: false,
|
||||||
searchType: null,
|
searchType: null,
|
||||||
lastToggled: null,
|
lastToggled: null,
|
||||||
allSelected: false,
|
allSelected: false,
|
||||||
@@ -178,6 +177,21 @@ class SearchIndex extends Component {
|
|||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
|
onAddIndexerPress = () => {
|
||||||
|
this.setState({ isAddIndexerModalOpen: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
onAddIndexerModalClose = ({ indexerSelected = false } = {}) => {
|
||||||
|
this.setState({
|
||||||
|
isAddIndexerModalOpen: false,
|
||||||
|
isEditIndexerModalOpen: indexerSelected
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onEditIndexerModalClose = () => {
|
||||||
|
this.setState({ isEditIndexerModalOpen: false });
|
||||||
|
};
|
||||||
|
|
||||||
onJumpBarItemPress = (jumpToCharacter) => {
|
onJumpBarItemPress = (jumpToCharacter) => {
|
||||||
this.setState({ jumpToCharacter });
|
this.setState({ jumpToCharacter });
|
||||||
};
|
};
|
||||||
@@ -239,7 +253,6 @@ class SearchIndex extends Component {
|
|||||||
onScroll,
|
onScroll,
|
||||||
onSortSelect,
|
onSortSelect,
|
||||||
onFilterSelect,
|
onFilterSelect,
|
||||||
isSmallScreen,
|
|
||||||
hasIndexers,
|
hasIndexers,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
@@ -247,6 +260,8 @@ class SearchIndex extends Component {
|
|||||||
const {
|
const {
|
||||||
scroller,
|
scroller,
|
||||||
jumpBarItems,
|
jumpBarItems,
|
||||||
|
isAddIndexerModalOpen,
|
||||||
|
isEditIndexerModalOpen,
|
||||||
jumpToCharacter,
|
jumpToCharacter,
|
||||||
selectedState,
|
selectedState,
|
||||||
allSelected,
|
allSelected,
|
||||||
@@ -255,7 +270,7 @@ class SearchIndex extends Component {
|
|||||||
|
|
||||||
const selectedIndexerIds = this.getSelectedIds();
|
const selectedIndexerIds = this.getSelectedIds();
|
||||||
|
|
||||||
const ViewComponent = getViewComponent(isSmallScreen);
|
const ViewComponent = getViewComponent();
|
||||||
const isLoaded = !!(!error && isPopulated && items.length && scroller);
|
const isLoaded = !!(!error && isPopulated && items.length && scroller);
|
||||||
const hasNoIndexer = !totalItems;
|
const hasNoIndexer = !totalItems;
|
||||||
|
|
||||||
@@ -369,6 +384,16 @@ class SearchIndex extends Component {
|
|||||||
onSearchPress={this.onSearchPress}
|
onSearchPress={this.onSearchPress}
|
||||||
onBulkGrabPress={this.onBulkGrabPress}
|
onBulkGrabPress={this.onBulkGrabPress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<AddIndexerModal
|
||||||
|
isOpen={isAddIndexerModalOpen}
|
||||||
|
onModalClose={this.onAddIndexerModalClose}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<EditIndexerModalConnector
|
||||||
|
isOpen={isEditIndexerModalOpen}
|
||||||
|
onModalClose={this.onEditIndexerModalClose}
|
||||||
|
/>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
.category {
|
.category {
|
||||||
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||||
|
|
||||||
flex: 0 0 130px;
|
flex: 0 0 110px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.age,
|
.age,
|
||||||
|
|||||||
@@ -18,20 +18,20 @@ function createMapStateToProps() {
|
|||||||
return createSelector(
|
return createSelector(
|
||||||
createReleaseSelector(),
|
createReleaseSelector(),
|
||||||
(
|
(
|
||||||
release
|
movie
|
||||||
) => {
|
) => {
|
||||||
|
|
||||||
// If a release is deleted this selector may fire before the parent
|
// If a movie is deleted this selector may fire before the parent
|
||||||
// selecors, which will result in an undefined release, if that happens
|
// selecors, which will result in an undefined movie, if that happens
|
||||||
// we want to return early here and again in the render function to avoid
|
// we want to return early here and again in the render function to avoid
|
||||||
// trying to show a release that has no information available.
|
// trying to show a movie that has no information available.
|
||||||
|
|
||||||
if (!release) {
|
if (!movie) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...release
|
...movie
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -41,7 +41,7 @@ const mapDispatchToProps = {
|
|||||||
dispatchExecuteCommand: executeCommand
|
dispatchExecuteCommand: executeCommand
|
||||||
};
|
};
|
||||||
|
|
||||||
class SearchIndexItemConnector extends Component {
|
class MovieIndexItemConnector extends Component {
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
@@ -66,9 +66,9 @@ class SearchIndexItemConnector extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SearchIndexItemConnector.propTypes = {
|
MovieIndexItemConnector.propTypes = {
|
||||||
guid: PropTypes.string,
|
guid: PropTypes.string,
|
||||||
component: PropTypes.elementType.isRequired
|
component: PropTypes.elementType.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(createMapStateToProps, mapDispatchToProps)(SearchIndexItemConnector);
|
export default connect(createMapStateToProps, mapDispatchToProps)(MovieIndexItemConnector);
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
.category {
|
.category {
|
||||||
composes: cell;
|
composes: cell;
|
||||||
|
|
||||||
flex: 0 0 130px;
|
flex: 0 0 110px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.age,
|
.age,
|
||||||
|
|||||||
@@ -71,19 +71,6 @@ class SearchIndexRow extends Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onSavePress = () => {
|
|
||||||
const {
|
|
||||||
downloadUrl,
|
|
||||||
fileName,
|
|
||||||
onSavePress
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
onSavePress({
|
|
||||||
downloadUrl,
|
|
||||||
fileName
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
@@ -98,6 +85,7 @@ class SearchIndexRow extends Component {
|
|||||||
publishDate,
|
publishDate,
|
||||||
title,
|
title,
|
||||||
infoUrl,
|
infoUrl,
|
||||||
|
downloadUrl,
|
||||||
indexer,
|
indexer,
|
||||||
size,
|
size,
|
||||||
files,
|
files,
|
||||||
@@ -312,7 +300,7 @@ class SearchIndexRow extends Component {
|
|||||||
className={styles.downloadLink}
|
className={styles.downloadLink}
|
||||||
name={icons.SAVE}
|
name={icons.SAVE}
|
||||||
title={translate('Save')}
|
title={translate('Save')}
|
||||||
onPress={this.onSavePress}
|
to={downloadUrl}
|
||||||
/>
|
/>
|
||||||
</VirtualTableRowCell>
|
</VirtualTableRowCell>
|
||||||
);
|
);
|
||||||
@@ -335,7 +323,6 @@ SearchIndexRow.propTypes = {
|
|||||||
ageMinutes: PropTypes.number.isRequired,
|
ageMinutes: PropTypes.number.isRequired,
|
||||||
publishDate: PropTypes.string.isRequired,
|
publishDate: PropTypes.string.isRequired,
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
fileName: PropTypes.string.isRequired,
|
|
||||||
infoUrl: PropTypes.string.isRequired,
|
infoUrl: PropTypes.string.isRequired,
|
||||||
downloadUrl: PropTypes.string.isRequired,
|
downloadUrl: PropTypes.string.isRequired,
|
||||||
indexerId: PropTypes.number.isRequired,
|
indexerId: PropTypes.number.isRequired,
|
||||||
@@ -348,7 +335,6 @@ SearchIndexRow.propTypes = {
|
|||||||
indexerFlags: PropTypes.arrayOf(PropTypes.string).isRequired,
|
indexerFlags: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
onGrabPress: PropTypes.func.isRequired,
|
onGrabPress: PropTypes.func.isRequired,
|
||||||
onSavePress: PropTypes.func.isRequired,
|
|
||||||
isGrabbing: PropTypes.bool.isRequired,
|
isGrabbing: PropTypes.bool.isRequired,
|
||||||
isGrabbed: PropTypes.bool.isRequired,
|
isGrabbed: PropTypes.bool.isRequired,
|
||||||
grabError: PropTypes.string,
|
grabError: PropTypes.string,
|
||||||
|
|||||||
@@ -51,8 +51,7 @@ class SearchIndexTable extends Component {
|
|||||||
timeFormat,
|
timeFormat,
|
||||||
selectedState,
|
selectedState,
|
||||||
onSelectedChange,
|
onSelectedChange,
|
||||||
onGrabPress,
|
onGrabPress
|
||||||
onSavePress
|
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const release = items[rowIndex];
|
const release = items[rowIndex];
|
||||||
@@ -72,7 +71,6 @@ class SearchIndexTable extends Component {
|
|||||||
longDateFormat={longDateFormat}
|
longDateFormat={longDateFormat}
|
||||||
timeFormat={timeFormat}
|
timeFormat={timeFormat}
|
||||||
onGrabPress={onGrabPress}
|
onGrabPress={onGrabPress}
|
||||||
onSavePress={onSavePress}
|
|
||||||
/>
|
/>
|
||||||
</VirtualTableRow>
|
</VirtualTableRow>
|
||||||
);
|
);
|
||||||
@@ -136,7 +134,6 @@ SearchIndexTable.propTypes = {
|
|||||||
timeFormat: PropTypes.string.isRequired,
|
timeFormat: PropTypes.string.isRequired,
|
||||||
onSortPress: PropTypes.func.isRequired,
|
onSortPress: PropTypes.func.isRequired,
|
||||||
onGrabPress: PropTypes.func.isRequired,
|
onGrabPress: PropTypes.func.isRequired,
|
||||||
onSavePress: PropTypes.func.isRequired,
|
|
||||||
allSelected: PropTypes.bool.isRequired,
|
allSelected: PropTypes.bool.isRequired,
|
||||||
allUnselected: PropTypes.bool.isRequired,
|
allUnselected: PropTypes.bool.isRequired,
|
||||||
selectedState: PropTypes.object.isRequired,
|
selectedState: PropTypes.object.isRequired,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { grabRelease, saveRelease, setReleasesSort } from 'Store/Actions/releaseActions';
|
import { grabRelease, setReleasesSort } from 'Store/Actions/releaseActions';
|
||||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||||
import SearchIndexTable from './SearchIndexTable';
|
import SearchIndexTable from './SearchIndexTable';
|
||||||
|
|
||||||
@@ -25,9 +25,6 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
},
|
},
|
||||||
onGrabPress(payload) {
|
onGrabPress(payload) {
|
||||||
dispatch(grabRelease(payload));
|
dispatch(grabRelease(payload));
|
||||||
},
|
|
||||||
onSavePress(payload) {
|
|
||||||
dispatch(saveRelease(payload));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class AddApplicationModalContent extends Component {
|
|||||||
return (
|
return (
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
{translate('AddApplication')}
|
Add Application
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
|
|||||||
@@ -71,14 +71,14 @@ class Application extends Component {
|
|||||||
{
|
{
|
||||||
syncLevel === 'addOnly' &&
|
syncLevel === 'addOnly' &&
|
||||||
<Label kind={kinds.WARNING}>
|
<Label kind={kinds.WARNING}>
|
||||||
{translate('AddRemoveOnly')}
|
Add and Remove Only
|
||||||
</Label>
|
</Label>
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
syncLevel === 'fullSync' &&
|
syncLevel === 'fullSync' &&
|
||||||
<Label kind={kinds.SUCCESS}>
|
<Label kind={kinds.SUCCESS}>
|
||||||
{translate('FullSync')}
|
Full Sync
|
||||||
</Label>
|
</Label>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ class Application extends Component {
|
|||||||
kind={kinds.DISABLED}
|
kind={kinds.DISABLED}
|
||||||
outline={true}
|
outline={true}
|
||||||
>
|
>
|
||||||
{translate('Disabled')}
|
Disabled
|
||||||
</Label>
|
</Label>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import Modal from 'Components/Modal/Modal';
|
|
||||||
import { sizes } from 'Helpers/Props';
|
|
||||||
import AddCategoryModalContentConnector from './AddCategoryModalContentConnector';
|
|
||||||
|
|
||||||
function AddCategoryModal({ isOpen, onModalClose, ...otherProps }) {
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
size={sizes.MEDIUM}
|
|
||||||
isOpen={isOpen}
|
|
||||||
onModalClose={onModalClose}
|
|
||||||
>
|
|
||||||
<AddCategoryModalContentConnector
|
|
||||||
{...otherProps}
|
|
||||||
onModalClose={onModalClose}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
AddCategoryModal.propTypes = {
|
|
||||||
isOpen: PropTypes.bool.isRequired,
|
|
||||||
onModalClose: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AddCategoryModal;
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
|
||||||
import AddCategoryModal from './AddCategoryModal';
|
|
||||||
|
|
||||||
function createMapDispatchToProps(dispatch, props) {
|
|
||||||
const section = 'settings.downloadClientCategories';
|
|
||||||
|
|
||||||
return {
|
|
||||||
dispatchClearPendingChanges() {
|
|
||||||
dispatch(clearPendingChanges({ section }));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class AddCategoryModalConnector extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onModalClose = () => {
|
|
||||||
this.props.dispatchClearPendingChanges();
|
|
||||||
this.props.onModalClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
dispatchClearPendingChanges,
|
|
||||||
...otherProps
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AddCategoryModal
|
|
||||||
{...otherProps}
|
|
||||||
onModalClose={this.onModalClose}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AddCategoryModalConnector.propTypes = {
|
|
||||||
onModalClose: PropTypes.func.isRequired,
|
|
||||||
dispatchClearPendingChanges: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(null, createMapDispatchToProps)(AddCategoryModalConnector);
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
.deleteButton {
|
|
||||||
composes: button from '~Components/Link/Button.css';
|
|
||||||
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import Form from 'Components/Form/Form';
|
|
||||||
import FormGroup from 'Components/Form/FormGroup';
|
|
||||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
|
||||||
import FormLabel from 'Components/Form/FormLabel';
|
|
||||||
import Button from 'Components/Link/Button';
|
|
||||||
import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton';
|
|
||||||
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 translate from 'Utilities/String/translate';
|
|
||||||
import styles from './AddCategoryModalContent.css';
|
|
||||||
|
|
||||||
function AddCategoryModalContent(props) {
|
|
||||||
const {
|
|
||||||
advancedSettings,
|
|
||||||
item,
|
|
||||||
onInputChange,
|
|
||||||
onFieldChange,
|
|
||||||
onCancelPress,
|
|
||||||
onSavePress,
|
|
||||||
onDeleteSpecificationPress,
|
|
||||||
...otherProps
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
clientCategory,
|
|
||||||
categories
|
|
||||||
} = item;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ModalContent onModalClose={onCancelPress}>
|
|
||||||
<ModalHeader>
|
|
||||||
{`${id ? 'Edit' : 'Add'} Category`}
|
|
||||||
</ModalHeader>
|
|
||||||
|
|
||||||
<ModalBody>
|
|
||||||
<Form
|
|
||||||
{...otherProps}
|
|
||||||
>
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>
|
|
||||||
{translate('DownloadClientCategory')}
|
|
||||||
</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.TEXT}
|
|
||||||
name="clientCategory"
|
|
||||||
{...clientCategory}
|
|
||||||
onChange={onInputChange}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>
|
|
||||||
{translate('MappedCategories')}
|
|
||||||
</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.CATEGORY_SELECT}
|
|
||||||
name="categories"
|
|
||||||
{...categories}
|
|
||||||
onChange={onInputChange}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
</Form>
|
|
||||||
</ModalBody>
|
|
||||||
<ModalFooter>
|
|
||||||
{
|
|
||||||
id &&
|
|
||||||
<Button
|
|
||||||
className={styles.deleteButton}
|
|
||||||
kind={kinds.DANGER}
|
|
||||||
onPress={onDeleteSpecificationPress}
|
|
||||||
>
|
|
||||||
{translate('Delete')}
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onPress={onCancelPress}
|
|
||||||
>
|
|
||||||
{translate('Cancel')}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<SpinnerErrorButton
|
|
||||||
isSpinning={false}
|
|
||||||
onPress={onSavePress}
|
|
||||||
>
|
|
||||||
{translate('Save')}
|
|
||||||
</SpinnerErrorButton>
|
|
||||||
</ModalFooter>
|
|
||||||
</ModalContent>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
AddCategoryModalContent.propTypes = {
|
|
||||||
advancedSettings: PropTypes.bool.isRequired,
|
|
||||||
item: PropTypes.object.isRequired,
|
|
||||||
onInputChange: PropTypes.func.isRequired,
|
|
||||||
onFieldChange: PropTypes.func.isRequired,
|
|
||||||
onCancelPress: PropTypes.func.isRequired,
|
|
||||||
onSavePress: PropTypes.func.isRequired,
|
|
||||||
onDeleteSpecificationPress: PropTypes.func
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AddCategoryModalContent;
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import { clearDownloadClientCategoryPending, saveDownloadClientCategory, setDownloadClientCategoryFieldValue, setDownloadClientCategoryValue } from 'Store/Actions/settingsActions';
|
|
||||||
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
|
||||||
import AddCategoryModalContent from './AddCategoryModalContent';
|
|
||||||
|
|
||||||
function createMapStateToProps() {
|
|
||||||
return createSelector(
|
|
||||||
(state) => state.settings.advancedSettings,
|
|
||||||
createProviderSettingsSelector('downloadClientCategories'),
|
|
||||||
(advancedSettings, specification) => {
|
|
||||||
return {
|
|
||||||
advancedSettings,
|
|
||||||
...specification
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
|
||||||
setDownloadClientCategoryValue,
|
|
||||||
setDownloadClientCategoryFieldValue,
|
|
||||||
saveDownloadClientCategory,
|
|
||||||
clearDownloadClientCategoryPending
|
|
||||||
};
|
|
||||||
|
|
||||||
class AddCategoryModalContentConnector extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onInputChange = ({ name, value }) => {
|
|
||||||
this.props.setDownloadClientCategoryValue({ name, value });
|
|
||||||
};
|
|
||||||
|
|
||||||
onFieldChange = ({ name, value }) => {
|
|
||||||
this.props.setDownloadClientCategoryFieldValue({ name, value });
|
|
||||||
};
|
|
||||||
|
|
||||||
onCancelPress = () => {
|
|
||||||
this.props.clearDownloadClientCategoryPending();
|
|
||||||
this.props.onModalClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
onSavePress = () => {
|
|
||||||
this.props.saveDownloadClientCategory({ id: this.props.id });
|
|
||||||
this.props.onModalClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<AddCategoryModalContent
|
|
||||||
{...this.props}
|
|
||||||
onCancelPress={this.onCancelPress}
|
|
||||||
onSavePress={this.onSavePress}
|
|
||||||
onInputChange={this.onInputChange}
|
|
||||||
onFieldChange={this.onFieldChange}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AddCategoryModalContentConnector.propTypes = {
|
|
||||||
id: PropTypes.number,
|
|
||||||
item: PropTypes.object.isRequired,
|
|
||||||
setDownloadClientCategoryValue: PropTypes.func.isRequired,
|
|
||||||
setDownloadClientCategoryFieldValue: PropTypes.func.isRequired,
|
|
||||||
clearDownloadClientCategoryPending: PropTypes.func.isRequired,
|
|
||||||
saveDownloadClientCategory: PropTypes.func.isRequired,
|
|
||||||
onModalClose: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(createMapStateToProps, mapDispatchToProps)(AddCategoryModalContentConnector);
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
.customFormat {
|
|
||||||
composes: card from '~Components/Card.css';
|
|
||||||
|
|
||||||
width: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nameContainer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name {
|
|
||||||
@add-mixin truncate;
|
|
||||||
|
|
||||||
margin-bottom: 5px;
|
|
||||||
font-weight: 300;
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.labels {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin-top: 5px;
|
|
||||||
pointer-events: all;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltipLabel {
|
|
||||||
composes: label from '~Components/Label.css';
|
|
||||||
|
|
||||||
margin: 0;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import Card from 'Components/Card';
|
|
||||||
import Label from 'Components/Label';
|
|
||||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
|
||||||
import { kinds } from 'Helpers/Props';
|
|
||||||
import translate from 'Utilities/String/translate';
|
|
||||||
import AddCategoryModalConnector from './AddCategoryModalConnector';
|
|
||||||
import styles from './Category.css';
|
|
||||||
|
|
||||||
class Category extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Lifecycle
|
|
||||||
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
isEditSpecificationModalOpen: false,
|
|
||||||
isDeleteSpecificationModalOpen: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onEditSpecificationPress = () => {
|
|
||||||
this.setState({ isEditSpecificationModalOpen: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
onEditSpecificationModalClose = () => {
|
|
||||||
this.setState({ isEditSpecificationModalOpen: false });
|
|
||||||
};
|
|
||||||
|
|
||||||
onDeleteSpecificationPress = () => {
|
|
||||||
this.setState({
|
|
||||||
isEditSpecificationModalOpen: false,
|
|
||||||
isDeleteSpecificationModalOpen: true
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onDeleteSpecificationModalClose = () => {
|
|
||||||
this.setState({ isDeleteSpecificationModalOpen: false });
|
|
||||||
};
|
|
||||||
|
|
||||||
onConfirmDeleteSpecification = () => {
|
|
||||||
this.props.onConfirmDeleteSpecification(this.props.id);
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Lifecycle
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
clientCategory,
|
|
||||||
categories
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card
|
|
||||||
className={styles.customFormat}
|
|
||||||
overlayContent={true}
|
|
||||||
onPress={this.onEditSpecificationPress}
|
|
||||||
>
|
|
||||||
<div className={styles.nameContainer}>
|
|
||||||
<div className={styles.name}>
|
|
||||||
{clientCategory}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Label kind={kinds.PRIMARY}>
|
|
||||||
{`${categories.length} ${categories.length > 1 ? translate('Categories') : translate('Category')}`}
|
|
||||||
</Label>
|
|
||||||
|
|
||||||
<AddCategoryModalConnector
|
|
||||||
id={id}
|
|
||||||
isOpen={this.state.isEditSpecificationModalOpen}
|
|
||||||
onModalClose={this.onEditSpecificationModalClose}
|
|
||||||
onDeleteSpecificationPress={this.onDeleteSpecificationPress}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ConfirmModal
|
|
||||||
isOpen={this.state.isDeleteSpecificationModalOpen}
|
|
||||||
kind={kinds.DANGER}
|
|
||||||
title={translate('DeleteClientCategory')}
|
|
||||||
message={
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
{translate('AreYouSureYouWantToDeleteCategory', [name])}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
confirmLabel={translate('Delete')}
|
|
||||||
onConfirm={this.onConfirmDeleteSpecification}
|
|
||||||
onCancel={this.onDeleteSpecificationModalClose}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Category.propTypes = {
|
|
||||||
id: PropTypes.number.isRequired,
|
|
||||||
categories: PropTypes.arrayOf(PropTypes.number).isRequired,
|
|
||||||
clientCategory: PropTypes.string.isRequired,
|
|
||||||
onConfirmDeleteSpecification: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Category;
|
|
||||||
@@ -1,14 +1,11 @@
|
|||||||
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 Alert from 'Components/Alert';
|
||||||
import Card from 'Components/Card';
|
|
||||||
import FieldSet from 'Components/FieldSet';
|
|
||||||
import Form from 'Components/Form/Form';
|
import Form from 'Components/Form/Form';
|
||||||
import FormGroup from 'Components/Form/FormGroup';
|
import FormGroup from 'Components/Form/FormGroup';
|
||||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||||
import FormLabel from 'Components/Form/FormLabel';
|
import FormLabel from 'Components/Form/FormLabel';
|
||||||
import ProviderFieldFormGroup from 'Components/Form/ProviderFieldFormGroup';
|
import ProviderFieldFormGroup from 'Components/Form/ProviderFieldFormGroup';
|
||||||
import Icon from 'Components/Icon';
|
|
||||||
import Button from 'Components/Link/Button';
|
import Button from 'Components/Link/Button';
|
||||||
import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton';
|
import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
@@ -16,33 +13,12 @@ import ModalBody from 'Components/Modal/ModalBody';
|
|||||||
import ModalContent from 'Components/Modal/ModalContent';
|
import ModalContent from 'Components/Modal/ModalContent';
|
||||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
import { icons, inputTypes, kinds } from 'Helpers/Props';
|
import { inputTypes, kinds } from 'Helpers/Props';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import AddCategoryModalConnector from './Categories/AddCategoryModalConnector';
|
|
||||||
import Category from './Categories/Category';
|
|
||||||
import styles from './EditDownloadClientModalContent.css';
|
import styles from './EditDownloadClientModalContent.css';
|
||||||
|
|
||||||
class EditDownloadClientModalContent extends Component {
|
class EditDownloadClientModalContent extends Component {
|
||||||
|
|
||||||
//
|
|
||||||
// Lifecycle
|
|
||||||
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
isAddCategoryModalOpen: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
onAddCategoryPress = () => {
|
|
||||||
this.setState({ isAddCategoryModalOpen: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
onAddCategoryModalClose = () => {
|
|
||||||
this.setState({ isAddCategoryModalOpen: false });
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
@@ -51,7 +27,6 @@ class EditDownloadClientModalContent extends Component {
|
|||||||
advancedSettings,
|
advancedSettings,
|
||||||
isFetching,
|
isFetching,
|
||||||
error,
|
error,
|
||||||
categories,
|
|
||||||
isSaving,
|
isSaving,
|
||||||
isTesting,
|
isTesting,
|
||||||
saveError,
|
saveError,
|
||||||
@@ -62,21 +37,15 @@ class EditDownloadClientModalContent extends Component {
|
|||||||
onSavePress,
|
onSavePress,
|
||||||
onTestPress,
|
onTestPress,
|
||||||
onDeleteDownloadClientPress,
|
onDeleteDownloadClientPress,
|
||||||
onConfirmDeleteCategory,
|
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
|
||||||
isAddCategoryModalOpen
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
implementationName,
|
implementationName,
|
||||||
name,
|
name,
|
||||||
enable,
|
enable,
|
||||||
priority,
|
priority,
|
||||||
supportsCategories,
|
|
||||||
fields,
|
fields,
|
||||||
message
|
message
|
||||||
} = item;
|
} = item;
|
||||||
@@ -167,43 +136,6 @@ class EditDownloadClientModalContent extends Component {
|
|||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
{
|
|
||||||
supportsCategories.value ?
|
|
||||||
<FieldSet legend={translate('MappedCategories')}>
|
|
||||||
<div className={styles.customFormats}>
|
|
||||||
{
|
|
||||||
categories.map((tag) => {
|
|
||||||
return (
|
|
||||||
<Category
|
|
||||||
key={tag.id}
|
|
||||||
{...tag}
|
|
||||||
onConfirmDeleteSpecification={onConfirmDeleteCategory}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
<Card
|
|
||||||
className={styles.addCategory}
|
|
||||||
onPress={this.onAddCategoryPress}
|
|
||||||
>
|
|
||||||
<div className={styles.center}>
|
|
||||||
<Icon
|
|
||||||
name={icons.ADD}
|
|
||||||
size={25}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</FieldSet> :
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
<AddCategoryModalConnector
|
|
||||||
isOpen={isAddCategoryModalOpen}
|
|
||||||
onModalClose={this.onAddCategoryModalClose}
|
|
||||||
/>
|
|
||||||
|
|
||||||
</Form>
|
</Form>
|
||||||
}
|
}
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
@@ -253,15 +185,13 @@ EditDownloadClientModalContent.propTypes = {
|
|||||||
isSaving: PropTypes.bool.isRequired,
|
isSaving: PropTypes.bool.isRequired,
|
||||||
saveError: PropTypes.object,
|
saveError: PropTypes.object,
|
||||||
isTesting: PropTypes.bool.isRequired,
|
isTesting: PropTypes.bool.isRequired,
|
||||||
categories: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
item: PropTypes.object.isRequired,
|
item: PropTypes.object.isRequired,
|
||||||
onInputChange: PropTypes.func.isRequired,
|
onInputChange: PropTypes.func.isRequired,
|
||||||
onFieldChange: PropTypes.func.isRequired,
|
onFieldChange: PropTypes.func.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired,
|
onModalClose: PropTypes.func.isRequired,
|
||||||
onSavePress: PropTypes.func.isRequired,
|
onSavePress: PropTypes.func.isRequired,
|
||||||
onTestPress: PropTypes.func.isRequired,
|
onTestPress: PropTypes.func.isRequired,
|
||||||
onDeleteDownloadClientPress: PropTypes.func,
|
onDeleteDownloadClientPress: PropTypes.func
|
||||||
onConfirmDeleteCategory: PropTypes.func.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default EditDownloadClientModalContent;
|
export default EditDownloadClientModalContent;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ 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';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { deleteDownloadClientCategory, fetchDownloadClientCategories, saveDownloadClient, setDownloadClientFieldValue, setDownloadClientValue, testDownloadClient } from 'Store/Actions/settingsActions';
|
import { saveDownloadClient, setDownloadClientFieldValue, setDownloadClientValue, testDownloadClient } from 'Store/Actions/settingsActions';
|
||||||
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
||||||
import EditDownloadClientModalContent from './EditDownloadClientModalContent';
|
import EditDownloadClientModalContent from './EditDownloadClientModalContent';
|
||||||
|
|
||||||
@@ -10,12 +10,10 @@ function createMapStateToProps() {
|
|||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.settings.advancedSettings,
|
(state) => state.settings.advancedSettings,
|
||||||
createProviderSettingsSelector('downloadClients'),
|
createProviderSettingsSelector('downloadClients'),
|
||||||
(state) => state.settings.downloadClientCategories,
|
(advancedSettings, downloadClient) => {
|
||||||
(advancedSettings, downloadClient, categories) => {
|
|
||||||
return {
|
return {
|
||||||
advancedSettings,
|
advancedSettings,
|
||||||
...downloadClient,
|
...downloadClient
|
||||||
categories: categories.items
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -25,9 +23,7 @@ const mapDispatchToProps = {
|
|||||||
setDownloadClientValue,
|
setDownloadClientValue,
|
||||||
setDownloadClientFieldValue,
|
setDownloadClientFieldValue,
|
||||||
saveDownloadClient,
|
saveDownloadClient,
|
||||||
testDownloadClient,
|
testDownloadClient
|
||||||
fetchDownloadClientCategories,
|
|
||||||
deleteDownloadClientCategory
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class EditDownloadClientModalContentConnector extends Component {
|
class EditDownloadClientModalContentConnector extends Component {
|
||||||
@@ -35,14 +31,6 @@ class EditDownloadClientModalContentConnector extends Component {
|
|||||||
//
|
//
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
tagsFromId
|
|
||||||
} = this.props;
|
|
||||||
this.props.fetchDownloadClientCategories({ id: tagsFromId || id });
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
if (prevProps.isSaving && !this.props.isSaving && !this.props.saveError) {
|
if (prevProps.isSaving && !this.props.isSaving && !this.props.saveError) {
|
||||||
this.props.onModalClose();
|
this.props.onModalClose();
|
||||||
@@ -68,10 +56,6 @@ class EditDownloadClientModalContentConnector extends Component {
|
|||||||
this.props.testDownloadClient({ id: this.props.id });
|
this.props.testDownloadClient({ id: this.props.id });
|
||||||
};
|
};
|
||||||
|
|
||||||
onConfirmDeleteCategory = (id) => {
|
|
||||||
this.props.deleteDownloadClientCategory({ id });
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
@@ -83,7 +67,6 @@ class EditDownloadClientModalContentConnector extends Component {
|
|||||||
onTestPress={this.onTestPress}
|
onTestPress={this.onTestPress}
|
||||||
onInputChange={this.onInputChange}
|
onInputChange={this.onInputChange}
|
||||||
onFieldChange={this.onFieldChange}
|
onFieldChange={this.onFieldChange}
|
||||||
onConfirmDeleteCategory={this.onConfirmDeleteCategory}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -91,13 +74,10 @@ class EditDownloadClientModalContentConnector extends Component {
|
|||||||
|
|
||||||
EditDownloadClientModalContentConnector.propTypes = {
|
EditDownloadClientModalContentConnector.propTypes = {
|
||||||
id: PropTypes.number,
|
id: PropTypes.number,
|
||||||
tagsFromId: PropTypes.number,
|
|
||||||
isFetching: PropTypes.bool.isRequired,
|
isFetching: PropTypes.bool.isRequired,
|
||||||
isSaving: PropTypes.bool.isRequired,
|
isSaving: PropTypes.bool.isRequired,
|
||||||
saveError: PropTypes.object,
|
saveError: PropTypes.object,
|
||||||
item: PropTypes.object.isRequired,
|
item: PropTypes.object.isRequired,
|
||||||
fetchDownloadClientCategories: PropTypes.func.isRequired,
|
|
||||||
deleteDownloadClientCategory: PropTypes.func.isRequired,
|
|
||||||
setDownloadClientValue: PropTypes.func.isRequired,
|
setDownloadClientValue: PropTypes.func.isRequired,
|
||||||
setDownloadClientFieldValue: PropTypes.func.isRequired,
|
setDownloadClientFieldValue: PropTypes.func.isRequired,
|
||||||
saveDownloadClient: PropTypes.func.isRequired,
|
saveDownloadClient: PropTypes.func.isRequired,
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const requiresRestartKeys = [
|
|||||||
'bindAddress',
|
'bindAddress',
|
||||||
'port',
|
'port',
|
||||||
'urlBase',
|
'urlBase',
|
||||||
|
'instanceName',
|
||||||
'enableSsl',
|
'enableSsl',
|
||||||
'sslPort',
|
'sslPort',
|
||||||
'sslCertPath',
|
'sslCertPath',
|
||||||
|
|||||||
@@ -11,20 +11,12 @@ 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';
|
||||||
|
|
||||||
export const authenticationRequiredWarning = translate('AuthenticationRequiredWarning');
|
const authenticationMethodOptions = [
|
||||||
|
{ key: 'none', value: 'None' },
|
||||||
export const authenticationMethodOptions = [
|
|
||||||
{ key: 'none', value: 'None', isDisabled: true },
|
|
||||||
{ key: 'external', value: 'External', isHidden: true },
|
|
||||||
{ key: 'basic', value: 'Basic (Browser Popup)' },
|
{ key: 'basic', value: 'Basic (Browser Popup)' },
|
||||||
{ key: 'forms', value: 'Forms (Login Page)' }
|
{ key: 'forms', value: 'Forms (Login Page)' }
|
||||||
];
|
];
|
||||||
|
|
||||||
export const authenticationRequiredOptions = [
|
|
||||||
{ key: 'enabled', value: 'Enabled' },
|
|
||||||
{ key: 'disabledForLocalAddresses', value: 'Disabled for Local Addresses' }
|
|
||||||
];
|
|
||||||
|
|
||||||
const certificateValidationOptions = [
|
const certificateValidationOptions = [
|
||||||
{ key: 'enabled', value: 'Enabled' },
|
{ key: 'enabled', value: 'Enabled' },
|
||||||
{ key: 'disabledForLocalAddresses', value: 'Disabled for Local Addresses' },
|
{ key: 'disabledForLocalAddresses', value: 'Disabled for Local Addresses' },
|
||||||
@@ -76,7 +68,6 @@ class SecuritySettings extends Component {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
authenticationMethod,
|
authenticationMethod,
|
||||||
authenticationRequired,
|
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
apiKey,
|
apiKey,
|
||||||
@@ -95,31 +86,13 @@ class SecuritySettings extends Component {
|
|||||||
name="authenticationMethod"
|
name="authenticationMethod"
|
||||||
values={authenticationMethodOptions}
|
values={authenticationMethodOptions}
|
||||||
helpText={translate('AuthenticationMethodHelpText')}
|
helpText={translate('AuthenticationMethodHelpText')}
|
||||||
helpTextWarning={authenticationRequiredWarning}
|
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...authenticationMethod}
|
{...authenticationMethod}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
{
|
{
|
||||||
authenticationEnabled ?
|
authenticationEnabled &&
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>{translate('AuthenticationRequired')}</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.SELECT}
|
|
||||||
name="authenticationRequired"
|
|
||||||
values={authenticationRequiredOptions}
|
|
||||||
helpText={translate('AuthenticationRequiredHelpText')}
|
|
||||||
onChange={onInputChange}
|
|
||||||
{...authenticationRequired}
|
|
||||||
/>
|
|
||||||
</FormGroup> :
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
authenticationEnabled ?
|
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>{translate('Username')}</FormLabel>
|
<FormLabel>{translate('Username')}</FormLabel>
|
||||||
|
|
||||||
@@ -129,12 +102,11 @@ class SecuritySettings extends Component {
|
|||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...username}
|
{...username}
|
||||||
/>
|
/>
|
||||||
</FormGroup> :
|
</FormGroup>
|
||||||
null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
authenticationEnabled ?
|
authenticationEnabled &&
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>{translate('Password')}</FormLabel>
|
<FormLabel>{translate('Password')}</FormLabel>
|
||||||
|
|
||||||
@@ -144,8 +116,7 @@ class SecuritySettings extends Component {
|
|||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...password}
|
{...password}
|
||||||
/>
|
/>
|
||||||
</FormGroup> :
|
</FormGroup>
|
||||||
null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ class IndexerProxy extends Component {
|
|||||||
kind={kinds.DISABLED}
|
kind={kinds.DISABLED}
|
||||||
outline={true}
|
outline={true}
|
||||||
>
|
>
|
||||||
{translate('Disabled')}
|
Disabled
|
||||||
</Label> :
|
</Label> :
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,9 +56,17 @@ class Notification extends Component {
|
|||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
onGrab,
|
onGrab,
|
||||||
|
onDownload,
|
||||||
|
onUpgrade,
|
||||||
|
onRename,
|
||||||
|
onDelete,
|
||||||
onHealthIssue,
|
onHealthIssue,
|
||||||
onApplicationUpdate,
|
onApplicationUpdate,
|
||||||
supportsOnGrab,
|
supportsOnGrab,
|
||||||
|
supportsOnDownload,
|
||||||
|
supportsOnUpgrade,
|
||||||
|
supportsOnRename,
|
||||||
|
supportsOnDelete,
|
||||||
supportsOnHealthIssue,
|
supportsOnHealthIssue,
|
||||||
supportsOnApplicationUpdate
|
supportsOnApplicationUpdate
|
||||||
} = this.props;
|
} = this.props;
|
||||||
@@ -80,6 +88,34 @@ class Notification extends Component {
|
|||||||
</Label>
|
</Label>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
supportsOnDelete && onDelete &&
|
||||||
|
<Label kind={kinds.SUCCESS}>
|
||||||
|
{translate('OnDelete')}
|
||||||
|
</Label>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
supportsOnDownload && onDownload &&
|
||||||
|
<Label kind={kinds.SUCCESS}>
|
||||||
|
{translate('OnImport')}
|
||||||
|
</Label>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
supportsOnUpgrade && onDownload && onUpgrade &&
|
||||||
|
<Label kind={kinds.SUCCESS}>
|
||||||
|
{translate('OnUpgrade')}
|
||||||
|
</Label>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
supportsOnRename && onRename &&
|
||||||
|
<Label kind={kinds.SUCCESS}>
|
||||||
|
{translate('OnRename')}
|
||||||
|
</Label>
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
supportsOnHealthIssue && onHealthIssue &&
|
supportsOnHealthIssue && onHealthIssue &&
|
||||||
<Label kind={kinds.SUCCESS}>
|
<Label kind={kinds.SUCCESS}>
|
||||||
@@ -96,7 +132,7 @@ class Notification extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!onGrab && !onHealthIssue && !onApplicationUpdate ?
|
!onGrab && !onDownload && !onRename && !onHealthIssue && !onDelete && !onApplicationUpdate ?
|
||||||
<Label
|
<Label
|
||||||
kind={kinds.DISABLED}
|
kind={kinds.DISABLED}
|
||||||
outline={true}
|
outline={true}
|
||||||
@@ -131,9 +167,17 @@ Notification.propTypes = {
|
|||||||
id: PropTypes.number.isRequired,
|
id: PropTypes.number.isRequired,
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
onGrab: PropTypes.bool.isRequired,
|
onGrab: PropTypes.bool.isRequired,
|
||||||
|
onDownload: PropTypes.bool.isRequired,
|
||||||
|
onUpgrade: PropTypes.bool.isRequired,
|
||||||
|
onRename: PropTypes.bool.isRequired,
|
||||||
|
onDelete: PropTypes.bool.isRequired,
|
||||||
onHealthIssue: PropTypes.bool.isRequired,
|
onHealthIssue: PropTypes.bool.isRequired,
|
||||||
onApplicationUpdate: PropTypes.bool.isRequired,
|
onApplicationUpdate: PropTypes.bool.isRequired,
|
||||||
supportsOnGrab: PropTypes.bool.isRequired,
|
supportsOnGrab: PropTypes.bool.isRequired,
|
||||||
|
supportsOnDownload: PropTypes.bool.isRequired,
|
||||||
|
supportsOnDelete: PropTypes.bool.isRequired,
|
||||||
|
supportsOnUpgrade: PropTypes.bool.isRequired,
|
||||||
|
supportsOnRename: PropTypes.bool.isRequired,
|
||||||
supportsOnHealthIssue: PropTypes.bool.isRequired,
|
supportsOnHealthIssue: PropTypes.bool.isRequired,
|
||||||
supportsOnApplicationUpdate: PropTypes.bool.isRequired,
|
supportsOnApplicationUpdate: PropTypes.bool.isRequired,
|
||||||
onConfirmDeleteNotification: PropTypes.func.isRequired
|
onConfirmDeleteNotification: PropTypes.func.isRequired
|
||||||
|
|||||||
@@ -15,11 +15,8 @@ function NotificationEventItems(props) {
|
|||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
onGrab,
|
|
||||||
onHealthIssue,
|
onHealthIssue,
|
||||||
onApplicationUpdate,
|
onApplicationUpdate,
|
||||||
supportsOnGrab,
|
|
||||||
includeManualGrabs,
|
|
||||||
supportsOnHealthIssue,
|
supportsOnHealthIssue,
|
||||||
includeHealthWarnings,
|
includeHealthWarnings,
|
||||||
supportsOnApplicationUpdate
|
supportsOnApplicationUpdate
|
||||||
@@ -34,31 +31,6 @@ function NotificationEventItems(props) {
|
|||||||
link="https://wiki.servarr.com/prowlarr/settings#connections"
|
link="https://wiki.servarr.com/prowlarr/settings#connections"
|
||||||
/>
|
/>
|
||||||
<div className={styles.events}>
|
<div className={styles.events}>
|
||||||
<div>
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.CHECK}
|
|
||||||
name="onGrab"
|
|
||||||
helpText={translate('OnGrabHelpText')}
|
|
||||||
isDisabled={!supportsOnGrab.value}
|
|
||||||
{...onGrab}
|
|
||||||
onChange={onInputChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{
|
|
||||||
onGrab.value &&
|
|
||||||
<div>
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.CHECK}
|
|
||||||
name="includeManualGrabs"
|
|
||||||
helpText={translate('IncludeManualGrabsHelpText')}
|
|
||||||
isDisabled={!supportsOnGrab.value}
|
|
||||||
{...includeManualGrabs}
|
|
||||||
onChange={onInputChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ function PendingChangesModal(props) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
bindShortcut('enter', onConfirm);
|
bindShortcut('enter', onConfirm);
|
||||||
}, [bindShortcut, onConfirm]);
|
}, [onConfirm]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ class UISettings extends Component {
|
|||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
const uiLanguages = languages.filter((item) => item.value !== 'Original');
|
||||||
|
|
||||||
const themeOptions = Object.keys(themes)
|
const themeOptions = Object.keys(themes)
|
||||||
.map((theme) => ({ key: theme, value: titleCase(theme) }));
|
.map((theme) => ({ key: theme, value: titleCase(theme) }));
|
||||||
|
|
||||||
@@ -170,7 +172,7 @@ class UISettings extends Component {
|
|||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.SELECT}
|
type={inputTypes.SELECT}
|
||||||
name="uiLanguage"
|
name="uiLanguage"
|
||||||
values={languages}
|
values={uiLanguages}
|
||||||
helpText={translate('UILanguageHelpText')}
|
helpText={translate('UILanguageHelpText')}
|
||||||
helpTextWarning={translate('UILanguageHelpTextWarning')}
|
helpTextWarning={translate('UILanguageHelpTextWarning')}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import React, { Component } from 'react';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||||
import { fetchLocalizationOptions } from 'Store/Actions/localizationActions';
|
|
||||||
import { fetchUISettings, saveUISettings, setUISettingsValue } from 'Store/Actions/settingsActions';
|
import { fetchUISettings, saveUISettings, setUISettingsValue } from 'Store/Actions/settingsActions';
|
||||||
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
|
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
|
||||||
import UISettings from './UISettings';
|
import UISettings from './UISettings';
|
||||||
@@ -12,17 +11,18 @@ const SECTION = 'ui';
|
|||||||
|
|
||||||
function createLanguagesSelector() {
|
function createLanguagesSelector() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.localization,
|
(state) => state.settings.languages,
|
||||||
(localization) => {
|
(languages) => {
|
||||||
const items = localization.items;
|
const items = languages.items;
|
||||||
|
const filterItems = ['Any', 'Unknown'];
|
||||||
|
|
||||||
if (!items) {
|
if (!items) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const newItems = items.filter((lang) => !items.includes(lang.name)).map((item) => {
|
const newItems = items.filter((lang) => !filterItems.includes(lang.name)).map((item) => {
|
||||||
return {
|
return {
|
||||||
key: item.value,
|
key: item.id,
|
||||||
value: item.name
|
value: item.name
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -51,7 +51,6 @@ const mapDispatchToProps = {
|
|||||||
setUISettingsValue,
|
setUISettingsValue,
|
||||||
saveUISettings,
|
saveUISettings,
|
||||||
fetchUISettings,
|
fetchUISettings,
|
||||||
fetchLocalizationOptions,
|
|
||||||
clearPendingChanges
|
clearPendingChanges
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -62,7 +61,6 @@ class UISettingsConnector extends Component {
|
|||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.fetchUISettings();
|
this.props.fetchUISettings();
|
||||||
this.props.fetchLocalizationOptions();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
@@ -98,7 +96,6 @@ UISettingsConnector.propTypes = {
|
|||||||
setUISettingsValue: PropTypes.func.isRequired,
|
setUISettingsValue: PropTypes.func.isRequired,
|
||||||
saveUISettings: PropTypes.func.isRequired,
|
saveUISettings: PropTypes.func.isRequired,
|
||||||
fetchUISettings: PropTypes.func.isRequired,
|
fetchUISettings: PropTypes.func.isRequired,
|
||||||
fetchLocalizationOptions: PropTypes.func.isRequired,
|
|
||||||
clearPendingChanges: PropTypes.func.isRequired
|
clearPendingChanges: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,169 +0,0 @@
|
|||||||
import { createAction } from 'redux-actions';
|
|
||||||
import { batchActions } from 'redux-batched-actions';
|
|
||||||
import createClearReducer from 'Store/Actions/Creators/Reducers/createClearReducer';
|
|
||||||
import createSetProviderFieldValueReducer from 'Store/Actions/Creators/Reducers/createSetProviderFieldValueReducer';
|
|
||||||
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
|
|
||||||
import { createThunk } from 'Store/thunks';
|
|
||||||
import getNextId from 'Utilities/State/getNextId';
|
|
||||||
import getProviderState from 'Utilities/State/getProviderState';
|
|
||||||
import getSectionState from 'Utilities/State/getSectionState';
|
|
||||||
import selectProviderSchema from 'Utilities/State/selectProviderSchema';
|
|
||||||
import { removeItem, set, update, updateItem } from '../baseActions';
|
|
||||||
|
|
||||||
//
|
|
||||||
// Variables
|
|
||||||
|
|
||||||
const section = 'settings.downloadClientCategories';
|
|
||||||
|
|
||||||
//
|
|
||||||
// Actions Types
|
|
||||||
|
|
||||||
export const FETCH_DOWNLOAD_CLIENT_CATEGORIES = 'settings/downloadClientCategories/fetchDownloadClientCategories';
|
|
||||||
export const FETCH_DOWNLOAD_CLIENT_CATEGORY_SCHEMA = 'settings/downloadClientCategories/fetchDownloadClientCategorySchema';
|
|
||||||
export const SELECT_DOWNLOAD_CLIENT_CATEGORY_SCHEMA = 'settings/downloadClientCategories/selectDownloadClientCategorySchema';
|
|
||||||
export const SET_DOWNLOAD_CLIENT_CATEGORY_VALUE = 'settings/downloadClientCategories/setDownloadClientCategoryValue';
|
|
||||||
export const SET_DOWNLOAD_CLIENT_CATEGORY_FIELD_VALUE = 'settings/downloadClientCategories/setDownloadClientCategoryFieldValue';
|
|
||||||
export const SAVE_DOWNLOAD_CLIENT_CATEGORY = 'settings/downloadClientCategories/saveDownloadClientCategory';
|
|
||||||
export const DELETE_DOWNLOAD_CLIENT_CATEGORY = 'settings/downloadClientCategories/deleteDownloadClientCategory';
|
|
||||||
export const DELETE_ALL_DOWNLOAD_CLIENT_CATEGORY = 'settings/downloadClientCategories/deleteAllDownloadClientCategory';
|
|
||||||
export const CLEAR_DOWNLOAD_CLIENT_CATEGORIES = 'settings/downloadClientCategories/clearDownloadClientCategories';
|
|
||||||
export const CLEAR_DOWNLOAD_CLIENT_CATEGORY_PENDING = 'settings/downloadClientCategories/clearDownloadClientCategoryPending';
|
|
||||||
//
|
|
||||||
// Action Creators
|
|
||||||
|
|
||||||
export const fetchDownloadClientCategories = createThunk(FETCH_DOWNLOAD_CLIENT_CATEGORIES);
|
|
||||||
export const fetchDownloadClientCategorySchema = createThunk(FETCH_DOWNLOAD_CLIENT_CATEGORY_SCHEMA);
|
|
||||||
export const selectDownloadClientCategorySchema = createAction(SELECT_DOWNLOAD_CLIENT_CATEGORY_SCHEMA);
|
|
||||||
|
|
||||||
export const saveDownloadClientCategory = createThunk(SAVE_DOWNLOAD_CLIENT_CATEGORY);
|
|
||||||
export const deleteDownloadClientCategory = createThunk(DELETE_DOWNLOAD_CLIENT_CATEGORY);
|
|
||||||
export const deleteAllDownloadClientCategory = createThunk(DELETE_ALL_DOWNLOAD_CLIENT_CATEGORY);
|
|
||||||
|
|
||||||
export const setDownloadClientCategoryValue = createAction(SET_DOWNLOAD_CLIENT_CATEGORY_VALUE, (payload) => {
|
|
||||||
return {
|
|
||||||
section,
|
|
||||||
...payload
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const setDownloadClientCategoryFieldValue = createAction(SET_DOWNLOAD_CLIENT_CATEGORY_FIELD_VALUE, (payload) => {
|
|
||||||
return {
|
|
||||||
section,
|
|
||||||
...payload
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const clearDownloadClientCategory = createAction(CLEAR_DOWNLOAD_CLIENT_CATEGORIES);
|
|
||||||
|
|
||||||
export const clearDownloadClientCategoryPending = createThunk(CLEAR_DOWNLOAD_CLIENT_CATEGORY_PENDING);
|
|
||||||
|
|
||||||
//
|
|
||||||
// Details
|
|
||||||
|
|
||||||
export default {
|
|
||||||
|
|
||||||
//
|
|
||||||
// State
|
|
||||||
|
|
||||||
defaultState: {
|
|
||||||
isPopulated: false,
|
|
||||||
error: null,
|
|
||||||
isSchemaFetching: false,
|
|
||||||
isSchemaPopulated: false,
|
|
||||||
schemaError: null,
|
|
||||||
schema: [],
|
|
||||||
selectedSchema: {},
|
|
||||||
isSaving: false,
|
|
||||||
saveError: null,
|
|
||||||
items: [],
|
|
||||||
pendingChanges: {}
|
|
||||||
},
|
|
||||||
|
|
||||||
//
|
|
||||||
// Action Handlers
|
|
||||||
|
|
||||||
actionHandlers: {
|
|
||||||
[FETCH_DOWNLOAD_CLIENT_CATEGORIES]: (getState, payload, dispatch) => {
|
|
||||||
let tags = [];
|
|
||||||
if (payload.id) {
|
|
||||||
const cfState = getSectionState(getState(), 'settings.downloadClients', true);
|
|
||||||
const cf = cfState.items[cfState.itemMap[payload.id]];
|
|
||||||
tags = cf.categories.map((tag, i) => {
|
|
||||||
return {
|
|
||||||
id: i + 1,
|
|
||||||
...tag
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(batchActions([
|
|
||||||
update({ section, data: tags }),
|
|
||||||
set({
|
|
||||||
section,
|
|
||||||
isPopulated: true
|
|
||||||
})
|
|
||||||
]));
|
|
||||||
},
|
|
||||||
|
|
||||||
[SAVE_DOWNLOAD_CLIENT_CATEGORY]: (getState, payload, dispatch) => {
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
...otherPayload
|
|
||||||
} = payload;
|
|
||||||
|
|
||||||
const saveData = getProviderState({ id, ...otherPayload }, getState, section, false);
|
|
||||||
|
|
||||||
// we have to set id since not actually posting to server yet
|
|
||||||
if (!saveData.id) {
|
|
||||||
saveData.id = getNextId(getState().settings.downloadClientCategories.items);
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(batchActions([
|
|
||||||
updateItem({ section, ...saveData }),
|
|
||||||
set({
|
|
||||||
section,
|
|
||||||
pendingChanges: {}
|
|
||||||
})
|
|
||||||
]));
|
|
||||||
},
|
|
||||||
|
|
||||||
[DELETE_DOWNLOAD_CLIENT_CATEGORY]: (getState, payload, dispatch) => {
|
|
||||||
const id = payload.id;
|
|
||||||
return dispatch(removeItem({ section, id }));
|
|
||||||
},
|
|
||||||
|
|
||||||
[DELETE_ALL_DOWNLOAD_CLIENT_CATEGORY]: (getState, payload, dispatch) => {
|
|
||||||
return dispatch(set({
|
|
||||||
section,
|
|
||||||
items: []
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
[CLEAR_DOWNLOAD_CLIENT_CATEGORY_PENDING]: (getState, payload, dispatch) => {
|
|
||||||
return dispatch(set({
|
|
||||||
section,
|
|
||||||
pendingChanges: {}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
//
|
|
||||||
// Reducers
|
|
||||||
|
|
||||||
reducers: {
|
|
||||||
[SET_DOWNLOAD_CLIENT_CATEGORY_VALUE]: createSetSettingValueReducer(section),
|
|
||||||
[SET_DOWNLOAD_CLIENT_CATEGORY_FIELD_VALUE]: createSetProviderFieldValueReducer(section),
|
|
||||||
|
|
||||||
[SELECT_DOWNLOAD_CLIENT_CATEGORY_SCHEMA]: (state, { payload }) => {
|
|
||||||
return selectProviderSchema(state, section, payload, (selectedSchema) => {
|
|
||||||
return selectedSchema;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
[CLEAR_DOWNLOAD_CLIENT_CATEGORIES]: createClearReducer(section, {
|
|
||||||
isPopulated: false,
|
|
||||||
error: null,
|
|
||||||
items: []
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -9,7 +9,6 @@ import createSetProviderFieldValueReducer from 'Store/Actions/Creators/Reducers/
|
|||||||
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
|
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
|
||||||
import { createThunk } from 'Store/thunks';
|
import { createThunk } from 'Store/thunks';
|
||||||
import selectProviderSchema from 'Utilities/State/selectProviderSchema';
|
import selectProviderSchema from 'Utilities/State/selectProviderSchema';
|
||||||
import { set } from '../baseActions';
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Variables
|
// Variables
|
||||||
@@ -91,34 +90,10 @@ export default {
|
|||||||
[FETCH_DOWNLOAD_CLIENTS]: createFetchHandler(section, '/downloadclient'),
|
[FETCH_DOWNLOAD_CLIENTS]: createFetchHandler(section, '/downloadclient'),
|
||||||
[FETCH_DOWNLOAD_CLIENT_SCHEMA]: createFetchSchemaHandler(section, '/downloadclient/schema'),
|
[FETCH_DOWNLOAD_CLIENT_SCHEMA]: createFetchSchemaHandler(section, '/downloadclient/schema'),
|
||||||
|
|
||||||
[SAVE_DOWNLOAD_CLIENT]: (getState, payload, dispatch) => {
|
[SAVE_DOWNLOAD_CLIENT]: createSaveProviderHandler(section, '/downloadclient'),
|
||||||
// move the format tags in as a pending change
|
|
||||||
const state = getState();
|
|
||||||
const pendingChanges = state.settings.downloadClients.pendingChanges;
|
|
||||||
pendingChanges.categories = state.settings.downloadClientCategories.items;
|
|
||||||
dispatch(set({
|
|
||||||
section,
|
|
||||||
pendingChanges
|
|
||||||
}));
|
|
||||||
|
|
||||||
createSaveProviderHandler(section, '/downloadclient')(getState, payload, dispatch);
|
|
||||||
},
|
|
||||||
|
|
||||||
[CANCEL_SAVE_DOWNLOAD_CLIENT]: createCancelSaveProviderHandler(section),
|
[CANCEL_SAVE_DOWNLOAD_CLIENT]: createCancelSaveProviderHandler(section),
|
||||||
[DELETE_DOWNLOAD_CLIENT]: createRemoveItemHandler(section, '/downloadclient'),
|
[DELETE_DOWNLOAD_CLIENT]: createRemoveItemHandler(section, '/downloadclient'),
|
||||||
|
[TEST_DOWNLOAD_CLIENT]: createTestProviderHandler(section, '/downloadclient'),
|
||||||
[TEST_DOWNLOAD_CLIENT]: (getState, payload, dispatch) => {
|
|
||||||
const state = getState();
|
|
||||||
const pendingChanges = state.settings.downloadClients.pendingChanges;
|
|
||||||
pendingChanges.categories = state.settings.downloadClientCategories.items;
|
|
||||||
dispatch(set({
|
|
||||||
section,
|
|
||||||
pendingChanges
|
|
||||||
}));
|
|
||||||
|
|
||||||
createTestProviderHandler(section, '/downloadclient')(getState, payload, dispatch);
|
|
||||||
},
|
|
||||||
|
|
||||||
[CANCEL_TEST_DOWNLOAD_CLIENT]: createCancelTestProviderHandler(section),
|
[CANCEL_TEST_DOWNLOAD_CLIENT]: createCancelTestProviderHandler(section),
|
||||||
[TEST_ALL_DOWNLOAD_CLIENTS]: createTestAllProvidersHandler(section, '/downloadclient')
|
[TEST_ALL_DOWNLOAD_CLIENTS]: createTestAllProvidersHandler(section, '/downloadclient')
|
||||||
},
|
},
|
||||||
|
|||||||
48
frontend/src/Store/Actions/Settings/languages.js
Normal file
48
frontend/src/Store/Actions/Settings/languages.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
|
||||||
|
import { createThunk } from 'Store/thunks';
|
||||||
|
|
||||||
|
//
|
||||||
|
// Variables
|
||||||
|
|
||||||
|
const section = 'settings.languages';
|
||||||
|
|
||||||
|
//
|
||||||
|
// Actions Types
|
||||||
|
|
||||||
|
export const FETCH_LANGUAGES = 'settings/languages/fetchLanguages';
|
||||||
|
|
||||||
|
//
|
||||||
|
// Action Creators
|
||||||
|
|
||||||
|
export const fetchLanguages = createThunk(FETCH_LANGUAGES);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Details
|
||||||
|
|
||||||
|
export default {
|
||||||
|
|
||||||
|
//
|
||||||
|
// State
|
||||||
|
|
||||||
|
defaultState: {
|
||||||
|
isFetching: false,
|
||||||
|
isPopulated: false,
|
||||||
|
error: null,
|
||||||
|
items: []
|
||||||
|
},
|
||||||
|
|
||||||
|
//
|
||||||
|
// Action Handlers
|
||||||
|
|
||||||
|
actionHandlers: {
|
||||||
|
[FETCH_LANGUAGES]: createFetchHandler(section, '/language')
|
||||||
|
},
|
||||||
|
|
||||||
|
//
|
||||||
|
// Reducers
|
||||||
|
|
||||||
|
reducers: {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
@@ -103,6 +103,9 @@ export default {
|
|||||||
[SELECT_NOTIFICATION_SCHEMA]: (state, { payload }) => {
|
[SELECT_NOTIFICATION_SCHEMA]: (state, { payload }) => {
|
||||||
return selectProviderSchema(state, section, payload, (selectedSchema) => {
|
return selectProviderSchema(state, section, payload, (selectedSchema) => {
|
||||||
selectedSchema.onGrab = selectedSchema.supportsOnGrab;
|
selectedSchema.onGrab = selectedSchema.supportsOnGrab;
|
||||||
|
selectedSchema.onDownload = selectedSchema.supportsOnDownload;
|
||||||
|
selectedSchema.onUpgrade = selectedSchema.supportsOnUpgrade;
|
||||||
|
selectedSchema.onRename = selectedSchema.supportsOnRename;
|
||||||
selectedSchema.onApplicationUpdate = selectedSchema.supportsOnApplicationUpdate;
|
selectedSchema.onApplicationUpdate = selectedSchema.supportsOnApplicationUpdate;
|
||||||
|
|
||||||
return selectedSchema;
|
return selectedSchema;
|
||||||
|
|||||||
@@ -36,31 +36,31 @@ export const defaultState = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'indexer',
|
name: 'indexer',
|
||||||
label: translate('Indexer'),
|
label: 'Indexer',
|
||||||
isSortable: false,
|
isSortable: false,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'query',
|
name: 'query',
|
||||||
label: translate('Query'),
|
label: 'Query',
|
||||||
isSortable: false,
|
isSortable: false,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'parameters',
|
name: 'parameters',
|
||||||
label: translate('Parameters'),
|
label: 'Parameters',
|
||||||
isSortable: false,
|
isSortable: false,
|
||||||
isVisible: false
|
isVisible: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'grabTitle',
|
name: 'grabTitle',
|
||||||
label: translate('GrabTitle'),
|
label: 'Grab Title',
|
||||||
isSortable: false,
|
isSortable: false,
|
||||||
isVisible: false
|
isVisible: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'categories',
|
name: 'categories',
|
||||||
label: translate('Categories'),
|
label: 'Categories',
|
||||||
isSortable: false,
|
isSortable: false,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
@@ -72,13 +72,13 @@ export const defaultState = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'source',
|
name: 'source',
|
||||||
label: translate('Source'),
|
label: 'Source',
|
||||||
isSortable: false,
|
isSortable: false,
|
||||||
isVisible: false
|
isVisible: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'elapsedTime',
|
name: 'elapsedTime',
|
||||||
label: translate('ElapsedTime'),
|
label: 'Elapsed Time',
|
||||||
isSortable: false,
|
isSortable: false,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import * as indexers from './indexerActions';
|
|||||||
import * as indexerIndex from './indexerIndexActions';
|
import * as indexerIndex from './indexerIndexActions';
|
||||||
import * as indexerStats from './indexerStatsActions';
|
import * as indexerStats from './indexerStatsActions';
|
||||||
import * as indexerStatus from './indexerStatusActions';
|
import * as indexerStatus from './indexerStatusActions';
|
||||||
import * as localization from './localizationActions';
|
|
||||||
import * as oAuth from './oAuthActions';
|
import * as oAuth from './oAuthActions';
|
||||||
import * as paths from './pathActions';
|
import * as paths from './pathActions';
|
||||||
import * as providerOptions from './providerOptionActions';
|
import * as providerOptions from './providerOptionActions';
|
||||||
@@ -26,7 +25,6 @@ export default [
|
|||||||
paths,
|
paths,
|
||||||
providerOptions,
|
providerOptions,
|
||||||
releases,
|
releases,
|
||||||
localization,
|
|
||||||
indexers,
|
indexers,
|
||||||
indexerIndex,
|
indexerIndex,
|
||||||
indexerStats,
|
indexerStats,
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export const defaultState = {
|
|||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
name: 'select',
|
name: 'select',
|
||||||
columnLabel: translate('Select'),
|
columnLabel: 'Select',
|
||||||
isSortable: false,
|
isSortable: false,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
isModifiable: false,
|
isModifiable: false,
|
||||||
@@ -51,7 +51,7 @@ export const defaultState = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'sortName',
|
name: 'sortName',
|
||||||
label: translate('IndexerName'),
|
label: 'Indexer Name',
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
isModifiable: false
|
isModifiable: false
|
||||||
@@ -88,7 +88,7 @@ export const defaultState = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'capabilities',
|
name: 'capabilities',
|
||||||
label: translate('Categories'),
|
label: 'Categories',
|
||||||
isSortable: false,
|
isSortable: false,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
|
|
||||||
import { createThunk, handleThunks } from 'Store/thunks';
|
|
||||||
import createHandleActions from './Creators/createHandleActions';
|
|
||||||
|
|
||||||
//
|
|
||||||
// Variables
|
|
||||||
|
|
||||||
export const section = 'localization';
|
|
||||||
|
|
||||||
//
|
|
||||||
// State
|
|
||||||
|
|
||||||
export const defaultState = {
|
|
||||||
isFetching: false,
|
|
||||||
isPopulated: false,
|
|
||||||
error: null,
|
|
||||||
items: []
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Actions Types
|
|
||||||
|
|
||||||
export const FETCH_LOCALIZATION_OPTIONS = 'localization/fetchLocalizationOptions';
|
|
||||||
|
|
||||||
//
|
|
||||||
// Action Creators
|
|
||||||
|
|
||||||
export const fetchLocalizationOptions = createThunk(FETCH_LOCALIZATION_OPTIONS);
|
|
||||||
|
|
||||||
//
|
|
||||||
// Action Handlers
|
|
||||||
export const actionHandlers = handleThunks({
|
|
||||||
|
|
||||||
[FETCH_LOCALIZATION_OPTIONS]: createFetchHandler(section, '/localization/options')
|
|
||||||
});
|
|
||||||
|
|
||||||
//
|
|
||||||
// Reducers
|
|
||||||
export const reducers = createHandleActions({}, defaultState, section);
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import $ from 'jquery';
|
|
||||||
import { createAction } from 'redux-actions';
|
import { createAction } from 'redux-actions';
|
||||||
import { batchActions } from 'redux-batched-actions';
|
import { batchActions } from 'redux-batched-actions';
|
||||||
import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props';
|
import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props';
|
||||||
@@ -230,7 +229,6 @@ export const CANCEL_FETCH_RELEASES = 'releases/cancelFetchReleases';
|
|||||||
export const SET_RELEASES_SORT = 'releases/setReleasesSort';
|
export const SET_RELEASES_SORT = 'releases/setReleasesSort';
|
||||||
export const CLEAR_RELEASES = 'releases/clearReleases';
|
export const CLEAR_RELEASES = 'releases/clearReleases';
|
||||||
export const GRAB_RELEASE = 'releases/grabRelease';
|
export const GRAB_RELEASE = 'releases/grabRelease';
|
||||||
export const SAVE_RELEASE = 'releases/saveRelease';
|
|
||||||
export const BULK_GRAB_RELEASES = 'release/bulkGrabReleases';
|
export const BULK_GRAB_RELEASES = 'release/bulkGrabReleases';
|
||||||
export const UPDATE_RELEASE = 'releases/updateRelease';
|
export const UPDATE_RELEASE = 'releases/updateRelease';
|
||||||
export const SET_RELEASES_FILTER = 'releases/setReleasesFilter';
|
export const SET_RELEASES_FILTER = 'releases/setReleasesFilter';
|
||||||
@@ -245,7 +243,6 @@ export const cancelFetchReleases = createThunk(CANCEL_FETCH_RELEASES);
|
|||||||
export const setReleasesSort = createAction(SET_RELEASES_SORT);
|
export const setReleasesSort = createAction(SET_RELEASES_SORT);
|
||||||
export const clearReleases = createAction(CLEAR_RELEASES);
|
export const clearReleases = createAction(CLEAR_RELEASES);
|
||||||
export const grabRelease = createThunk(GRAB_RELEASE);
|
export const grabRelease = createThunk(GRAB_RELEASE);
|
||||||
export const saveRelease = createThunk(SAVE_RELEASE);
|
|
||||||
export const bulkGrabReleases = createThunk(BULK_GRAB_RELEASES);
|
export const bulkGrabReleases = createThunk(BULK_GRAB_RELEASES);
|
||||||
export const updateRelease = createAction(UPDATE_RELEASE);
|
export const updateRelease = createAction(UPDATE_RELEASE);
|
||||||
export const setReleasesFilter = createAction(SET_RELEASES_FILTER);
|
export const setReleasesFilter = createAction(SET_RELEASES_FILTER);
|
||||||
@@ -307,38 +304,14 @@ export const actionHandlers = handleThunks({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
[SAVE_RELEASE]: function(getState, payload, dispatch) {
|
|
||||||
const link = payload.downloadUrl;
|
|
||||||
const file = payload.fileName;
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: link,
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'X-Prowlarr-Client': true
|
|
||||||
},
|
|
||||||
xhrFields: {
|
|
||||||
responseType: 'blob'
|
|
||||||
},
|
|
||||||
success: function(data) {
|
|
||||||
const a = document.createElement('a');
|
|
||||||
const url = window.URL.createObjectURL(data);
|
|
||||||
a.href = url;
|
|
||||||
a.download = file;
|
|
||||||
document.body.append(a);
|
|
||||||
a.click();
|
|
||||||
a.remove();
|
|
||||||
window.URL.revokeObjectURL(url);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
[BULK_GRAB_RELEASES]: function(getState, payload, dispatch) {
|
[BULK_GRAB_RELEASES]: function(getState, payload, dispatch) {
|
||||||
dispatch(set({
|
dispatch(set({
|
||||||
section,
|
section,
|
||||||
isGrabbing: true
|
isGrabbing: true
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
console.log(payload);
|
||||||
|
|
||||||
const promise = createAjaxRequest({
|
const promise = createAjaxRequest({
|
||||||
url: '/search/bulk',
|
url: '/search/bulk',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
@@ -4,19 +4,19 @@ import createHandleActions from './Creators/createHandleActions';
|
|||||||
import applications from './Settings/applications';
|
import applications from './Settings/applications';
|
||||||
import appProfiles from './Settings/appProfiles';
|
import appProfiles from './Settings/appProfiles';
|
||||||
import development from './Settings/development';
|
import development from './Settings/development';
|
||||||
import downloadClientCategories from './Settings/downloadClientCategories';
|
|
||||||
import downloadClients from './Settings/downloadClients';
|
import downloadClients from './Settings/downloadClients';
|
||||||
import general from './Settings/general';
|
import general from './Settings/general';
|
||||||
import indexerCategories from './Settings/indexerCategories';
|
import indexerCategories from './Settings/indexerCategories';
|
||||||
import indexerProxies from './Settings/indexerProxies';
|
import indexerProxies from './Settings/indexerProxies';
|
||||||
|
import languages from './Settings/languages';
|
||||||
import notifications from './Settings/notifications';
|
import notifications from './Settings/notifications';
|
||||||
import ui from './Settings/ui';
|
import ui from './Settings/ui';
|
||||||
|
|
||||||
export * from './Settings/downloadClientCategories';
|
|
||||||
export * from './Settings/downloadClients';
|
export * from './Settings/downloadClients';
|
||||||
export * from './Settings/general';
|
export * from './Settings/general';
|
||||||
export * from './Settings/indexerCategories';
|
export * from './Settings/indexerCategories';
|
||||||
export * from './Settings/indexerProxies';
|
export * from './Settings/indexerProxies';
|
||||||
|
export * from './Settings/languages';
|
||||||
export * from './Settings/notifications';
|
export * from './Settings/notifications';
|
||||||
export * from './Settings/applications';
|
export * from './Settings/applications';
|
||||||
export * from './Settings/appProfiles';
|
export * from './Settings/appProfiles';
|
||||||
@@ -34,11 +34,11 @@ export const section = 'settings';
|
|||||||
export const defaultState = {
|
export const defaultState = {
|
||||||
advancedSettings: false,
|
advancedSettings: false,
|
||||||
|
|
||||||
downloadClientCategories: downloadClientCategories.defaultState,
|
|
||||||
downloadClients: downloadClients.defaultState,
|
downloadClients: downloadClients.defaultState,
|
||||||
general: general.defaultState,
|
general: general.defaultState,
|
||||||
indexerCategories: indexerCategories.defaultState,
|
indexerCategories: indexerCategories.defaultState,
|
||||||
indexerProxies: indexerProxies.defaultState,
|
indexerProxies: indexerProxies.defaultState,
|
||||||
|
languages: languages.defaultState,
|
||||||
notifications: notifications.defaultState,
|
notifications: notifications.defaultState,
|
||||||
applications: applications.defaultState,
|
applications: applications.defaultState,
|
||||||
appProfiles: appProfiles.defaultState,
|
appProfiles: appProfiles.defaultState,
|
||||||
@@ -64,11 +64,11 @@ export const toggleAdvancedSettings = createAction(TOGGLE_ADVANCED_SETTINGS);
|
|||||||
// Action Handlers
|
// Action Handlers
|
||||||
|
|
||||||
export const actionHandlers = handleThunks({
|
export const actionHandlers = handleThunks({
|
||||||
...downloadClientCategories.actionHandlers,
|
|
||||||
...downloadClients.actionHandlers,
|
...downloadClients.actionHandlers,
|
||||||
...general.actionHandlers,
|
...general.actionHandlers,
|
||||||
...indexerCategories.actionHandlers,
|
...indexerCategories.actionHandlers,
|
||||||
...indexerProxies.actionHandlers,
|
...indexerProxies.actionHandlers,
|
||||||
|
...languages.actionHandlers,
|
||||||
...notifications.actionHandlers,
|
...notifications.actionHandlers,
|
||||||
...applications.actionHandlers,
|
...applications.actionHandlers,
|
||||||
...appProfiles.actionHandlers,
|
...appProfiles.actionHandlers,
|
||||||
@@ -85,11 +85,11 @@ export const reducers = createHandleActions({
|
|||||||
return Object.assign({}, state, { advancedSettings: !state.advancedSettings });
|
return Object.assign({}, state, { advancedSettings: !state.advancedSettings });
|
||||||
},
|
},
|
||||||
|
|
||||||
...downloadClientCategories.reducers,
|
|
||||||
...downloadClients.reducers,
|
...downloadClients.reducers,
|
||||||
...general.reducers,
|
...general.reducers,
|
||||||
...indexerCategories.reducers,
|
...indexerCategories.reducers,
|
||||||
...indexerProxies.reducers,
|
...indexerProxies.reducers,
|
||||||
|
...languages.reducers,
|
||||||
...notifications.reducers,
|
...notifications.reducers,
|
||||||
...applications.reducers,
|
...applications.reducers,
|
||||||
...appProfiles.reducers,
|
...appProfiles.reducers,
|
||||||
|
|||||||
@@ -168,11 +168,10 @@ module.exports = {
|
|||||||
//
|
//
|
||||||
// Popover
|
// Popover
|
||||||
|
|
||||||
popoverTitleBackgroundColor: '#424242',
|
popoverTitleBackgroundColor: '#f7f7f7',
|
||||||
popoverTitleBorderColor: '#2a2a2a',
|
popoverTitleBorderColor: '#ebebeb',
|
||||||
popoverBodyBackgroundColor: '#2a2a2a',
|
|
||||||
popoverShadowColor: 'rgba(0, 0, 0, 0.2)',
|
popoverShadowColor: 'rgba(0, 0, 0, 0.2)',
|
||||||
popoverArrowBorderColor: '#2a2a2a',
|
popoverArrowBorderColor: '#fff',
|
||||||
|
|
||||||
popoverTitleBackgroundInverseColor: '#595959',
|
popoverTitleBackgroundInverseColor: '#595959',
|
||||||
popoverTitleBorderInverseColor: '#707070',
|
popoverTitleBorderInverseColor: '#707070',
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
import * as dark from './dark';
|
import * as dark from './dark';
|
||||||
import * as light from './light';
|
import * as light from './light';
|
||||||
|
|
||||||
const defaultDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
||||||
const auto = defaultDark ? { ...dark } : { ...light };
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
auto,
|
|
||||||
light,
|
light,
|
||||||
dark
|
dark
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -170,7 +170,6 @@ module.exports = {
|
|||||||
|
|
||||||
popoverTitleBackgroundColor: '#f7f7f7',
|
popoverTitleBackgroundColor: '#f7f7f7',
|
||||||
popoverTitleBorderColor: '#ebebeb',
|
popoverTitleBorderColor: '#ebebeb',
|
||||||
popoverBodyBackgroundColor: '#e9e9e9',
|
|
||||||
popoverShadowColor: 'rgba(0, 0, 0, 0.2)',
|
popoverShadowColor: 'rgba(0, 0, 0, 0.2)',
|
||||||
popoverArrowBorderColor: '#fff',
|
popoverArrowBorderColor: '#fff',
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const columns = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'size',
|
name: 'size',
|
||||||
label: translate('Size'),
|
label: 'Size',
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class Donations extends Component {
|
|||||||
return (
|
return (
|
||||||
<FieldSet legend={translate('Donations')}>
|
<FieldSet legend={translate('Donations')}>
|
||||||
<div className={styles.logoContainer} title="Radarr">
|
<div className={styles.logoContainer} title="Radarr">
|
||||||
<Link to="https://radarr.video/donate">
|
<Link to="https://opencollective.com/radarr">
|
||||||
<img
|
<img
|
||||||
className={styles.logo}
|
className={styles.logo}
|
||||||
src={`${window.Prowlarr.urlBase}/Content/Images/Icons/logo-radarr.png`}
|
src={`${window.Prowlarr.urlBase}/Content/Images/Icons/logo-radarr.png`}
|
||||||
@@ -21,7 +21,7 @@ class Donations extends Component {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.logoContainer} title="Lidarr">
|
<div className={styles.logoContainer} title="Lidarr">
|
||||||
<Link to="https://lidarr.audio/donate">
|
<Link to="https://opencollective.com/lidarr">
|
||||||
<img
|
<img
|
||||||
className={styles.logo}
|
className={styles.logo}
|
||||||
src={`${window.Prowlarr.urlBase}/Content/Images/Icons/logo-lidarr.png`}
|
src={`${window.Prowlarr.urlBase}/Content/Images/Icons/logo-lidarr.png`}
|
||||||
@@ -29,7 +29,7 @@ class Donations extends Component {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.logoContainer} title="Readarr">
|
<div className={styles.logoContainer} title="Readarr">
|
||||||
<Link to="https://readarr.com/donate">
|
<Link to="https://opencollective.com/readarr">
|
||||||
<img
|
<img
|
||||||
className={styles.logo}
|
className={styles.logo}
|
||||||
src={`${window.Prowlarr.urlBase}/Content/Images/Icons/logo-readarr.png`}
|
src={`${window.Prowlarr.urlBase}/Content/Images/Icons/logo-readarr.png`}
|
||||||
@@ -37,7 +37,7 @@ class Donations extends Component {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.logoContainer} title="Prowlarr">
|
<div className={styles.logoContainer} title="Prowlarr">
|
||||||
<Link to="https://prowlarr.com/donate">
|
<Link to="https://opencollective.com/prowlarr">
|
||||||
<img
|
<img
|
||||||
className={styles.logo}
|
className={styles.logo}
|
||||||
src={`${window.Prowlarr.urlBase}/Content/Images/Icons/logo-prowlarr.png`}
|
src={`${window.Prowlarr.urlBase}/Content/Images/Icons/logo-prowlarr.png`}
|
||||||
|
|||||||
@@ -15,27 +15,27 @@ const columns = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'commandName',
|
name: 'commandName',
|
||||||
label: translate('Name'),
|
label: 'Name',
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'queued',
|
name: 'queued',
|
||||||
label: translate('Queued'),
|
label: 'Queued',
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'started',
|
name: 'started',
|
||||||
label: translate('Started'),
|
label: 'Started',
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'ended',
|
name: 'ended',
|
||||||
label: translate('Ended'),
|
label: 'Ended',
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'duration',
|
name: 'duration',
|
||||||
label: translate('Duration'),
|
label: 'Duration',
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,27 +10,27 @@ import ScheduledTaskRowConnector from './ScheduledTaskRowConnector';
|
|||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
name: 'name',
|
name: 'name',
|
||||||
label: translate('Name'),
|
label: 'Name',
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'interval',
|
name: 'interval',
|
||||||
label: translate('Interval'),
|
label: 'Interval',
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'lastExecution',
|
name: 'lastExecution',
|
||||||
label: translate('LastExecution'),
|
label: 'Last Execution',
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'lastDuration',
|
name: 'lastDuration',
|
||||||
label: translate('LastDuration'),
|
label: 'Last Duration',
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'nextExecution',
|
name: 'nextExecution',
|
||||||
label: translate('NextExecution'),
|
label: 'Next Execution',
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ class Updates extends Component {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className={styles.message}>
|
<div className={styles.message}>
|
||||||
{translate('TheLatestVersionIsAlreadyInstalled', ['Prowlarr'])}
|
The latest version of Prowlarr is already installed
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { filesize } from 'filesize';
|
import filesize from 'filesize';
|
||||||
|
|
||||||
function formatBytes(input) {
|
function formatBytes(input) {
|
||||||
const size = Number(input);
|
const size = Number(input);
|
||||||
|
|||||||
@@ -16,11 +16,6 @@ function addApiKey(ajaxOptions) {
|
|||||||
ajaxOptions.headers['X-Api-Key'] = window.Prowlarr.apiKey;
|
ajaxOptions.headers['X-Api-Key'] = window.Prowlarr.apiKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addUIHeader(ajaxOptions) {
|
|
||||||
ajaxOptions.headers = ajaxOptions.headers || {};
|
|
||||||
ajaxOptions.headers['X-Prowlarr-Client'] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addContentType(ajaxOptions) {
|
function addContentType(ajaxOptions) {
|
||||||
if (
|
if (
|
||||||
ajaxOptions.contentType == null &&
|
ajaxOptions.contentType == null &&
|
||||||
@@ -47,7 +42,6 @@ export default function createAjaxRequest(originalAjaxOptions) {
|
|||||||
if (isRelative(ajaxOptions)) {
|
if (isRelative(ajaxOptions)) {
|
||||||
addRootUrl(ajaxOptions);
|
addRootUrl(ajaxOptions);
|
||||||
addApiKey(ajaxOptions);
|
addApiKey(ajaxOptions);
|
||||||
addUIHeader(ajaxOptions);
|
|
||||||
addContentType(ajaxOptions);
|
addContentType(ajaxOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<!-- Windows Phone -->
|
<!-- Windows Phone -->
|
||||||
<meta name="msapplication-navbutton-color" content="#3a3f51" />
|
<meta name="msapplication-navbutton-color" content="#3a3f51" />
|
||||||
|
|
||||||
<meta name="description" content="Prowlarr" />
|
<meta name="description" content="Prowlarr (Preview)" />
|
||||||
|
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-icon"
|
rel="apple-touch-icon"
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
<link rel="stylesheet" type="text/css" href="/Content/Fonts/fonts.css">
|
<link rel="stylesheet" type="text/css" href="/Content/Fonts/fonts.css">
|
||||||
<!-- webpack bundles head -->
|
<!-- webpack bundles head -->
|
||||||
|
|
||||||
<title>Prowlarr</title>
|
<title>Prowlarr (Preview)</title>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
The super basic styling for .root will live here,
|
The super basic styling for .root will live here,
|
||||||
|
|||||||
116
package.json
116
package.json
@@ -11,8 +11,7 @@
|
|||||||
"lint": "esprint check",
|
"lint": "esprint check",
|
||||||
"lint-fix": "esprint check --fix",
|
"lint-fix": "esprint check --fix",
|
||||||
"stylelint-linux": "stylelint $(find frontend -name '*.css') --config frontend/.stylelintrc",
|
"stylelint-linux": "stylelint $(find frontend -name '*.css') --config frontend/.stylelintrc",
|
||||||
"stylelint-windows": "stylelint frontend/**/*.css --config frontend/.stylelintrc",
|
"stylelint-windows": "stylelint frontend/**/*.css --config frontend/.stylelintrc"
|
||||||
"check-modules": "are-you-es5 check . -r"
|
|
||||||
},
|
},
|
||||||
"repository": "https://github.com/Prowlarr/Prowlarr",
|
"repository": "https://github.com/Prowlarr/Prowlarr",
|
||||||
"author": "Team Prowlarr",
|
"author": "Team Prowlarr",
|
||||||
@@ -26,110 +25,107 @@
|
|||||||
"not chrome < 60"
|
"not chrome < 60"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "6.2.1",
|
"@fortawesome/fontawesome-free": "6.1.1",
|
||||||
"@fortawesome/fontawesome-svg-core": "6.2.1",
|
"@fortawesome/fontawesome-svg-core": "6.1.1",
|
||||||
"@fortawesome/free-regular-svg-icons": "6.2.1",
|
"@fortawesome/free-regular-svg-icons": "6.1.1",
|
||||||
"@fortawesome/free-solid-svg-icons": "6.2.1",
|
"@fortawesome/free-solid-svg-icons": "6.1.1",
|
||||||
"@fortawesome/react-fontawesome": "0.2.0",
|
"@fortawesome/react-fontawesome": "0.1.18",
|
||||||
"@microsoft/signalr": "6.0.13",
|
"@microsoft/signalr": "6.0.3",
|
||||||
"@sentry/browser": "7.28.0",
|
"@sentry/browser": "6.19.2",
|
||||||
"@sentry/integrations": "7.28.0",
|
"@sentry/integrations": "6.19.2",
|
||||||
"chart.js": "4.1.1",
|
"chart.js": "3.7.1",
|
||||||
"classnames": "2.3.2",
|
"classnames": "2.3.1",
|
||||||
"clipboard": "2.0.11",
|
"clipboard": "2.0.10",
|
||||||
"connected-react-router": "6.9.3",
|
"connected-react-router": "6.9.1",
|
||||||
"element-class": "0.2.2",
|
"element-class": "0.2.2",
|
||||||
"filesize": "10.0.6",
|
"filesize": "6.3.0",
|
||||||
"history": "4.10.1",
|
"history": "4.10.1",
|
||||||
"https-browserify": "1.0.0",
|
"https-browserify": "1.0.0",
|
||||||
"jdu": "1.0.0",
|
"jdu": "1.0.0",
|
||||||
"jquery": "3.6.2",
|
"jquery": "3.6.0",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"mobile-detect": "1.4.5",
|
"mobile-detect": "1.4.5",
|
||||||
"moment": "2.29.4",
|
"moment": "2.29.2",
|
||||||
"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.0",
|
"qs": "6.10.3",
|
||||||
"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",
|
||||||
"react-autosuggest": "10.1.0",
|
"react-autosuggest": "10.1.0",
|
||||||
"react-custom-scrollbars-2": "4.5.0",
|
"react-custom-scrollbars-2": "4.4.0",
|
||||||
"react-dnd": "14.0.4",
|
"react-dnd": "14.0.4",
|
||||||
"react-dnd-html5-backend": "14.0.2",
|
"react-dnd-html5-backend": "14.0.2",
|
||||||
"react-dnd-multi-backend": "6.0.2",
|
"react-dnd-multi-backend": "6.0.2",
|
||||||
"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.9.2",
|
"react-focus-lock": "2.5.0",
|
||||||
"react-google-recaptcha": "2.1.0",
|
"react-google-recaptcha": "2.1.0",
|
||||||
"react-lazyload": "3.2.0",
|
"react-lazyload": "3.2.0",
|
||||||
"react-measure": "1.4.7",
|
"react-measure": "1.4.7",
|
||||||
"react-popper": "1.3.7",
|
"react-popper": "1.3.7",
|
||||||
"react-redux": "8.0.5",
|
"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-text-truncate": "0.19.0",
|
|
||||||
"react-virtualized": "9.21.1",
|
"react-virtualized": "9.21.1",
|
||||||
"redux": "4.2.0",
|
"redux": "4.1.0",
|
||||||
"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.4.2",
|
"redux-thunk": "2.3.0",
|
||||||
"reselect": "4.1.7"
|
"reselect": "4.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.20.5",
|
"@babel/core": "7.17.8",
|
||||||
"@babel/eslint-parser": "7.19.1",
|
"@babel/eslint-parser": "7.17.0",
|
||||||
"@babel/plugin-proposal-class-properties": "7.18.6",
|
"@babel/plugin-proposal-class-properties": "7.16.7",
|
||||||
"@babel/plugin-proposal-decorators": "7.20.5",
|
"@babel/plugin-proposal-decorators": "7.17.8",
|
||||||
"@babel/plugin-proposal-export-default-from": "7.18.10",
|
"@babel/plugin-proposal-export-default-from": "7.16.7",
|
||||||
"@babel/plugin-proposal-export-namespace-from": "7.18.9",
|
"@babel/plugin-proposal-export-namespace-from": "7.16.7",
|
||||||
"@babel/plugin-proposal-function-sent": "7.18.6",
|
"@babel/plugin-proposal-function-sent": "7.16.7",
|
||||||
"@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6",
|
"@babel/plugin-proposal-nullish-coalescing-operator": "7.16.7",
|
||||||
"@babel/plugin-proposal-numeric-separator": "7.18.6",
|
"@babel/plugin-proposal-numeric-separator": "7.16.7",
|
||||||
"@babel/plugin-proposal-optional-chaining": "7.18.9",
|
"@babel/plugin-proposal-optional-chaining": "7.16.7",
|
||||||
"@babel/plugin-proposal-throw-expressions": "7.18.6",
|
"@babel/plugin-proposal-throw-expressions": "7.16.7",
|
||||||
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
||||||
"@babel/preset-env": "7.20.2",
|
"@babel/preset-env": "7.16.11",
|
||||||
"@babel/preset-react": "7.18.6",
|
"@babel/preset-react": "7.16.7",
|
||||||
"are-you-es5": "2.1.2",
|
"autoprefixer": "10.4.4",
|
||||||
"autoprefixer": "10.4.13",
|
"babel-loader": "8.2.4",
|
||||||
"babel-loader": "9.1.0",
|
|
||||||
"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.26.1",
|
"core-js": "3.21.1",
|
||||||
"css-loader": "6.7.3",
|
"css-loader": "6.7.1",
|
||||||
"eslint": "8.30.0",
|
"eslint": "8.11.0",
|
||||||
"eslint-plugin-filenames": "1.3.2",
|
"eslint-plugin-filenames": "1.3.2",
|
||||||
"eslint-plugin-import": "2.26.0",
|
"eslint-plugin-import": "2.25.4",
|
||||||
"eslint-plugin-react": "7.31.11",
|
"eslint-plugin-react": "7.29.4",
|
||||||
"eslint-plugin-react-hooks": "4.6.0",
|
"eslint-plugin-simple-import-sort": "7.0.0",
|
||||||
"eslint-plugin-simple-import-sort": "8.0.0",
|
"esprint": "3.3.0",
|
||||||
"esprint": "3.6.0",
|
|
||||||
"file-loader": "6.2.0",
|
"file-loader": "6.2.0",
|
||||||
"filemanager-webpack-plugin": "8.0.0",
|
"filemanager-webpack-plugin": "6.1.7",
|
||||||
"html-webpack-plugin": "5.5.0",
|
"html-webpack-plugin": "5.5.0",
|
||||||
"loader-utils": "^3.2.1",
|
"loader-utils": "^3.0.0",
|
||||||
"mini-css-extract-plugin": "2.7.2",
|
"mini-css-extract-plugin": "2.6.0",
|
||||||
"postcss": "8.4.20",
|
"postcss": "8.4.12",
|
||||||
"postcss-color-function": "4.1.0",
|
"postcss-color-function": "4.1.0",
|
||||||
"postcss-loader": "7.0.2",
|
"postcss-loader": "6.2.1",
|
||||||
"postcss-mixins": "9.0.4",
|
"postcss-mixins": "9.0.2",
|
||||||
"postcss-nested": "6.0.0",
|
"postcss-nested": "5.0.6",
|
||||||
"postcss-simple-vars": "7.0.1",
|
"postcss-simple-vars": "6.0.3",
|
||||||
"postcss-url": "10.1.3",
|
"postcss-url": "10.1.3",
|
||||||
"require-nocache": "1.0.0",
|
"require-nocache": "1.0.0",
|
||||||
"rimraf": "3.0.2",
|
"rimraf": "3.0.2",
|
||||||
"run-sequence": "2.2.1",
|
"run-sequence": "2.2.1",
|
||||||
"streamqueue": "1.1.2",
|
"streamqueue": "1.1.2",
|
||||||
"style-loader": "3.3.1",
|
"style-loader": "3.3.1",
|
||||||
"stylelint": "14.16.0",
|
"stylelint": "14.6.0",
|
||||||
"stylelint-order": "5.0.0",
|
"stylelint-order": "5.0.0",
|
||||||
"url-loader": "4.1.1",
|
"url-loader": "4.1.1",
|
||||||
"webpack": "5.75.0",
|
"webpack": "5.70.0",
|
||||||
"webpack-cli": "5.0.1",
|
"webpack-cli": "4.9.2",
|
||||||
"webpack-livereload-plugin": "3.0.2"
|
"webpack-livereload-plugin": "3.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
is_global = true
|
|
||||||
|
|
||||||
dotnet_diagnostic.CA1014.severity = none
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<!-- Common to all Prowlarr Projects -->
|
<!-- Common to all Prowlarr Projects -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<AnalysisLevel>6.0-all</AnalysisLevel>
|
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
|
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
@@ -95,7 +94,7 @@
|
|||||||
|
|
||||||
<!-- Standard testing packages -->
|
<!-- Standard testing packages -->
|
||||||
<ItemGroup Condition="'$(TestProject)'=='true'">
|
<ItemGroup Condition="'$(TestProject)'=='true'">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||||
<PackageReference Include="NunitXml.TestLogger" Version="3.0.117" />
|
<PackageReference Include="NunitXml.TestLogger" Version="3.0.117" />
|
||||||
|
|||||||
@@ -44,9 +44,9 @@ namespace NzbDrone.Automation.Test
|
|||||||
|
|
||||||
driver.Manage().Window.Size = new System.Drawing.Size(1920, 1080);
|
driver.Manage().Window.Size = new System.Drawing.Size(1920, 1080);
|
||||||
|
|
||||||
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger(), null);
|
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger());
|
||||||
_runner.KillAll();
|
_runner.KillAll();
|
||||||
_runner.Start(true);
|
_runner.Start();
|
||||||
|
|
||||||
driver.Url = "http://localhost:9696";
|
driver.Url = "http://localhost:9696";
|
||||||
|
|
||||||
|
|||||||
@@ -10,16 +10,6 @@ namespace NzbDrone.Common.Test.DiskTests
|
|||||||
public abstract class DiskProviderFixtureBase<TSubject> : TestBase<TSubject>
|
public abstract class DiskProviderFixtureBase<TSubject> : TestBase<TSubject>
|
||||||
where TSubject : class, IDiskProvider
|
where TSubject : class, IDiskProvider
|
||||||
{
|
{
|
||||||
[Test]
|
|
||||||
public void writealltext_should_truncate_existing()
|
|
||||||
{
|
|
||||||
var file = GetTempFilePath();
|
|
||||||
|
|
||||||
Subject.WriteAllText(file, "A pretty long string");
|
|
||||||
Subject.WriteAllText(file, "A short string");
|
|
||||||
Subject.ReadAllText(file).Should().Be("A short string");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
[Retry(5)]
|
[Retry(5)]
|
||||||
public void directory_exist_should_be_able_to_find_existing_folder()
|
public void directory_exist_should_be_able_to_find_existing_folder()
|
||||||
|
|||||||
@@ -402,40 +402,6 @@ namespace NzbDrone.Common.Test.DiskTests
|
|||||||
VerifyCopyFolder(source.FullName, destination.FullName);
|
VerifyCopyFolder(source.FullName, destination.FullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void CopyFolder_should_detect_caseinsensitive_parents()
|
|
||||||
{
|
|
||||||
WindowsOnly();
|
|
||||||
|
|
||||||
WithRealDiskProvider();
|
|
||||||
|
|
||||||
var original = GetFilledTempFolder();
|
|
||||||
var root = new DirectoryInfo(GetTempFilePath());
|
|
||||||
var source = new DirectoryInfo(root.FullName + "A/series");
|
|
||||||
var destination = new DirectoryInfo(root.FullName + "a/series");
|
|
||||||
|
|
||||||
Subject.TransferFolder(original.FullName, source.FullName, TransferMode.Copy);
|
|
||||||
|
|
||||||
Assert.Throws<IOException>(() => Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Copy));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void CopyFolder_should_detect_caseinsensitive_folder()
|
|
||||||
{
|
|
||||||
WindowsOnly();
|
|
||||||
|
|
||||||
WithRealDiskProvider();
|
|
||||||
|
|
||||||
var original = GetFilledTempFolder();
|
|
||||||
var root = new DirectoryInfo(GetTempFilePath());
|
|
||||||
var source = new DirectoryInfo(root.FullName + "A/series");
|
|
||||||
var destination = new DirectoryInfo(root.FullName + "A/Series");
|
|
||||||
|
|
||||||
Subject.TransferFolder(original.FullName, source.FullName, TransferMode.Copy);
|
|
||||||
|
|
||||||
Assert.Throws<IOException>(() => Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Copy));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void CopyFolder_should_ignore_nfs_temp_file()
|
public void CopyFolder_should_ignore_nfs_temp_file()
|
||||||
{
|
{
|
||||||
@@ -485,42 +451,6 @@ namespace NzbDrone.Common.Test.DiskTests
|
|||||||
VerifyMoveFolder(original.FullName, source.FullName, destination.FullName);
|
VerifyMoveFolder(original.FullName, source.FullName, destination.FullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void MoveFolder_should_detect_caseinsensitive_parents()
|
|
||||||
{
|
|
||||||
WindowsOnly();
|
|
||||||
|
|
||||||
WithRealDiskProvider();
|
|
||||||
|
|
||||||
var original = GetFilledTempFolder();
|
|
||||||
var root = new DirectoryInfo(GetTempFilePath());
|
|
||||||
var source = new DirectoryInfo(root.FullName + "A/series");
|
|
||||||
var destination = new DirectoryInfo(root.FullName + "a/series");
|
|
||||||
|
|
||||||
Subject.TransferFolder(original.FullName, source.FullName, TransferMode.Copy);
|
|
||||||
|
|
||||||
Assert.Throws<IOException>(() => Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Move));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void MoveFolder_should_rename_caseinsensitive_folder()
|
|
||||||
{
|
|
||||||
WindowsOnly();
|
|
||||||
|
|
||||||
WithRealDiskProvider();
|
|
||||||
|
|
||||||
var original = GetFilledTempFolder();
|
|
||||||
var root = new DirectoryInfo(GetTempFilePath());
|
|
||||||
var source = new DirectoryInfo(root.FullName + "A/series");
|
|
||||||
var destination = new DirectoryInfo(root.FullName + "A/Series");
|
|
||||||
|
|
||||||
Subject.TransferFolder(original.FullName, source.FullName, TransferMode.Copy);
|
|
||||||
|
|
||||||
Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Move);
|
|
||||||
|
|
||||||
source.FullName.GetActualCasing().Should().Be(destination.FullName);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_throw_if_destination_is_readonly()
|
public void should_throw_if_destination_is_readonly()
|
||||||
{
|
{
|
||||||
@@ -623,23 +553,6 @@ namespace NzbDrone.Common.Test.DiskTests
|
|||||||
VerifyCopyFolder(original.FullName, destination.FullName);
|
VerifyCopyFolder(original.FullName, destination.FullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void MirrorFolder_should_handle_trailing_slash()
|
|
||||||
{
|
|
||||||
WithRealDiskProvider();
|
|
||||||
|
|
||||||
var original = GetFilledTempFolder();
|
|
||||||
var source = new DirectoryInfo(GetTempFilePath());
|
|
||||||
var destination = new DirectoryInfo(GetTempFilePath());
|
|
||||||
|
|
||||||
Subject.TransferFolder(original.FullName, source.FullName, TransferMode.Copy);
|
|
||||||
|
|
||||||
var count = Subject.MirrorFolder(source.FullName + Path.DirectorySeparatorChar, destination.FullName);
|
|
||||||
|
|
||||||
count.Should().Equals(3);
|
|
||||||
VerifyCopyFolder(original.FullName, destination.FullName);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TransferFolder_should_use_movefolder_if_on_same_mount()
|
public void TransferFolder_should_use_movefolder_if_on_same_mount()
|
||||||
{
|
{
|
||||||
@@ -839,10 +752,6 @@ namespace NzbDrone.Common.Test.DiskTests
|
|||||||
.Setup(v => v.CreateFolder(It.IsAny<string>()))
|
.Setup(v => v.CreateFolder(It.IsAny<string>()))
|
||||||
.Callback<string>(v => Directory.CreateDirectory(v));
|
.Callback<string>(v => Directory.CreateDirectory(v));
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
|
||||||
.Setup(v => v.MoveFolder(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>()))
|
|
||||||
.Callback<string, string, bool>((v, r, b) => Directory.Move(v, r));
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
.Setup(v => v.DeleteFolder(It.IsAny<string>(), It.IsAny<bool>()))
|
.Setup(v => v.DeleteFolder(It.IsAny<string>(), It.IsAny<bool>()))
|
||||||
.Callback<string, bool>((v, r) => Directory.Delete(v, r));
|
.Callback<string, bool>((v, r) => Directory.Delete(v, r));
|
||||||
|
|||||||
@@ -28,6 +28,14 @@ namespace NzbDrone.Common.Test.DiskTests
|
|||||||
Subject.GetAvailableSpace(Path.Combine(path, "invalidFolder")).Should().NotBe(0);
|
Subject.GetAvailableSpace(Path.Combine(path, "invalidFolder")).Should().NotBe(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Ignore("Docker")]
|
||||||
|
[Test]
|
||||||
|
public void should_be_able_to_check_space_on_ramdrive()
|
||||||
|
{
|
||||||
|
PosixOnly();
|
||||||
|
Subject.GetAvailableSpace("/run/").Should().NotBe(0);
|
||||||
|
}
|
||||||
|
|
||||||
[Ignore("Docker")]
|
[Ignore("Docker")]
|
||||||
[Test]
|
[Test]
|
||||||
public void should_return_free_disk_space()
|
public void should_return_free_disk_space()
|
||||||
@@ -36,6 +44,35 @@ namespace NzbDrone.Common.Test.DiskTests
|
|||||||
result.Should().BeGreaterThan(0);
|
result.Should().BeGreaterThan(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_be_able_to_get_space_on_unc()
|
||||||
|
{
|
||||||
|
WindowsOnly();
|
||||||
|
|
||||||
|
var result = Subject.GetAvailableSpace(@"\\localhost\c$\Windows");
|
||||||
|
result.Should().BeGreaterThan(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_throw_if_drive_doesnt_exist()
|
||||||
|
{
|
||||||
|
WindowsOnly();
|
||||||
|
|
||||||
|
// Find a drive that doesn't exist.
|
||||||
|
for (char driveletter = 'Z'; driveletter > 'D'; driveletter--)
|
||||||
|
{
|
||||||
|
if (new DriveInfo(driveletter.ToString()).IsReady)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Throws<DirectoryNotFoundException>(() => Subject.GetAvailableSpace(driveletter + @":\NOT_A_REAL_PATH\DOES_NOT_EXIST".AsOsAgnostic()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Inconclusive("No drive available for testing.");
|
||||||
|
}
|
||||||
|
|
||||||
[Ignore("Docker")]
|
[Ignore("Docker")]
|
||||||
[Test]
|
[Test]
|
||||||
public void should_be_able_to_get_space_on_folder_that_doesnt_exist()
|
public void should_be_able_to_get_space_on_folder_that_doesnt_exist()
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user