1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-04-14 20:55:01 -04:00

Compare commits

...

75 Commits

Author SHA1 Message Date
Bogdan
8fd4e41c85 Add translations for tags in manage indexers 2023-07-08 00:38:49 +03:00
Weblate
8984fd735b Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Kevin Orel Edry <techg9@gmail.com>
Co-authored-by: Tacit <1750630216@qq.com>
Co-authored-by: reloxx <reloxx@interia.pl>
Co-authored-by: 阿卡林刘 <ScottLiu_NonWin@outlook.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2023-07-08 00:10:34 +03:00
Bogdan
3321123043 Fix translation to Apply Changes 2023-07-08 00:04:17 +03:00
Bogdan
23a13b5c23 Use SetProviderCharacteristics in bulk endpoint 2023-07-08 00:00:08 +03:00
Bogdan
304a07e23f Add translation to manage indexers 2023-07-07 20:19:32 +03:00
Bogdan
1e0ec4aefb Fix tests in ImportListSyncServiceFixture 2023-07-05 09:22:32 +03:00
Bogdan
5c46c75ce7 Fix cleaning library in ImportListSyncService 2023-07-05 09:01:53 +03:00
Bogdan
1c26dd4aca Disable library cleanup temporarily 2023-07-05 08:31:41 +03:00
Weblate
6fae00f51c Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/
Translation: Servarr/Radarr
2023-07-05 08:11:15 +03:00
Servarr
653ef0a501 Automated API Docs update 2023-07-04 13:03:49 -05:00
Qstick
e606ff05a4 Add package to Sentry release to ensure apps don't mix 2023-07-04 12:12:44 -05:00
Agneev Mukherjee
dd3ac26604 Set frontend theme-color to sidebar color (#6891) 2023-07-04 11:32:42 -05:00
Bakerboy448
122d0056ea Fixed: Quality Parsing Regex Improvements
Fixed: Parse Blu.Ray as Bluray
2023-07-04 11:20:51 -05:00
Ricardo Christmann
fe41aada06 New: Add tags field to MovieCollection (#8736)
* New: Add tags field to MovieCollection
* Added Tag input prepopulation for new collection movies
* Handle editting of collection tags
* Revert changes to CollectionController.cs
2023-07-04 11:18:44 -05:00
Bogdan
bd1844030d New: Rework List sync interval logic
* New: Rework List sync interval logic

(cherry picked from commit c522cd120d08757e7e43c2348be4d7f05a254fac)

* Minor alignment with Sonarr

* Remove ListUpdateInterval

---------

Co-authored-by: Qstick <qstick@gmail.com>
2023-07-04 19:17:28 +03:00
Qstick
148ee5983d Bump version to 4.7.0 2023-07-03 22:29:48 -05:00
Qstick
372d15ecf3 New DSN for 4.6 2023-07-03 11:16:06 -05:00
Bogdan
b95431500d Fixed: Sorting queue by year
Fixes #8765
2023-07-03 17:00:14 +03:00
Bogdan
3da72f54ef Add ContentSummary to HDBits requests
(cherry picked from commit b6f3bcb3093267c483a57e10ac425e871f1f7ba8)

Closes #8757
2023-07-03 11:22:43 +03:00
Bogdan
7cfff20cad Create overload for ToJson() with Formatting param
(cherry picked from commit aa2b0031671b6846eaa75e0914cd03ae7bbb0da7)
2023-07-03 11:20:02 +03:00
Qstick
d3895dec8f Fixed: Allow restore to process backups up to ~500MB
(cherry picked from commit 551edb9e655d2a541a2232f85e79a5e3f7b433aa)
2023-07-03 08:55:11 +01:00
Weblate
690bab3264 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: BeardedWatermelon <periklis.karantonis@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/el/
Translation: Servarr/Radarr
2023-06-30 13:55:34 +03:00
Bogdan
986128e100 Removed suspicious URL in the default definitions
Co-authored-by: Rumplin <35449956+rumplin@users.noreply.github.com>
2023-06-29 18:06:04 +03:00
Weblate
dda0885f91 Translated using Weblate (Portuguese (Brazil)) [skip ci]
Currently translated at 100.0% (1185 of 1185 strings)

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2023-06-28 13:55:21 +03:00
Mark McDowall
7e218a886d Handle null for parent name with clean path is empty
(cherry picked from commit bd3816dac9b540d2f95960a254bb21ab3c91421e)
2023-06-28 08:27:19 +03:00
Mark McDowall
77cde138dc Fixed: Don't handle images in metadata folder as Roksbox images
(cherry picked from commit 95a8f59a32d55550d5367f08a964859a97a0df44)
2023-06-28 08:27:19 +03:00
Bogdan
239109e3dd New: Improve empty list messaging
(cherry picked from commit ee843259bca2d9764a3919b123524a51a3a16cce)

Closes #8738
2023-06-27 06:00:03 +03:00
Bogdan
4804eb0769 Fixed: Invalid image URL if Movie is missing background image
(cherry picked from commit dd096e0fda71b3afa9b09c2900abbf226d8a5204)

Closes #8742
2023-06-27 05:46:30 +03:00
Bogdan
60a55dfdac Allow array of string as value in EnhancedSelectInput
(cherry picked from commit 6816767fad9e4e839e77c5fe40aece97033cd052)
2023-06-26 23:49:23 +01:00
Servarr
cd82865303 Automated API Docs update 2023-06-26 02:25:31 +03:00
Servarr
439adb4ac6 Translated using Weblate (Hungarian) [skip ci] (#8734)
Currently translated at 97.6% (1156 of 1184 strings)


Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translation: Servarr/Radarr

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: sutoramon <sutoramon@gmail.com>
2023-06-26 02:25:08 +03:00
Bogdan
fd0ff78791 Add translation to movie collection filters 2023-06-26 02:23:26 +03:00
Ricardo Christmann
cbae355402 New: Added filter and sort options to Collections (#8731)
* New: Added filter and sort options to Collections

* Add AllMovieWithCollectionsTmdbIds method to MovieService and MovieRepository
2023-06-25 10:04:57 -04:00
Qstick
fed98a648f Bump version to 4.6.4 2023-06-25 01:10:05 -04:00
bakerboy448
e1c5656cff Add more trace logs related info to bug_report.yml [skip ci] [common] 2023-06-22 16:38:09 +03:00
Weblate
e3f88e1711 Translated using Weblate (Portuguese (Brazil)) [skip ci]
Currently translated at 100.0% (1184 of 1184 strings)

Translated using Weblate (Chinese (Traditional) (zh_TW)) [skip ci]

Currently translated at 9.2% (109 of 1184 strings)

Translated using Weblate (Portuguese (Brazil)) [skip ci]

Currently translated at 100.0% (1184 of 1184 strings)

Translated using Weblate (Vietnamese) [skip ci]

Currently translated at 87.6% (1038 of 1184 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 100.0% (1184 of 1184 strings)

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: KHng0284 <giakhang021109@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: reloxx <reloxx@interia.pl>
Co-authored-by: ted09080037 <ted09080037@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_TW/
Translation: Servarr/Radarr
2023-06-21 06:53:25 +03:00
Servarr
8fd267580a Automated API Docs update 2023-06-21 06:45:42 +03:00
Bogdan
8974aa823b Remove not implemented endpoints from API docs
Closes #8724
2023-06-21 06:34:07 +03:00
Bogdan
41492efd2e Convert to 'using' declaration in Housekeeping Tasks
Closes #8723
2023-06-21 06:25:44 +03:00
Bogdan
d008768fff Prevent NullRef when deleting missing backups
(cherry picked from commit 0ff0fe2e68f3abf7b8e4d6bf0c1e9dee4eb68227)

Closes #8721
2023-06-21 06:19:30 +03:00
Bogdan
cb21fe535d Fix translation for Unreleased 2023-06-20 19:39:16 +03:00
Bogdan
4cce2727e2 Update translations
(cherry picked from commit 26031389757f6b5270bbe5591101b08e58debb73)
2023-06-20 02:38:55 +03:00
Stevie Robinson
b1ff82da37 Fixed: Parsing Vyndros as release group
(cherry picked from commit f2ddd4757c897c522b553de8bafb5340746253c9)

Closes #8569
2023-06-19 07:09:04 +03:00
Mark McDowall
c5266152c5 Fixed: Strip additional domains from release names
(cherry picked from commit e273f16c3905e0c2451f43cf98b9b7ad1cbdc777)

Closes #8603
2023-06-19 07:00:22 +03:00
Bogdan
783878be1e Minor improvements in health checks
(cherry picked from commit a22f598b0c129110f2a3b663e9b40c84f3a1f02b)

Closes #8615
2023-06-19 06:38:51 +03:00
Bogdan
0cbfb4ca65 New: (UI) Search library by imdbId and tmdbId 2023-06-19 04:19:12 +03:00
bakerboy448
c22c9400c2 New: Indexer Messaging and Error Improvements
(cherry picked from commit 3b505d8734dcbe3fa53acba7f94f1361151e6a44)
2023-06-18 12:06:55 +03:00
Bogdan
0288c4b704 Bump version to 4.6.3 2023-06-18 12:05:11 +03:00
Márki-Zay Ferenc
e4429d2919 Fixed: Close database connection in housekeeping tasks 2023-06-17 03:52:12 +03:00
Bogdan
7052a7a5ec New: Improved page loading errors
Closes #8706
2023-06-16 23:45:26 +03:00
Weblate
b38912851b Translated using Weblate (Portuguese (Brazil)) [skip ci]
Currently translated at 100.0% (1184 of 1184 strings)

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2023-06-16 02:21:40 +03:00
Bogdan
1354c2c337 Fix sorting history items by movie titles 2023-06-15 23:21:16 +03:00
Servarr
e259235df6 Automated API Docs update 2023-06-14 09:17:16 +03:00
Bogdan
0cc1fe8308 Add HelpTextWarning support in FieldDefinition
(cherry picked from commit 0e07d54ee77d5f83716e17b6757e23f38ff73694)

Closes #8687
2023-06-14 07:55:44 +03:00
Bogdan
f4fe18a440 Require ApiKey for all actions in RadarrImport
(cherry picked from commit 19b8fbe13bf584b915a05fe9fc87622adbaee0b7)

Closes #8692
2023-06-14 07:50:53 +03:00
Bogdan
eeed935e3a Update cleansing rules for RSS TL feed and homedir for Mac
(cherry picked from commit e5ff4aafa3f0b855fec332788e9fc490a03dfce3)

Closes #8684
2023-06-14 07:48:26 +03:00
Bogdan
1b3701371a Fixed: Treat redirects as errors in Radarr Import List
(cherry picked from commit 059a156f4a34c6b9cbe139fa1973b814e8a534ae)
2023-06-14 07:40:06 +03:00
Qstick
d56f3ec2e7 Fixed: Correctly handle 302 and 303 redirects in HttpClient
(cherry picked from commit ed7c5a937f4b50fcdf819e8fe347c8c0bc6bd2e7)

(cherry picked from commit 11bd764a75d3b97117098738d3489c4b3329738f)
2023-06-14 07:37:37 +03:00
Weblate
e7e3aac971 Update translation files [skip ci]
Updated by "Cleanup translation files" hook in Weblate.

Update translation files  [skip ci]

Updated by "Cleanup translation files" hook in Weblate.

Translated using Weblate (Ukrainian) [skip ci]

Currently translated at 97.5% (1155 of 1184 strings)

Translated using Weblate (Norwegian Bokmål) [skip ci]

Currently translated at 22.1% (262 of 1184 strings)

Translated using Weblate (Catalan) [skip ci]

Currently translated at 97.8% (1158 of 1184 strings)

Translated using Weblate (Portuguese (Brazil)) [skip ci]

Currently translated at 100.0% (1184 of 1184 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 99.5% (1179 of 1184 strings)

Update translation files  [skip ci]

Updated by "Cleanup translation files" hook in Weblate.

Translated using Weblate (Indonesian) [skip ci]

Currently translated at 7.5% (89 of 1180 strings)

Translated using Weblate (Croatian) [skip ci]

Currently translated at 21.8% (258 of 1180 strings)

Translated using Weblate (Ukrainian) [skip ci]

Currently translated at 97.9% (1156 of 1180 strings)

Translated using Weblate (Slovak) [skip ci]

Currently translated at 21.1% (249 of 1180 strings)

Translated using Weblate (Norwegian Bokmål) [skip ci]

Currently translated at 22.2% (263 of 1180 strings)

Translated using Weblate (Catalan) [skip ci]

Currently translated at 98.2% (1159 of 1180 strings)

Translated using Weblate (Arabic) [skip ci]

Currently translated at 88.8% (1048 of 1180 strings)

Translated using Weblate (Chinese (Simplified) (zh_CN)) [skip ci]

Currently translated at 98.8% (1166 of 1180 strings)

Translated using Weblate (Portuguese (Brazil)) [skip ci]

Currently translated at 99.9% (1179 of 1180 strings)

Translated using Weblate (Thai) [skip ci]

Currently translated at 88.2% (1041 of 1180 strings)

Translated using Weblate (Bulgarian) [skip ci]

Currently translated at 86.2% (1018 of 1180 strings)

Translated using Weblate (Hindi) [skip ci]

Currently translated at 88.2% (1041 of 1180 strings)

Translated using Weblate (Romanian) [skip ci]

Currently translated at 85.9% (1014 of 1180 strings)

Translated using Weblate (Vietnamese) [skip ci]

Currently translated at 88.2% (1041 of 1180 strings)

Translated using Weblate (Turkish) [skip ci]

Currently translated at 88.1% (1040 of 1180 strings)

Translated using Weblate (Swedish) [skip ci]

Currently translated at 89.0% (1051 of 1180 strings)

Translated using Weblate (Russian) [skip ci]

Currently translated at 98.2% (1159 of 1180 strings)

Translated using Weblate (Portuguese) [skip ci]

Currently translated at 98.3% (1160 of 1180 strings)

Translated using Weblate (Polish) [skip ci]

Currently translated at 97.1% (1146 of 1180 strings)

Translated using Weblate (Dutch) [skip ci]

Currently translated at 98.4% (1162 of 1180 strings)

Translated using Weblate (Korean) [skip ci]

Currently translated at 21.3% (252 of 1180 strings)

Translated using Weblate (Japanese) [skip ci]

Currently translated at 88.2% (1041 of 1180 strings)

Translated using Weblate (Icelandic) [skip ci]

Currently translated at 88.3% (1042 of 1180 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 98.1% (1158 of 1180 strings)

Translated using Weblate (Hebrew) [skip ci]

Currently translated at 91.2% (1077 of 1180 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 97.9% (1156 of 1180 strings)

Translated using Weblate (Greek) [skip ci]

Currently translated at 99.6% (1176 of 1180 strings)

Translated using Weblate (Danish) [skip ci]

Currently translated at 88.3% (1042 of 1180 strings)

Translated using Weblate (Czech) [skip ci]

Currently translated at 89.1% (1052 of 1180 strings)

Translated using Weblate (Italian) [skip ci]

Currently translated at 93.8% (1107 of 1180 strings)

Translated using Weblate (Spanish) [skip ci]

Currently translated at 98.2% (1159 of 1180 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 99.4% (1173 of 1180 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 99.9% (1179 of 1180 strings)

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/id/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/zh_CN/
Translation: Servarr/Radarr
2023-06-14 07:37:13 +03:00
Bogdan
d2cb36c88a Check only clients not in failure status in DownloadClientSortingCheck
(cherry picked from commit 0b872803358bac1297cc7618ea3c13a32a92b5a4)
2023-06-14 07:34:21 +03:00
bakerboy448
2fe28cb1dc Fixed: Handle checkingResumeData state form qBittorrent
(cherry picked from commit 8d8a16225ff7772ccb57784f272ca31e28bb8455)
2023-06-14 07:33:55 +03:00
Bogdan
5d65b4cae4 Fix sorting queue items by size 2023-06-14 04:46:19 +03:00
Bogdan
b0f56e2840 Update translations 2023-06-13 02:03:51 +03:00
Weblate
5593837482 Update translation files [skip ci]
Updated by "Cleanup translation files" hook in Weblate.

Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/
Translation: Servarr/Radarr
2023-06-13 01:40:09 +03:00
Qstick
8231290c7b Update Remote Path Mapping delete modal title
(cherry picked from commit 18716a00516a971f7f2eb369b920266bea24fe08)

Closes #8675
2023-06-12 22:52:50 +03:00
Weblate
0c1b88c60a Translated using Weblate (Indonesian) [skip ci]
Currently translated at 7.3% (87 of 1179 strings)

Co-authored-by: liimee <git.taaa@fedora.email>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/id/
Translation: Servarr/Radarr
2023-06-12 11:49:17 +03:00
Qstick
0b8478e4a1 Bump version to 4.6.2 2023-06-11 09:39:17 -05:00
Weblate
69e09c8687 Translated using Weblate (Russian) [skip ci]
Currently translated at 98.3% (1159 of 1179 strings)

Co-authored-by: Андрей <andryfly7@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ru/
Translation: Servarr/Radarr
2023-06-10 22:43:53 +03:00
Bogdan
3f2ea49023 Add style for default kind in ProgressBar 2023-06-10 03:13:47 +03:00
Bogdan
32f09633e9 Use more specific styling for kinds in ProgressBar
(cherry picked from commit dd31c913d2a974d95f3be251714ce749cfd99a72)

Fixes #8669
2023-06-10 01:45:40 +03:00
Qstick
3542b263c7 Fixed: Don't die on movie refresh when collection has been deleted from TMDB
Fixes #8664
2023-06-07 20:12:59 -05:00
Weblate
d5cc84d8c8 Translated using Weblate (Greek) [skip ci]
Currently translated at 99.5% (1174 of 1179 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 99.4% (1172 of 1179 strings)

Translated using Weblate (Portuguese) [skip ci]

Currently translated at 98.3% (1159 of 1179 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 100.0% (1179 of 1179 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 100.0% (1179 of 1179 strings)

Co-authored-by: MoowGlax <matthieu.derouet.pro@gmail.com>
Co-authored-by: Thodoris Kalatzis <teo.kal@hotmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: emacsdias <emacs.dias@gmail.com>
Co-authored-by: reloxx <reloxx@interia.pl>
Co-authored-by: splifter <a.strahlke@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt/
Translation: Servarr/Radarr
2023-06-07 17:57:09 +03:00
Bogdan
c0790060fb Fixed: (UI) Sort last movies with no release dates
Towards #8662
2023-06-07 06:52:12 +03:00
Bogdan
5ec7e86488 Add language names as hints in TMDbSettings
Add Romanian in TMDb Language Codes
Move DateTime.TryParse to if condition in TMDbParser
2023-06-05 22:17:32 +03:00
Qstick
b8abafd72f Bump version to 4.6.1 2023-06-04 11:43:43 -05:00
222 changed files with 1794 additions and 1610 deletions

View File

@@ -76,7 +76,7 @@ body:
- type: checkboxes
attributes:
label: Trace Logs have been provided as applicable. Reports may be closed if the required logs are not provided.
description: Trace logs are generally required for all bug reports
description: Trace logs are generally required for all bug reports and contain `trace`. Info logs are invalid for bug reports and do not contain `debug` nor `trace`
options:
- label: I have followed the steps in the wiki link above and provided the required trace logs that are relevant and show this issue.
- label: I have read and followed the steps in the wiki link above and provided the required trace logs - the logs contain `trace` - that are relevant and show this issue.
required: true

View File

@@ -9,7 +9,7 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '4.6.0'
majorVersion: '4.7.0'
minorVersion: $[counter('minorVersion', 2000)]
radarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
@@ -363,7 +363,7 @@ stages:
- bash: |
echo "Uploading source maps to sentry"
curl -sL https://sentry.io/get-cli/ | bash
RELEASENAME="${RADARRVERSION}-${BUILD_SOURCEBRANCHNAME}"
RELEASENAME="Radarr@${RADARRVERSION}-${BUILD_SOURCEBRANCHNAME}"
sentry-cli releases new --finalize -p radarr -p radarr-ui -p radarr-update "${RELEASENAME}"
sentry-cli releases -p radarr-ui files "${RELEASENAME}" upload-sourcemaps _output/UI/ --rewrite
sentry-cli releases set-commits --auto "${RELEASENAME}"

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import PageContent from 'Components/Page/PageContent';
@@ -156,16 +157,16 @@ class Blocklist extends Component {
{
!isFetching && !!error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadBlocklist')}
</div>
</Alert>
}
{
isPopulated && !error && !items.length &&
<div>
{translate('NoHistory')}
</div>
<Alert kind={kinds.INFO}>
{translate('NoHistoryBlocklist')}
</Alert>
}
{
@@ -209,7 +210,7 @@ class Blocklist extends Component {
isOpen={isConfirmRemoveModalOpen}
kind={kinds.DANGER}
title={translate('RemoveSelected')}
message={translate('AreYouSureYouWantToRemoveTheSelectedItemsFromBlocklist')}
message={translate('RemoveSelectedItemBlocklistMessageText')}
confirmLabel={translate('RemoveSelected')}
onConfirm={this.onRemoveSelectedConfirmed}
onCancel={this.onConfirmRemoveModalClose}

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import FilterMenu from 'Components/Menu/FilterMenu';
import PageContent from 'Components/Page/PageContent';
@@ -11,7 +12,7 @@ import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import TablePager from 'Components/Table/TablePager';
import { align, icons } from 'Helpers/Props';
import { align, icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import HistoryRowConnector from './HistoryRowConnector';
@@ -83,9 +84,9 @@ class History extends Component {
{
!isFetchingAny && hasError &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadHistory')}
</div>
</Alert>
}
{
@@ -93,9 +94,9 @@ class History extends Component {
// wait for the episodes to populate because they are never coming.
isPopulated && !hasError && !items.length &&
<div>
<Alert kind={kinds.INFO}>
{translate('NoHistory')}
</div>
</Alert>
}
{

View File

@@ -1,6 +1,7 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
@@ -12,7 +13,7 @@ import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import TablePager from 'Components/Table/TablePager';
import { align, icons } from 'Helpers/Props';
import { align, icons, kinds } from 'Helpers/Props';
import getRemovedItems from 'Utilities/Object/getRemovedItems';
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
import translate from 'Utilities/String/translate';
@@ -231,17 +232,17 @@ class Queue extends Component {
{
!isRefreshing && hasError ?
<div>
<Alert kind={kinds.DANGER}>
{translate('FailedToLoadQueue')}
</div> :
</Alert> :
null
}
{
isAllPopulated && !hasError && !items.length ?
<div>
<Alert kind={kinds.INFO}>
{translate('QueueIsEmpty')}
</div> :
</Alert> :
null
}

View File

@@ -88,7 +88,7 @@ class RemoveQueueItemsModal extends Component {
<ModalBody>
<div className={styles.message}>
{selectedCount > 1 ? translate('AreYouSureYouWantToRemoveSelectedItemsFromQueue', selectedCount) : translate('AreYouSureYouWantToRemoveSelectedItemFromQueue')}
{selectedCount > 1 ? translate('RemoveSelectedItemsQueueMessageText', selectedCount) : translate('RemoveSelectedItemQueueMessageText')}
</div>
{
@@ -133,7 +133,7 @@ class RemoveQueueItemsModal extends Component {
kind={kinds.DANGER}
onPress={this.onRemoveConfirmed}
>
Remove
{translate('Remove')}
</Button>
</ModalFooter>
</ModalContent>

View File

@@ -1,9 +1,11 @@
import { reduce } from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected';
@@ -105,9 +107,9 @@ class ImportMovie extends Component {
{
!rootFoldersFetching && !!rootFoldersError ?
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadRootFolders')}
</div> :
</Alert> :
null
}
@@ -116,9 +118,9 @@ class ImportMovie extends Component {
!rootFoldersFetching &&
rootFoldersPopulated &&
!unmappedFolders.length ?
<div>
<Alert kind={kinds.INFO}>
{translate('AllMoviesInPathHaveBeenImported', [path])}
</div> :
</Alert> :
null
}

View File

@@ -92,9 +92,9 @@ class ImportMovieSelectFolder extends Component {
{
!isFetching && error ?
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadRootFolders')}
</div> :
</Alert> :
null
}

View File

@@ -1,6 +1,8 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import AgendaConnector from './Agenda/AgendaConnector';
import * as calendarViews from './calendarViews';
@@ -31,9 +33,9 @@ class Calendar extends Component {
{
!isFetching && !!error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadTheCalendar')}
</div>
</Alert>
}
{

View File

@@ -28,7 +28,7 @@ function createMapStateToProps() {
qualityProfileId: collection.qualityProfileId,
minimumAvailability: collection.minimumAvailability,
searchForMovie: collection.searchOnAdd,
tags: []
tags: collection.tags || []
};
const {

View File

@@ -1,6 +1,7 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
@@ -9,7 +10,7 @@ import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import { align, icons, sortDirections } from 'Helpers/Props';
import { align, icons, kinds, sortDirections } from 'Helpers/Props';
import styles from 'Movie/Index/MovieIndex.css';
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
import translate from 'Utilities/String/translate';
@@ -313,9 +314,9 @@ class Collection extends Component {
{
!isFetching && !!error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadCollections')}
</div>
</Alert>
}
{

View File

@@ -2,17 +2,12 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createAllMoviesSelector from 'Store/Selectors/createAllMoviesSelector';
import createCollectionSelector from 'Store/Selectors/createCollectionSelector';
function createMapStateToProps() {
return createSelector(
createCollectionSelector(),
createAllMoviesSelector(),
(
collection,
allMovies
) => {
(collection) => {
// 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
@@ -22,21 +17,11 @@ function createMapStateToProps() {
return {};
}
let allGenres = [];
let libraryMovies = 0;
collection.movies.forEach((movie) => {
allGenres = allGenres.concat(movie.genres);
if (allMovies.find((libraryMovie) => libraryMovie.tmdbId === movie.tmdbId)) {
libraryMovies++;
}
});
const allGenres = collection.movies.flatMap((movie) => movie.genres);
return {
...collection,
genres: Array.from(new Set(allGenres)).slice(0, 3),
missingMovies: collection.movies.length - libraryMovies
genres: Array.from(new Set(allGenres)).slice(0, 3)
};
}
);

View File

@@ -50,6 +50,7 @@ class EditCollectionModalContent extends Component {
minimumAvailability,
// Id,
rootFolderPath,
tags,
searchOnAdd
} = item;
@@ -126,6 +127,17 @@ class EditCollectionModalContent extends Component {
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Tags')}</FormLabel>
<FormInputGroup
type={inputTypes.TAG}
name="tags"
onChange={onInputChange}
{...tags}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('SearchOnAdd')}</FormLabel>

View File

@@ -42,6 +42,7 @@ function createMapStateToProps() {
qualityProfileId: collection.qualityProfileId,
minimumAvailability: collection.minimumAvailability,
rootFolderPath: collection.rootFolderPath,
tags: collection.tags,
searchOnAdd: collection.searchOnAdd
};

View File

@@ -28,6 +28,14 @@ function CollectionSortMenu(props) {
>
{translate('Title')}
</SortMenuItem>
<SortMenuItem
name="missingMovies"
sortKey={sortKey}
sortDirection={sortDirection}
onPress={onSortSelect}
>
{translate('Missing')}
</SortMenuItem>
</MenuContent>
</SortMenu>
);

View File

@@ -580,7 +580,7 @@ EnhancedSelectInput.propTypes = {
className: PropTypes.string,
disabledClassName: PropTypes.string,
name: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.arrayOf(PropTypes.number)]).isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.arrayOf(PropTypes.string), PropTypes.arrayOf(PropTypes.number)]).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
isDisabled: PropTypes.bool.isRequired,
isFetching: PropTypes.bool.isRequired,

View File

@@ -63,6 +63,7 @@ function ProviderFieldFormGroup(props) {
name,
label,
helpText,
helpTextWarning,
helpLink,
placeholder,
value,
@@ -96,6 +97,7 @@ function ProviderFieldFormGroup(props) {
name={name}
label={label}
helpText={helpText}
helpTextWarning={helpTextWarning}
helpLink={helpLink}
placeholder={placeholder}
value={value}
@@ -122,6 +124,7 @@ ProviderFieldFormGroup.propTypes = {
name: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
helpText: PropTypes.string,
helpTextWarning: PropTypes.string,
helpLink: PropTypes.string,
placeholder: PropTypes.string,
value: PropTypes.any,

View File

@@ -19,6 +19,8 @@ function createCleanMovieSelector() {
year,
images,
alternateTitles = [],
tmdbId,
imdbId,
tags = []
} = movie;
@@ -29,6 +31,8 @@ function createCleanMovieSelector() {
year,
images,
alternateTitles,
tmdbId,
imdbId,
firstCharacter: title.charAt(0).toLowerCase(),
tags: tags.reduce((acc, id) => {
const matchingTag = allTags.find((tag) => tag.id === id);

View File

@@ -12,6 +12,8 @@ function MovieSearchResult(props) {
year,
images,
alternateTitles,
tmdbId,
imdbId,
tags
} = props;
@@ -47,6 +49,22 @@ function MovieSearchResult(props) {
null
}
{
match.key === 'tmdbId' && tmdbId ?
<div className={styles.alternateTitle}>
TmdbId: {tmdbId}
</div> :
null
}
{
match.key === 'imdbId' && imdbId ?
<div className={styles.alternateTitle}>
ImdbId: {imdbId}
</div> :
null
}
{
tag ?
<div className={styles.tagContainer}>
@@ -69,6 +87,8 @@ MovieSearchResult.propTypes = {
year: PropTypes.number.isRequired,
images: PropTypes.arrayOf(PropTypes.object).isRequired,
alternateTitles: PropTypes.arrayOf(PropTypes.object).isRequired,
tmdbId: PropTypes.number,
imdbId: PropTypes.string,
tags: PropTypes.arrayOf(PropTypes.object).isRequired,
match: PropTypes.object.isRequired
};

View File

@@ -9,6 +9,8 @@ const fuseOptions = {
keys: [
'title',
'alternateTitles.title',
'tmdbId',
'imdbId',
'tags.label'
]
};

View File

@@ -1,6 +1,8 @@
import PropTypes from 'prop-types';
import React from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import { kinds } from 'Helpers/Props';
function PageSectionContent(props) {
const {
@@ -17,7 +19,7 @@ function PageSectionContent(props) {
);
} else if (!isFetching && !!error) {
return (
<div>{errorMessage}</div>
<Alert kind={kinds.DANGER}>{errorMessage}</Alert>
);
} else if (isPopulated && !error) {
return (

View File

@@ -16,6 +16,46 @@
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
color: var(--white);
transition: width 0.6s ease;
&.default {
background-color: var(--darkGray);
}
&.primary {
background-color: var(--primaryColor);
}
&.danger {
background-color: var(--dangerColor);
&:global(.colorImpaired) {
background: repeating-linear-gradient(90deg, color(#f05050 shade(5%)), color(#f05050 shade(5%)) 5px, color(#f05050 shade(15%)) 5px, color(#f05050 shade(15%)) 10px);
}
}
&.success {
background-color: var(--successColor);
}
&.purple {
background-color: var(--purple);
}
&.warning {
background-color: var(--warningColor);
&:global(.colorImpaired) {
background: repeating-linear-gradient(45deg, #ffa500, #ffa500 5px, color(#ffa500 tint(15%)) 5px, color(#ffa500 tint(15%)) 10px);
}
}
&.info {
background-color: var(--infoColor);
}
&.queue {
background-color: var(--queueColor);
}
}
.frontTextContainer {
@@ -45,46 +85,6 @@
cursor: default;
}
.default {
background-color: var(--darkGray);
}
.primary {
background-color: var(--primaryColor);
}
.danger {
background-color: var(--dangerColor);
&:global(.colorImpaired) {
background: repeating-linear-gradient(90deg, color(#f05050 shade(5%)), color(#f05050 shade(5%)) 5px, color(#f05050 shade(15%)) 5px, color(#f05050 shade(15%)) 10px);
}
}
.success {
background-color: var(--successColor);
}
.purple {
background-color: var(--purple);
}
.warning {
background-color: var(--warningColor);
&:global(.colorImpaired) {
background: repeating-linear-gradient(45deg, #ffa500, #ffa500 5px, color(#ffa500 tint(15%)) 5px, color(#ffa500 tint(15%)) 10px);
}
}
.info {
background-color: var(--infoColor);
}
.queue {
background-color: var(--queueColor);
}
.small {
height: $progressBarSmallHeight;

View File

@@ -38,7 +38,7 @@ function ProgressBar(props) {
{
showText && width ?
<div
className={styles.backTextContainer}
className={classNames(styles.backTextContainer, styles[kind])}
style={{ width: actualWidth }}
>
<div className={styles.backText}>
@@ -67,7 +67,7 @@ function ProgressBar(props) {
{
showText ?
<div
className={styles.frontTextContainer}
className={classNames(styles.frontTextContainer, styles[kind])}
style={{ width: progressPercent }}
>
<div

View File

@@ -1,6 +1,7 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
@@ -10,7 +11,7 @@ import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
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 { align, icons, kinds, sortDirections } from 'Helpers/Props';
import styles from 'Movie/Index/MovieIndex.css';
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
import translate from 'Utilities/String/translate';
@@ -369,9 +370,9 @@ class DiscoverMovie extends Component {
{
!isFetching && !!error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadMovies')}
</div>
</Alert>
}
{

View File

@@ -2,6 +2,7 @@ import React, { useCallback, useState } from 'react';
import { useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import { LanguageSettingsAppState } from 'App/State/SettingsAppState';
import Alert from 'Components/Alert';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
@@ -86,7 +87,9 @@ function SelectLanguageModalContent(props: SelectLanguageModalContentProps) {
{isFetching ? <LoadingIndicator /> : null}
{!isFetching && error ? (
<div>{translate('UnableToLoadLanguages')}</div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadLanguages')}
</Alert>
) : null}
{isPopulated && !error ? (

View File

@@ -3,6 +3,7 @@ import { useDispatch, useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import { Error } from 'App/State/AppSectionState';
import AppState from 'App/State/AppState';
import Alert from 'Components/Alert';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
@@ -130,7 +131,9 @@ function SelectQualityModalContent(props: SelectQualityModalContentProps) {
{isFetching && <LoadingIndicator />}
{!isFetching && error ? (
<div>{translate('UnableToLoadQualities')}</div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadQualities')}
</Alert>
) : null}
{isPopulated && !error ? (

View File

@@ -303,6 +303,7 @@ class MovieDetails extends Component {
selectedTabIndex
} = this.state;
const fanartUrl = getFanartUrl(images);
const marqueeWidth = isSmallScreen ? titleWidth : (titleWidth - 150);
return (
@@ -361,9 +362,11 @@ class MovieDetails extends Component {
<div className={styles.header}>
<div
className={styles.backdrop}
style={{
backgroundImage: `url(${getFanartUrl(images)})`
}}
style={
fanartUrl ?
{ backgroundImage: `url(${fanartUrl})` } :
null
}
>
<div className={styles.backdropOverlay} />
</div>

View File

@@ -10,6 +10,7 @@ import { SelectProvider } from 'App/SelectContext';
import ClientSideCollectionAppState from 'App/State/ClientSideCollectionAppState';
import MoviesAppState, { MovieIndexAppState } from 'App/State/MoviesAppState';
import { RSS_SYNC } from 'Commands/commandNames';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
@@ -20,7 +21,7 @@ import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import withScrollPosition from 'Components/withScrollPosition';
import { align, icons } from 'Helpers/Props';
import { align, icons, kinds } from 'Helpers/Props';
import SortDirection from 'Helpers/Props/SortDirection';
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
import NoMovie from 'Movie/NoMovie';
@@ -337,7 +338,9 @@ const MovieIndex = withScrollPosition((props: MovieIndexProps) => {
{isFetching && !isPopulated ? <LoadingIndicator /> : null}
{!isFetching && !!error ? (
<div>{translate('UnableToLoadMovies')}</div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadMovies')}
</Alert>
) : null}
{isLoaded ? (

View File

@@ -203,7 +203,7 @@ function EditMoviesModalContent(props: EditMoviesModalContentProps) {
<Button onPress={onModalClose}>{translate('Cancel')}</Button>
<Button onPress={onSavePressWrapper}>
{translate('Apply Changes')}
{translate('ApplyChanges')}
</Button>
</div>
</ModalFooter>

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
@@ -109,9 +110,9 @@ class FileEditModalContent extends Component {
{
!isFetching && !!error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadQualities')}
</div>
</Alert>
}
{

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
@@ -92,9 +93,9 @@ class SelectQualityModalContent extends Component {
{
!isFetching && !!error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadQualities')}
</div>
</Alert>
}
{

View File

@@ -1,8 +1,10 @@
import PropTypes from 'prop-types';
import React from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import RootFolderRowConnector from './RootFolderRowConnector';
@@ -44,9 +46,9 @@ function RootFolders(props) {
if (!isFetching && !!error) {
return (
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadRootFolders')}
</div>
</Alert>
);
}

View File

@@ -152,13 +152,7 @@ class CustomFormat extends Component {
isOpen={this.state.isDeleteCustomFormatModalOpen}
kind={kinds.DANGER}
title={translate('DeleteCustomFormat')}
message={
<div>
<div>
{translate('AreYouSureYouWantToDeleteFormat', [name])}
</div>
</div>
}
message={translate('DeleteCustomFormatMessageText', [name])}
confirmLabel={translate('Delete')}
isSpinning={isDeleting}
onConfirm={this.onConfirmDeleteCustomFormat}

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import Button from 'Components/Link/Button';
import ClipboardButton from 'Components/Link/ClipboardButton';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
@@ -41,9 +42,9 @@ class ExportCustomFormatModalContent extends Component {
{
!isFetching && !!error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadCustomFormats')}
</div>
</Alert>
}
{

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
@@ -11,7 +12,7 @@ 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, sizes } from 'Helpers/Props';
import { inputTypes, kinds, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './ImportCustomFormatModalContent.css';
@@ -95,9 +96,9 @@ class ImportCustomFormatModalContent extends Component {
{
!isFetching && !!error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadCustomFormats')}
</div>
</Alert>
}
{

View File

@@ -78,7 +78,7 @@ class Specification extends Component {
<IconButton
className={styles.cloneButton}
title={translate('CloneFormatTag')}
title={translate('CloneCondition')}
name={icons.CLONE}
onPress={this.onCloneSpecificationPress}
/>
@@ -114,14 +114,8 @@ class Specification extends Component {
<ConfirmModal
isOpen={this.state.isDeleteSpecificationModalOpen}
kind={kinds.DANGER}
title={translate('DeleteCustomFormat')}
message={
<div>
<div>
{translate('AreYouSureYouWantToDeleteFormat', [name])}
</div>
</div>
}
title={translate('DeleteCondition')}
message={translate('DeleteConditionMessageText', [name])}
confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeleteSpecification}
onCancel={this.onDeleteSpecificationModalClose}

View File

@@ -170,7 +170,7 @@ function ManageDownloadClientsEditModalContent(
<div>
<Button onPress={onModalClose}>{translate('Cancel')}</Button>
<Button onPress={save}>{translate('Apply Changes')}</Button>
<Button onPress={save}>{translate('ApplyChanges')}</Button>
</div>
</ModalFooter>
</ModalContent>

View File

@@ -29,9 +29,9 @@ function DownloadClientOptions(props) {
{
!isFetching && error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadDownloadClientOptions')}
</div>
</Alert>
}
{

View File

@@ -88,8 +88,8 @@ class RemotePathMapping extends Component {
<ConfirmModal
isOpen={this.state.isDeleteRemotePathMappingModalOpen}
kind={kinds.DANGER}
title={translate('DeleteDelayProfile')}
message={translate('AreYouSureYouWantToDeleteThisRemotePathMapping')}
title={translate('DeleteRemotePathMapping')}
message={translate('DeleteRemotePathMappingMessageText')}
confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeleteRemotePathMapping}
onCancel={this.onDeleteRemotePathMappingModalClose}

View File

@@ -1,6 +1,7 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import Form from 'Components/Form/Form';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import ConfirmModal from 'Components/Modal/ConfirmModal';
@@ -123,9 +124,9 @@ class GeneralSettings extends Component {
{
!isFetching && error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadGeneralSettings')}
</div>
</Alert>
}
{

View File

@@ -168,7 +168,7 @@ class SecuritySettings extends Component {
isOpen={this.state.isConfirmApiKeyResetModalOpen}
kind={kinds.DANGER}
title={translate('ResetAPIKey')}
message={translate('AreYouSureYouWantToResetYourAPIKey')}
message={translate('ResetAPIKeyMessageText')}
confirmLabel={translate('Reset')}
onConfirm={this.onConfirmResetApiKey}
onCancel={this.onCloseResetApiKeyModal}

View File

@@ -89,7 +89,7 @@ class ImportListExclusion extends Component {
isOpen={this.state.isDeleteImportExclusionModalOpen}
kind={kinds.DANGER}
title={translate('DeleteImportListExclusion')}
message={translate('AreYouSureYouWantToDeleteThisImportListExclusion')}
message={translate('DeleteImportListExclusionMessageText')}
confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeleteImportExclusion}
onCancel={this.onDeleteImportExclusionModalClose}

View File

@@ -3,3 +3,9 @@
margin-right: auto;
}
.message {
composes: alert from '~Components/Alert.css';
margin-bottom: 30px;
}

View File

@@ -2,6 +2,7 @@
// Please do not change this file!
interface CssExports {
'deleteButton': string;
'message': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

@@ -14,6 +14,7 @@ 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 formatShortTimeSpan from 'Utilities/Date/formatShortTimeSpan';
import translate from 'Utilities/String/translate';
import styles from './EditImportListModalContent.css';
@@ -42,6 +43,7 @@ function EditImportListModalContent(props) {
name,
enabled,
enableAuto,
minRefreshInterval,
monitor,
minimumAvailability,
qualityProfileId,
@@ -85,6 +87,14 @@ function EditImportListModalContent(props) {
{message.value.message}
</Alert>
}
<Alert
kind={kinds.INFO}
className={styles.message}
>
{translate('ListWillRefreshEveryInterp', [formatShortTimeSpan(minRefreshInterval.value)])}
</Alert>
<FormGroup>
<FormLabel>{translate('Name')}</FormLabel>

View File

@@ -4,6 +4,7 @@ import Card from 'Components/Card';
import Label from 'Components/Label';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import { kinds } from 'Helpers/Props';
import formatShortTimeSpan from 'Utilities/Date/formatShortTimeSpan';
import translate from 'Utilities/String/translate';
import EditImportListModalConnector from './EditImportListModalConnector';
import styles from './ImportList.css';
@@ -56,7 +57,8 @@ class ImportList extends Component {
id,
name,
enabled,
enableAuto
enableAuto,
minRefreshInterval
} = this.props;
return (
@@ -96,6 +98,12 @@ class ImportList extends Component {
}
</div>
<div className={styles.enabled}>
<Label kind={kinds.INFO} title='List Refresh Interval'>
{`${translate('Refresh')}: ${formatShortTimeSpan(minRefreshInterval)}`}
</Label>
</div>
<EditImportListModalConnector
id={id}
isOpen={this.state.isEditImportListModalOpen}
@@ -122,6 +130,7 @@ ImportList.propTypes = {
name: PropTypes.string.isRequired,
enabled: PropTypes.bool.isRequired,
enableAuto: PropTypes.bool.isRequired,
minRefreshInterval: PropTypes.string.isRequired,
onConfirmDeleteImportList: PropTypes.func.isRequired
};

View File

@@ -1,12 +1,13 @@
import PropTypes from 'prop-types';
import React from 'react';
import Alert from 'Components/Alert';
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 LoadingIndicator from 'Components/Loading/LoadingIndicator';
import { inputTypes } from 'Helpers/Props';
import { inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
function ImportListOptions(props) {
@@ -37,31 +38,14 @@ function ImportListOptions(props) {
{
!isFetching && error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadListOptions')}
</div>
</Alert>
}
{
hasSettings && !isFetching && !error &&
<Form>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>{translate('ListUpdateInterval')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="importListSyncInterval"
min={6}
unit="hours"
helpText={translate('ImportListSyncIntervalHelpText')}
onChange={onInputChange}
{...settings.importListSyncInterval}
/>
</FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}

View File

@@ -27,9 +27,9 @@ interface ManageIndexersEditModalContentProps {
const NO_CHANGE = 'noChange';
const enableOptions = [
{ key: NO_CHANGE, value: 'No Change', disabled: true },
{ key: 'enabled', value: 'Enabled' },
{ key: 'disabled', value: 'Disabled' },
{ key: NO_CHANGE, value: translate('NoChange'), disabled: true },
{ key: 'enabled', value: translate('Enabled') },
{ key: 'disabled', value: translate('Disabled') },
];
function ManageIndexersEditModalContent(
@@ -97,7 +97,7 @@ function ManageIndexersEditModalContent(
setPriority(value);
break;
default:
console.warn('EditIndexersModalContent Unknown Input');
console.warn(`EditIndexersModalContent Unknown Input: '${name}'`);
}
},
[]
@@ -111,7 +111,7 @@ function ManageIndexersEditModalContent(
<ModalBody>
<FormGroup>
<FormLabel>{translate('EnableRss')}</FormLabel>
<FormLabel>{translate('EnableRSS')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
@@ -162,13 +162,13 @@ function ManageIndexersEditModalContent(
<ModalFooter className={styles.modalFooter}>
<div className={styles.selected}>
{translate('{count} indexers selected', { count: selectedCount })}
{translate('CountIndexersSelected', [selectedCount])}
</div>
<div>
<Button onPress={onModalClose}>{translate('Cancel')}</Button>
<Button onPress={save}>{translate('Apply Changes')}</Button>
<Button onPress={save}>{translate('ApplyChanges')}</Button>
</div>
</ModalFooter>
</ModalContent>

View File

@@ -20,6 +20,7 @@ import {
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import { SelectStateInputProps } from 'typings/props';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
import translate from 'Utilities/String/translate';
import getSelectedIds from 'Utilities/Table/getSelectedIds';
import ManageIndexersEditModal from './Edit/ManageIndexersEditModal';
import ManageIndexersModalRow from './ManageIndexersModalRow';
@@ -34,43 +35,43 @@ type OnSelectedChangeCallback = React.ComponentProps<
const COLUMNS = [
{
name: 'name',
label: 'Name',
label: translate('Name'),
isSortable: true,
isVisible: true,
},
{
name: 'implementation',
label: 'Implementation',
label: translate('Implementation'),
isSortable: true,
isVisible: true,
},
{
name: 'enableRss',
label: 'Enable RSS',
label: translate('EnableRSS'),
isSortable: true,
isVisible: true,
},
{
name: 'enableAutomaticSearch',
label: 'Enable Automatic Search',
label: translate('EnableAutomaticSearch'),
isSortable: true,
isVisible: true,
},
{
name: 'enableInteractiveSearch',
label: 'Enable Interactive Search',
label: translate('EnableInteractiveSearch'),
isSortable: true,
isVisible: true,
},
{
name: 'priority',
label: 'Priority',
label: translate('Priority'),
isSortable: true,
isVisible: true,
},
{
name: 'tags',
label: 'Tags',
label: translate('Tags'),
isSortable: true,
isVisible: true,
},
@@ -189,12 +190,12 @@ function ManageIndexersModalContent(props: ManageIndexersModalContentProps) {
[items, setSelectState]
);
const errorMessage = getErrorMessage(error, 'Unable to load import lists.');
const errorMessage = getErrorMessage(error, 'Unable to load indexers.');
const anySelected = selectedCount > 0;
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>Manage Import Lists</ModalHeader>
<ModalHeader>{translate('ManageIndexers')}</ModalHeader>
<ModalBody>
{isFetching ? <LoadingIndicator /> : null}
@@ -234,7 +235,7 @@ function ManageIndexersModalContent(props: ManageIndexersModalContentProps) {
isDisabled={!anySelected}
onPress={onDeletePress}
>
Delete
{translate('Delete')}
</SpinnerButton>
<SpinnerButton
@@ -242,7 +243,7 @@ function ManageIndexersModalContent(props: ManageIndexersModalContentProps) {
isDisabled={!anySelected}
onPress={onEditPress}
>
Edit
{translate('Edit')}
</SpinnerButton>
<SpinnerButton
@@ -250,11 +251,11 @@ function ManageIndexersModalContent(props: ManageIndexersModalContentProps) {
isDisabled={!anySelected}
onPress={onTagsPress}
>
Set Tags
{translate('SetTags')}
</SpinnerButton>
</div>
<Button onPress={onModalClose}>Close</Button>
<Button onPress={onModalClose}>{translate('Close')}</Button>
</ModalFooter>
<ManageIndexersEditModal
@@ -274,9 +275,9 @@ function ManageIndexersModalContent(props: ManageIndexersModalContentProps) {
<ConfirmModal
isOpen={isDeleteModalOpen}
kind={kinds.DANGER}
title="Delete Import List(s)"
message={`Are you sure you want to delete ${selectedIds.length} import list(s)?`}
confirmLabel="Delete"
title={translate('DeleteIndexers')}
message={translate('DeleteIndexersMessageText', [selectedIds.length])}
confirmLabel={translate('Delete')}
onConfirm={onConfirmDelete}
onCancel={onDeleteModalClose}
/>

View File

@@ -5,6 +5,7 @@ import Column from 'Components/Table/Column';
import TableRow from 'Components/Table/TableRow';
import TagListConnector from 'Components/TagListConnector';
import { SelectStateInputProps } from 'typings/props';
import translate from 'Utilities/String/translate';
import styles from './ManageIndexersModalRow.css';
interface ManageIndexersModalRowProps {
@@ -59,15 +60,15 @@ function ManageIndexersModalRow(props: ManageIndexersModalRowProps) {
</TableRowCell>
<TableRowCell className={styles.enableRss}>
{enableRss ? 'Yes' : 'No'}
{enableRss ? translate('Yes') : translate('No')}
</TableRowCell>
<TableRowCell className={styles.enableAutomaticSearch}>
{enableAutomaticSearch ? 'Yes' : 'No'}
{enableAutomaticSearch ? translate('Yes') : translate('No')}
</TableRowCell>
<TableRowCell className={styles.enableInteractiveSearch}>
{enableInteractiveSearch ? 'Yes' : 'No'}
{enableInteractiveSearch ? translate('Yes') : translate('No')}
</TableRowCell>
<TableRowCell className={styles.priority}>{priority}</TableRowCell>

View File

@@ -17,6 +17,7 @@ import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds, sizes } from 'Helpers/Props';
import createTagsSelector from 'Store/Selectors/createTagsSelector';
import Indexer from 'typings/Indexer';
import translate from 'Utilities/String/translate';
import styles from './TagsModalContent.css';
interface TagsModalContentProps {
@@ -69,19 +70,19 @@ function TagsModalContent(props: TagsModalContentProps) {
}, [tags, applyTags, onApplyTagsPress]);
const applyTagsOptions = [
{ key: 'add', value: 'Add' },
{ key: 'remove', value: 'Remove' },
{ key: 'replace', value: 'Replace' },
{ key: 'add', value: translate('Add') },
{ key: 'remove', value: translate('Remove') },
{ key: 'replace', value: translate('Replace') },
];
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>Tags</ModalHeader>
<ModalHeader>{translate('Tags')}</ModalHeader>
<ModalBody>
<Form>
<FormGroup>
<FormLabel>Tags</FormLabel>
<FormLabel>{translate('Tags')}</FormLabel>
<FormInputGroup
type={inputTypes.TAG}
@@ -92,7 +93,7 @@ function TagsModalContent(props: TagsModalContentProps) {
</FormGroup>
<FormGroup>
<FormLabel>Apply Tags</FormLabel>
<FormLabel>{translate('ApplyTags')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
@@ -110,7 +111,7 @@ function TagsModalContent(props: TagsModalContentProps) {
</FormGroup>
<FormGroup>
<FormLabel>Result</FormLabel>
<FormLabel>{translate('Result')}</FormLabel>
<div className={styles.result}>
{seriesTags.map((id) => {
@@ -127,7 +128,11 @@ function TagsModalContent(props: TagsModalContentProps) {
return (
<Label
key={tag.id}
title={removeTag ? 'Removing tag' : 'Existing tag'}
title={
removeTag
? translate('RemovingTag')
: translate('ExistingTag')
}
kind={removeTag ? kinds.INVERSE : kinds.INFO}
size={sizes.LARGE}
>
@@ -151,7 +156,7 @@ function TagsModalContent(props: TagsModalContentProps) {
return (
<Label
key={tag.id}
title={'Adding tag'}
title={translate('AddingTag')}
kind={kinds.SUCCESS}
size={sizes.LARGE}
>
@@ -165,10 +170,10 @@ function TagsModalContent(props: TagsModalContentProps) {
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>Cancel</Button>
<Button onPress={onModalClose}>{translate('Cancel')}</Button>
<Button kind={kinds.PRIMARY} onPress={onApplyPress}>
Apply
{translate('Apply')}
</Button>
</ModalFooter>
</ModalContent>

View File

@@ -1,12 +1,13 @@
import PropTypes from 'prop-types';
import React from 'react';
import Alert from 'Components/Alert';
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 LoadingIndicator from 'Components/Loading/LoadingIndicator';
import { inputTypes } from 'Helpers/Props';
import { inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
function IndexerOptions(props) {
@@ -28,9 +29,9 @@ function IndexerOptions(props) {
{
!isFetching && error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadIndexerOptions')}
</div>
</Alert>
}
{

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import FieldSet from 'Components/FieldSet';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
@@ -8,7 +9,7 @@ import FormLabel from 'Components/Form/FormLabel';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import { inputTypes, sizes } from 'Helpers/Props';
import { inputTypes, kinds, sizes } from 'Helpers/Props';
import RootFoldersConnector from 'RootFolder/RootFoldersConnector';
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
import translate from 'Utilities/String/translate';
@@ -72,9 +73,9 @@ class MediaManagement extends Component {
{
!isFetching && error ?
<FieldSet legend={translate('NamingSettings')}>
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadMediaManagementSettings')}
</div>
</Alert>
</FieldSet> : null
}

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import FieldSet from 'Components/FieldSet';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
@@ -7,7 +8,7 @@ import FormInputButton from 'Components/Form/FormInputButton';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import { inputTypes, sizes } from 'Helpers/Props';
import { inputTypes, kinds, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import NamingModal from './NamingModal';
import styles from './Naming.css';
@@ -110,9 +111,9 @@ class Naming extends Component {
{
!isFetching && error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadNamingSettings')}
</div>
</Alert>
}
{

View File

@@ -6,8 +6,9 @@ import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import { inputTypes } from 'Helpers/Props';
import { inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import Alert from '../../../Components/Alert';
// Note: Do Not Translate Certification Countries
@@ -43,9 +44,9 @@ function MetadataOptions(props) {
{
!isFetching && error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadIndexerOptions')}
</div>
</Alert>
}
{

View File

@@ -141,7 +141,7 @@ class DelayProfile extends Component {
isOpen={this.state.isDeleteDelayProfileModalOpen}
kind={kinds.DANGER}
title={translate('DeleteDelayProfile')}
message={translate('AreYouSureYouWantToDeleteThisDelayProfile')}
message={translate('DeleteDelayProfileMessageText')}
confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeleteDelayProfile}
onCancel={this.onDeleteDelayProfileModalClose}

View File

@@ -60,17 +60,19 @@ class ResetQualityDefinitionsModalContent extends Component {
<ModalBody>
<div className={styles.messageContainer}>
{translate('AreYouSureYouWantToResetQualityDefinitions')}
{translate('ResetQualityDefinitionsMessageText')}
</div>
<FormGroup>
<FormLabel>{translate('ResetTitles')}</FormLabel>
<FormLabel>
{translate('ResetTitles')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="resetDefinitionTitles"
value={resetDefinitionTitles}
helpText={translate('ResetTitlesHelpText')}
helpText={translate('ResetDefinitionTitlesHelpText')}
onChange={this.onResetDefinitionTitlesChange}
/>
</FormGroup>

View File

@@ -1,7 +1,9 @@
import PropTypes from 'prop-types';
import React from 'react';
import Alert from 'Components/Alert';
import FieldSet from 'Components/FieldSet';
import PageSectionContent from 'Components/Page/PageSectionContent';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import TagConnector from './TagConnector';
import styles from './Tags.css';
@@ -14,9 +16,9 @@ function Tags(props) {
if (!items.length) {
return (
<div>
<Alert kind={kinds.INFO}>
{translate('NoTagsHaveBeenAddedYet')}
</div>
</Alert>
);
}

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import FieldSet from 'Components/FieldSet';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
@@ -8,7 +9,7 @@ import FormLabel from 'Components/Form/FormLabel';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import { inputTypes } from 'Helpers/Props';
import { inputTypes, kinds } from 'Helpers/Props';
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
import themes from 'Styles/Themes';
import titleCase from 'Utilities/String/titleCase';
@@ -87,9 +88,9 @@ class UISettings extends Component {
{
!isFetching && error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadUISettings')}
</div>
</Alert>
}
{

View File

@@ -234,6 +234,18 @@ export const sortPredicates = {
}
return padNumber(result.toString(), 2) + qualityName;
},
inCinemas: function(item) {
return item.inCinemas || '';
},
physicalRelease: function(item) {
return item.physicalRelease || '';
},
digitalRelease: function(item) {
return item.digitalRelease || '';
}
};

View File

@@ -1,7 +1,7 @@
import _ from 'lodash';
import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
import { filterBuilderTypes, filterBuilderValueTypes, filterTypePredicates, sortDirections } from 'Helpers/Props';
import { filterBuilderTypes, filterBuilderValueTypes, filterTypePredicates, filterTypes, sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks';
import sortByName from 'Utilities/Array/sortByName';
import createAjaxRequest from 'Utilities/createAjaxRequest';
@@ -60,8 +60,30 @@ export const defaultState = {
filters: [
{
key: 'all',
label: 'All',
label: translate('All'),
filters: []
},
{
key: 'missing',
label: translate('Missing'),
filters: [
{
key: 'missingMovies',
value: 0,
type: filterTypes.GREATER_THAN
}
]
},
{
key: 'complete',
label: translate('Complete'),
filters: [
{
key: 'missingMovies',
value: 0,
type: filterTypes.EQUAL
}
]
}
],

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
@@ -8,7 +9,7 @@ import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import { icons } from 'Helpers/Props';
import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import BackupRow from './BackupRow';
import RestoreBackupModalConnector from './RestoreBackupModalConnector';
@@ -107,16 +108,16 @@ class Backups extends Component {
{
!isFetching && !!error &&
<div>
<Alert kind={kinds.DANGER}>
{translate('UnableToLoadBackups')}
</div>
</Alert>
}
{
noBackups &&
<div>
<Alert kind={kinds.INFO}>
{translate('NoBackupsAreAvailable')}
</div>
</Alert>
}
{

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import FilterMenu from 'Components/Menu/FilterMenu';
import PageContent from 'Components/Page/PageContent';
@@ -11,7 +12,7 @@ import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import TablePager from 'Components/Table/TablePager';
import { align, icons } from 'Helpers/Props';
import { align, icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import LogsTableRow from './LogsTableRow';
@@ -82,9 +83,9 @@ function LogsTable(props) {
{
isPopulated && !error && !items.length &&
<div>
<Alert kind={kinds.INFO}>
{translate('NoEventsFound')}
</div>
</Alert>
}
{

View File

@@ -11,7 +11,7 @@ import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import { icons } from 'Helpers/Props';
import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import LogsNavMenu from '../LogsNavMenu';
import LogFilesTableRow from './LogFilesTableRow';
@@ -118,9 +118,9 @@ class LogFiles extends Component {
{
!isFetching && !items.length &&
<div>
<Alert kind={kinds.INFO}>
{translate('NoLogFiles')}
</div>
</Alert>
}
</PageContentBody>
</PageContent>

View File

@@ -1,6 +1,7 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component, Fragment } from 'react';
import Alert from 'Components/Alert';
import Icon from 'Components/Icon';
import Label from 'Components/Label';
import SpinnerButton from 'Components/Link/SpinnerButton';
@@ -61,9 +62,9 @@ class Updates extends Component {
{
noUpdates &&
<div>
<Alert kind={kinds.INFO}>
{translate('NoUpdatesAreAvailable')}
</div>
</Alert>
}
{

View File

@@ -0,0 +1,25 @@
import moment from 'moment';
function formatShortTimeSpan(timeSpan) {
if (!timeSpan) {
return '';
}
const duration = moment.duration(timeSpan);
const hours = Math.floor(duration.asHours());
const minutes = Math.floor(duration.asMinutes());
const seconds = Math.floor(duration.asSeconds());
if (hours > 0) {
return `${hours} hour(s)`;
}
if (minutes > 0) {
return `${minutes} minute(s)`;
}
return `${seconds} second(s)`;
}
export default formatShortTimeSpan;

View File

@@ -6,8 +6,8 @@
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<!-- Chrome, Opera, and Firefox OS -->
<meta name="theme-color" content="#3a3f51" />
<!-- Chrome, Safari, Opera, and Firefox OS -->
<meta name="theme-color" content="#595959" />
<!-- Windows Phone -->
<meta name="msapplication-navbutton-color" content="#3a3f51" />

View File

@@ -6,8 +6,8 @@
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<!-- Chrome, Opera, and Firefox OS -->
<meta name="theme-color" content="#464b51" />
<!-- Chrome, Safari, Opera, and Firefox OS -->
<meta name="theme-color" content="#595959" />
<!-- Windows Phone -->
<meta name="msapplication-navbutton-color" content="#464b51" />

View File

@@ -27,19 +27,47 @@ namespace NzbDrone.Api.Test.ClientSchemaTests
var schema = SchemaBuilder.ToSchema(model);
schema.Should().Contain(c => c.Order == 1 && c.Name == "lastName" && c.Label == "Last Name" && c.HelpText == "Your Last Name" && (string)c.Value == "Poop");
schema.Should().Contain(c => c.Order == 0 && c.Name == "firstName" && c.Label == "First Name" && c.HelpText == "Your First Name" && (string)c.Value == "Bob");
schema.Should().Contain(c => c.Order == 1 && c.Name == "lastName" && c.Label == "Last Name" && c.HelpText == "Your Last Name" && c.HelpTextWarning == "Mandatory Last Name" && (string)c.Value == "Poop");
schema.Should().Contain(c => c.Order == 0 && c.Name == "firstName" && c.Label == "First Name" && c.HelpText == "Your First Name" && c.HelpTextWarning == "Mandatory First Name" && (string)c.Value == "Bob");
}
[Test]
public void schema_should_have_nested_fields()
{
var model = new NestedTestModel
{
Name =
{
FirstName = "Bob",
LastName = "Poop"
}
};
var schema = SchemaBuilder.ToSchema(model);
schema.Should().Contain(c => c.Order == 0 && c.Name == "name.firstName" && c.Label == "First Name" && c.HelpText == "Your First Name" && c.HelpTextWarning == "Mandatory First Name" && (string)c.Value == "Bob");
schema.Should().Contain(c => c.Order == 1 && c.Name == "name.lastName" && c.Label == "Last Name" && c.HelpText == "Your Last Name" && c.HelpTextWarning == "Mandatory Last Name" && (string)c.Value == "Poop");
schema.Should().Contain(c => c.Order == 2 && c.Name == "quote" && c.Label == "Quote" && c.HelpText == "Your Favorite Quote");
}
}
public class TestModel
{
[FieldDefinition(0, Label = "First Name", HelpText = "Your First Name")]
[FieldDefinition(0, Label = "First Name", HelpText = "Your First Name", HelpTextWarning = "Mandatory First Name")]
public string FirstName { get; set; }
[FieldDefinition(1, Label = "Last Name", HelpText = "Your Last Name")]
[FieldDefinition(1, Label = "Last Name", HelpText = "Your Last Name", HelpTextWarning = "Mandatory Last Name")]
public string LastName { get; set; }
public string Other { get; set; }
}
public class NestedTestModel
{
[FieldDefinition(0)]
public TestModel Name { get; set; } = new TestModel();
[FieldDefinition(1, Label = "Quote", HelpText = "Your Favorite Quote")]
public string Quote { get; set; }
}
}

View File

@@ -10,7 +10,9 @@ namespace NzbDrone.Common.Test.InstrumentationTests
// Indexer Urls
[TestCase(@"https://iptorrents.com/torrents/rss?u=mySecret;tp=mySecret;l5;download")]
[TestCase(@"http://rss.torrentleech.org/mySecret")]
[TestCase(@"http://rss.torrentleech.org/rss/download/12345/01233210/filename.torrent")]
[TestCase(@"https://rss24h.torrentleech.org/mySecret")]
[TestCase(@"http://rss.torrentleech.org/rss/download/12345/01233210/file.name-RLSGRP.torrent")]
[TestCase(@"https://www.torrentleech.org/rss/download/12345/01233210/file.name-RLSGRP.torrent")]
[TestCase(@"http://www.bitmetv.org/rss.php?uid=mySecret&passkey=mySecret")]
[TestCase(@"https://rss.omgwtfnzbs.org/rss-search.php?catid=19,20&user=sonarr&api=mySecret&eng=1")]
[TestCase(@"https://dognzb.cr/fetch/2b51db35e1912ffc138825a12b9933d2/2b51db35e1910123321025a12b9933d2")]
@@ -44,6 +46,7 @@ namespace NzbDrone.Common.Test.InstrumentationTests
// Deluge
[TestCase(@",{""download_location"": ""C:\Users\\mySecret mySecret\\Downloads""}")]
[TestCase(@",{""download_location"": ""/home/mySecret/Downloads""}")]
[TestCase(@",{""download_location"": ""/Users/mySecret/Downloads""}")]
[TestCase(@"auth.login(""mySecret"")")]
// Download Station
@@ -59,8 +62,11 @@ namespace NzbDrone.Common.Test.InstrumentationTests
// Internal
[TestCase(@"OutputPath=/home/mySecret/Downloads")]
[TestCase(@"OutputPath=/Users/mySecret/Downloads")]
[TestCase("Hardlinking episode file: /home/mySecret/Downloads to /media/abc.mkv")]
[TestCase("Hardlinking episode file: /Users/mySecret/Downloads to /media/abc.mkv")]
[TestCase("Hardlink '/home/mySecret/Downloads/abs.mkv' to '/media/abc.mkv' failed.")]
[TestCase("Hardlink '/Users/mySecret/Downloads/abs.mkv' to '/media/abc.mkv' failed.")]
[TestCase("https://notifiarr.com/notifier.php: api=1234530f-422f-4aac-b6b3-01233210aaaa&radarr_health_issue_message=Download")]
[TestCase("/readarr/signalr/messages/negotiate?access_token=1234530f422f4aacb6b301233210aaaa&negotiateVersion=1")]
[TestCase(@"[Info] MigrationController: *** Migrating Database=radarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;Enlist=False ***")]

View File

@@ -158,6 +158,25 @@ namespace NzbDrone.Common.Test
path.GetParentPath().Should().Be(parentPath);
}
[TestCase(@"C:\Test\mydir", "Test")]
[TestCase(@"C:\Test\", @"C:\")]
[TestCase(@"C:\", null)]
[TestCase(@"\\server\share", null)]
[TestCase(@"\\server\share\test", @"\\server\share")]
public void path_should_return_parent_name_windows(string path, string parentPath)
{
WindowsOnly();
path.GetParentName().Should().Be(parentPath);
}
[TestCase(@"/", null)]
[TestCase(@"/test", "/")]
public void path_should_return_parent_name_mono(string path, string parentPath)
{
PosixOnly();
path.GetParentName().Should().Be(parentPath);
}
[Test]
public void path_should_return_parent_for_oversized_path()
{

View File

@@ -87,9 +87,7 @@ namespace NzbDrone.Common.Extensions
public static string GetParentPath(this string childPath)
{
var cleanPath = OsInfo.IsWindows
? PARENT_PATH_END_SLASH_REGEX.Replace(childPath, "")
: childPath.TrimEnd(Path.DirectorySeparatorChar);
var cleanPath = childPath.GetCleanPath();
if (cleanPath.IsNullOrWhiteSpace())
{
@@ -99,6 +97,18 @@ namespace NzbDrone.Common.Extensions
return Directory.GetParent(cleanPath)?.FullName;
}
public static string GetParentName(this string childPath)
{
var cleanPath = childPath.GetCleanPath();
if (cleanPath.IsNullOrWhiteSpace())
{
return null;
}
return Directory.GetParent(cleanPath)?.Name;
}
public static string GetCleanPath(this string path)
{
var cleanPath = OsInfo.IsWindows

View File

@@ -60,8 +60,7 @@ namespace NzbDrone.Common.Http
if (request.AllowAutoRedirect && response.HasHttpRedirect)
{
var autoRedirectChain = new List<string>();
autoRedirectChain.Add(request.Url.ToString());
var autoRedirectChain = new List<string> { request.Url.ToString() };
do
{
@@ -75,6 +74,14 @@ namespace NzbDrone.Common.Http
throw new WebException($"Too many automatic redirections were attempted for {autoRedirectChain.Join(" -> ")}", WebExceptionStatus.ProtocolError);
}
// 302 or 303 should default to GET on redirect even if POST on original
if (RequestRequiresForceGet(response.StatusCode, response.Request.Method))
{
request.Method = HttpMethod.Get;
request.ContentData = null;
request.ContentSummary = null;
}
response = ExecuteRequest(request, cookieContainer);
}
while (response.HasHttpRedirect);
@@ -105,6 +112,16 @@ namespace NzbDrone.Common.Http
return response;
}
private static bool RequestRequiresForceGet(HttpStatusCode statusCode, HttpMethod requestMethod)
{
return statusCode switch
{
HttpStatusCode.Moved or HttpStatusCode.Found or HttpStatusCode.MultipleChoices => requestMethod == HttpMethod.Post,
HttpStatusCode.SeeOther => requestMethod != HttpMethod.Get && requestMethod != HttpMethod.Head,
_ => false,
};
}
private HttpResponse ExecuteRequest(HttpRequest request, CookieContainer cookieContainer)
{
foreach (var interceptor in _requestInterceptors)

View File

@@ -50,6 +50,8 @@ namespace NzbDrone.Common.Http
public bool HasHttpError => (int)StatusCode >= 400;
public bool HasHttpServerError => (int)StatusCode >= 500;
public bool HasHttpRedirect => StatusCode == HttpStatusCode.Moved ||
StatusCode == HttpStatusCode.MovedPermanently ||
StatusCode == HttpStatusCode.Found ||

View File

@@ -7,55 +7,55 @@ namespace NzbDrone.Common.Instrumentation
{
public class CleanseLogMessage
{
private static readonly Regex[] CleansingRules = new[]
{
// Url
new Regex(@"(?<=\?|&|: )(apikey|(?:access[-_]?)?token|passkey|auth|authkey|user|uid|api|[a-z_]*apikey|account|passwd)=(?<secret>[^&=]+?)(?=[ ""&=]|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=\?|&)[^=]*?(username|password)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"torrentleech\.org/(?!rss)(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"torrentleech\.org/rss/download/[0-9]+/(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"iptorrents\.com/[/a-z0-9?&;]*?(?:[?&;](u|tp)=(?<secret>[^&=;]+?))+(?= |;|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"/fetch/[a-z0-9]{32}/(?<secret>[a-z0-9]{32})", RegexOptions.Compiled),
new Regex(@"getnzb.*?(?<=\?|&)(r)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"\b(\w*)?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
private static readonly Regex[] CleansingRules =
{
// Url
new (@"(?<=\?|&|: )(apikey|(?:access[-_]?)?token|passkey|auth|authkey|user|uid|api|[a-z_]*apikey|account|passwd)=(?<secret>[^&=]+?)(?=[ ""&=]|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"(?<=\?|&)[^=]*?(username|password)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"rss(24h)?\.torrentleech\.org/(?!rss)(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"torrentleech\.org/rss/download/[0-9]+/(?<secret>[0-9a-z]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"iptorrents\.com/[/a-z0-9?&;]*?(?:[?&;](u|tp)=(?<secret>[^&=;]+?))+(?= |;|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"/fetch/[a-z0-9]{32}/(?<secret>[a-z0-9]{32})", RegexOptions.Compiled),
new (@"getnzb.*?(?<=\?|&)(r)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"\b(\w*)?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Trackers Announce Keys; Designed for Qbit Json; should work for all in theory
new Regex(@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce"),
// Trackers Announce Keys; Designed for Qbit Json; should work for all in theory
new (@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce"),
// Path
new Regex(@"C:\\Users\\(?<secret>[^\""]+?)(\\|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"/home/(?<secret>[^/""]+?)(/|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Path
new (@"C:\\Users\\(?<secret>[^\""]+?)(\\|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"/(home|Users)/(?<secret>[^/""]+?)(/|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// NzbGet
new Regex(@"""Name""\s*:\s*""[^""]*(username|password)""\s*,\s*""Value""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// NzbGet
new (@"""Name""\s*:\s*""[^""]*(username|password)""\s*,\s*""Value""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Sabnzbd
new Regex(@"""[^""]*(username|password|api_?key|nzb_key)""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"""email_(account|to|from|pwd)""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Sabnzbd
new (@"""[^""]*(username|password|api_?key|nzb_key)""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"""email_(account|to|from|pwd)""\s*:\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// uTorrent
new Regex(@"\[""[a-z._]*(username|password)"",\d,""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"\[""(boss_key|boss_key_salt|proxy\.proxy)"",\d,""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// uTorrent
new (@"\[""[a-z._]*(username|password)"",\d,""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"\[""(boss_key|boss_key_salt|proxy\.proxy)"",\d,""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Deluge
new Regex(@"auth.login\(""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Deluge
new (@"auth.login\(""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// BroadcastheNet
new Regex(@"""?method""?\s*:\s*""(getTorrents)"",\s*""?params""?\s*:\s*\[\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"getTorrents\(""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new Regex(@"(?<=\?|&)(authkey|torrent_pass)=(?<secret>[^&=]+?)(?=""|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// BroadcastheNet
new (@"""?method""?\s*:\s*""(getTorrents)"",\s*""?params""?\s*:\s*\[\s*""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"getTorrents\(""(?<secret>[^""]+?)""", RegexOptions.Compiled | RegexOptions.IgnoreCase),
new (@"(?<=\?|&)(authkey|torrent_pass)=(?<secret>[^&=]+?)(?=""|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Plex
new Regex(@"(?<=\?|&)(X-Plex-Client-Identifier|X-Plex-Token)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Plex
new (@"(?<=\?|&)(X-Plex-Client-Identifier|X-Plex-Token)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Notifiarr
new Regex(@"api/v[0-9]/notification/radarr/(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Notifiarr
new (@"api/v[0-9]/notification/radarr/(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Discord
new Regex(@"discord.com/api/webhooks/((?<secret>[\w-]+)/)?(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase)
};
// Discord
new (@"discord.com/api/webhooks/((?<secret>[\w-]+)/)?(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase)
};
private static readonly Regex CleanseRemoteIPRegex = new Regex(@"(?:Auth-\w+(?<!Failure|Unauthorized) ip|from) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", RegexOptions.Compiled);
private static readonly Regex CleanseRemoteIPRegex = new (@"(?:Auth-\w+(?<!Failure|Unauthorized) ip|from) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", RegexOptions.Compiled);
public static string Cleanse(string message)
{
@@ -67,15 +67,15 @@ namespace NzbDrone.Common.Instrumentation
foreach (var regex in CleansingRules)
{
message = regex.Replace(message, m =>
{
var value = m.Value;
foreach (var capture in m.Groups["secret"].Captures.OfType<Capture>().Reverse())
{
var value = m.Value;
foreach (var capture in m.Groups["secret"].Captures.OfType<Capture>().Reverse())
{
value = value.Replace(capture.Index - m.Index, capture.Length, "(removed)");
}
value = value.Replace(capture.Index - m.Index, capture.Length, "(removed)");
}
return value;
});
return value;
});
}
message = CleanseRemoteIPRegex.Replace(message, CleanseRemoteIP);
@@ -86,7 +86,6 @@ namespace NzbDrone.Common.Instrumentation
private static string CleanseRemoteIP(Match match)
{
var group = match.Groups[1];
var valueAll = match.Value;
var valueIP = group.Value;
if (IPAddress.TryParse(valueIP, out var address) && !address.IsLocalAddress())

View File

@@ -73,7 +73,7 @@ namespace NzbDrone.Common.Instrumentation
else
{
dsn = RuntimeInfo.IsProduction
? "https://26668106d708406b9ddf5a2bda34fcbb@sentry.servarr.com/9"
? "https://40f1288c1b4d495cbafdb5c89f7f01be@sentry.servarr.com/9"
: "https://998b4673d4c849ccb5277b5966ed5bc2@sentry.servarr.com/10";
}

View File

@@ -106,7 +106,7 @@ namespace NzbDrone.Common.Instrumentation.Sentry
o.Dsn = dsn;
o.AttachStacktrace = true;
o.MaxBreadcrumbs = 200;
o.Release = BuildInfo.Release;
o.Release = $"{BuildInfo.AppName}@{BuildInfo.Release}";
o.BeforeSend = x => SentryCleanser.CleanseEvent(x);
o.BeforeBreadcrumb = x => SentryCleanser.CleanseBreadcrumb(x);
o.Environment = BuildInfo.Branch;

View File

@@ -121,6 +121,11 @@ namespace NzbDrone.Common.Serializer
return JsonConvert.SerializeObject(obj, SerializerSettings);
}
public static string ToJson(this object obj, Formatting formatting)
{
return JsonConvert.SerializeObject(obj, formatting, SerializerSettings);
}
public static void Serialize<TModel>(TModel model, TextWriter outputStream)
{
var jsonTextWriter = new JsonTextWriter(outputStream);

View File

@@ -0,0 +1,64 @@
using System;
using System.Linq;
using Dapper;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Datastore.Migration;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore.Migration
{
[TestFixture]
public class remove_invalid_roksbox_metadata_imagesFixture : MigrationTest<remove_invalid_roksbox_metadata_images>
{
[Test]
public void should_remove_incorrect_roksbox_metadata_images()
{
var db = WithDapperMigrationTestDb(c =>
{
c.Insert.IntoTable("MetadataFiles").Row(new
{
MovieId = 1,
Consumer = "RoksboxMetadata",
Type = 5,
RelativePath = @"metadata\Movie Title (2023).jpg",
LastUpdated = "2023-01-21 00:00:00.000",
Added = "2023-01-21 00:00:00.000",
MovieFileId = 1,
Extension = ".jpg"
});
c.Insert.IntoTable("MetadataFiles").Row(new
{
MovieId = 1,
Consumer = "RoksboxMetadata",
Type = 5,
RelativePath = @"Movie Title (2023).jpg",
LastUpdated = "2023-01-21 00:00:00.000",
MovieFileId = 1,
Added = "2023-01-21 00:00:00.000",
Extension = ".jpg"
});
});
var metadataFiles = db.Query<MetadataFile223>("SELECT * FROM \"MetadataFiles\"");
metadataFiles.Should().HaveCount(1);
metadataFiles.First().RelativePath.Should().NotContain("metadata");
}
}
public class MetadataFile223
{
public int Id { get; set; }
public int MovieId { get; set; }
public int? MovieFileId { get; set; }
public string RelativePath { get; set; }
public DateTime Added { get; set; }
public DateTime LastUpdated { get; set; }
public string Extension { get; set; }
public string Hash { get; set; }
public string Consumer { get; set; }
public int Type { get; set; }
}
}

View File

@@ -225,6 +225,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
[TestCase("checkingDL")]
[TestCase("checkingUP")]
[TestCase("metaDL")]
[TestCase("checkingResumeData")]
public void queued_item_should_have_required_properties(string state)
{
var torrent = new QBittorrentTorrent

View File

@@ -27,7 +27,7 @@ namespace NzbDrone.Core.Test.ImportListTests
_blockedLists = new List<ImportListStatus>();
Mocker.GetMock<IImportListFactory>()
.Setup(v => v.Enabled())
.Setup(v => v.Enabled(It.IsAny<bool>()))
.Returns(_importLists);
Mocker.GetMock<IImportListStatusService>()
@@ -183,7 +183,7 @@ namespace NzbDrone.Core.Test.ImportListTests
var listResult = Subject.Fetch();
listResult.AnyFailure.Should().BeFalse();
listResult.Movies.Count.Should().Be(10);
listResult.Movies.Count.Should().Be(5);
}
}
}

View File

@@ -72,7 +72,7 @@ namespace NzbDrone.Core.Test.ImportList
};
Mocker.GetMock<IImportListFactory>()
.Setup(v => v.Enabled())
.Setup(v => v.Enabled(It.IsAny<bool>()))
.Returns(_importLists);
Mocker.GetMock<IImportExclusionsService>()
@@ -156,6 +156,10 @@ namespace NzbDrone.Core.Test.ImportList
.Setup(v => v.GetAllMovies())
.Returns(_existingMovies);
Mocker.GetMock<IImportListMovieService>()
.Setup(v => v.GetAllListMovies())
.Returns(_list1Movies);
Subject.Execute(_commandAll);
Mocker.GetMock<IMovieService>()
@@ -179,6 +183,10 @@ namespace NzbDrone.Core.Test.ImportList
.Setup(v => v.GetAllMovies())
.Returns(_existingMovies);
Mocker.GetMock<IImportListMovieService>()
.Setup(v => v.GetAllListMovies())
.Returns(_list1Movies);
Subject.Execute(_commandAll);
Mocker.GetMock<IMovieService>()
@@ -204,6 +212,10 @@ namespace NzbDrone.Core.Test.ImportList
.Setup(v => v.GetAllMovies())
.Returns(_existingMovies);
Mocker.GetMock<IImportListMovieService>()
.Setup(v => v.GetAllListMovies())
.Returns(_list1Movies);
Subject.Execute(_commandAll);
Mocker.GetMock<IMovieService>()
@@ -224,6 +236,10 @@ namespace NzbDrone.Core.Test.ImportList
.Setup(v => v.GetAllMovies())
.Returns(_existingMovies);
Mocker.GetMock<IImportListMovieService>()
.Setup(v => v.GetAllListMovies())
.Returns(_list1Movies);
Subject.Execute(_commandAll);
Mocker.GetMock<IMovieService>()
@@ -241,6 +257,10 @@ namespace NzbDrone.Core.Test.ImportList
.Setup(v => v.GetAllMovies())
.Returns(_existingMovies);
Mocker.GetMock<IImportListMovieService>()
.Setup(v => v.GetAllListMovies())
.Returns(_list1Movies);
Subject.Execute(_commandAll);
Mocker.GetMock<IMovieService>()
@@ -267,6 +287,10 @@ namespace NzbDrone.Core.Test.ImportList
.Setup(v => v.GetAllMovies())
.Returns(_existingMovies);
Mocker.GetMock<IImportListMovieService>()
.Setup(v => v.GetAllListMovies())
.Returns(_list1Movies);
Subject.Execute(_commandAll);
Mocker.GetMock<IMovieService>()
@@ -347,7 +371,7 @@ namespace NzbDrone.Core.Test.ImportList
}
[Test]
public void should_not_add_duplicate_movies_from_seperate_lists()
public void should_not_add_duplicate_movies_from_separate_lists()
{
_list2Movies.ForEach(m => m.ListId = 2);
_importListFetch.Movies.ForEach(m => m.ListId = 1);
@@ -384,7 +408,7 @@ namespace NzbDrone.Core.Test.ImportList
Subject.Execute(_commandAll);
Mocker.GetMock<IAddMovieService>()
.Verify(v => v.AddMovies(It.Is<List<Movie>>(s => s.Count == 7 && !s.Any(m => m.TmdbId == _existingMovies[0].TmdbId)), true), Times.Once());
.Verify(v => v.AddMovies(It.Is<List<Movie>>(s => s.Count == 7 && s.All(m => m.TmdbId != _existingMovies[0].TmdbId)), true), Times.Once());
}
[Test]
@@ -406,7 +430,7 @@ namespace NzbDrone.Core.Test.ImportList
Subject.Execute(_commandAll);
Mocker.GetMock<IAddMovieService>()
.Verify(v => v.AddMovies(It.Is<List<Movie>>(s => s.Count == 7 && !s.Any(m => m.TmdbId == _existingMovies[0].TmdbId)), true), Times.Once());
.Verify(v => v.AddMovies(It.Is<List<Movie>>(s => s.Count == 7 && s.All(m => m.TmdbId != _existingMovies[0].TmdbId)), true), Times.Once());
}
}
}

View File

@@ -361,6 +361,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("The German 2021 Bluray AVC")]
[TestCase("German.Only.Movie.2021.French.1080p.BluRay.AVC-UNTAVC")]
[TestCase("Movie.Title.2008.US.Directors.Cut.UHD.BD66.Blu-ray")]
[TestCase("Movie.2009.Blu.ray.AVC.DTS.HD.MA.5.1")]
public void should_parse_brdisk_1080p_quality(string title)
{
ParseAndVerifyQuality(title, Source.BLURAY, false, Resolution.R1080p, Modifier.BRDISK);

View File

@@ -109,6 +109,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Movie.Should.Not.Use.Dots.2022.1080p.BluRay.x265.10bit.Tigole", "Tigole")]
[TestCase("Movie.Title.2005.2160p.UHD.BluRay.TrueHD 7.1.Atmos.x265 - HQMUX", "HQMUX")]
[TestCase("Movie.Name.2022.1080p.BluRay.x264-VARYG (Blue Lock, Multi-Subs)", "VARYG")]
[TestCase("Movie Title (2023) (1080p BluRay x265 SDR AAC 2.0 English Vyndros)", "Vyndros")]
public void should_parse_exception_release_group(string title, string expected)
{
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);

View File

@@ -0,0 +1,43 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.ParserTests
{
[TestFixture]
public class UrlFixture : CoreTest
{
[TestCase("[www.test.com] - Movie.Title.2023.720p.HDTV.X264-DIMENSION", "Movie Title")]
[TestCase("test.net - Movie.Title.2023.720p.HDTV.X264-DIMENSION", "Movie Title")]
[TestCase("[www.test-hyphen.com] - Movie.Title.2023.720p.HDTV.X264-DIMENSION", "Movie Title")]
[TestCase("www.test123.org - Movie.Title.2023.720p.HDTV.X264-DIMENSION", "Movie Title")]
[TestCase("[test.co.uk] - Movie.Title.2023.720p.HDTV.X264-DIMENSION", "Movie Title")]
[TestCase("www.test-hyphen.net.au - Movie.Title.2023.720p.HDTV.X264-DIMENSION", "Movie Title")]
[TestCase("[www.test123.co.nz] - Movie.Title.2023.720p.HDTV.X264-DIMENSION", "Movie Title")]
[TestCase("test-hyphen123.org.au - Movie.Title.2023.720p.HDTV.X264-DIMENSION", "Movie Title")]
[TestCase("[www.test123.de] - Mad Movie Title 2023 [Bluray720p]", "Mad Movie Title")]
[TestCase("www.test-hyphen.de - Mad Movie Title 2023 [Bluray1080p]", "Mad Movie Title")]
[TestCase("www.test123.co.za - The Movie Title Bros. (2023)", "The Movie Title Bros.")]
[TestCase("[www.test-hyphen.ca] - Movie Title (2023)", "Movie Title")]
[TestCase("test123.ca - Movie Time 2023 720p HDTV x264 CRON", "Movie Time")]
[TestCase("[www.test-hyphen123.co.za] - Movie Title 2023", "Movie Title")]
public void should_not_parse_url_in_name(string postTitle, string title)
{
var result = Parser.Parser.ParseMovieTitle(postTitle).MovieTitle.CleanMovieTitle();
result.Should().Be(title.CleanMovieTitle());
}
[TestCase("Movie.2023.English.HDTV.XviD-LOL[www.abb.com]", "LOL")]
[TestCase("Movie Title 2023 English HDTV XviD LOL[www.academy.org]", null)]
[TestCase("Movie Title Now 2023 DVDRip XviD RUNNER[www.aetna.net]", null)]
[TestCase("Movie.Title.2023.DVDRip.XviD-RUNNER[www.alfaromeo.io]", "RUNNER")]
[TestCase("Movie.Title.2023.English.HDTV.XviD-LOL[www.abbott.gov]", "LOL")]
[TestCase("Movie Title 2023 English HDTV XviD LOL[www.actor.org]", null)]
[TestCase("Movie Title Future 2023 DVDRip XviD RUNNER[www.allstate.net]", null)]
public void should_not_parse_url_in_group(string title, string expected)
{
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);
}
}
}

View File

@@ -15,6 +15,7 @@ namespace NzbDrone.Core.Annotations
public string Label { get; set; }
public string Unit { get; set; }
public string HelpText { get; set; }
public string HelpTextWarning { get; set; }
public string HelpLink { get; set; }
public FieldType Type { get; set; }
public bool Advanced { get; set; }

View File

@@ -116,13 +116,6 @@ namespace NzbDrone.Core.Configuration
set { SetValue("AvailabilityDelay", value); }
}
public int ImportListSyncInterval
{
get { return GetValueInt("ImportListSyncInterval", 24); }
set { SetValue("ImportListSyncInterval", value); }
}
public string ListSyncLevel
{
get { return GetValue("ListSyncLevel", "disabled"); }

View File

@@ -60,7 +60,6 @@ namespace NzbDrone.Core.Configuration
bool AllowHardcodedSubs { get; set; }
string WhitelistedHardcodedSubs { get; set; }
int ImportListSyncInterval { get; set; }
string ListSyncLevel { get; set; }
string ImportExclusions { get; set; }

View File

@@ -0,0 +1,15 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(223)]
public class remove_invalid_roksbox_metadata_images : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
IfDatabase("sqlite").Execute.Sql("DELETE FROM \"MetadataFiles\" WHERE \"Consumer\" = 'RoksboxMetadata' AND \"Type\" = 5 AND (\"RelativePath\" LIKE 'metadata/%' OR \"RelativePath\" LIKE 'metadata\\%')");
IfDatabase("postgresql").Execute.Sql("DELETE FROM \"MetadataFiles\" WHERE \"Consumer\" = 'RoksboxMetadata' AND \"Type\" = 5 AND (\"RelativePath\" LIKE 'metadata/%' OR \"RelativePath\" LIKE 'metadata\\\\%')");
}
}
}

View File

@@ -0,0 +1,18 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(224)]
public class list_sync_time : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Delete.Column("LastSyncListInfo").FromTable("ImportListStatus");
Alter.Table("ImportListStatus").AddColumn("LastInfoSync").AsDateTimeOffset().Nullable();
Delete.FromTable("Config").Row(new { Key = "importlistsyncinterval" });
}
}
}

View File

@@ -0,0 +1,14 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(225)]
public class add_tags_to_collections : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("Collections").AddColumn("Tags").AsString().Nullable();
}
}
}

View File

@@ -80,6 +80,7 @@ namespace NzbDrone.Core.Datastore
Mapper.Entity<ImportListDefinition>("ImportLists").RegisterModel()
.Ignore(x => x.ImplementationName)
.Ignore(i => i.ListType)
.Ignore(i => i.MinRefreshInterval)
.Ignore(i => i.Enable);
Mapper.Entity<NotificationDefinition>("Notifications").RegisterModel()

View File

@@ -251,6 +251,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
case "queuedDL": // queuing is enabled and torrent is queued for download
case "checkingDL": // same as checkingUP, but torrent has NOT finished downloading
case "checkingUP": // torrent has finished downloading and is being checked. Set when `recheck torrent on completion` is enabled. In the event the check fails we shouldn't treat it as completed.
case "checkingResumeData": // torrent is checking resume data on load
item.Status = DownloadItemStatus.Queued;
break;

View File

@@ -84,7 +84,8 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Roksbox
if (extension == ".jpg")
{
if (Path.GetFileNameWithoutExtension(filename).Equals(parentdir.Name, StringComparison.InvariantCultureIgnoreCase))
if (Path.GetFileNameWithoutExtension(filename).Equals(parentdir.Name, StringComparison.InvariantCultureIgnoreCase) &&
!path.GetParentName().Equals("metadata", StringComparison.InvariantCultureIgnoreCase))
{
metadata.Type = MetadataType.MovieImage;
return metadata;

View File

@@ -48,13 +48,11 @@ namespace NzbDrone.Core.HealthCheck.Checks
try
{
var status = client.GetStatus();
var folders = status.OutputRootFolders;
var folders = status.OutputRootFolders.Where(folder => rootFolders.Any(r => r.Path.PathEquals(folder.FullPath)));
foreach (var folder in folders)
{
if (rootFolders.Any(r => r.Path.PathEquals(folder.FullPath)))
{
return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format(_localizationService.GetLocalizedString("DownloadClientCheckDownloadingToRoot"), client.Definition.Name, folder.FullPath), "#downloads-in-root-folder");
}
return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format(_localizationService.GetLocalizedString("DownloadClientCheckDownloadingToRoot"), client.Definition.Name, folder.FullPath), "#downloads-in-root-folder");
}
}
catch (DownloadClientException ex)

View File

@@ -32,7 +32,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
public override HealthCheck Check()
{
var clients = _downloadClientProvider.GetDownloadClients();
var clients = _downloadClientProvider.GetDownloadClients(true);
foreach (var client in clients)
{

View File

@@ -23,12 +23,14 @@ namespace NzbDrone.Core.HealthCheck.Checks
public override HealthCheck Check()
{
var jackettAllProviders = _providerFactory.All().Where(
i => i.ConfigContract.Equals("TorznabSettings") &&
((i.Settings as TorznabSettings).BaseUrl.Contains("/torznab/all/api", StringComparison.InvariantCultureIgnoreCase) ||
(i.Settings as TorznabSettings).BaseUrl.Contains("/api/v2.0/indexers/all/results/torznab", StringComparison.InvariantCultureIgnoreCase) ||
(i.Settings as TorznabSettings).ApiPath.Contains("/torznab/all/api", StringComparison.InvariantCultureIgnoreCase) ||
(i.Settings as TorznabSettings).ApiPath.Contains("/api/v2.0/indexers/all/results/torznab", StringComparison.InvariantCultureIgnoreCase)));
var jackettAllProviders = _providerFactory.All()
.Where(
i => i.ConfigContract.Equals("TorznabSettings") &&
(((TorznabSettings)i.Settings).BaseUrl.Contains("/torznab/all/api", StringComparison.InvariantCultureIgnoreCase) ||
((TorznabSettings)i.Settings).BaseUrl.Contains("/api/v2.0/indexers/all/results/torznab", StringComparison.InvariantCultureIgnoreCase) ||
((TorznabSettings)i.Settings).ApiPath.Contains("/torznab/all/api", StringComparison.InvariantCultureIgnoreCase) ||
((TorznabSettings)i.Settings).ApiPath.Contains("/api/v2.0/indexers/all/results/torznab", StringComparison.InvariantCultureIgnoreCase)))
.ToArray();
if (jackettAllProviders.Empty())
{

View File

@@ -28,13 +28,12 @@ namespace NzbDrone.Core.HealthCheck.Checks
{
var enabledProviders = _providerFactory.GetAvailableProviders();
var backOffProviders = enabledProviders.Join(_providerStatusService.GetBlockedProviders(),
i => i.Definition.Id,
s => s.ProviderId,
(i, s) => new { Provider = i, Status = s })
.Where(p => p.Status.InitialFailure.HasValue &&
p.Status.InitialFailure.Value.Before(
DateTime.UtcNow.AddHours(-6)))
.ToList();
i => i.Definition.Id,
s => s.ProviderId,
(i, s) => new { Provider = i, Status = s })
.Where(p => p.Status.InitialFailure.HasValue &&
p.Status.InitialFailure.Value.Before(DateTime.UtcNow.AddHours(-6)))
.ToList();
if (backOffProviders.Empty())
{

View File

@@ -26,13 +26,12 @@ namespace NzbDrone.Core.HealthCheck.Checks
{
var enabledProviders = _providerFactory.GetAvailableProviders();
var backOffProviders = enabledProviders.Join(_providerStatusService.GetBlockedProviders(),
i => i.Definition.Id,
s => s.ProviderId,
(i, s) => new { Provider = i, Status = s })
.Where(p => p.Status.InitialFailure.HasValue &&
p.Status.InitialFailure.Value.After(
DateTime.UtcNow.AddHours(-6)))
.ToList();
i => i.Definition.Id,
s => s.ProviderId,
(i, s) => new { Provider = i, Status = s })
.Where(p => p.Status.InitialFailure.HasValue &&
p.Status.InitialFailure.Value.After(DateTime.UtcNow.AddHours(-6)))
.ToList();
if (backOffProviders.Empty())
{

View File

@@ -21,10 +21,10 @@ namespace NzbDrone.Core.HealthCheck.Checks
{
// Not best for optimization but due to possible symlinks and junctions, we get mounts based on series path so internals can handle mount resolution.
var mounts = _movieService.AllMoviePaths()
.Select(p => _diskProvider.GetMount(p.Value))
.Where(m => m != null && m.MountOptions != null && m.MountOptions.IsReadOnly)
.DistinctBy(m => m.RootDirectory)
.ToList();
.Select(p => _diskProvider.GetMount(p.Value))
.Where(m => m is { MountOptions.IsReadOnly: true })
.DistinctBy(m => m.RootDirectory)
.ToList();
if (mounts.Any())
{

Some files were not shown because too many files have changed in this diff Show More