mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2026-03-07 13:59:57 -05:00
Compare commits
8 Commits
paging
...
changelog-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2eb8f0740f | ||
|
|
e5176e79d6 | ||
|
|
0c78258e06 | ||
|
|
16641b8006 | ||
|
|
58576a5577 | ||
|
|
2f80678a8b | ||
|
|
b14727580d | ||
|
|
c255085628 |
@@ -117,6 +117,7 @@ dotnet_diagnostic.CA1003.severity = suggestion
|
||||
dotnet_diagnostic.CA1008.severity = suggestion
|
||||
dotnet_diagnostic.CA1010.severity = suggestion
|
||||
dotnet_diagnostic.CA1012.severity = suggestion
|
||||
dotnet_diagnostic.CA1014.severity = suggestion
|
||||
dotnet_diagnostic.CA1016.severity = suggestion
|
||||
dotnet_diagnostic.CA1017.severity = suggestion
|
||||
dotnet_diagnostic.CA1018.severity = suggestion
|
||||
@@ -162,7 +163,6 @@ dotnet_diagnostic.CA1309.severity = suggestion
|
||||
dotnet_diagnostic.CA1310.severity = suggestion
|
||||
dotnet_diagnostic.CA1401.severity = suggestion
|
||||
dotnet_diagnostic.CA1416.severity = suggestion
|
||||
dotnet_diagnostic.CA1419.severity = suggestion
|
||||
dotnet_diagnostic.CA1507.severity = suggestion
|
||||
dotnet_diagnostic.CA1508.severity = suggestion
|
||||
dotnet_diagnostic.CA1707.severity = suggestion
|
||||
@@ -178,6 +178,9 @@ dotnet_diagnostic.CA1720.severity = suggestion
|
||||
dotnet_diagnostic.CA1721.severity = suggestion
|
||||
dotnet_diagnostic.CA1724.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.CA1810.severity = suggestion
|
||||
dotnet_diagnostic.CA1812.severity = suggestion
|
||||
@@ -189,14 +192,13 @@ dotnet_diagnostic.CA1819.severity = suggestion
|
||||
dotnet_diagnostic.CA1822.severity = suggestion
|
||||
dotnet_diagnostic.CA1823.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.CA2002.severity = suggestion
|
||||
dotnet_diagnostic.CA2007.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.CA2013.severity = suggestion
|
||||
dotnet_diagnostic.CA2100.severity = suggestion
|
||||
@@ -227,7 +229,6 @@ dotnet_diagnostic.CA2243.severity = suggestion
|
||||
dotnet_diagnostic.CA2244.severity = suggestion
|
||||
dotnet_diagnostic.CA2245.severity = suggestion
|
||||
dotnet_diagnostic.CA2246.severity = suggestion
|
||||
dotnet_diagnostic.CA2254.severity = suggestion
|
||||
dotnet_diagnostic.CA3061.severity = suggestion
|
||||
dotnet_diagnostic.CA3075.severity = suggestion
|
||||
dotnet_diagnostic.CA3076.severity = suggestion
|
||||
@@ -254,7 +255,6 @@ dotnet_diagnostic.CA5385.severity = suggestion
|
||||
dotnet_diagnostic.CA5392.severity = suggestion
|
||||
dotnet_diagnostic.CA5394.severity = suggestion
|
||||
dotnet_diagnostic.CA5397.severity = suggestion
|
||||
dotnet_diagnostic.CA5401.severity = suggestion
|
||||
|
||||
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
|
||||
attributes:
|
||||
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:
|
||||
- label: I have searched the existing open and closed issues
|
||||
- label: I have searched the existing issues
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
4
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -5,9 +5,9 @@ body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
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:
|
||||
- label: I have searched the existing open and closed issues
|
||||
- label: I have searched the existing issues
|
||||
required: true
|
||||
- type: textarea
|
||||
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
|
||||
@@ -9,13 +9,13 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '1.3.1'
|
||||
majorVersion: '0.4.5'
|
||||
minorVersion: $[counter('minorVersion', 1)]
|
||||
prowlarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
|
||||
sentryOrg: 'servarr'
|
||||
sentryUrl: 'https://sentry.servarr.com'
|
||||
dotnetVersion: '6.0.405'
|
||||
dotnetVersion: '6.0.301'
|
||||
innoVersion: '6.2.0'
|
||||
nodeVersion: '16.x'
|
||||
windowsImage: 'windows-2022'
|
||||
@@ -748,7 +748,7 @@ stages:
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: Packages
|
||||
itemPattern: '**/$(pattern)'
|
||||
itemPattern: '/$(pattern)'
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
- bash: |
|
||||
mkdir -p ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin
|
||||
@@ -1108,5 +1108,4 @@ stages:
|
||||
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
||||
DISCORDCHANNELID: $(discordChannelId)
|
||||
DISCORDWEBHOOKKEY: $(discordWebhookKey)
|
||||
DISCORDTHREADID: $(discordThreadId)
|
||||
|
||||
|
||||
55
changelogs/CHANGELOG-v0.2.0.1448.md
Normal file
55
changelogs/CHANGELOG-v0.2.0.1448.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# New Beta Release
|
||||
|
||||
Prowlarr v0.2.0.1448 has been released on `develop`
|
||||
|
||||
A reminder about the `develop` branch
|
||||
|
||||
- **develop - Current Develop/Beta - (Beta): This is the testing edge. Released after tested in nightly to ensure no immediate issues. New features and bug fixes released here first. This version will receive updates either weeklyish or bi-weeklyish depending on development.**
|
||||
|
||||
# Announcements
|
||||
|
||||
- Automated API Documentation Updates recently implemented
|
||||
- [*Coming Soon* - Newznab & All Indexer Definitions to YML - Cardigann v5](https://github.com/Prowlarr/Prowlarr/pull/823)
|
||||
- Note that users of Newznab (Usenet) Indexers may see that the UI shows Indexers as added that are not.
|
||||
- This will be fixed with Cardigann v5 and is due to all the Newznab Indexers sharing the same definition.
|
||||
- https://i.imgur.com/tijCHlk.png
|
||||
|
||||
# Additional Commentary
|
||||
|
||||
- Lidarr v1 coming to `develop` as beta soon^(tm)
|
||||
- Readarr official beta on `develop` coming soon^(tm) currently dealing with metadata issues
|
||||
- [Radarr](https://www.reddit.com/r/radarr/comments/sgrsb3/new_stable_release_master_v4045909/) v4.0.4 released to `master` (stable)
|
||||
- [Radarr Postgres Database Support coming soon (PR#6873)](https://github.com/radarr/radarr/pull/6873)
|
||||
- [Lidarr Postgres Database Support in development (Draft PR#2625)](https://github.com/Lidarr/Lidarr/pull/2625)
|
||||
|
||||
# Releases
|
||||
|
||||
## Native
|
||||
|
||||
- [GitHub Releases](https://github.com/Prowlarr/Prowlarr/releases)
|
||||
|
||||
- [Wiki Installation Instructions](https://wiki.servarr.com/prowlarr/installation)
|
||||
|
||||
## Docker
|
||||
|
||||
- [hotio/Prowlarr:testing](https://hotio.dev/containers/prowlarr)
|
||||
|
||||
- [lscr.io/linuxserver/Prowlarr:develop](https://docs.linuxserver.io/images/docker-prowlarr)
|
||||
|
||||
------------
|
||||
|
||||
# Release Notes
|
||||
|
||||
## v0.2.0.1448 (changes since v0.2.0.1426)
|
||||
|
||||
- Sync Indexers on app start, go to http if not sync'd yet
|
||||
|
||||
- Misc definition handling improvements
|
||||
|
||||
- Fixed: Updated ruTorrent stopped state helptext
|
||||
|
||||
- Fixed: Added missing translate for Database
|
||||
|
||||
- Fixed: Download limit check was using the query limit instead of the grab limit.
|
||||
|
||||
- Other bug fixes and improvements, see github history
|
||||
212
changelogs/CHANGELOG-v0.2.0.1678.md
Normal file
212
changelogs/CHANGELOG-v0.2.0.1678.md
Normal file
@@ -0,0 +1,212 @@
|
||||
# New Beta Release
|
||||
|
||||
Prowlarr v0.2.0.1678 has been released on `develop`
|
||||
|
||||
- **Users who do not wish to be on the alpha `nightly` testing branch should take advantage of this parity and switch to `develop`
|
||||
|
||||
A reminder about the `develop` and `nightly` branches
|
||||
|
||||
- **develop** - Current Develop/Beta - (Beta): This is the testing edge. Released after tested in nightly to ensure no immediate issues. New features and bug fixes released here first after nightly. It can be considered semi-stable, but is still beta.**
|
||||
- **nightly** - Current Nightly/Unstable - (Alpha/Unstable) : This is the bleeding edge. It is released as soon as code is committed and passes all automated tests. This build may have not been used by us or other users yet. There is no guarantee that it will even run in some cases. This branch is only recommended for advanced users. Issues and self investigation are expected in this branch. Use this branch only if you know what you are doing and are willing to get your hands dirty to recover a failed update. This version is updated immediately.**
|
||||
|
||||
# Announcements
|
||||
|
||||
- Automated API Documentation Updates recently implemented
|
||||
- [*Coming Soon* - Newznab & All Indexer Definitions to YML - Cardigann v6](https://github.com/Prowlarr/Prowlarr/pull/823)
|
||||
- Note that users of Newznab (Usenet) Indexers may see that the UI shows Indexers as added that are not.
|
||||
- This will be fixed with Cardigann v6 and is due to all the Newznab Indexers sharing the same definition.
|
||||
- https://i.imgur.com/tijCHlk.png
|
||||
|
||||
# Additional Commentary
|
||||
|
||||
- Lidarr v1 coming to `develop` as beta soon^(tm)
|
||||
- [Lidarr](https://lidarr.audio/donate), [Prowlarr](https://prowlarr.com/donate), [Radarr](https://radarr.video/donate), [Readarr](https://readarr.com/donate) now accept direct bitcoin donations
|
||||
- [Readarr official beta on `develop` announced](https://www.reddit.com/r/Readarr/comments/sxvj8y/new_beta_release_develop_v0101248/)
|
||||
- Radarr Postgres Database Support in `nightly`
|
||||
- [Lidarr Postgres Database Support in development (Draft PR#2625)](https://github.com/Lidarr/Lidarr/pull/2625)
|
||||
|
||||
# Releases
|
||||
|
||||
## Native
|
||||
|
||||
- [GitHub Releases](https://github.com/Prowlarr/Prowlarr/releases)
|
||||
|
||||
- [Wiki Installation Instructions](https://wiki.servarr.com/prowlarr/installation)
|
||||
|
||||
## Docker
|
||||
|
||||
- [hotio/Prowlarr:testing](https://hotio.dev/containers/prowlarr)
|
||||
|
||||
- [lscr.io/linuxserver/Prowlarr:develop](https://docs.linuxserver.io/images/docker-prowlarr)
|
||||
|
||||
## NAS Packages
|
||||
|
||||
- Synology - Please ask the SynoCommunity to update the base package; however, you can update in-app normally
|
||||
|
||||
- QNAP - Please ask the QNAP to update the base package; however, you should be able to update in-app normally
|
||||
|
||||
------------
|
||||
|
||||
# Release Notes
|
||||
|
||||
## v0.2.0.1678 (changes since v0.2.0.1448)
|
||||
|
||||
- Bump moment from 2.29.1 to 2.29.2
|
||||
|
||||
- #834 #256 fix for unable to load the Indexes page
|
||||
|
||||
- Fix .editorconfig to disallow `this`
|
||||
|
||||
- New: MyAnonamouse freeleech support
|
||||
|
||||
- Fixed: (BHD) TMDb Parsing Exception
|
||||
|
||||
- Fixed: MoreThanTV indexer from browse page layout changes (#922)
|
||||
|
||||
- We don't have two Radarrs
|
||||
|
||||
- Fix indent from 37c393a659
|
||||
|
||||
- Fixed: (HDBits) Treat 403 as Query Limit
|
||||
|
||||
- Fixed: (PTP) Treat 403 as Query Limit
|
||||
|
||||
- New: (BTN) Rate Limit to 1 Query per 5 Seconds
|
||||
|
||||
- Fixed: (BTN) Handle Query Limit Error
|
||||
|
||||
- New: (Lidarr/Radarr/Readarr/Sonarr) Improved Errors
|
||||
|
||||
- Fixed: Loading old commands from database
|
||||
|
||||
- Fixed: Cleanup Temp files after backup creation
|
||||
|
||||
- Add Support
|
||||
|
||||
- Translated using Weblate (Finnish)
|
||||
|
||||
- Fixed: Indexer Infobox Error (#920)
|
||||
|
||||
- New: Indexer Description in Add Indexer Modal
|
||||
|
||||
- Fixed: Missing Translates
|
||||
|
||||
- New: Add Search Capabilities to Indexer API & InfoBox
|
||||
|
||||
- Fixed: Update from version in logs
|
||||
|
||||
- Automated API Docs update
|
||||
|
||||
- Translated using Weblate (Chinese (Simplified) (zh_CN))
|
||||
|
||||
- Translated using Weblate (Portuguese (Brazil))
|
||||
|
||||
- Fixed: Validation when testing indexers, connections and download clients
|
||||
|
||||
- Fixed: Prevent delete of last profile
|
||||
|
||||
- New: Load more (page) results on Search UI
|
||||
|
||||
- Update webpack packages
|
||||
|
||||
- Frontend Package Updates
|
||||
|
||||
- Backend Package Updates
|
||||
|
||||
- Bump dotnet to 6.0.3
|
||||
|
||||
- Translated using Weblate (Spanish)
|
||||
|
||||
- Fixed: (Gazelle) Replace Periods for Space in Search Term
|
||||
|
||||
- Fixed: (HDSpace) Replace Periods for Space in Search Term
|
||||
|
||||
- Fixed: (Anthelion) Replace Periods for Space in Search Term
|
||||
|
||||
- Fixed: (Redacted) Map Categories Comedy & E-Learning Videos to 'Other'
|
||||
|
||||
- Fixed: No longer require first run as admin on windows (#885)
|
||||
|
||||
- Translated using Weblate (Chinese (Simplified) (zh_CN))
|
||||
|
||||
- indexer(xthor): moved to YAML definition v5
|
||||
|
||||
- Fixed: '/indexers' URL Base breaking UI navigation
|
||||
|
||||
- Translated using Weblate (French)
|
||||
|
||||
- Fix app settings delete modal not closing and reloading app profiles
|
||||
|
||||
- Translated using Weblate (French)
|
||||
|
||||
- Bump Swashbuckle to 6.3.0
|
||||
|
||||
- Translated using Weblate (Portuguese (Brazil))
|
||||
|
||||
- fixup! New: (DanishBytes) Move to YML
|
||||
|
||||
- New: (DanishBytes) Move to YML
|
||||
|
||||
- Update translation files
|
||||
|
||||
- New: (RuTracker.org) add .bet mirror (#876)
|
||||
|
||||
- Fixed:(pornolab) language formatting
|
||||
|
||||
- New: Housekeeper for ApplicationStatus
|
||||
|
||||
- Fixed: Cleanse Tracker api_token from logs
|
||||
|
||||
- New: (HDTorrents) Add hd-torrents.org as Url option
|
||||
|
||||
- New: (Cardigann) Allow JSON filters
|
||||
|
||||
- Fixed: Convert List<HistoryEventTypes> to Int before passing to DB
|
||||
|
||||
- Fixed: WhereBuilder for Postgres
|
||||
|
||||
- Translated using Weblate (Finnish)
|
||||
|
||||
- Fixed: Make authentication cookie name unique to Prowlarr
|
||||
|
||||
- Update Categories
|
||||
|
||||
- Fixed: Enable response compression over https
|
||||
|
||||
- Fixed: (RuTracker) Update Cats
|
||||
|
||||
- Fixed: Clarify App Sync Settings (#847)
|
||||
|
||||
- Set version header to X-Application-Version (missing hyphen)
|
||||
|
||||
- Go to http if def exists on def server
|
||||
|
||||
- Fixed: (BHD) Handle API Auth Errors
|
||||
|
||||
- Fixed: (Immortalseed) Keywordless Search
|
||||
|
||||
- Fixed: (Cardigann) TraktId was mapping to TvRageId
|
||||
|
||||
- New: (Cardigann) - Cardigann v4 Support for Genre, Year, and TraktID
|
||||
|
||||
- New: (Cardigann) - Cardigann v4 Support for categorydesc
|
||||
|
||||
- New: (Cardigann) - Cardigann v4 Add Support for MapTrackerCatDescToNewznab
|
||||
|
||||
- New: (Cardigann) - Cardigann v4 Improved Search Logging
|
||||
|
||||
- Fixed: Corrected Query Limit and Grab Limit HelpText
|
||||
|
||||
- New: (Avistaz) Better error reporting for unauthorized tests
|
||||
|
||||
- Fixed: (Cardigann) Requests Failing for Definitions without LegacyLinks
|
||||
|
||||
- Bump SharpZipLib from 1.3.1 to 1.3.3 in /src/NzbDrone.Common
|
||||
|
||||
- Fixed: (Cardigann) Smarter redirect domain compare
|
||||
|
||||
- Fixed: (Cardigann) Treat "Refresh" header as redirect
|
||||
|
||||
- Fixed: (Cardigann) Replace legacy links with default link when making requests
|
||||
|
||||
- Other bug fixes and improvements, see GitHub history
|
||||
117
changelogs/CHANGELOG-v0.3.0.1730.md
Normal file
117
changelogs/CHANGELOG-v0.3.0.1730.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# New Beta Release
|
||||
|
||||
Prowlarr v0.3.0.1730 has been released on `develop`
|
||||
|
||||
- **Users who do not wish to be on the alpha `nightly` testing branch should take advantage of this parity and switch to `develop`**
|
||||
|
||||
A reminder about the `develop` and `nightly` branches
|
||||
|
||||
- **develop** - Current Develop/Beta - (Beta): This is the testing edge. Released after tested in nightly to ensure no immediate issues. New features and bug fixes released here first after nightly. It can be considered semi-stable, but is still beta.
|
||||
- **nightly** - Current Nightly/Unstable - (Alpha/Unstable) : This is the bleeding edge. It is released as soon as code is committed and passes all automated tests. This build may have not been used by us or other users yet. There is no guarantee that it will even run in some cases. This branch is only recommended for advanced users. Issues and self investigation are expected in this branch. Use this branch only if you know what you are doing and are willing to get your hands dirty to recover a failed update. This version is updated immediately.
|
||||
|
||||
# Announcements
|
||||
|
||||
- Automated API Documentation Updates recently implemented
|
||||
- [*Coming Soon* - Better \*Arr App Sync](https://github.com/Prowlarr/Prowlarr/pull/983)
|
||||
- [*Coming Soon* - Newznab & All Indexer Definitions to YML - Cardigann v6](https://github.com/Prowlarr/Prowlarr/pull/823)
|
||||
- Note that users of Newznab (Usenet) Indexers may see that the UI shows Indexers as added that are not.
|
||||
- This will be fixed with Cardigann v6 and is due to all the Newznab Indexers sharing the same definition.
|
||||
- https://i.imgur.com/tijCHlk.png
|
||||
|
||||
# Additional Commentary
|
||||
|
||||
- Lidarr v1 coming to `develop` as beta soon^(tm)
|
||||
- [Lidarr](https://lidarr.audio/donate), [Prowlarr](https://prowlarr.com/donate), [Radarr](https://radarr.video/donate), [Readarr](https://readarr.com/donate) now accept direct bitcoin donations
|
||||
- [Readarr official beta on `develop` announced](https://www.reddit.com/r/Readarr/comments/sxvj8y/new_beta_release_develop_v0101248/)
|
||||
- Radarr Postgres Database Support in `nightly`
|
||||
- [Lidarr Postgres Database Support in development (Draft PR#2625)](https://github.com/Lidarr/Lidarr/pull/2625)
|
||||
|
||||
# Releases
|
||||
|
||||
## Native
|
||||
|
||||
- [GitHub Releases](https://github.com/Prowlarr/Prowlarr/releases)
|
||||
|
||||
- [Wiki Installation Instructions](https://wiki.servarr.com/prowlarr/installation)
|
||||
|
||||
## Docker
|
||||
|
||||
- [hotio/Prowlarr:testing](https://hotio.dev/containers/prowlarr)
|
||||
|
||||
- [lscr.io/linuxserver/Prowlarr:develop](https://docs.linuxserver.io/images/docker-prowlarr)
|
||||
|
||||
## NAS Packages
|
||||
|
||||
- Synology - Please ask the SynoCommunity to update the base package; however, you can update in-app normally
|
||||
|
||||
- QNAP - Please ask the QNAP to update the base package; however, you should be able to update in-app normally
|
||||
|
||||
------------
|
||||
|
||||
# Release Notes
|
||||
|
||||
## v0.3.0.1730 (changes since v0.3.0.1724)
|
||||
|
||||
- Fixed: Prevent endless loop when calling IndexerUrls for Torznab
|
||||
|
||||
- Deleted translation using Weblate (Chinese (Min Nan))
|
||||
|
||||
- Fix some translations
|
||||
|
||||
- Other bug fixes and improvements, see GitHub history
|
||||
|
||||
## v0.3.0.1724 (changes since v0.2.0.1678)
|
||||
|
||||
- Fixed: Prevent endless loop when calling IndexerUrls for Newznab ( #982 )
|
||||
|
||||
- Fixed: Default List for Cardigann LegacyLinks
|
||||
|
||||
- New: Auto map known legacy BaseUrls for non-Cardigann
|
||||
|
||||
- Fixed: (BTN) Move to HTTPS ( #979 )
|
||||
|
||||
- Typo for myanonamouse.
|
||||
|
||||
- Fixed: (MoreThanTV) Better Response Cleansing ( #928 )
|
||||
|
||||
- New: SceneHD Indexer
|
||||
|
||||
- Fixed: (MaM) Handle Auth Errors & Session Expiry
|
||||
|
||||
- Fixed: Remove Indexer if categories were changed to not include in sync ( #912 )
|
||||
|
||||
- Fixed: Sync Indexers on App Edit
|
||||
|
||||
- Cleanup Config Values ( #894 )
|
||||
|
||||
- Fixed: (Cardigann) Handle json field selector that returns arrays ( #950 )
|
||||
|
||||
- New: Schedule refresh and process monitored download tasks at high priority
|
||||
|
||||
- Centralise image choice, update to latest images
|
||||
|
||||
- Don't return early after re-running checks after startup grace period ( #7147 )
|
||||
|
||||
- Fixed: Delay health check notifications on startup
|
||||
|
||||
- New: Add date picker for custom filter dates
|
||||
|
||||
- Bump Monotorrent to 2.0.5
|
||||
|
||||
- Remove old DotNetVersion method and dep
|
||||
|
||||
- New: Add backup size information ( #957 )
|
||||
|
||||
- Fixed: (BeyondHD) Use TryCoerceInt for tmdbId ( #960 )
|
||||
|
||||
- Fixed: (TorrentDay) TV Search returning Series not S/E Results ( #816 )
|
||||
|
||||
- Fixed: (CinemaZ and ExoticaZ) Better Log Cleansing
|
||||
|
||||
- Fixed: (exoticaz) Category Parsing
|
||||
|
||||
- Fixed: (Indexer) HDTorrents search imdbid + season/episode
|
||||
|
||||
- Bump version to 0.3.0
|
||||
|
||||
- Other bug fixes and improvements, see GitHub history
|
||||
203
changelogs/CHANGELOG-v0.4.2.1879.md
Normal file
203
changelogs/CHANGELOG-v0.4.2.1879.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# New Beta Release
|
||||
|
||||
Prowlarr v0.4.2.1879 has been released on `develop`
|
||||
|
||||
- **Users who do not wish to be on the alpha `nightly` testing branch should take advantage of this parity and switch to `develop`**
|
||||
|
||||
A reminder about the `develop` and `nightly` branches
|
||||
|
||||
- **develop** - Current Develop/Beta - (Beta): This is the testing edge. Released after tested in nightly to ensure no immediate issues. New features and bug fixes released here first after nightly. It can be considered semi-stable, but is still beta.
|
||||
- **nightly** - Current Nightly/Unstable - (Alpha/Unstable) : This is the bleeding edge. It is released as soon as code is committed and passes all automated tests. This build may have not been used by us or other users yet. There is no guarantee that it will even run in some cases. This branch is only recommended for advanced users. Issues and self investigation are expected in this branch. Use this branch only if you know what you are doing and are willing to get your hands dirty to recover a failed update. This version is updated immediately.
|
||||
|
||||
# Announcements
|
||||
|
||||
- [Prowlarr Cardigann Definitions Schema Versions and Validations created](https://github.com/Prowlarr/indexers#schemas)
|
||||
- [*Coming Soon* - Newznab & All Indexer Definitions to YML - Cardigann v7](https://github.com/Prowlarr/Prowlarr/pull/823)
|
||||
- Note that users of Newznab (Usenet) Indexers may see that the UI shows Indexers as added that are not.
|
||||
- This will be fixed with Cardigann v6 and is due to all the Newznab Indexers sharing the same definition.
|
||||
- https://i.imgur.com/tijCHlk.png
|
||||
|
||||
|
||||
# Additional Commentary
|
||||
|
||||
- [Lidarr v1 coming to `master` as recently released](https://www.reddit.com/r/Lidarr/comments/v5fdhi/new_stable_release_master_v1022592/)
|
||||
- [Lidarr](https://lidarr.audio/donate), [Prowlarr](https://prowlarr.com/donate), [Radarr](https://radarr.video/donate), [Readarr](https://readarr.com/donate) now accept direct bitcoin donations
|
||||
- [Readarr official beta on `develop` announced](https://www.reddit.com/r/Readarr/comments/sxvj8y/new_beta_release_develop_v0101248/)
|
||||
- Radarr Postgres Database Support in `nightly` and `develop`
|
||||
- Prowlarr Postgres Database Support in `nightly` and `develop`
|
||||
- [Lidarr Postgres Database Support in development (Draft PR#2625)](https://github.com/Lidarr/Lidarr/pull/2625)
|
||||
- \*Arrs Wiki Contributions welcomed and strongly encouraged, simply auth with GitHub on the wiki and update the page
|
||||
|
||||
# Releases
|
||||
|
||||
## Native
|
||||
|
||||
- [GitHub Releases](https://github.com/Prowlarr/Prowlarr/releases)
|
||||
|
||||
- [Wiki Installation Instructions](https://wiki.servarr.com/prowlarr/installation)
|
||||
|
||||
## Docker
|
||||
|
||||
- [hotio/Prowlarr:testing](https://hotio.dev/containers/prowlarr)
|
||||
|
||||
- [lscr.io/linuxserver/Prowlarr:develop](https://docs.linuxserver.io/images/docker-prowlarr)
|
||||
|
||||
## NAS Packages
|
||||
|
||||
- Synology - Please ask the SynoCommunity to update the base package; however, you can update in-app normally
|
||||
|
||||
- QNAP - Please ask the QNAP to update the base package; however, you should be able to update in-app normally
|
||||
|
||||
------------
|
||||
|
||||
# Release Notes
|
||||
|
||||
## v0.4.2.1879 (changes since v0.3.0.1730)
|
||||
|
||||
- Don't require user agent for IPTorrents
|
||||
|
||||
- Fixed: (Applications) ApiPath can be null from -arr in some cases
|
||||
|
||||
- ProtectionService Test Fixture
|
||||
|
||||
- Fixed: Lidarr null ref when building indexer for sync
|
||||
|
||||
- Fixed: Lidarr null ref when building indexer for sync
|
||||
|
||||
- Double MultipartBodyLengthLimit for Backup Restore to 256MB
|
||||
|
||||
- Fixed: (IPTorrents) Allow UA override for CF
|
||||
|
||||
- Fixed: Log Cleanse Indexer Response Logic and Test Cases
|
||||
|
||||
- Fixed: Set update executable permissions correctly
|
||||
|
||||
- Fixed: Don't call for server notifications on event driven check
|
||||
|
||||
- Update file and folder handling methods from Radarr (#1051)
|
||||
|
||||
- Running Integration Tests against Postgres Database (#838)
|
||||
|
||||
- Updated NLog Version (#7365)
|
||||
|
||||
- Add additional link logging to DownloadService
|
||||
|
||||
- Fixed: Correctly remove TorrentParadiseMl
|
||||
|
||||
- V6 Cardigann Changes (#1045)
|
||||
|
||||
- Sliding expiration for auth cookie and a little clean up
|
||||
|
||||
- Bump version to 0.4.2
|
||||
|
||||
- Update Sentry to 3.18.0
|
||||
|
||||
- Update Swashbuckle to 6.3.1
|
||||
|
||||
- Bump dotnet to 6.0.6
|
||||
|
||||
- Update AngleSharp to 0.17.0
|
||||
|
||||
- Remove ShowRSS C# Implementation
|
||||
|
||||
- Swallow HTTP issues on analytics call
|
||||
|
||||
- Fix NullRef in analytics service
|
||||
|
||||
- Bump version to 0.4.1
|
||||
|
||||
- Fix Donation Links
|
||||
|
||||
- Fix Tooltips in Dark Theme
|
||||
|
||||
- Fixed: (AnimeBytes) Cleanse Passkey from response
|
||||
|
||||
- Fixed: (Cardigann) Use variables in keywordsfilters block
|
||||
|
||||
- New: (BeyondHD) Better status messages for failures
|
||||
|
||||
- Fixed: VIP Healthcheck not triggered for expired indexers
|
||||
|
||||
- Use DryIoc for Automoqer, drop Unity dependency
|
||||
|
||||
- New: Send description element in nab response
|
||||
|
||||
- (Filelist) Update help text for pass key (#1039)
|
||||
|
||||
- Fixed: (Exoticaz) Category parsing kills search/feed
|
||||
|
||||
- New: (PassThePopcorn) Freeleech only option
|
||||
|
||||
- Fixed: (Cardigann) Searching with nab Parent should also use Child categories
|
||||
|
||||
- Fixed: Better Cleansing of Tracker Announce Keys
|
||||
|
||||
- Automated API Docs update
|
||||
|
||||
- Update FE dev dependencies
|
||||
|
||||
- Ensure .Mono and .Windows projects have all dependencies in build output
|
||||
|
||||
- Fixed: (Gazelle) Parse grouptime as long or date
|
||||
|
||||
- Fixed: (ExoticaZ) Category Parsing
|
||||
|
||||
- Fixed: Input options background color on mobile
|
||||
|
||||
- Fixed: Update AltHub API URL (#1010)
|
||||
|
||||
- Automated API Docs update
|
||||
|
||||
- New: Dark Theme
|
||||
|
||||
- New: Move to CSS Variables for Colorings
|
||||
|
||||
- New: Native Theme Engine
|
||||
|
||||
- diversify chartcolors for doughnut & stackedbar
|
||||
|
||||
- Translated using Weblate (Chinese (Simplified) (zh_CN))
|
||||
|
||||
- Catch Postgres log connection errors
|
||||
|
||||
- Clean lingering Postgres Connections on Close
|
||||
|
||||
- New: Instance name in System/Status API endpoint
|
||||
|
||||
- New: Instance name for Page Title
|
||||
|
||||
- New: Instance Name used for Syslog
|
||||
|
||||
- New: Set Instance Name
|
||||
|
||||
- Fixed: Use separate guid for download protection
|
||||
|
||||
- Fixed: (RuTracker) Support Raw search from apps
|
||||
|
||||
- Fixed: Localization for two part language dialects
|
||||
|
||||
- Fixed: (AnimeBytes) Handle series synonyms with commas (#984)
|
||||
|
||||
- New: Add Lidarr and Readarr DiscographySeedTime Sync
|
||||
|
||||
- New: Add Sonarr SeasonSeedTime Sync
|
||||
|
||||
- Fixed: Indexer Tags Helptext
|
||||
|
||||
- Automated API Docs update
|
||||
|
||||
- New: Seed Settings Sync
|
||||
|
||||
- New: Only sync indexers with matching app tags
|
||||
|
||||
- Indexer Cleanup
|
||||
|
||||
- Bump version to 0.4.0
|
||||
|
||||
- Bump version to 0.3.1
|
||||
|
||||
- Translated using Weblate (Chinese (Simplified) (zh_CN))
|
||||
|
||||
- Fixed: Correct User-Agent api logging
|
||||
|
||||
- Other bug fixes and improvements, see GitHub history
|
||||
110
changelogs/CHANGELOG-v0.4.3.1921.md
Normal file
110
changelogs/CHANGELOG-v0.4.3.1921.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# New Beta Release
|
||||
|
||||
Prowlarr v0.4.3.1921 has been released on `develop`
|
||||
|
||||
- **Users who do not wish to be on the alpha `nightly` testing branch should take advantage of this parity and switch to `develop`**
|
||||
|
||||
A reminder about the `develop` and `nightly` branches
|
||||
|
||||
- **develop** - Current Develop/Beta - (Beta): This is the testing edge. Released after tested in nightly to ensure no immediate issues. New features and bug fixes released here first after nightly. It can be considered semi-stable, but is still beta.
|
||||
- **nightly** - Current Nightly/Unstable - (Alpha/Unstable) : This is the bleeding edge. It is released as soon as code is committed and passes all automated tests. This build may have not been used by us or other users yet. There is no guarantee that it will even run in some cases. This branch is only recommended for advanced users. Issues and self investigation are expected in this branch. Use this branch only if you know what you are doing and are willing to get your hands dirty to recover a failed update. This version is updated immediately.
|
||||
|
||||
# Announcements
|
||||
|
||||
- [Prowlarr Cardigann Definitions Schema Versions and Validations created](https://github.com/Prowlarr/indexers#schemas)
|
||||
- [*Coming Soon* - Newznab & All Indexer Definitions to YML - Cardigann v8](https://github.com/Prowlarr/Prowlarr/pull/823)
|
||||
- Note that users of Newznab (Usenet) Indexers may see that the UI shows Indexers as added that are not.
|
||||
- This will be fixed with Cardigann v8 and is due to all the Newznab Indexers sharing the same definition.
|
||||
- https://i.imgur.com/tijCHlk.png
|
||||
|
||||
|
||||
# Additional Commentary
|
||||
|
||||
- [Radarr Develop recently released](https://www.reddit.com/r/radarr/comments/w3kik4/new_release_develop_v4206438/)
|
||||
- [Lidarr](https://lidarr.audio/donate), [Prowlarr](https://prowlarr.com/donate), [Radarr](https://radarr.video/donate), [Readarr](https://readarr.com/donate) now accept direct bitcoin donations
|
||||
- Radarr Postgres Database Support in `nightly` and `develop`
|
||||
- Prowlarr Postgres Database Support in `nightly` and `develop`
|
||||
- [Lidarr Postgres Database Support in development (Draft PR#2625)](https://github.com/Lidarr/Lidarr/pull/2625)
|
||||
- \*Arrs Wiki Contributions welcomed and strongly encouraged, simply auth with GitHub on the wiki and update the page
|
||||
|
||||
# Releases
|
||||
|
||||
## Native
|
||||
|
||||
- [GitHub Releases](https://github.com/Prowlarr/Prowlarr/releases)
|
||||
|
||||
- [Wiki Installation Instructions](https://wiki.servarr.com/prowlarr/installation)
|
||||
|
||||
## Docker
|
||||
|
||||
- [hotio/Prowlarr:testing](https://hotio.dev/containers/prowlarr)
|
||||
|
||||
- [lscr.io/linuxserver/Prowlarr:develop](https://docs.linuxserver.io/images/docker-prowlarr)
|
||||
|
||||
## NAS Packages
|
||||
|
||||
- Synology - Please ask the SynoCommunity to update the base package; however, you can update in-app normally
|
||||
|
||||
- QNAP - Please ask the QNAP to update the base package; however, you should be able to update in-app normally
|
||||
|
||||
------------
|
||||
|
||||
# Release Notes
|
||||
|
||||
## v0.4.3.1921 (changes since v0.4.2.1879)
|
||||
|
||||
- Fixed: (GazelleGames) Use API instead of scraping
|
||||
|
||||
- Translated using Weblate (Hungarian)
|
||||
|
||||
- Automated API Docs update
|
||||
|
||||
- New: Search by DoubanId
|
||||
|
||||
- Fixed: UI Typos (#1072)
|
||||
|
||||
- Translated using Weblate (Chinese (Traditional) (zh_TW))
|
||||
|
||||
- Update README.md
|
||||
|
||||
- Automated API Docs update
|
||||
|
||||
- Debounce analytics service
|
||||
|
||||
- Fixed: Set Download and Upload Factors from Generic Torznab
|
||||
|
||||
- Translated using Weblate (Portuguese (Brazil))
|
||||
|
||||
- Translation Improvements
|
||||
|
||||
- Cleanup Language and Localization code
|
||||
|
||||
- Added translation using Weblate (Lithuanian)
|
||||
|
||||
- Fixed: BeyondHD using improperly cased Content-Type header
|
||||
|
||||
- Fix NullRef in Cloudflare detection service
|
||||
|
||||
- New: (AvistaZ) Parse Languages and Subs, pass in response
|
||||
|
||||
- Rework Cloudflare Protection Detection
|
||||
|
||||
- New: (FlareSolverr) DDOS Guard Support
|
||||
|
||||
- Bump Mailkit to 3.3.0 (#1054)
|
||||
|
||||
- New: Add linux-x86 builds
|
||||
|
||||
- Remove unused XmlRPC dependency
|
||||
|
||||
- Fixed: (Cardigann) Use Indexer Encoding for Form Parameters
|
||||
|
||||
- Fixed: (Cardigann) Use Session Cookie when making SimpleCaptchaCall
|
||||
|
||||
- Fixed: Delete CustomFilters not handled properly
|
||||
|
||||
- Modern HTTP Client (#685)
|
||||
|
||||
- Bump version to 0.4.3
|
||||
|
||||
- Other bug fixes and improvements, see GitHub history
|
||||
83
changelogs/CHANGELOG-v0.4.4.1947.md
Normal file
83
changelogs/CHANGELOG-v0.4.4.1947.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# New Beta Release
|
||||
|
||||
Prowlarr v0.4.4.1947 has been released on `develop`
|
||||
|
||||
- **Users who do not wish to be on the alpha `nightly` testing branch should take advantage of this parity and switch to `develop`**
|
||||
|
||||
A reminder about the `develop` and `nightly` branches
|
||||
|
||||
- **develop** - Current Develop/Beta - (Beta): This is the testing edge. Released after tested in nightly to ensure no immediate issues. New features and bug fixes released here first after nightly. It can be considered semi-stable, but is still beta.
|
||||
- **nightly** - Current Nightly/Unstable - (Alpha/Unstable) : This is the bleeding edge. It is released as soon as code is committed and passes all automated tests. This build may have not been used by us or other users yet. There is no guarantee that it will even run in some cases. This branch is only recommended for advanced users. Issues and self investigation are expected in this branch. Use this branch only if you know what you are doing and are willing to get your hands dirty to recover a failed update. This version is updated immediately.
|
||||
|
||||
# Announcements
|
||||
|
||||
- [Prowlarr Cardigann Definitions Schema Versions and Validations created](https://github.com/Prowlarr/indexers#schemas)
|
||||
- [*Coming Soon* - Newznab & All Indexer Definitions to YML - Cardigann v8](https://github.com/Prowlarr/Prowlarr/pull/823)
|
||||
- Note that users of Newznab (Usenet) Indexers may see that the UI shows Indexers as added that are not.
|
||||
- This will be fixed with Cardigann v8 and is due to all the Newznab Indexers sharing the same definition.
|
||||
- https://i.imgur.com/tijCHlk.png
|
||||
|
||||
|
||||
# Additional Commentary
|
||||
|
||||
- [Radarr Develop recently released](https://www.reddit.com/r/radarr/comments/w3kik4/new_release_develop_v4206438/)
|
||||
- [Lidarr](https://lidarr.audio/donate), [Prowlarr](https://prowlarr.com/donate), [Radarr](https://radarr.video/donate), [Readarr](https://readarr.com/donate) now accept direct bitcoin donations
|
||||
- Radarr Postgres Database Support in `nightly` and `develop`
|
||||
- Prowlarr Postgres Database Support in `nightly` and `develop`
|
||||
- Readarr Postgres Database Support in `nightly`
|
||||
- [Lidarr Postgres Database Support in development (Draft PR#2625)](https://github.com/Lidarr/Lidarr/pull/2625)
|
||||
- \*Arrs Wiki Contributions welcomed and strongly encouraged, simply auth with GitHub on the wiki and update the page
|
||||
|
||||
# Releases
|
||||
|
||||
## Native
|
||||
|
||||
- [GitHub Releases](https://github.com/Prowlarr/Prowlarr/releases)
|
||||
|
||||
- [Wiki Installation Instructions](https://wiki.servarr.com/prowlarr/installation)
|
||||
|
||||
## Docker
|
||||
|
||||
- [hotio/Prowlarr:testing](https://hotio.dev/containers/prowlarr)
|
||||
|
||||
- [lscr.io/linuxserver/Prowlarr:develop](https://docs.linuxserver.io/images/docker-prowlarr)
|
||||
|
||||
## NAS Packages
|
||||
|
||||
- Synology - Please ask the SynoCommunity to update the base package; however, you can update in-app normally
|
||||
|
||||
- QNAP - Please ask the QNAP to update the base package; however, you should be able to update in-app normally
|
||||
|
||||
------------
|
||||
|
||||
# Release Notes
|
||||
|
||||
## v0.4.4.1947 (changes since [v0.4.3.1921](https://www.reddit.com/r/prowlarr/comments/wbanhd/new_develop_release_v0431921/))
|
||||
|
||||
- Translated using Weblate (Chinese (Simplified) (zh_CN))
|
||||
|
||||
- Fixed: Correctly persist FlareSolverr Cookies to ensure it doesn't run on every request
|
||||
|
||||
- Fixed: Correctly use FlareSolverr User Agent
|
||||
|
||||
- Remove duplicate package NLog.Extensions in Prowlarr.Common
|
||||
|
||||
- Fixed: (Cardigann) fix imatch for rows
|
||||
|
||||
- Support for digest auth with HttpRequests
|
||||
|
||||
- Fixed: (Cardigann) Genre is optional
|
||||
|
||||
- Fixed: (Cardigann) Genre Parsing
|
||||
|
||||
- Automated API Docs update
|
||||
|
||||
- Fixed: (Cardigann) Genre Parsing for Releases
|
||||
|
||||
- Fixed: (Cardigann) messy row strdump
|
||||
|
||||
- New: (Cardigann) Additional query support
|
||||
|
||||
- Bump version to 0.4.4
|
||||
|
||||
- Other bug fixes and improvements, see GitHub history
|
||||
6
changelogs/templates/announcements.md
Normal file
6
changelogs/templates/announcements.md
Normal file
@@ -0,0 +1,6 @@
|
||||
- [Prowlarr Cardigann Definitions Schema Versions and Validations created](https://github.com/Prowlarr/indexers#schemas)
|
||||
- [*Coming Soon* - Newznab & All Indexer Definitions to YML - Cardigann v8](https://github.com/Prowlarr/Prowlarr/pull/823)
|
||||
- Note that users of Newznab (Usenet) Indexers may see that the UI shows Indexers as added that are not.
|
||||
- This will be fixed with Cardigann v8 and is due to all the Newznab Indexers sharing the same definition.
|
||||
- https://i.imgur.com/tijCHlk.png
|
||||
|
||||
6
changelogs/templates/branch-develop.md
Normal file
6
changelogs/templates/branch-develop.md
Normal file
@@ -0,0 +1,6 @@
|
||||
- **Users who do not wish to be on the alpha `nightly` testing branch should take advantage of this parity and switch to `develop`**
|
||||
|
||||
A reminder about the `develop` and `nightly` branches
|
||||
|
||||
- **develop** - Current Develop/Beta - (Beta): This is the testing edge. Released after tested in nightly to ensure no immediate issues. New features and bug fixes released here first after nightly. It can be considered semi-stable, but is still beta.
|
||||
- **nightly** - Current Nightly/Unstable - (Alpha/Unstable) : This is the bleeding edge. It is released as soon as code is committed and passes all automated tests. This build may have not been used by us or other users yet. There is no guarantee that it will even run in some cases. This branch is only recommended for advanced users. Issues and self investigation are expected in this branch. Use this branch only if you know what you are doing and are willing to get your hands dirty to recover a failed update. This version is updated immediately.
|
||||
6
changelogs/templates/branch-master.md
Normal file
6
changelogs/templates/branch-master.md
Normal file
@@ -0,0 +1,6 @@
|
||||
- **Users who do not wish to be on the alpha `nightly` or beta `develop` testing branches should take advantage of this parity and switch to `master`
|
||||
|
||||
A reminder about the `develop` and `nightly` branches
|
||||
|
||||
- **develop** - Current Develop/Beta - (Beta): This is the testing edge. Released after tested in nightly to ensure no immediate issues. New features and bug fixes released here first after nightly. It can be considered semi-stable, but is still beta.**
|
||||
- **nightly** - Current Nightly/Unstable - (Alpha/Unstable) : This is the bleeding edge. It is released as soon as code is committed and passes all automated tests. This build may have not been used by us or other users yet. There is no guarantee that it will even run in some cases. This branch is only recommended for advanced users. Issues and self investigation are expected in this branch. Use this branch only if you know what you are doing and are willing to get your hands dirty to recover a failed update. This version is updated immediately.**
|
||||
7
changelogs/templates/commentary.md
Normal file
7
changelogs/templates/commentary.md
Normal file
@@ -0,0 +1,7 @@
|
||||
- [Radarr Develop recently released](https://www.reddit.com/r/radarr/comments/w3kik4/new_release_develop_v4206438/)
|
||||
- [Lidarr](https://lidarr.audio/donate), [Prowlarr](https://prowlarr.com/donate), [Radarr](https://radarr.video/donate), [Readarr](https://readarr.com/donate) now accept direct bitcoin donations
|
||||
- Radarr Postgres Database Support in `nightly` and `develop`
|
||||
- Prowlarr Postgres Database Support in `nightly` and `develop`
|
||||
- Readarr Postgres Database Support in `nightly`
|
||||
- [Lidarr Postgres Database Support in development (Draft PR#2625)](https://github.com/Lidarr/Lidarr/pull/2625)
|
||||
- \*Arrs Wiki Contributions welcomed and strongly encouraged, simply auth with GitHub on the wiki and update the page
|
||||
@@ -39,7 +39,6 @@ module.exports = {
|
||||
plugins: [
|
||||
'filenames',
|
||||
'react',
|
||||
'react-hooks',
|
||||
'simple-import-sort',
|
||||
'import'
|
||||
],
|
||||
@@ -309,9 +308,7 @@ module.exports = {
|
||||
'react/react-in-jsx-scope': 2,
|
||||
'react/self-closing-comp': 2,
|
||||
'react/sort-comp': 2,
|
||||
'react/jsx-wrap-multilines': 2,
|
||||
'react-hooks/rules-of-hooks': 'error',
|
||||
'react-hooks/exhaustive-deps': 'error'
|
||||
'react/jsx-wrap-multilines': 2
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
|
||||
@@ -142,8 +142,8 @@ module.exports = (env) => {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.jsx?$/,
|
||||
exclude: /[\\/]node_modules[\\/](?!(@sentry\/browser|@sentry\/integrations|chart.js|filesize|normalize.css)[\\/])/,
|
||||
test: /\.js?$/,
|
||||
exclude: /(node_modules|JsLibraries)/,
|
||||
use: [
|
||||
{
|
||||
loader: 'babel-loader',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Fragment, useCallback, useEffect } from 'react';
|
||||
import React, { Fragment, useEffect } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import themes from 'Styles/Themes';
|
||||
@@ -19,8 +19,7 @@ function createMapStateToProps() {
|
||||
|
||||
function ApplyTheme({ theme, children }) {
|
||||
// Update the CSS Variables
|
||||
|
||||
const updateCSSVariables = useCallback(() => {
|
||||
function updateCSSVariables() {
|
||||
const arrayOfVariableKeys = Object.keys(themes[theme]);
|
||||
const arrayOfVariableValues = Object.values(themes[theme]);
|
||||
|
||||
@@ -32,12 +31,12 @@ function ApplyTheme({ theme, children }) {
|
||||
arrayOfVariableValues[index]
|
||||
);
|
||||
});
|
||||
}, [theme]);
|
||||
}
|
||||
|
||||
// On Component Mount and Component Update
|
||||
useEffect(() => {
|
||||
updateCSSVariables(theme);
|
||||
}, [updateCSSVariables, theme]);
|
||||
}, [theme]);
|
||||
|
||||
return <Fragment>{children}</Fragment>;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ function CustomFiltersModalContent(props) {
|
||||
|
||||
<div className={styles.addButtonContainer}>
|
||||
<Button onPress={onAddCustomFilter}>
|
||||
{translate('AddCustomFilter')}
|
||||
Add Custom Filter
|
||||
</Button>
|
||||
</div>
|
||||
</ModalBody>
|
||||
|
||||
@@ -16,7 +16,6 @@ import FormInputHelpText from './FormInputHelpText';
|
||||
import IndexerFlagsSelectInputConnector from './IndexerFlagsSelectInputConnector';
|
||||
import InfoInput from './InfoInput';
|
||||
import KeyValueListInput from './KeyValueListInput';
|
||||
import NewznabCategorySelectInputConnector from './NewznabCategorySelectInputConnector';
|
||||
import NumberInput from './NumberInput';
|
||||
import OAuthInputConnector from './OAuthInputConnector';
|
||||
import PasswordInput from './PasswordInput';
|
||||
@@ -69,9 +68,6 @@ function getComponent(type) {
|
||||
case inputTypes.PATH:
|
||||
return PathInputConnector;
|
||||
|
||||
case inputTypes.CATEGORY_SELECT:
|
||||
return NewznabCategorySelectInputConnector;
|
||||
|
||||
case inputTypes.INDEXER_FLAGS_SELECT:
|
||||
return IndexerFlagsSelectInputConnector;
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ function createMapStateToProps() {
|
||||
});
|
||||
|
||||
return {
|
||||
value: value || [],
|
||||
value,
|
||||
values
|
||||
};
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ class TagInputInput extends Component {
|
||||
<div
|
||||
ref={forwardedRef}
|
||||
className={className}
|
||||
component="div"
|
||||
onMouseDown={this.onMouseDown}
|
||||
>
|
||||
{
|
||||
|
||||
@@ -36,5 +36,5 @@
|
||||
/** Outline **/
|
||||
|
||||
.outline {
|
||||
background-color: var(--cardBackgroundColor);
|
||||
background-color: var(--white);
|
||||
}
|
||||
|
||||
@@ -108,5 +108,5 @@
|
||||
/** Outline **/
|
||||
|
||||
.outline {
|
||||
background-color: var(--cardBackgroundColor);
|
||||
background-color: var(--white);
|
||||
}
|
||||
|
||||
@@ -30,10 +30,10 @@ function ConfirmModal(props) {
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
bindShortcut('enter', onConfirm);
|
||||
|
||||
return () => unbindShortcut('enter', onConfirm);
|
||||
} else {
|
||||
unbindShortcut('enter', onConfirm);
|
||||
}
|
||||
}, [bindShortcut, unbindShortcut, isOpen, onConfirm]);
|
||||
}, [onConfirm]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
color: #515253;
|
||||
color: var(--toobarButtonHoverColor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import AppUpdatedModalConnector from 'App/AppUpdatedModalConnector';
|
||||
import ColorImpairedContext from 'App/ColorImpairedContext';
|
||||
import ConnectionLostModalConnector from 'App/ConnectionLostModalConnector';
|
||||
import SignalRConnector from 'Components/SignalRConnector';
|
||||
import AuthenticationRequiredModal from 'FirstRun/AuthenticationRequiredModal';
|
||||
import locationShape from 'Helpers/Props/Shapes/locationShape';
|
||||
import PageHeader from './Header/PageHeader';
|
||||
import PageSidebar from './Sidebar/PageSidebar';
|
||||
@@ -76,7 +75,6 @@ class Page extends Component {
|
||||
isSmallScreen,
|
||||
isSidebarVisible,
|
||||
enableColorImpairedMode,
|
||||
authenticationEnabled,
|
||||
onSidebarToggle,
|
||||
onSidebarVisibleChange
|
||||
} = this.props;
|
||||
@@ -111,10 +109,6 @@ class Page extends Component {
|
||||
isOpen={this.state.isConnectionLostModalOpen}
|
||||
onModalClose={this.onConnectionLostModalClose}
|
||||
/>
|
||||
|
||||
<AuthenticationRequiredModal
|
||||
isOpen={!authenticationEnabled}
|
||||
/>
|
||||
</div>
|
||||
</ColorImpairedContext.Provider>
|
||||
);
|
||||
@@ -130,7 +124,6 @@ Page.propTypes = {
|
||||
isUpdated: PropTypes.bool.isRequired,
|
||||
isDisconnected: PropTypes.bool.isRequired,
|
||||
enableColorImpairedMode: PropTypes.bool.isRequired,
|
||||
authenticationEnabled: PropTypes.bool.isRequired,
|
||||
onResize: PropTypes.func.isRequired,
|
||||
onSidebarToggle: PropTypes.func.isRequired,
|
||||
onSidebarVisibleChange: PropTypes.func.isRequired
|
||||
|
||||
@@ -11,7 +11,6 @@ import { fetchAppProfiles, fetchGeneralSettings, fetchIndexerCategories, fetchUI
|
||||
import { fetchStatus } from 'Store/Actions/systemActions';
|
||||
import { fetchTags } from 'Store/Actions/tagActions';
|
||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
|
||||
import ErrorPage from './ErrorPage';
|
||||
import LoadingPage from './LoadingPage';
|
||||
import Page from './Page';
|
||||
@@ -134,21 +133,18 @@ function createMapStateToProps() {
|
||||
selectErrors,
|
||||
selectAppProps,
|
||||
createDimensionsSelector(),
|
||||
createSystemStatusSelector(),
|
||||
(
|
||||
enableColorImpairedMode,
|
||||
isPopulated,
|
||||
errors,
|
||||
app,
|
||||
dimensions,
|
||||
systemStatus
|
||||
dimensions
|
||||
) => {
|
||||
return {
|
||||
...app,
|
||||
...errors,
|
||||
isPopulated,
|
||||
isSmallScreen: dimensions.isSmallScreen,
|
||||
authenticationEnabled: systemStatus.authentication !== 'none',
|
||||
enableColorImpairedMode
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 INFO = 'info';
|
||||
export const MOVIE_MONITORED_SELECT = 'movieMonitoredSelect';
|
||||
export const CATEGORY_SELECT = 'newznabCategorySelect';
|
||||
export const NUMBER = 'number';
|
||||
export const OAUTH = 'oauth';
|
||||
export const PASSWORD = 'password';
|
||||
@@ -33,7 +32,6 @@ export const all = [
|
||||
KEY_VALUE_LIST,
|
||||
INFO,
|
||||
MOVIE_MONITORED_SELECT,
|
||||
CATEGORY_SELECT,
|
||||
NUMBER,
|
||||
OAUTH,
|
||||
PASSWORD,
|
||||
|
||||
@@ -226,42 +226,6 @@ class HistoryRow extends Component {
|
||||
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 ?
|
||||
<HistoryRowParameter
|
||||
@@ -279,15 +243,6 @@ class HistoryRow extends Component {
|
||||
/> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
data.publisher ?
|
||||
<HistoryRowParameter
|
||||
title='Publisher'
|
||||
value={data.publisher}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ class AddIndexerModalContent extends Component {
|
||||
const filteredIndexers = indexers.filter((indexer) => {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -61,15 +61,15 @@ class TagsModalContent extends Component {
|
||||
} = this.state;
|
||||
|
||||
const applyTagsOptions = [
|
||||
{ key: 'add', value: translate('Add') },
|
||||
{ key: 'remove', value: translate('Remove') },
|
||||
{ key: 'replace', value: translate('Replace') }
|
||||
{ key: 'add', value: 'Add' },
|
||||
{ key: 'remove', value: 'Remove' },
|
||||
{ key: 'replace', value: 'Replace' }
|
||||
];
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
{translate('Tags')}
|
||||
Tags
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
|
||||
@@ -221,7 +221,7 @@ class IndexerIndex extends Component {
|
||||
|
||||
onKeyUp = (event) => {
|
||||
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) {
|
||||
this.setState({ jumpToCharacter: jumpBarItems[0] });
|
||||
}
|
||||
@@ -272,7 +272,6 @@ class IndexerIndex extends Component {
|
||||
saveError,
|
||||
isDeleting,
|
||||
isTestingAll,
|
||||
isSyncingIndexers,
|
||||
deleteError,
|
||||
onScroll,
|
||||
onSortSelect,
|
||||
@@ -310,15 +309,6 @@ class IndexerIndex extends Component {
|
||||
onPress={this.onAddIndexerPress}
|
||||
/>
|
||||
|
||||
<PageToolbarSeparator />
|
||||
|
||||
<PageToolbarButton
|
||||
label={translate('SyncAppIndexers')}
|
||||
iconName={icons.REFRESH}
|
||||
isSpinning={isSyncingIndexers}
|
||||
onPress={this.props.onAppIndexerSyncPress}
|
||||
/>
|
||||
|
||||
<PageToolbarButton
|
||||
label={translate('TestAllIndexers')}
|
||||
iconName={icons.TEST}
|
||||
@@ -503,12 +493,10 @@ IndexerIndex.propTypes = {
|
||||
saveError: PropTypes.object,
|
||||
isDeleting: PropTypes.bool.isRequired,
|
||||
isTestingAll: PropTypes.bool.isRequired,
|
||||
isSyncingIndexers: PropTypes.bool.isRequired,
|
||||
deleteError: PropTypes.object,
|
||||
onSortSelect: PropTypes.func.isRequired,
|
||||
onFilterSelect: PropTypes.func.isRequired,
|
||||
onTestAllPress: PropTypes.func.isRequired,
|
||||
onAppIndexerSyncPress: PropTypes.func.isRequired,
|
||||
onScroll: PropTypes.func.isRequired,
|
||||
onSaveSelected: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -2,13 +2,10 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import * as commandNames from 'Commands/commandNames';
|
||||
import withScrollPosition from 'Components/withScrollPosition';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import { testAllIndexers } from 'Store/Actions/indexerActions';
|
||||
import { saveIndexerEditor, setMovieFilter, setMovieSort, setMovieTableOption } from 'Store/Actions/indexerIndexActions';
|
||||
import scrollPositions from 'Store/scrollPositions';
|
||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||
import createIndexerClientSideCollectionItemsSelector from 'Store/Selectors/createIndexerClientSideCollectionItemsSelector';
|
||||
import IndexerIndex from './IndexerIndex';
|
||||
@@ -16,16 +13,13 @@ import IndexerIndex from './IndexerIndex';
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createIndexerClientSideCollectionItemsSelector('indexerIndex'),
|
||||
createCommandExecutingSelector(commandNames.APP_INDEXER_SYNC),
|
||||
createDimensionsSelector(),
|
||||
(
|
||||
indexers,
|
||||
isSyncingIndexers,
|
||||
dimensionsState
|
||||
) => {
|
||||
return {
|
||||
...indexers,
|
||||
isSyncingIndexers,
|
||||
isSmallScreen: dimensionsState.isSmallScreen
|
||||
};
|
||||
}
|
||||
@@ -52,12 +46,6 @@ function createMapDispatchToProps(dispatch, props) {
|
||||
|
||||
onTestAllPress() {
|
||||
dispatch(testAllIndexers());
|
||||
},
|
||||
|
||||
onAppIndexerSyncPress() {
|
||||
dispatch(executeCommand({
|
||||
name: commandNames.APP_INDEXER_SYNC
|
||||
}));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ function IndexerIndexSortMenu(props) {
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{translate('Status')}
|
||||
Status
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
@@ -62,7 +62,7 @@ function IndexerIndexSortMenu(props) {
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{translate('Priority')}
|
||||
{'Priority'}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
@@ -71,7 +71,7 @@ function IndexerIndexSortMenu(props) {
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{translate('Protocol')}
|
||||
{'Protocol'}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
@@ -80,7 +80,7 @@ function IndexerIndexSortMenu(props) {
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{translate('Privacy')}
|
||||
{'Privacy'}
|
||||
</SortMenuItem>
|
||||
</MenuContent>
|
||||
</SortMenu>
|
||||
|
||||
@@ -14,7 +14,7 @@ function CapabilitiesLabel(props) {
|
||||
let filteredList = categories.filter((item) => item.id < 100000);
|
||||
|
||||
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();
|
||||
|
||||
@@ -79,7 +79,6 @@ class IndexerIndexRow extends Component {
|
||||
privacy,
|
||||
priority,
|
||||
status,
|
||||
fields,
|
||||
appProfile,
|
||||
added,
|
||||
capabilities,
|
||||
@@ -97,8 +96,6 @@ class IndexerIndexRow extends Component {
|
||||
isIndexerInfoModalOpen
|
||||
} = this.state;
|
||||
|
||||
const baseUrl = fields.find((field) => field.name === 'baseUrl')?.value ?? (Array.isArray(indexerUrls) ? indexerUrls[0] : undefined);
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
@@ -248,12 +245,12 @@ class IndexerIndexRow extends Component {
|
||||
/>
|
||||
|
||||
{
|
||||
baseUrl ?
|
||||
indexerUrls ?
|
||||
<IconButton
|
||||
className={styles.externalLink}
|
||||
name={icons.EXTERNAL_LINK}
|
||||
title={translate('Website')}
|
||||
to={baseUrl.replace(/(:\/\/)api\./, '$1')}
|
||||
to={indexerUrls[0].replace('api.', '')}
|
||||
/> : null
|
||||
}
|
||||
|
||||
@@ -302,7 +299,6 @@ IndexerIndexRow.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
enable: PropTypes.bool.isRequired,
|
||||
redirect: PropTypes.bool.isRequired,
|
||||
fields: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
appProfile: PropTypes.object.isRequired,
|
||||
status: PropTypes.object,
|
||||
capabilities: PropTypes.object,
|
||||
|
||||
@@ -20,14 +20,11 @@ function IndexerInfoModalContent(props) {
|
||||
encoding,
|
||||
language,
|
||||
indexerUrls,
|
||||
fields,
|
||||
protocol,
|
||||
capabilities,
|
||||
onModalClose
|
||||
} = props;
|
||||
|
||||
const baseUrl = fields.find((field) => field.name === 'baseUrl')?.value ?? indexerUrls[0];
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
@@ -60,7 +57,7 @@ function IndexerInfoModalContent(props) {
|
||||
/>
|
||||
<DescriptionListItemTitle>{translate('IndexerSite')}</DescriptionListItemTitle>
|
||||
<DescriptionListItemDescription>
|
||||
<Link to={baseUrl}>{baseUrl}</Link>
|
||||
<Link to={indexerUrls[0]}>{indexerUrls[0]}</Link>
|
||||
</DescriptionListItemDescription>
|
||||
<DescriptionListItemTitle>{`${protocol === 'usenet' ? 'Newznab' : 'Torznab'} Url`}</DescriptionListItemTitle>
|
||||
<DescriptionListItemDescription>
|
||||
@@ -117,7 +114,6 @@ IndexerInfoModalContent.propTypes = {
|
||||
encoding: PropTypes.string.isRequired,
|
||||
language: PropTypes.string.isRequired,
|
||||
indexerUrls: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
fields: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
protocol: PropTypes.string.isRequired,
|
||||
capabilities: PropTypes.object.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);
|
||||
@@ -11,6 +11,8 @@ import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||
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 * as keyCodes from 'Utilities/Constants/keyCodes';
|
||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||
@@ -21,17 +23,12 @@ import selectAll from 'Utilities/Table/selectAll';
|
||||
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||
import SearchIndexFilterMenu from './Menus/SearchIndexFilterMenu';
|
||||
import SearchIndexSortMenu from './Menus/SearchIndexSortMenu';
|
||||
import SearchIndexOverviewsConnector from './Mobile/SearchIndexOverviewsConnector';
|
||||
import NoSearchResults from './NoSearchResults';
|
||||
import SearchFooterConnector from './SearchFooterConnector';
|
||||
import SearchIndexTableConnector from './Table/SearchIndexTableConnector';
|
||||
import styles from './SearchIndex.css';
|
||||
|
||||
function getViewComponent(isSmallScreen) {
|
||||
if (isSmallScreen) {
|
||||
return SearchIndexOverviewsConnector;
|
||||
}
|
||||
|
||||
function getViewComponent() {
|
||||
return SearchIndexTableConnector;
|
||||
}
|
||||
|
||||
@@ -47,6 +44,8 @@ class SearchIndex extends Component {
|
||||
scroller: null,
|
||||
jumpBarItems: { order: [] },
|
||||
jumpToCharacter: null,
|
||||
isAddIndexerModalOpen: false,
|
||||
isEditIndexerModalOpen: false,
|
||||
searchType: null,
|
||||
lastToggled: null,
|
||||
allSelected: false,
|
||||
@@ -178,6 +177,21 @@ class SearchIndex extends Component {
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onAddIndexerPress = () => {
|
||||
this.setState({ isAddIndexerModalOpen: true });
|
||||
};
|
||||
|
||||
onAddIndexerModalClose = ({ indexerSelected = false } = {}) => {
|
||||
this.setState({
|
||||
isAddIndexerModalOpen: false,
|
||||
isEditIndexerModalOpen: indexerSelected
|
||||
});
|
||||
};
|
||||
|
||||
onEditIndexerModalClose = () => {
|
||||
this.setState({ isEditIndexerModalOpen: false });
|
||||
};
|
||||
|
||||
onJumpBarItemPress = (jumpToCharacter) => {
|
||||
this.setState({ jumpToCharacter });
|
||||
};
|
||||
@@ -239,7 +253,6 @@ class SearchIndex extends Component {
|
||||
onScroll,
|
||||
onSortSelect,
|
||||
onFilterSelect,
|
||||
isSmallScreen,
|
||||
hasIndexers,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
@@ -247,6 +260,8 @@ class SearchIndex extends Component {
|
||||
const {
|
||||
scroller,
|
||||
jumpBarItems,
|
||||
isAddIndexerModalOpen,
|
||||
isEditIndexerModalOpen,
|
||||
jumpToCharacter,
|
||||
selectedState,
|
||||
allSelected,
|
||||
@@ -255,7 +270,7 @@ class SearchIndex extends Component {
|
||||
|
||||
const selectedIndexerIds = this.getSelectedIds();
|
||||
|
||||
const ViewComponent = getViewComponent(isSmallScreen);
|
||||
const ViewComponent = getViewComponent();
|
||||
const isLoaded = !!(!error && isPopulated && items.length && scroller);
|
||||
const hasNoIndexer = !totalItems;
|
||||
|
||||
@@ -369,6 +384,16 @@ class SearchIndex extends Component {
|
||||
onSearchPress={this.onSearchPress}
|
||||
onBulkGrabPress={this.onBulkGrabPress}
|
||||
/>
|
||||
|
||||
<AddIndexerModal
|
||||
isOpen={isAddIndexerModalOpen}
|
||||
onModalClose={this.onAddIndexerModalClose}
|
||||
/>
|
||||
|
||||
<EditIndexerModalConnector
|
||||
isOpen={isEditIndexerModalOpen}
|
||||
onModalClose={this.onEditIndexerModalClose}
|
||||
/>
|
||||
</PageContent>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
.category {
|
||||
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||
|
||||
flex: 0 0 130px;
|
||||
flex: 0 0 110px;
|
||||
}
|
||||
|
||||
.age,
|
||||
|
||||
@@ -18,20 +18,20 @@ function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createReleaseSelector(),
|
||||
(
|
||||
release
|
||||
movie
|
||||
) => {
|
||||
|
||||
// If a release is deleted this selector may fire before the parent
|
||||
// selecors, which will result in an undefined release, if that happens
|
||||
// If a movie is deleted this selector may fire before the parent
|
||||
// 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
|
||||
// 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 {
|
||||
...release
|
||||
...movie
|
||||
};
|
||||
}
|
||||
);
|
||||
@@ -41,7 +41,7 @@ const mapDispatchToProps = {
|
||||
dispatchExecuteCommand: executeCommand
|
||||
};
|
||||
|
||||
class SearchIndexItemConnector extends Component {
|
||||
class MovieIndexItemConnector extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
@@ -66,9 +66,9 @@ class SearchIndexItemConnector extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
SearchIndexItemConnector.propTypes = {
|
||||
MovieIndexItemConnector.propTypes = {
|
||||
guid: PropTypes.string,
|
||||
component: PropTypes.elementType.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(SearchIndexItemConnector);
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(MovieIndexItemConnector);
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
.category {
|
||||
composes: cell;
|
||||
|
||||
flex: 0 0 130px;
|
||||
flex: 0 0 110px;
|
||||
}
|
||||
|
||||
.age,
|
||||
|
||||
@@ -71,19 +71,6 @@ class SearchIndexRow extends Component {
|
||||
});
|
||||
};
|
||||
|
||||
onSavePress = () => {
|
||||
const {
|
||||
downloadUrl,
|
||||
fileName,
|
||||
onSavePress
|
||||
} = this.props;
|
||||
|
||||
onSavePress({
|
||||
downloadUrl,
|
||||
fileName
|
||||
});
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
@@ -98,6 +85,7 @@ class SearchIndexRow extends Component {
|
||||
publishDate,
|
||||
title,
|
||||
infoUrl,
|
||||
downloadUrl,
|
||||
indexer,
|
||||
size,
|
||||
files,
|
||||
@@ -312,7 +300,7 @@ class SearchIndexRow extends Component {
|
||||
className={styles.downloadLink}
|
||||
name={icons.SAVE}
|
||||
title={translate('Save')}
|
||||
onPress={this.onSavePress}
|
||||
to={downloadUrl}
|
||||
/>
|
||||
</VirtualTableRowCell>
|
||||
);
|
||||
@@ -335,7 +323,6 @@ SearchIndexRow.propTypes = {
|
||||
ageMinutes: PropTypes.number.isRequired,
|
||||
publishDate: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
fileName: PropTypes.string.isRequired,
|
||||
infoUrl: PropTypes.string.isRequired,
|
||||
downloadUrl: PropTypes.string.isRequired,
|
||||
indexerId: PropTypes.number.isRequired,
|
||||
@@ -348,7 +335,6 @@ SearchIndexRow.propTypes = {
|
||||
indexerFlags: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onGrabPress: PropTypes.func.isRequired,
|
||||
onSavePress: PropTypes.func.isRequired,
|
||||
isGrabbing: PropTypes.bool.isRequired,
|
||||
isGrabbed: PropTypes.bool.isRequired,
|
||||
grabError: PropTypes.string,
|
||||
|
||||
@@ -51,8 +51,7 @@ class SearchIndexTable extends Component {
|
||||
timeFormat,
|
||||
selectedState,
|
||||
onSelectedChange,
|
||||
onGrabPress,
|
||||
onSavePress
|
||||
onGrabPress
|
||||
} = this.props;
|
||||
|
||||
const release = items[rowIndex];
|
||||
@@ -72,7 +71,6 @@ class SearchIndexTable extends Component {
|
||||
longDateFormat={longDateFormat}
|
||||
timeFormat={timeFormat}
|
||||
onGrabPress={onGrabPress}
|
||||
onSavePress={onSavePress}
|
||||
/>
|
||||
</VirtualTableRow>
|
||||
);
|
||||
@@ -136,7 +134,6 @@ SearchIndexTable.propTypes = {
|
||||
timeFormat: PropTypes.string.isRequired,
|
||||
onSortPress: PropTypes.func.isRequired,
|
||||
onGrabPress: PropTypes.func.isRequired,
|
||||
onSavePress: PropTypes.func.isRequired,
|
||||
allSelected: PropTypes.bool.isRequired,
|
||||
allUnselected: PropTypes.bool.isRequired,
|
||||
selectedState: PropTypes.object.isRequired,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { connect } from 'react-redux';
|
||||
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 SearchIndexTable from './SearchIndexTable';
|
||||
|
||||
@@ -25,9 +25,6 @@ function createMapDispatchToProps(dispatch, props) {
|
||||
},
|
||||
onGrabPress(payload) {
|
||||
dispatch(grabRelease(payload));
|
||||
},
|
||||
onSavePress(payload) {
|
||||
dispatch(saveRelease(payload));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ class AddApplicationModalContent extends Component {
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
{translate('AddApplication')}
|
||||
Add Application
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
|
||||
@@ -71,14 +71,14 @@ class Application extends Component {
|
||||
{
|
||||
syncLevel === 'addOnly' &&
|
||||
<Label kind={kinds.WARNING}>
|
||||
{translate('AddRemoveOnly')}
|
||||
Add and Remove Only
|
||||
</Label>
|
||||
}
|
||||
|
||||
{
|
||||
syncLevel === 'fullSync' &&
|
||||
<Label kind={kinds.SUCCESS}>
|
||||
{translate('FullSync')}
|
||||
Full Sync
|
||||
</Label>
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ class Application extends Component {
|
||||
kind={kinds.DISABLED}
|
||||
outline={true}
|
||||
>
|
||||
{translate('Disabled')}
|
||||
Disabled
|
||||
</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 React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import Card from 'Components/Card';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
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 ProviderFieldFormGroup from 'Components/Form/ProviderFieldFormGroup';
|
||||
import Icon from 'Components/Icon';
|
||||
import Button from 'Components/Link/Button';
|
||||
import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
@@ -16,33 +13,12 @@ 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 { icons, inputTypes, kinds } from 'Helpers/Props';
|
||||
import { inputTypes, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import AddCategoryModalConnector from './Categories/AddCategoryModalConnector';
|
||||
import Category from './Categories/Category';
|
||||
import styles from './EditDownloadClientModalContent.css';
|
||||
|
||||
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
|
||||
|
||||
@@ -51,7 +27,6 @@ class EditDownloadClientModalContent extends Component {
|
||||
advancedSettings,
|
||||
isFetching,
|
||||
error,
|
||||
categories,
|
||||
isSaving,
|
||||
isTesting,
|
||||
saveError,
|
||||
@@ -62,21 +37,15 @@ class EditDownloadClientModalContent extends Component {
|
||||
onSavePress,
|
||||
onTestPress,
|
||||
onDeleteDownloadClientPress,
|
||||
onConfirmDeleteCategory,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
isAddCategoryModalOpen
|
||||
} = this.state;
|
||||
|
||||
const {
|
||||
id,
|
||||
implementationName,
|
||||
name,
|
||||
enable,
|
||||
priority,
|
||||
supportsCategories,
|
||||
fields,
|
||||
message
|
||||
} = item;
|
||||
@@ -167,43 +136,6 @@ class EditDownloadClientModalContent extends Component {
|
||||
/>
|
||||
</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>
|
||||
}
|
||||
</ModalBody>
|
||||
@@ -253,15 +185,13 @@ EditDownloadClientModalContent.propTypes = {
|
||||
isSaving: PropTypes.bool.isRequired,
|
||||
saveError: PropTypes.object,
|
||||
isTesting: PropTypes.bool.isRequired,
|
||||
categories: PropTypes.arrayOf(PropTypes.object),
|
||||
item: PropTypes.object.isRequired,
|
||||
onInputChange: PropTypes.func.isRequired,
|
||||
onFieldChange: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired,
|
||||
onSavePress: PropTypes.func.isRequired,
|
||||
onTestPress: PropTypes.func.isRequired,
|
||||
onDeleteDownloadClientPress: PropTypes.func,
|
||||
onConfirmDeleteCategory: PropTypes.func.isRequired
|
||||
onDeleteDownloadClientPress: PropTypes.func
|
||||
};
|
||||
|
||||
export default EditDownloadClientModalContent;
|
||||
|
||||
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
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 EditDownloadClientModalContent from './EditDownloadClientModalContent';
|
||||
|
||||
@@ -10,12 +10,10 @@ function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.settings.advancedSettings,
|
||||
createProviderSettingsSelector('downloadClients'),
|
||||
(state) => state.settings.downloadClientCategories,
|
||||
(advancedSettings, downloadClient, categories) => {
|
||||
(advancedSettings, downloadClient) => {
|
||||
return {
|
||||
advancedSettings,
|
||||
...downloadClient,
|
||||
categories: categories.items
|
||||
...downloadClient
|
||||
};
|
||||
}
|
||||
);
|
||||
@@ -25,9 +23,7 @@ const mapDispatchToProps = {
|
||||
setDownloadClientValue,
|
||||
setDownloadClientFieldValue,
|
||||
saveDownloadClient,
|
||||
testDownloadClient,
|
||||
fetchDownloadClientCategories,
|
||||
deleteDownloadClientCategory
|
||||
testDownloadClient
|
||||
};
|
||||
|
||||
class EditDownloadClientModalContentConnector extends Component {
|
||||
@@ -35,14 +31,6 @@ class EditDownloadClientModalContentConnector extends Component {
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
const {
|
||||
id,
|
||||
tagsFromId
|
||||
} = this.props;
|
||||
this.props.fetchDownloadClientCategories({ id: tagsFromId || id });
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevProps.isSaving && !this.props.isSaving && !this.props.saveError) {
|
||||
this.props.onModalClose();
|
||||
@@ -68,10 +56,6 @@ class EditDownloadClientModalContentConnector extends Component {
|
||||
this.props.testDownloadClient({ id: this.props.id });
|
||||
};
|
||||
|
||||
onConfirmDeleteCategory = (id) => {
|
||||
this.props.deleteDownloadClientCategory({ id });
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
@@ -83,7 +67,6 @@ class EditDownloadClientModalContentConnector extends Component {
|
||||
onTestPress={this.onTestPress}
|
||||
onInputChange={this.onInputChange}
|
||||
onFieldChange={this.onFieldChange}
|
||||
onConfirmDeleteCategory={this.onConfirmDeleteCategory}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -91,13 +74,10 @@ class EditDownloadClientModalContentConnector extends Component {
|
||||
|
||||
EditDownloadClientModalContentConnector.propTypes = {
|
||||
id: PropTypes.number,
|
||||
tagsFromId: PropTypes.number,
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isSaving: PropTypes.bool.isRequired,
|
||||
saveError: PropTypes.object,
|
||||
item: PropTypes.object.isRequired,
|
||||
fetchDownloadClientCategories: PropTypes.func.isRequired,
|
||||
deleteDownloadClientCategory: PropTypes.func.isRequired,
|
||||
setDownloadClientValue: PropTypes.func.isRequired,
|
||||
setDownloadClientFieldValue: PropTypes.func.isRequired,
|
||||
saveDownloadClient: PropTypes.func.isRequired,
|
||||
|
||||
@@ -11,20 +11,12 @@ import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import { icons, inputTypes, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
export const authenticationRequiredWarning = translate('AuthenticationRequiredWarning');
|
||||
|
||||
export const authenticationMethodOptions = [
|
||||
{ key: 'none', value: 'None', isDisabled: true },
|
||||
{ key: 'external', value: 'External', isHidden: true },
|
||||
const authenticationMethodOptions = [
|
||||
{ key: 'none', value: 'None' },
|
||||
{ key: 'basic', value: 'Basic (Browser Popup)' },
|
||||
{ key: 'forms', value: 'Forms (Login Page)' }
|
||||
];
|
||||
|
||||
export const authenticationRequiredOptions = [
|
||||
{ key: 'enabled', value: 'Enabled' },
|
||||
{ key: 'disabledForLocalAddresses', value: 'Disabled for Local Addresses' }
|
||||
];
|
||||
|
||||
const certificateValidationOptions = [
|
||||
{ key: 'enabled', value: 'Enabled' },
|
||||
{ key: 'disabledForLocalAddresses', value: 'Disabled for Local Addresses' },
|
||||
@@ -76,7 +68,6 @@ class SecuritySettings extends Component {
|
||||
|
||||
const {
|
||||
authenticationMethod,
|
||||
authenticationRequired,
|
||||
username,
|
||||
password,
|
||||
apiKey,
|
||||
@@ -95,31 +86,13 @@ class SecuritySettings extends Component {
|
||||
name="authenticationMethod"
|
||||
values={authenticationMethodOptions}
|
||||
helpText={translate('AuthenticationMethodHelpText')}
|
||||
helpTextWarning={authenticationRequiredWarning}
|
||||
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 ?
|
||||
authenticationEnabled &&
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Username')}</FormLabel>
|
||||
|
||||
@@ -129,12 +102,11 @@ class SecuritySettings extends Component {
|
||||
onChange={onInputChange}
|
||||
{...username}
|
||||
/>
|
||||
</FormGroup> :
|
||||
null
|
||||
</FormGroup>
|
||||
}
|
||||
|
||||
{
|
||||
authenticationEnabled ?
|
||||
authenticationEnabled &&
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Password')}</FormLabel>
|
||||
|
||||
@@ -144,8 +116,7 @@ class SecuritySettings extends Component {
|
||||
onChange={onInputChange}
|
||||
{...password}
|
||||
/>
|
||||
</FormGroup> :
|
||||
null
|
||||
</FormGroup>
|
||||
}
|
||||
|
||||
<FormGroup>
|
||||
|
||||
@@ -106,7 +106,7 @@ class IndexerProxy extends Component {
|
||||
kind={kinds.DISABLED}
|
||||
outline={true}
|
||||
>
|
||||
{translate('Disabled')}
|
||||
Disabled
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
|
||||
@@ -56,9 +56,17 @@ class Notification extends Component {
|
||||
id,
|
||||
name,
|
||||
onGrab,
|
||||
onDownload,
|
||||
onUpgrade,
|
||||
onRename,
|
||||
onDelete,
|
||||
onHealthIssue,
|
||||
onApplicationUpdate,
|
||||
supportsOnGrab,
|
||||
supportsOnDownload,
|
||||
supportsOnUpgrade,
|
||||
supportsOnRename,
|
||||
supportsOnDelete,
|
||||
supportsOnHealthIssue,
|
||||
supportsOnApplicationUpdate
|
||||
} = this.props;
|
||||
@@ -80,6 +88,34 @@ class Notification extends Component {
|
||||
</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 &&
|
||||
<Label kind={kinds.SUCCESS}>
|
||||
@@ -96,7 +132,7 @@ class Notification extends Component {
|
||||
}
|
||||
|
||||
{
|
||||
!onGrab && !onHealthIssue && !onApplicationUpdate ?
|
||||
!onGrab && !onDownload && !onRename && !onHealthIssue && !onDelete && !onApplicationUpdate ?
|
||||
<Label
|
||||
kind={kinds.DISABLED}
|
||||
outline={true}
|
||||
@@ -131,9 +167,17 @@ Notification.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
name: PropTypes.string.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,
|
||||
onApplicationUpdate: 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,
|
||||
supportsOnApplicationUpdate: PropTypes.bool.isRequired,
|
||||
onConfirmDeleteNotification: PropTypes.func.isRequired
|
||||
|
||||
@@ -15,11 +15,8 @@ function NotificationEventItems(props) {
|
||||
} = props;
|
||||
|
||||
const {
|
||||
onGrab,
|
||||
onHealthIssue,
|
||||
onApplicationUpdate,
|
||||
supportsOnGrab,
|
||||
includeManualGrabs,
|
||||
supportsOnHealthIssue,
|
||||
includeHealthWarnings,
|
||||
supportsOnApplicationUpdate
|
||||
@@ -34,31 +31,6 @@ function NotificationEventItems(props) {
|
||||
link="https://wiki.servarr.com/prowlarr/settings#connections"
|
||||
/>
|
||||
<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>
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
|
||||
@@ -20,7 +20,7 @@ function PendingChangesModal(props) {
|
||||
|
||||
useEffect(() => {
|
||||
bindShortcut('enter', onConfirm);
|
||||
}, [bindShortcut, onConfirm]);
|
||||
}, [onConfirm]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
||||
@@ -14,6 +14,8 @@ function createLanguagesSelector() {
|
||||
return createSelector(
|
||||
(state) => state.localization,
|
||||
(localization) => {
|
||||
console.log(localization);
|
||||
|
||||
const items = localization.items;
|
||||
|
||||
if (!items) {
|
||||
|
||||
@@ -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 { createThunk } from 'Store/thunks';
|
||||
import selectProviderSchema from 'Utilities/State/selectProviderSchema';
|
||||
import { set } from '../baseActions';
|
||||
|
||||
//
|
||||
// Variables
|
||||
@@ -91,34 +90,10 @@ export default {
|
||||
[FETCH_DOWNLOAD_CLIENTS]: createFetchHandler(section, '/downloadclient'),
|
||||
[FETCH_DOWNLOAD_CLIENT_SCHEMA]: createFetchSchemaHandler(section, '/downloadclient/schema'),
|
||||
|
||||
[SAVE_DOWNLOAD_CLIENT]: (getState, payload, dispatch) => {
|
||||
// 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);
|
||||
},
|
||||
|
||||
[SAVE_DOWNLOAD_CLIENT]: createSaveProviderHandler(section, '/downloadclient'),
|
||||
[CANCEL_SAVE_DOWNLOAD_CLIENT]: createCancelSaveProviderHandler(section),
|
||||
[DELETE_DOWNLOAD_CLIENT]: createRemoveItemHandler(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);
|
||||
},
|
||||
|
||||
[TEST_DOWNLOAD_CLIENT]: createTestProviderHandler(section, '/downloadclient'),
|
||||
[CANCEL_TEST_DOWNLOAD_CLIENT]: createCancelTestProviderHandler(section),
|
||||
[TEST_ALL_DOWNLOAD_CLIENTS]: createTestAllProvidersHandler(section, '/downloadclient')
|
||||
},
|
||||
|
||||
@@ -103,6 +103,9 @@ export default {
|
||||
[SELECT_NOTIFICATION_SCHEMA]: (state, { payload }) => {
|
||||
return selectProviderSchema(state, section, payload, (selectedSchema) => {
|
||||
selectedSchema.onGrab = selectedSchema.supportsOnGrab;
|
||||
selectedSchema.onDownload = selectedSchema.supportsOnDownload;
|
||||
selectedSchema.onUpgrade = selectedSchema.supportsOnUpgrade;
|
||||
selectedSchema.onRename = selectedSchema.supportsOnRename;
|
||||
selectedSchema.onApplicationUpdate = selectedSchema.supportsOnApplicationUpdate;
|
||||
|
||||
return selectedSchema;
|
||||
|
||||
@@ -54,7 +54,7 @@ export const defaultState = {
|
||||
},
|
||||
{
|
||||
name: 'grabTitle',
|
||||
label: translate('GrabTitle'),
|
||||
label: translate('Grab Title'),
|
||||
isSortable: false,
|
||||
isVisible: false
|
||||
},
|
||||
@@ -78,7 +78,7 @@ export const defaultState = {
|
||||
},
|
||||
{
|
||||
name: 'elapsedTime',
|
||||
label: translate('ElapsedTime'),
|
||||
label: translate('Elapsed Time'),
|
||||
isSortable: false,
|
||||
isVisible: true
|
||||
},
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import $ from 'jquery';
|
||||
import { createAction } from 'redux-actions';
|
||||
import { batchActions } from 'redux-batched-actions';
|
||||
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 CLEAR_RELEASES = 'releases/clearReleases';
|
||||
export const GRAB_RELEASE = 'releases/grabRelease';
|
||||
export const SAVE_RELEASE = 'releases/saveRelease';
|
||||
export const BULK_GRAB_RELEASES = 'release/bulkGrabReleases';
|
||||
export const UPDATE_RELEASE = 'releases/updateRelease';
|
||||
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 clearReleases = createAction(CLEAR_RELEASES);
|
||||
export const grabRelease = createThunk(GRAB_RELEASE);
|
||||
export const saveRelease = createThunk(SAVE_RELEASE);
|
||||
export const bulkGrabReleases = createThunk(BULK_GRAB_RELEASES);
|
||||
export const updateRelease = createAction(UPDATE_RELEASE);
|
||||
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) {
|
||||
dispatch(set({
|
||||
section,
|
||||
isGrabbing: true
|
||||
}));
|
||||
|
||||
console.log(payload);
|
||||
|
||||
const promise = createAjaxRequest({
|
||||
url: '/search/bulk',
|
||||
method: 'POST',
|
||||
|
||||
@@ -4,7 +4,6 @@ import createHandleActions from './Creators/createHandleActions';
|
||||
import applications from './Settings/applications';
|
||||
import appProfiles from './Settings/appProfiles';
|
||||
import development from './Settings/development';
|
||||
import downloadClientCategories from './Settings/downloadClientCategories';
|
||||
import downloadClients from './Settings/downloadClients';
|
||||
import general from './Settings/general';
|
||||
import indexerCategories from './Settings/indexerCategories';
|
||||
@@ -12,7 +11,6 @@ import indexerProxies from './Settings/indexerProxies';
|
||||
import notifications from './Settings/notifications';
|
||||
import ui from './Settings/ui';
|
||||
|
||||
export * from './Settings/downloadClientCategories';
|
||||
export * from './Settings/downloadClients';
|
||||
export * from './Settings/general';
|
||||
export * from './Settings/indexerCategories';
|
||||
@@ -34,7 +32,6 @@ export const section = 'settings';
|
||||
export const defaultState = {
|
||||
advancedSettings: false,
|
||||
|
||||
downloadClientCategories: downloadClientCategories.defaultState,
|
||||
downloadClients: downloadClients.defaultState,
|
||||
general: general.defaultState,
|
||||
indexerCategories: indexerCategories.defaultState,
|
||||
@@ -64,7 +61,6 @@ export const toggleAdvancedSettings = createAction(TOGGLE_ADVANCED_SETTINGS);
|
||||
// Action Handlers
|
||||
|
||||
export const actionHandlers = handleThunks({
|
||||
...downloadClientCategories.actionHandlers,
|
||||
...downloadClients.actionHandlers,
|
||||
...general.actionHandlers,
|
||||
...indexerCategories.actionHandlers,
|
||||
@@ -85,7 +81,6 @@ export const reducers = createHandleActions({
|
||||
return Object.assign({}, state, { advancedSettings: !state.advancedSettings });
|
||||
},
|
||||
|
||||
...downloadClientCategories.reducers,
|
||||
...downloadClients.reducers,
|
||||
...general.reducers,
|
||||
...indexerCategories.reducers,
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import * as dark from './dark';
|
||||
import * as light from './light';
|
||||
|
||||
const defaultDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const auto = defaultDark ? { ...dark } : { ...light };
|
||||
|
||||
export default {
|
||||
auto,
|
||||
light,
|
||||
dark
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ const columns = [
|
||||
},
|
||||
{
|
||||
name: 'size',
|
||||
label: translate('Size'),
|
||||
label: 'Size',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
|
||||
@@ -113,7 +113,7 @@ class Updates extends Component {
|
||||
/>
|
||||
|
||||
<div className={styles.message}>
|
||||
{translate('TheLatestVersionIsAlreadyInstalled', ['Prowlarr'])}
|
||||
The latest version of Prowlarr is already installed
|
||||
</div>
|
||||
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { filesize } from 'filesize';
|
||||
import filesize from 'filesize';
|
||||
|
||||
function formatBytes(input) {
|
||||
const size = Number(input);
|
||||
|
||||
@@ -16,11 +16,6 @@ function addApiKey(ajaxOptions) {
|
||||
ajaxOptions.headers['X-Api-Key'] = window.Prowlarr.apiKey;
|
||||
}
|
||||
|
||||
function addUIHeader(ajaxOptions) {
|
||||
ajaxOptions.headers = ajaxOptions.headers || {};
|
||||
ajaxOptions.headers['X-Prowlarr-Client'] = true;
|
||||
}
|
||||
|
||||
function addContentType(ajaxOptions) {
|
||||
if (
|
||||
ajaxOptions.contentType == null &&
|
||||
@@ -47,7 +42,6 @@ export default function createAjaxRequest(originalAjaxOptions) {
|
||||
if (isRelative(ajaxOptions)) {
|
||||
addRootUrl(ajaxOptions);
|
||||
addApiKey(ajaxOptions);
|
||||
addUIHeader(ajaxOptions);
|
||||
addContentType(ajaxOptions);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<!-- Windows Phone -->
|
||||
<meta name="msapplication-navbutton-color" content="#3a3f51" />
|
||||
|
||||
<meta name="description" content="Prowlarr" />
|
||||
<meta name="description" content="Prowlarr (Preview)" />
|
||||
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
@@ -50,7 +50,7 @@
|
||||
<link rel="stylesheet" type="text/css" href="/Content/Fonts/fonts.css">
|
||||
<!-- webpack bundles head -->
|
||||
|
||||
<title>Prowlarr</title>
|
||||
<title>Prowlarr (Preview)</title>
|
||||
|
||||
<!--
|
||||
The super basic styling for .root will live here,
|
||||
|
||||
112
package.json
112
package.json
@@ -11,8 +11,7 @@
|
||||
"lint": "esprint check",
|
||||
"lint-fix": "esprint check --fix",
|
||||
"stylelint-linux": "stylelint $(find frontend -name '*.css') --config frontend/.stylelintrc",
|
||||
"stylelint-windows": "stylelint frontend/**/*.css --config frontend/.stylelintrc",
|
||||
"check-modules": "are-you-es5 check . -r"
|
||||
"stylelint-windows": "stylelint frontend/**/*.css --config frontend/.stylelintrc"
|
||||
},
|
||||
"repository": "https://github.com/Prowlarr/Prowlarr",
|
||||
"author": "Team Prowlarr",
|
||||
@@ -26,110 +25,107 @@
|
||||
"not chrome < 60"
|
||||
],
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "6.2.1",
|
||||
"@fortawesome/fontawesome-svg-core": "6.2.1",
|
||||
"@fortawesome/free-regular-svg-icons": "6.2.1",
|
||||
"@fortawesome/free-solid-svg-icons": "6.2.1",
|
||||
"@fortawesome/react-fontawesome": "0.2.0",
|
||||
"@microsoft/signalr": "6.0.13",
|
||||
"@sentry/browser": "7.28.0",
|
||||
"@sentry/integrations": "7.28.0",
|
||||
"chart.js": "4.1.1",
|
||||
"classnames": "2.3.2",
|
||||
"clipboard": "2.0.11",
|
||||
"connected-react-router": "6.9.3",
|
||||
"@fortawesome/fontawesome-free": "6.1.1",
|
||||
"@fortawesome/fontawesome-svg-core": "6.1.1",
|
||||
"@fortawesome/free-regular-svg-icons": "6.1.1",
|
||||
"@fortawesome/free-solid-svg-icons": "6.1.1",
|
||||
"@fortawesome/react-fontawesome": "0.1.18",
|
||||
"@microsoft/signalr": "6.0.6",
|
||||
"@sentry/browser": "6.19.2",
|
||||
"@sentry/integrations": "6.19.2",
|
||||
"chart.js": "3.7.1",
|
||||
"classnames": "2.3.1",
|
||||
"clipboard": "2.0.10",
|
||||
"connected-react-router": "6.9.1",
|
||||
"element-class": "0.2.2",
|
||||
"filesize": "10.0.6",
|
||||
"filesize": "6.3.0",
|
||||
"history": "4.10.1",
|
||||
"https-browserify": "1.0.0",
|
||||
"jdu": "1.0.0",
|
||||
"jquery": "3.6.2",
|
||||
"jquery": "3.6.0",
|
||||
"lodash": "4.17.21",
|
||||
"mobile-detect": "1.4.5",
|
||||
"moment": "2.29.4",
|
||||
"moment": "2.29.2",
|
||||
"mousetrap": "1.6.5",
|
||||
"normalize.css": "8.0.1",
|
||||
"prop-types": "15.8.1",
|
||||
"qs": "6.11.0",
|
||||
"qs": "6.10.3",
|
||||
"react": "17.0.2",
|
||||
"react-addons-shallow-compare": "15.6.3",
|
||||
"react-async-script": "1.2.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-html5-backend": "14.0.2",
|
||||
"react-dnd-multi-backend": "6.0.2",
|
||||
"react-dnd-touch-backend": "14.1.1",
|
||||
"react-document-title": "2.0.3",
|
||||
"react-dom": "17.0.2",
|
||||
"react-focus-lock": "2.9.2",
|
||||
"react-focus-lock": "2.5.0",
|
||||
"react-google-recaptcha": "2.1.0",
|
||||
"react-lazyload": "3.2.0",
|
||||
"react-measure": "1.4.7",
|
||||
"react-popper": "1.3.7",
|
||||
"react-redux": "8.0.5",
|
||||
"react-redux": "7.2.4",
|
||||
"react-router": "5.2.0",
|
||||
"react-router-dom": "5.2.0",
|
||||
"react-text-truncate": "0.19.0",
|
||||
"react-virtualized": "9.21.1",
|
||||
"redux": "4.2.0",
|
||||
"redux": "4.1.0",
|
||||
"redux-actions": "2.6.5",
|
||||
"redux-batched-actions": "0.5.0",
|
||||
"redux-localstorage": "0.4.1",
|
||||
"redux-thunk": "2.4.2",
|
||||
"reselect": "4.1.7"
|
||||
"redux-thunk": "2.3.0",
|
||||
"reselect": "4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.20.5",
|
||||
"@babel/eslint-parser": "7.19.1",
|
||||
"@babel/plugin-proposal-class-properties": "7.18.6",
|
||||
"@babel/plugin-proposal-decorators": "7.20.5",
|
||||
"@babel/plugin-proposal-export-default-from": "7.18.10",
|
||||
"@babel/plugin-proposal-export-namespace-from": "7.18.9",
|
||||
"@babel/plugin-proposal-function-sent": "7.18.6",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6",
|
||||
"@babel/plugin-proposal-numeric-separator": "7.18.6",
|
||||
"@babel/plugin-proposal-optional-chaining": "7.18.9",
|
||||
"@babel/plugin-proposal-throw-expressions": "7.18.6",
|
||||
"@babel/core": "7.18.2",
|
||||
"@babel/eslint-parser": "7.18.2",
|
||||
"@babel/plugin-proposal-class-properties": "7.17.12",
|
||||
"@babel/plugin-proposal-decorators": "7.18.2",
|
||||
"@babel/plugin-proposal-export-default-from": "7.17.12",
|
||||
"@babel/plugin-proposal-export-namespace-from": "7.17.12",
|
||||
"@babel/plugin-proposal-function-sent": "7.18.2",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "7.17.12",
|
||||
"@babel/plugin-proposal-numeric-separator": "7.16.7",
|
||||
"@babel/plugin-proposal-optional-chaining": "7.17.12",
|
||||
"@babel/plugin-proposal-throw-expressions": "7.16.7",
|
||||
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
||||
"@babel/preset-env": "7.20.2",
|
||||
"@babel/preset-react": "7.18.6",
|
||||
"are-you-es5": "2.1.2",
|
||||
"autoprefixer": "10.4.13",
|
||||
"babel-loader": "9.1.0",
|
||||
"@babel/preset-env": "7.18.2",
|
||||
"@babel/preset-react": "7.17.12",
|
||||
"autoprefixer": "10.4.7",
|
||||
"babel-loader": "8.2.5",
|
||||
"babel-plugin-inline-classnames": "2.0.1",
|
||||
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
|
||||
"core-js": "3.26.1",
|
||||
"css-loader": "6.7.3",
|
||||
"eslint": "8.30.0",
|
||||
"core-js": "3.22.8",
|
||||
"css-loader": "6.7.1",
|
||||
"eslint": "8.17.0",
|
||||
"eslint-plugin-filenames": "1.3.2",
|
||||
"eslint-plugin-import": "2.26.0",
|
||||
"eslint-plugin-react": "7.31.11",
|
||||
"eslint-plugin-react-hooks": "4.6.0",
|
||||
"eslint-plugin-simple-import-sort": "8.0.0",
|
||||
"eslint-plugin-react": "7.30.0",
|
||||
"eslint-plugin-simple-import-sort": "7.0.0",
|
||||
"esprint": "3.6.0",
|
||||
"file-loader": "6.2.0",
|
||||
"filemanager-webpack-plugin": "8.0.0",
|
||||
"filemanager-webpack-plugin": "6.1.7",
|
||||
"html-webpack-plugin": "5.5.0",
|
||||
"loader-utils": "^3.2.1",
|
||||
"mini-css-extract-plugin": "2.7.2",
|
||||
"postcss": "8.4.20",
|
||||
"loader-utils": "^3.0.0",
|
||||
"mini-css-extract-plugin": "2.6.0",
|
||||
"postcss": "8.4.14",
|
||||
"postcss-color-function": "4.1.0",
|
||||
"postcss-loader": "7.0.2",
|
||||
"postcss-mixins": "9.0.4",
|
||||
"postcss-nested": "6.0.0",
|
||||
"postcss-simple-vars": "7.0.1",
|
||||
"postcss-loader": "6.2.1",
|
||||
"postcss-mixins": "9.0.2",
|
||||
"postcss-nested": "5.0.6",
|
||||
"postcss-simple-vars": "6.0.3",
|
||||
"postcss-url": "10.1.3",
|
||||
"require-nocache": "1.0.0",
|
||||
"rimraf": "3.0.2",
|
||||
"run-sequence": "2.2.1",
|
||||
"streamqueue": "1.1.2",
|
||||
"style-loader": "3.3.1",
|
||||
"stylelint": "14.16.0",
|
||||
"stylelint": "14.8.5",
|
||||
"stylelint-order": "5.0.0",
|
||||
"url-loader": "4.1.1",
|
||||
"webpack": "5.75.0",
|
||||
"webpack-cli": "5.0.1",
|
||||
"webpack": "5.73.0",
|
||||
"webpack-cli": "4.9.2",
|
||||
"webpack-livereload-plugin": "3.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
105
scripts/prowlarr_changelog_post.sh
Normal file
105
scripts/prowlarr_changelog_post.sh
Normal file
@@ -0,0 +1,105 @@
|
||||
#!/bin/bash
|
||||
# Generate a Markdown change log of pull requests from commits between two tags
|
||||
scriptDir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)
|
||||
ghRepo="Prowlarr"
|
||||
#branch="develop"
|
||||
#read -r -p "What Repo?: " ghRepo
|
||||
#read -r -p "What Org?: [Default:$ghRepo]" ghOrg
|
||||
read -r -p "What Branch? [master|develop|nightly]:" branch
|
||||
ghOrg=${ghOrg:-$ghRepo}
|
||||
ghRepoUrl=https://github.com/$ghOrg/$ghRepo
|
||||
|
||||
case "${branch}" in
|
||||
master)
|
||||
hotioBranch='release'
|
||||
lsioBranch='latest'
|
||||
branchType='Stable'
|
||||
;;
|
||||
develop)
|
||||
hotioBranch='testing'
|
||||
lsioBranch='develop'
|
||||
branchType='Beta'
|
||||
;;
|
||||
nightly)
|
||||
hotioBranch='nightly'
|
||||
lsioBranch='nightly'
|
||||
branchType='Alpha'
|
||||
;;
|
||||
esac
|
||||
baseDir=$(dirname "$scriptDir")
|
||||
changelogDir="$baseDir/changelogs/"
|
||||
templateDir="$changelogDir/templates/"
|
||||
# Get a list of all tags in reverse order
|
||||
# Assumes the tags are in version format like v1.2.3
|
||||
gitTags=$(git ls-remote -t --exit-code --refs --sort='-v:refname' "$ghRepoUrl" | sed -E 's/^[[:xdigit:]]+[[:space:]]+refs\/tags\/(.+)/\1/g')
|
||||
|
||||
# Make the tags an array
|
||||
|
||||
# shellcheck disable=SC2206
|
||||
tags=($gitTags)
|
||||
|
||||
latestTag=${tags[0]}
|
||||
previousTag=${tags[1]}
|
||||
|
||||
# Get a log of commits that occurred between two tags
|
||||
# See Pretty format placeholders at https://git-scm.com/docs/pretty-formats
|
||||
# -i -E --grep="(Fixed:|New:)"'
|
||||
commits=$(git log --pretty=format:' - %s%n' "$previousTag".."$latestTag")
|
||||
# Store our changelog in a variable to be saved to a file at the end
|
||||
markdown="# New ${branchType^} Release"
|
||||
markdown+='\n\n'
|
||||
markdown+="$ghRepo $latestTag has been released on \`$branch\`"
|
||||
markdown+='\n\n'
|
||||
branchmsg=$(cat "$templateDir"/branch-$branch.md)
|
||||
if [ -n "$branchmsg" ]; then
|
||||
{
|
||||
markdown+=$branchmsg
|
||||
markdown+='\n\n'
|
||||
}
|
||||
fi
|
||||
markdown+="# Announcements"
|
||||
markdown+='\n\n'
|
||||
markdown+=$(cat "$templateDir"/announcements.md)
|
||||
markdown+='\n\n'
|
||||
markdown+="# Additional Commentary"
|
||||
markdown+='\n\n'
|
||||
markdown+=$(cat "$templateDir"/commentary.md)
|
||||
markdown+='\n\n'
|
||||
markdown+="# Releases"
|
||||
markdown+='\n\n'
|
||||
markdown+="## Native"
|
||||
markdown+="\n\n"
|
||||
markdown+="- [GitHub Releases]($ghRepoUrl/releases)"
|
||||
markdown+="\n\n"
|
||||
markdown+="- [Wiki Installation Instructions](https://wiki.servarr.com/${ghRepo,,}/installation)"
|
||||
markdown+="\n\n"
|
||||
markdown+="## Docker"
|
||||
markdown+="\n\n"
|
||||
markdown+="- [hotio/$ghRepo:$hotioBranch](https://hotio.dev/containers/${ghRepo,,})"
|
||||
markdown+="\n\n"
|
||||
markdown+="- [lscr.io/linuxserver/$ghRepo:$lsioBranch](https://docs.linuxserver.io/images/docker-${ghRepo,,})"
|
||||
markdown+="\n\n"
|
||||
markdown+="## NAS Packages"
|
||||
markdown+="\n\n"
|
||||
markdown+="- Synology - Please ask the SynoCommunity to update the base package; however, you can update in-app normally"
|
||||
markdown+="\n\n"
|
||||
markdown+="- QNAP - Please ask the QNAP to update the base package; however, you should be able to update in-app normally"
|
||||
markdown+="\n\n"
|
||||
markdown+="------------"
|
||||
markdown+="\n\n"
|
||||
markdown+="# Release Notes"
|
||||
markdown+="\n\n"
|
||||
markdown+="## $latestTag (changes since $previousTag)"
|
||||
markdown+="\n\n"
|
||||
markdown+="$commits"
|
||||
markdown+="\n\n"
|
||||
markdown+=" - Other bug fixes and improvements, see GitHub history"
|
||||
# Loop over each commit and look for merged pull requests
|
||||
#for COMMIT in $COMMITS; do
|
||||
|
||||
#done
|
||||
|
||||
# Save our markdown to a file
|
||||
mkdir -p "$changelogDir"
|
||||
echo -e "$markdown" >"$changelogDir/CHANGELOG-$latestTag.md"
|
||||
exit 0
|
||||
@@ -1,3 +0,0 @@
|
||||
is_global = true
|
||||
|
||||
dotnet_diagnostic.CA1014.severity = none
|
||||
@@ -1,7 +1,6 @@
|
||||
<Project>
|
||||
<!-- Common to all Prowlarr Projects -->
|
||||
<PropertyGroup>
|
||||
<AnalysisLevel>6.0-all</AnalysisLevel>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
@@ -95,7 +94,7 @@
|
||||
|
||||
<!-- Standard testing packages -->
|
||||
<ItemGroup Condition="'$(TestProject)'=='true'">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||
<PackageReference Include="NunitXml.TestLogger" Version="3.0.117" />
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace NzbDrone.Automation.Test
|
||||
|
||||
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger(), null);
|
||||
_runner.KillAll();
|
||||
_runner.Start(true);
|
||||
_runner.Start();
|
||||
|
||||
driver.Url = "http://localhost:9696";
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
using System.Globalization;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Common.Test.ExtensionTests.StringExtensionTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class IsValidIPAddressFixture
|
||||
{
|
||||
[TestCase("192.168.0.1")]
|
||||
[TestCase("::1")]
|
||||
[TestCase("2001:db8:4006:812::200e")]
|
||||
public void should_validate_ip_address(string input)
|
||||
{
|
||||
input.IsValidIpAddress().Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestCase("sonarr.tv")]
|
||||
public void should_not_parse_non_ip_address(string input)
|
||||
{
|
||||
input.IsValidIpAddress().Should().BeFalse();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -212,7 +212,6 @@ namespace NzbDrone.Common.Test.Http
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Platform(Exclude = "MacOsX", Reason = "Azure agent update prevents brotli on OSX")]
|
||||
public void should_execute_get_using_brotli()
|
||||
{
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/brotli");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using FluentAssertions;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Test.Common;
|
||||
@@ -10,7 +10,6 @@ namespace NzbDrone.Common.Test.Http
|
||||
[TestCase("abc://my_host.com:8080/root/api/")]
|
||||
[TestCase("abc://my_host.com:8080//root/api/")]
|
||||
[TestCase("abc://my_host.com:8080/root//api/")]
|
||||
[TestCase("abc://[::1]:8080/root//api/")]
|
||||
public void should_parse(string uri)
|
||||
{
|
||||
var newUri = new HttpUri(uri);
|
||||
@@ -53,26 +52,6 @@ namespace NzbDrone.Common.Test.Http
|
||||
newUri.FullUri.Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase("", "./relative", "relative")]
|
||||
[TestCase("/", "./relative", "/relative")]
|
||||
[TestCase("/base", "./relative", "/relative")]
|
||||
[TestCase("/base/sub", "./relative", "/base/relative")]
|
||||
[TestCase("/base/sub/", "./relative", "/base/sub/relative")]
|
||||
[TestCase("base/sub", "./relative", "base/relative")]
|
||||
[TestCase("base/sub/", "./relative", "base/sub/relative")]
|
||||
[TestCase("", "../relative", "relative")]
|
||||
[TestCase("/", "../relative", "/relative")]
|
||||
[TestCase("/base", "../relative", "/relative")]
|
||||
[TestCase("/base/sub", "../relative", "/base/relative")]
|
||||
[TestCase("/base/sub/", "../relative", "/base/sub/relative")]
|
||||
[TestCase("base/sub", "../relative", "base/relative")]
|
||||
[TestCase("base/sub/", "../relative", "base/sub/relative")]
|
||||
public void should_combine_uri_with_dot_segment(string basePath, string relativePath, string expected)
|
||||
{
|
||||
var newUri = new HttpUri(basePath) + new HttpUri(relativePath);
|
||||
newUri.FullUri.Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase("", "", "")]
|
||||
[TestCase("/", "", "/")]
|
||||
[TestCase("base", "", "base")]
|
||||
|
||||
@@ -24,16 +24,12 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
||||
[TestCase(@"https://beyond-hd.me/api/torrents/2b51db35e1912ffc138825a12b9933d2")]
|
||||
[TestCase(@"Req: [POST] https://www3.yggtorrent.nz/user/login: id=mySecret&pass=mySecret&ci_csrf_token=2b51db35e1912ffc138825a12b9933d2")]
|
||||
[TestCase(@"https://torrentseeds.org/api/torrents/filter?api_token=2b51db35e1912ffc138825a12b9933d2&name=&sortField=created_at&sortDirection=desc&perPage=100&page=1")]
|
||||
[TestCase(@"https://beyond-hd.me/torrent/download/the-next-365-days-2022-2160p-nf-web-dl-dual-ddp-51-dovi-hdr-hevc-apex.225146.2b51db35e1912ffc138825a12b9933d2")]
|
||||
[TestCase(@"https://anthelion.me/api.php?api_key=2b51db35e1910123321025a12b9933d2&o=json&t=movie&q=&tmdb=&imdb=&cat=&limit=100&offset=0")]
|
||||
[TestCase(@"https://avistaz.to/api/v1/jackett/auth: username=mySecret&password=mySecret&pid=mySecret")]
|
||||
|
||||
// Indexer and Download Client Responses
|
||||
|
||||
// avistaz response
|
||||
[TestCase(@"""download"":""https://avistaz.to/rss/download/2b51db35e1910123321025a12b9933d2/tb51db35e1910123321025a12b9933d2.torrent"",")]
|
||||
[TestCase(@",""info_hash"":""2b51db35e1910123321025a12b9933d2"",")]
|
||||
[TestCase(@"""token"":""2b51db35e1910123321025a12b9933d2""")]
|
||||
|
||||
// animebytes response
|
||||
[TestCase(@"""Link"":""https://animebytes.tv/torrent/994064/download/tb51db35e1910123321025a12b9933d2"",")]
|
||||
@@ -102,30 +98,15 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
||||
// Internal
|
||||
[TestCase(@"[Info] MigrationController: *** Migrating Database=prowlarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;Enlist=False ***")]
|
||||
[TestCase("/readarr/signalr/messages/negotiate?access_token=1234530f422f4aacb6b301233210aaaa&negotiateVersion=1")]
|
||||
[TestCase(@"[Info] MigrationController: *** Migrating Database=prowlarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;token=mySecret;Enlist=False&username=mySecret;mypassword=mySecret;mypass=shouldkeep1;test_token=mySecret;password=123%@%_@!#^#@;use_password=mySecret;get_token=shouldkeep2;usetoken=shouldkeep3;passwrd=mySecret;")]
|
||||
|
||||
public void should_clean_message(string message)
|
||||
{
|
||||
var cleansedMessage = CleanseLogMessage.Cleanse(message);
|
||||
|
||||
cleansedMessage.Should().NotContain("mySecret");
|
||||
cleansedMessage.Should().NotContain("123%@%_@!#^#@");
|
||||
cleansedMessage.Should().NotContain("01233210");
|
||||
}
|
||||
|
||||
[TestCase(@"[Info] MigrationController: *** Migrating Database=radarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;token=mySecret;Enlist=False&username=mySecret;mypassword=mySecret;mypass=shouldkeep1;test_token=mySecret;password=123%@%_@!#^#@;use_password=mySecret;get_token=shouldkeep2;usetoken=shouldkeep3;passwrd=mySecret;")]
|
||||
public void should_keep_message(string message)
|
||||
{
|
||||
var cleansedMessage = CleanseLogMessage.Cleanse(message);
|
||||
|
||||
cleansedMessage.Should().NotContain("mySecret");
|
||||
cleansedMessage.Should().NotContain("123%@%_@!#^#@");
|
||||
cleansedMessage.Should().NotContain("01233210");
|
||||
|
||||
cleansedMessage.Should().Contain("shouldkeep1");
|
||||
cleansedMessage.Should().Contain("shouldkeep2");
|
||||
cleansedMessage.Should().Contain("shouldkeep3");
|
||||
}
|
||||
|
||||
[TestCase(@"Some message (from 32.2.3.5 user agent)")]
|
||||
[TestCase(@"Auth-Invalidated ip 32.2.3.5")]
|
||||
[TestCase(@"Auth-Success ip 32.2.3.5")]
|
||||
|
||||
@@ -278,7 +278,7 @@ namespace NzbDrone.Common.Test
|
||||
[Test]
|
||||
public void GetUpdateClientExePath()
|
||||
{
|
||||
GetIAppDirectoryInfo().GetUpdateClientExePath().Should().BeEquivalentTo(@"C:\Temp\prowlarr_update\Prowlarr.Update".AsOsAgnostic().ProcessNameToExe());
|
||||
GetIAppDirectoryInfo().GetUpdateClientExePath(PlatformType.DotNet).Should().BeEquivalentTo(@"C:\Temp\prowlarr_update\Prowlarr.Update.exe".AsOsAgnostic());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -9,7 +9,6 @@ namespace NzbDrone.Common.EnvironmentInfo
|
||||
bool IsAdmin { get; }
|
||||
bool IsWindowsService { get; }
|
||||
bool IsWindowsTray { get; }
|
||||
bool IsStarting { get; set; }
|
||||
bool IsExiting { get; set; }
|
||||
bool IsTray { get; }
|
||||
RuntimeMode Mode { get; }
|
||||
|
||||
@@ -2,6 +2,13 @@ using System;
|
||||
|
||||
namespace NzbDrone.Common.EnvironmentInfo
|
||||
{
|
||||
public enum PlatformType
|
||||
{
|
||||
DotNet = 0,
|
||||
Mono = 1,
|
||||
NetCore = 2
|
||||
}
|
||||
|
||||
public interface IPlatformInfo
|
||||
{
|
||||
Version Version { get; }
|
||||
@@ -9,18 +16,36 @@ namespace NzbDrone.Common.EnvironmentInfo
|
||||
|
||||
public class PlatformInfo : IPlatformInfo
|
||||
{
|
||||
private static PlatformType _platform;
|
||||
private static Version _version;
|
||||
|
||||
static PlatformInfo()
|
||||
{
|
||||
_platform = PlatformType.NetCore;
|
||||
_version = Environment.Version;
|
||||
}
|
||||
|
||||
public static PlatformType Platform => _platform;
|
||||
public static bool IsMono => Platform == PlatformType.Mono;
|
||||
public static bool IsDotNet => Platform == PlatformType.DotNet;
|
||||
public static bool IsNetCore => Platform == PlatformType.NetCore;
|
||||
|
||||
public static string PlatformName
|
||||
{
|
||||
get
|
||||
{
|
||||
return ".NET";
|
||||
if (IsDotNet)
|
||||
{
|
||||
return ".NET";
|
||||
}
|
||||
else if (IsMono)
|
||||
{
|
||||
return "Mono";
|
||||
}
|
||||
else
|
||||
{
|
||||
return ".NET Core";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ namespace NzbDrone.Common.EnvironmentInfo
|
||||
_logger = logger;
|
||||
|
||||
IsWindowsService = hostLifetime is WindowsServiceLifetime;
|
||||
IsStarting = true;
|
||||
|
||||
// net6.0 will return Radarr.dll for entry assembly, we need the actual
|
||||
// executable name (Radarr on linux). On mono this will return the location of
|
||||
@@ -83,7 +82,6 @@ namespace NzbDrone.Common.EnvironmentInfo
|
||||
|
||||
public bool IsWindowsService { get; private set; }
|
||||
|
||||
public bool IsStarting { get; set; }
|
||||
public bool IsExiting { get; set; }
|
||||
public bool IsTray
|
||||
{
|
||||
|
||||
@@ -7,50 +7,34 @@ namespace NzbDrone.Common.Extensions
|
||||
{
|
||||
public static bool IsLocalAddress(this IPAddress ipAddress)
|
||||
{
|
||||
// Map back to IPv4 if mapped to IPv6, for example "::ffff:1.2.3.4" to "1.2.3.4".
|
||||
if (ipAddress.IsIPv4MappedToIPv6)
|
||||
if (ipAddress.IsIPv6LinkLocal)
|
||||
{
|
||||
ipAddress = ipAddress.MapToIPv4();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Checks loopback ranges for both IPv4 and IPv6.
|
||||
if (IPAddress.IsLoopback(ipAddress))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// IPv4
|
||||
if (ipAddress.AddressFamily == AddressFamily.InterNetwork)
|
||||
{
|
||||
return IsLocalIPv4(ipAddress.GetAddressBytes());
|
||||
}
|
||||
|
||||
// IPv6
|
||||
if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6)
|
||||
{
|
||||
return ipAddress.IsIPv6LinkLocal ||
|
||||
ipAddress.IsIPv6UniqueLocal ||
|
||||
ipAddress.IsIPv6SiteLocal;
|
||||
byte[] bytes = ipAddress.GetAddressBytes();
|
||||
switch (bytes[0])
|
||||
{
|
||||
case 10:
|
||||
case 127:
|
||||
return true;
|
||||
case 172:
|
||||
return bytes[1] < 32 && bytes[1] >= 16;
|
||||
case 192:
|
||||
return bytes[1] == 168;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsLocalIPv4(byte[] ipv4Bytes)
|
||||
{
|
||||
// Link local (no IP assigned by DHCP): 169.254.0.0 to 169.254.255.255 (169.254.0.0/16)
|
||||
bool IsLinkLocal() => ipv4Bytes[0] == 169 && ipv4Bytes[1] == 254;
|
||||
|
||||
// Class A private range: 10.0.0.0 – 10.255.255.255 (10.0.0.0/8)
|
||||
bool IsClassA() => ipv4Bytes[0] == 10;
|
||||
|
||||
// Class B private range: 172.16.0.0 – 172.31.255.255 (172.16.0.0/12)
|
||||
bool IsClassB() => ipv4Bytes[0] == 172 && ipv4Bytes[1] >= 16 && ipv4Bytes[1] <= 31;
|
||||
|
||||
// Class C private range: 192.168.0.0 – 192.168.255.255 (192.168.0.0/16)
|
||||
bool IsClassC() => ipv4Bytes[0] == 192 && ipv4Bytes[1] == 168;
|
||||
|
||||
return IsLinkLocal() || IsClassA() || IsClassC() || IsClassB();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace NzbDrone.Common.Extensions
|
||||
|
||||
var info = new FileInfo(path.Trim());
|
||||
|
||||
// UNC
|
||||
//UNC
|
||||
if (OsInfo.IsWindows && info.FullName.StartsWith(@"\\"))
|
||||
{
|
||||
return info.FullName.TrimEnd('/', '\\', ' ');
|
||||
@@ -166,7 +166,7 @@ namespace NzbDrone.Common.Extensions
|
||||
var parentDirInfo = dirInfo.Parent;
|
||||
if (parentDirInfo == null)
|
||||
{
|
||||
// Drive letter
|
||||
//Drive letter
|
||||
return dirInfo.Name.ToUpper();
|
||||
}
|
||||
|
||||
@@ -238,9 +238,9 @@ namespace NzbDrone.Common.Extensions
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string ProcessNameToExe(this string processName)
|
||||
public static string ProcessNameToExe(this string processName, PlatformType runtime)
|
||||
{
|
||||
if (OsInfo.IsWindows)
|
||||
if (OsInfo.IsWindows || runtime != PlatformType.NetCore)
|
||||
{
|
||||
processName += ".exe";
|
||||
}
|
||||
@@ -248,6 +248,11 @@ namespace NzbDrone.Common.Extensions
|
||||
return processName;
|
||||
}
|
||||
|
||||
public static string ProcessNameToExe(this string processName)
|
||||
{
|
||||
return processName.ProcessNameToExe(PlatformInfo.Platform);
|
||||
}
|
||||
|
||||
public static string GetAppDataPath(this IAppFolderInfo appFolderInfo)
|
||||
{
|
||||
return appFolderInfo.AppDataFolder;
|
||||
@@ -313,9 +318,9 @@ namespace NzbDrone.Common.Extensions
|
||||
return Path.Combine(GetUpdatePackageFolder(appFolderInfo), UPDATE_CLIENT_FOLDER_NAME);
|
||||
}
|
||||
|
||||
public static string GetUpdateClientExePath(this IAppFolderInfo appFolderInfo)
|
||||
public static string GetUpdateClientExePath(this IAppFolderInfo appFolderInfo, PlatformType runtime)
|
||||
{
|
||||
return Path.Combine(GetUpdateSandboxFolder(appFolderInfo), UPDATE_CLIENT_EXE_NAME).ProcessNameToExe();
|
||||
return Path.Combine(GetUpdateSandboxFolder(appFolderInfo), UPDATE_CLIENT_EXE_NAME).ProcessNameToExe(runtime);
|
||||
}
|
||||
|
||||
public static string GetDatabase(this IAppFolderInfo appFolderInfo)
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
@@ -131,7 +130,7 @@ namespace NzbDrone.Common.Extensions
|
||||
|
||||
public static string WrapInQuotes(this string text)
|
||||
{
|
||||
if (!text.Contains(' '))
|
||||
if (!text.Contains(" "))
|
||||
{
|
||||
return text;
|
||||
}
|
||||
@@ -232,43 +231,5 @@ namespace NzbDrone.Common.Extensions
|
||||
.Replace("'", "%27")
|
||||
.Replace("%7E", "~");
|
||||
}
|
||||
|
||||
public static bool IsValidIpAddress(this string value)
|
||||
{
|
||||
if (!IPAddress.TryParse(value, out var parsedAddress))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parsedAddress.Equals(IPAddress.Parse("255.255.255.255")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parsedAddress.IsIPv6Multicast)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return parsedAddress.AddressFamily == AddressFamily.InterNetwork || parsedAddress.AddressFamily == AddressFamily.InterNetworkV6;
|
||||
}
|
||||
|
||||
public static string ToUrlHost(this string input)
|
||||
{
|
||||
return input.Contains(':') ? $"[{input}]" : input;
|
||||
}
|
||||
|
||||
public static bool IsAllDigits(this string input)
|
||||
{
|
||||
foreach (var c in input)
|
||||
{
|
||||
if (c < '0' || c > '9')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user