Compare commits

...

245 Commits

Author SHA1 Message Date
Mark McDowall
c664eaa9b5 New: Don't treat 400 responses from Notifiarr as errors
(cherry picked from commit 5eb420bbe12f59d0a5392abf3d351be28ca210e6)
2023-10-07 23:02:44 +03:00
Bogdan
b7e57f0c08 Fixed: (Nebulance) Filter releases by season and episode for ID based searches 2023-10-07 03:09:44 +03:00
Bogdan
c06bf0e4ea Fixed: (TorrentDay) Update categories
Fixes #1888
2023-10-05 22:41:59 +03:00
Bogdan
c6db30c35a Parse description in RSS Parser 2023-10-05 01:54:40 +03:00
Bogdan
75c30dd318 Add year to XML results 2023-10-04 19:51:49 +03:00
Bogdan
6e7bf55dbd Add poster URL to PassThePopcorn 2023-10-04 19:16:55 +03:00
Bogdan
eb642dd2f9 Fix document being disposed before returning 2023-10-04 18:36:31 +03:00
Bogdan
19a196e2c7 Ensure the correct use of disposable parsed documents 2023-10-04 18:11:23 +03:00
Weblate
93ec6cf89b Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dimitri <dimitridroeck@gmail.com>
Co-authored-by: Garkus98 <ivan12061998@gmail.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: RicardoVelaC <ricardovelac@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: 宿命 <331874545@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2023-10-04 07:00:00 +03:00
Bogdan
52c6b56a4c Cleanup BooleanConverter to STJson 2023-10-04 05:19:03 +03:00
Bogdan
82688d8a55 Use ExecuteAuth in AvistazBase 2023-10-04 04:27:04 +03:00
Bogdan
c81cbc801a Fixed: (AvistaZBase) Parse response with STJson
Also ensure GetToken is using a proxied request and rate limit
2023-10-04 02:27:32 +03:00
Bogdan
993d189c61 Fixed: (Nebulance) Parse response with STJson 2023-10-04 02:27:29 +03:00
Bogdan
1901af5a51 Fixed: (BeyondHD) Parse response with STJson 2023-10-04 02:27:24 +03:00
Bogdan
c1b399be39 Fixed: (FileList) Parse response with STJson 2023-10-04 02:27:19 +03:00
Bogdan
2100e96570 Fixed: (PassThePopcorn) Use UTC for publish dates 2023-10-02 04:35:57 +03:00
Bogdan
3ff144421d Fixed: (PassThePopcorn) Cleanup and ensure pagination is working in Radarr 2023-10-02 02:55:34 +03:00
Bogdan
f37ccba3f9 Fixed: (Shizaproject) Title improvements 2023-10-02 00:47:33 +03:00
Bogdan
181cb2e0fe Revert "New: (Orpheus) Add options to prevent downloads without FL tokens"
This reverts commit 93c81bb7d3.
2023-10-01 19:47:28 +03:00
Bogdan
93c81bb7d3 New: (Orpheus) Add options to prevent downloads without FL tokens 2023-10-01 17:03:43 +03:00
Weblate
7dd289b5f9 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Jaspils <jasperkemper@gmail.com>
Co-authored-by: Stevie Robinson <stevie.robinson@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: 宿命 <331874545@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2023-10-01 17:03:03 +03:00
Bogdan
09cef8cf94 Bump version to 1.9.3 2023-10-01 17:01:35 +03:00
Bogdan
ca08c818e6 Fixed: (TorrentPotato) Use full IMDb Id 2023-09-30 22:41:25 +03:00
Bogdan
3e95bc4056 Fixed: (TorrentPotato) Title not being decoded 2023-09-30 22:15:25 +03:00
ilike2burnthing
e241112915 Fixed: (Shizaproject) Available again 2023-09-29 22:13:14 +03:00
Bogdan
0d98c12fa2 Fix the description for Use Filenames for Single Episodes 2023-09-29 20:45:04 +03:00
Bogdan
a0bcf5c9ae Allow using filename for single episodes along with generated titles in AnimeBytes 2023-09-29 20:26:35 +03:00
Bogdan
e318a47b3a Extend the torrent settings interface in TorrentPotato settings class 2023-09-27 23:23:43 +03:00
Bogdan
b8df720c6c Bump version to 1.9.2 2023-09-24 16:22:57 +03:00
Bogdan
9625be723d Fixed: (Search) Releases deduplication 2023-09-22 19:54:11 +03:00
Bogdan
d4b037db78 New: (AlphaRatio) Add pagination support 2023-09-21 20:46:34 +03:00
Bogdan
add2988789 Fixed: (Cardigann) Fallback variables to empty string to prevent NullRef 2023-09-20 02:54:25 +03:00
Bogdan
9869c2272a Avoid returning null in static resource mapper Task 2023-09-19 20:17:05 +03:00
Bogdan
4c8b0c9eec Fixed: Ignore releases without title 2023-09-19 14:06:15 +03:00
Bogdan
43cb22ff2b Bump migration timeout to 10 minutes 2023-09-19 00:57:06 +03:00
Bogdan
3cabc0589a Simplify use the group name when the release name is empty 2023-09-18 21:17:36 +03:00
Bogdan
cdb3ed36f6 Fixed: (Nebulance) Use the group name when the release name is empty 2023-09-18 21:16:13 +03:00
Bogdan
840f2ae3e6 Sync static resource controller with upstream
(cherry picked from commit ad1f185330a30a2a9d27c9d3f18d384e66727c2a)
2023-09-18 03:53:14 +03:00
Bogdan
3ed6ef0336 Use await on reading the response content
(cherry picked from commit 82d586e7015d7ea06356ca436024a8af5a4fb677)
2023-09-18 03:49:31 +03:00
Bogdan
c2ae0cce03 Bump version to 1.9.1 2023-09-17 12:01:01 +03:00
Weblate
934b908b37 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: stormaac <yxc.frank@gmail.com>
Co-authored-by: 宿命 <331874545@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2023-09-16 08:51:49 -04:00
Weblate
6c831f11a6 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: leotpp <yangdom_li@126.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/id/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_TW/
Translation: Servarr/Prowlarr
2023-09-15 21:49:25 -05:00
Bogdan
9adbfd2391 Return 1 seeder as fallback only in Torrent RSS Feed 2023-09-14 21:48:32 +03:00
bakerboy448
4a7cc82f0d Correction to Improve tags helptext and add warnings 2023-09-14 20:56:24 +03:00
bakerboy448
c061c309bd Improve tags helptext and add warnings 2023-09-14 20:48:28 +03:00
Bogdan
0f3a77c336 Return 1 seeder as fallback in Torrent RSS Feed 2023-09-14 18:55:07 +03:00
Bogdan
478d5a624f Fixed: (Animedia) Removed
Co-authored-by: ilike2burnthing <59480337+ilike2burnthing@users.noreply.github.com>
2023-09-14 18:42:41 +03:00
Bogdan
3283d144f5 Ignore invalid cookies when adding response cookies to CookieContainer
Fixes #1868
2023-09-14 18:22:53 +03:00
Bogdan
1a9ec4febd Fixed: (Apps) Check if the indexers have valid settings 2023-09-14 17:50:54 +03:00
Bogdan
0598211319 Fixed: Ignore inaccessible mount points
(cherry picked from commit 60f18249b05daa20523542beef54bc126d963d1e)
2023-09-14 12:15:37 +03:00
Bogdan
0b0d6b7590 Fixed: (SubsPlease) Update category mappings for movie releases
Co-authored-by: Lemres <45440100+Calemy@users.noreply.github.com>

Fixes #1866
2023-09-13 12:30:06 +03:00
Servarr
86cec51ebe Automated API Docs update 2023-09-12 22:59:27 +03:00
Bogdan
80e5ac4aa9 New: Add custom filter by protocol for indexer stats 2023-09-12 15:25:48 +03:00
Bogdan
ee5ed0c91b Sonarr > Prowlarr 2023-09-11 11:59:51 +03:00
Denis Gheorghescu
ba278930ed New: Pushcut notifications
(cherry picked from commit 5f09f2b25f9144666b8e5182e20e263e74d5f5ca)
2023-09-11 11:57:35 +03:00
Mark McDowall
6449b89eb6 Fixed parsing of multiple languages from Newznab indexer releases
(cherry picked from commit 2a241294b5eeb9e95c46e030828191da09d05e88)
2023-09-11 11:29:07 +03:00
Weblate
73b85e240e Multiple Translations updated by Weblate
ignore-downstream

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/prowlarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/id/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_TW/
Translation: Servarr/Prowlarr
2023-09-10 20:30:01 +03:00
Bogdan
6338460ff4 Bump version to 1.9.0 2023-09-10 19:09:26 +03:00
Bogdan
0463e66881 Rename ISearchForNzb to IReleaseSearchService 2023-09-10 11:19:17 +03:00
Bogdan
bd75621437 Update magnet trackers 2023-09-10 09:46:39 +03:00
Weblate
9615c1183d Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/
Translation: Servarr/Prowlarr
2023-09-10 08:33:44 +03:00
Weblate
bbf042ed55 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: AlexR-sf <omg.portal.supp@gmail.com>
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: ChewyGlitter <lulu3dddsss@gmail.com>
Co-authored-by: DavidJares <david.jares@me.com>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: He Zhu <zhuhe202@qq.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2023-09-10 00:31:50 -05:00
Bogdan
98e948dbb2 Fix indexer flags column label 2023-09-09 14:15:58 +03:00
Bogdan
2af9f7eb8d Fixed: macOS version detection
(cherry picked from commit 060be6177a5477c94823e6a423c42064dedc1afb)
2023-09-08 05:10:05 +03:00
Bogdan
96413f99c7 New: Add hour as interval unit for indexer limits 2023-09-06 17:08:33 +03:00
Bogdan
d44b946d30 Add hourly limits as defaults for FL and BTN 2023-09-06 17:08:33 +03:00
Bogdan
fe9cad5697 Move clone button to the left 2023-09-04 11:00:37 +03:00
Servarr
098be3cff6 Automated API Docs update 2023-09-04 07:03:50 +03:00
Bogdan
8f2fea0be8 Fixed: (Nebulance) Add fallback TV search without season/episode 2023-09-04 06:58:24 +03:00
Qstick
8d035c6c1f New: Add support for MaxAge to Newznab API 2023-09-03 22:36:11 -05:00
Bogdan
7dbfa74c40 Add external links for indexer history 2023-09-04 05:53:33 +03:00
Qstick
caaf50ed9c New: Save categories to history for grabs 2023-09-03 20:30:44 -05:00
Bogdan
b472a022a6 Add history parameters to indexer history 2023-09-04 04:12:56 +03:00
Qstick
0a439a4a96 Show all queries in Total Queries block 2023-09-03 20:11:58 -05:00
Bogdan
4410636b97 Use method group in History controller 2023-09-04 02:22:23 +03:00
Servarr
ba3ebc7574 Automated API Docs update 2023-09-04 02:14:55 +03:00
Bogdan
2ce49a0785 Migrate to merged proposals now included in babel/present-env
Bump core-js too.
2023-09-04 02:09:04 +03:00
Qstick
d7df946c2b New: Summary Stats 2023-09-03 18:04:39 -05:00
Qstick
3dd3c80b54 New: Indexer history in indexer info modal 2023-09-03 18:04:39 -05:00
Qstick
0f160707d3 Chart style updates 2023-09-03 18:04:39 -05:00
Qstick
b608e38454 New: Custom Filters for Stats 2023-09-03 18:04:39 -05:00
Bogdan
c873b3ffac New: (UI) Add clone indexer in info modal 2023-09-03 23:08:15 +03:00
Bogdan
07b98f4137 Move scene flag to TorrentInfo and return tag in response 2023-09-03 23:07:59 +03:00
Bogdan
09606af351 Bump dotnet to 6.0.21 2023-09-03 10:33:50 -05:00
Bogdan
1d79b92fca Bump version to 1.8.6 2023-09-03 07:26:15 +03:00
Stevie Robinson
fbcf1b03c5 Translate Frontend Store
(cherry picked from commit d31fcbb2dfe98a540a359b98b204d101d554cf03)
2023-09-02 06:04:57 +03:00
Bogdan
dee98ac46f Display a not allowed cursor for disabled select options 2023-09-02 03:47:07 +03:00
Bogdan
4267b8a244 Cleanse API Keys from sync requests sent to applications 2023-09-02 02:07:26 +03:00
Bogdan
00dc55996c Remove Label and Unit columns from Apps Fields 2023-09-02 01:59:11 +03:00
Bogdan
b912cc6110 Some logging improvements for filtered releases
Closes #1847
2023-09-02 00:47:47 +03:00
Bogdan
56f0c137f8 Fixed translations for history parameters 2023-09-01 15:50:22 +03:00
Bogdan
1b8ff9b989 Simplify user check in Authentication Required for newer installs 2023-08-29 19:12:06 +03:00
Bogdan
bfecf35a8b Fix user check in Authentication Required for newer installs 2023-08-29 18:56:21 +03:00
Bogdan
80da5ce165 Add missing app name token for translation 2023-08-29 03:18:11 +03:00
Bogdan
60ca0db26f Improve messaging in Authentication Required modal
Co-authored-by: bakerboy448 <55419169+bakerboy448@users.noreply.github.com>
2023-08-29 03:09:42 +03:00
Bogdan
288a3d1495 Fix loading translations 2023-08-29 03:05:51 +03:00
Weblate
4c42907eb2 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: AlexR-sf <omg.portal.supp@gmail.com>
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: ChewyGlitter <lulu3dddsss@gmail.com>
Co-authored-by: DavidJares <david.jares@me.com>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: He Zhu <zhuhe202@qq.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2023-08-27 19:21:30 +03:00
Bogdan
6300eb1442 Bump version to 1.8.5 2023-08-27 08:00:53 +03:00
Bogdan
e4c0edf24c Remove Reddit from issue templates 2023-08-26 21:14:23 +03:00
Bogdan
74a9fa784a Fixed: (Nebulance) Detect TV UHD releases 2023-08-26 20:21:41 +03:00
ilike2burnthing
1b0c9adf24 Remove Reddit from issues config 2023-08-22 00:30:49 +03:00
Bogdan
0eaa538e8a Allow deselecting values when using search through url query params 2023-08-21 21:32:26 +03:00
Bogdan
39a54eb8f6 Prevent health checks warnings for disabled notifications 2023-08-21 21:04:30 +03:00
Bogdan
5ad6237785 Improve music and console search results for AnimeBytes
Also prevent duplicate categories showing in the indexer info modal
2023-08-21 18:08:43 +03:00
Bogdan
9fee4f914f Bump version to 1.8.4 2023-08-20 12:23:24 +03:00
Weblate
ba2aab6bb3 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Robert A. Viana <robert.abreu@outlook.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: w2861 <hfagfc@163.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/id/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2023-08-20 03:52:43 +03:00
Bogdan
5c8ae82f11 Prevent useless builds 2023-08-19 16:23:16 +03:00
Servarr
bcbeac1e83 Automated API Docs update [skip ci] 2023-08-19 13:55:35 +03:00
Mark McDowall
b36d793d85 Allow decimals in number inputs
(cherry picked from commit 7f5ddff568ce9f87bd45420cbd36690b190bd633)
2023-08-19 13:49:18 +03:00
Mark McDowall
b0162ccc5b New: Success check mark on blue buttons is now white instead of green
(cherry picked from commit 566fae9d5857a10bd69c718368e7847e5a733faa)
2023-08-19 13:47:56 +03:00
Robin Dadswell
f0892eb4b8 Adds Pipeline testing for Postgres15 Databases 2023-08-18 21:12:09 +03:00
Robin Dadswell
e456979467 bump Npgsql to 7.0.4 2023-08-18 21:12:09 +03:00
Bogdan
66ca47b615 Fix flaky automation tests 2023-08-18 20:36:44 +03:00
Weblate
2b7771bfe0 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Robert A. Viana <robert.abreu@outlook.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: w2861 <hfagfc@163.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/id/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2023-08-18 11:40:45 +03:00
Mark McDowall
955bc472a1 Fixed: Hidden files being ignored
(cherry picked from commit d493f8762fcb1684b44e182753c21d7a493db787)
2023-08-18 11:33:21 +03:00
Bogdan
e024bba6b6 Replace docker detection for cgroup v2
(cherry picked from commit 78d4dee4610c5f3f90cc69469004008aa64900b8)
2023-08-18 11:32:39 +03:00
Bogdan
aeb3b7d8b5 Add retry to flaky automation tests 2023-08-17 14:38:48 +03:00
bakerboy448
a7b25b8b93 Remove reddit from readme 2023-08-16 17:25:12 +03:00
Qstick
130257fdd4 New: Notifications (Connect) Status
(cherry picked from commit e3545801721e00d4e5cac3fa534e66dcbe9d2d05)
2023-08-14 16:07:23 +03:00
Qstick
b618f23bc0 Cleanup other provider status code
(cherry picked from commit c281a7818adce8db728d2a104f4444cb9c0baf2c)
2023-08-14 16:07:23 +03:00
Bogdan
a758161e31 New: Default name when adding providers 2023-08-14 16:07:23 +03:00
Weblate
27928103c5 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/
Translation: Servarr/Prowlarr
2023-08-14 14:06:06 +03:00
Weblate
d5b3961e8a Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: David Molero <contact@dolvem.com>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: deepserket <deepserket@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ro/
Translation: Servarr/Prowlarr
2023-08-14 13:48:57 +03:00
Stevie Robinson
307adf053e Translate Updated and Connection Lost Modals in frontend
(cherry picked from commit 074aa6f4457bf83173e6ba7209c452a6e0659a35)
2023-08-14 01:22:09 +03:00
Bogdan
31261f66ad Use named tokens in frontend translate function 2023-08-13 22:59:56 +03:00
Bogdan
5dbb59dfaa Bump version to 1.8.3 2023-08-13 13:04:41 +03:00
Weblate
25c1803d0e Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Nir Israel Hen <nirisraelh@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: wilfriedarma <wilfriedarma.collet@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ro/
Translation: Servarr/Prowlarr
2023-08-13 12:51:24 +03:00
Bogdan
9f4c9d3344 Show successful grabs in Search with green icon 2023-08-12 12:12:51 +03:00
Bogdan
dfb00d9bb1 Fixed: Ensure grab notifications are sent according to tags requirements 2023-08-12 12:07:17 +03:00
Bogdan
f7727855b5 Rework adding one minute back-off level for all providers
(cherry picked from commit d8f314ff0ef64e8d90b21b7865e46be74db5e570)
2023-08-12 09:32:07 +03:00
Stepan Goremykin
1e4c67dcdb Update FluentAssertions
(cherry picked from commit 951a9ade00d7c9105f03608cb598450d706b826f)
2023-08-12 09:28:42 +03:00
Robin Dadswell
26afcb0071 Fixed: PostgreSQL timezone issues
(cherry picked from commit d55864f86914199aa0c4ee37df1e42e6ad71ef4f)
2023-08-10 23:03:27 +01:00
Bogdan
7a937e85a4 Fixed: Retain user settings not-affiliated with Prowlarr 2023-08-10 18:05:00 +03:00
Bogdan
7cd82321b4 Bump Npgsql version to 6.0.9
Fixes #1819
2023-08-09 23:41:09 +03:00
Bogdan
8c9adba516 Fixed color for links 2023-08-09 19:30:25 +03:00
Bogdan
03fa9254e3 Prevent NullRef in IsPathValid for null paths 2023-08-09 13:35:13 +03:00
Weblate
e66ecf5c95 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: byakurau <byakurau1@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translation: Servarr/Prowlarr
2023-08-08 18:49:41 +03:00
Bogdan
e0dddfa215 Remove Order and Help columns from Apps Fields 2023-08-08 18:30:25 +03:00
Bogdan
bcb8afadf8 New: Add Content Summary for requests to apps 2023-08-08 18:26:09 +03:00
Bogdan
fc4a0979c3 Fixed: Detect Docker when using control group v2 2023-08-07 19:15:21 +03:00
Bogdan
5f643b2ced Fixed: (Indexers) Don't fetch releases when using unsupported capabilities 2023-08-06 20:30:59 +03:00
Bogdan
6f09b0f4f5 Bump version to 1.8.2 2023-08-06 08:42:24 +03:00
Qstick
95c2531107 Filter user issues from Sentry
(cherry picked from commit 03d361f5537bfc0caba1b86085f974570942fdbc)
2023-08-05 21:38:44 +03:00
Bogdan
f83828cc22 Fixed border for actions in health status 2023-08-05 17:56:13 +03:00
Bogdan
cdea548ce2 New: Add internal links for apps and download clients health checks 2023-08-05 14:26:53 +03:00
Bogdan
cae1da0ce2 Fixed: (Apps) Lower the severity for testing messages 2023-08-05 14:12:06 +03:00
Bogdan
765f354c51 New: Add test all action for apps and download clients to status health 2023-08-05 14:11:50 +03:00
Bogdan
5cbbffb018 Fix translation typo in sync level options 2023-08-05 14:11:50 +03:00
Bogdan
b2c5448cbf Fixed: Run health checks for applications and download clients on bulk events 2023-08-05 11:40:15 +03:00
Bogdan
3dae84705c Fixed: Ensure failing providers are marked as failed when testing all 2023-08-05 10:02:55 +03:00
Weblate
2321d278d6 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Albert <zuozl1992@foxmail.com>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Ivan Mazzoli <dreadtank27@gmail.com>
Co-authored-by: Magnus <magnus.fladvad@gmail.com>
Co-authored-by: Stjepan <stjepstjepanovic@gmail.com>
Co-authored-by: Thirrian <matthiaslantermann@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: stormaac <yxc.frank@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2023-08-04 21:07:31 +03:00
TwentyNine78
ea73466f6a Fixed: Compatibility with the new Download Station API
(cherry picked from commit 49e90463e57929e7b9885f1b7b0eb05bd7cc3ebe)
2023-08-04 21:06:45 +03:00
Bogdan
6961c5a1c6 Fixed: (AlphaRatio) Use FL tokens only if canUseToken is true
Fixes #1811
2023-08-04 17:20:28 +03:00
Mark McDowall
141f1597dc New: Ignore inaccessible files with getting files
(cherry picked from commit e5aa8584100d96a2077c57f74ae5b2ceab63de19)
2023-08-04 13:17:37 +03:00
Bogdan
1100f350ae Fix translations for option values 2023-08-04 07:05:31 +03:00
Bogdan
3c5eefc349 New: Health check for indexers with invalid download client
(cherry picked from commit 377fce6fe15c0875c4bd33f1371a31af79c9310c)
2023-08-04 06:50:48 +03:00
Mark McDowall
0bfb557470 Prevent NullRef in ContainsInvalidPathChars
(cherry picked from commit 5f7217844533907d7fc6287a48efb31987736c4c)
2023-08-04 06:43:40 +03:00
Servarr
c93d6cff63 Automated API Docs update [skip ci] 2023-08-03 17:40:31 +03:00
Bogdan
7e4980b855 New: Add translations for columns 2023-08-03 17:20:36 +03:00
Bogdan
419ef4b3bf New: More translations for columns
(cherry picked from commit aee8579d1823b7dfb94c0055fe33b5fb5a7fbf17)
2023-08-03 16:10:13 +03:00
Mark McDowall
c56d49ab60 Fixed: Translations for columns
(cherry picked from commit 6d53d2a153a98070c42d0619c15902b6bd5dfab4)
2023-08-03 16:07:00 +03:00
Mark McDowall
1a40924db3 Fixed: Improve translation loading
(cherry picked from commit 73c5ec1da4dd00301e1b0dddbcea37590a99b045)
2023-08-03 16:05:39 +03:00
Mark McDowall
d55906d49a UI loading improvements
Fixed: Caching for dynamically loaded JS files
Fixed: Incorrect caching of initialize.js
(cherry picked from commit f0cb5b81f140c67fa84162e094cc4e0f3476f5da)
2023-08-03 15:57:52 +03:00
Bogdan
bc53fab966 Fixed: Don't fetch capabilities for disabled Newznab/Torznab indexers on create
Also prevent NullRef in GetProxy since definition is null when using FetchCapabilities on add
2023-08-02 14:54:01 +03:00
Bogdan
d897b50f80 New: (UI) Show Magnet Link in search results if any 2023-08-01 13:29:12 +03:00
Bogdan
cc66cee71c Fixed: (Apps) Avoid force saving remote indexers when it's not necessary 2023-07-31 10:33:57 +03:00
Bogdan
f5e96f3f51 Ensure yarn packages are installed when running only LintUI 2023-07-31 08:18:45 +03:00
Mark McDowall
d52e1259a1 Re-order frontend build steps
(cherry picked from commit 97ad6682f7d54af8886144bc5a179fa7242f1f1f)
2023-07-31 07:56:20 +03:00
Bogdan
72e6d66269 New: (Apps) Add force sync indexers for applications 2023-07-31 07:22:08 +03:00
Bogdan
e51b85449d Convert store selectors to Typescript 2023-07-30 21:06:44 +03:00
Bogdan
efd5e92ca5 Support categories with Transmission 2023-07-30 12:38:33 +03:00
Bogdan
d153746a98 Bump version to 1.8.1 2023-07-30 10:45:08 +03:00
Bogdan
a1927e1e0f Sort indexers by name in search footer dropdown 2023-07-29 13:48:10 +03:00
Bogdan
630a4ce800 Fixed: Ensure failing indexers are marked as failed when testing all
(cherry picked from commit b407eba61284d5fb855df6a2868805853aa6f448)
2023-07-29 12:14:58 +03:00
Bogdan
8b1dd78300 Fixed: (Apps) Ensure populated capabilities for Torznab/Newznab definitions 2023-07-29 12:08:48 +03:00
Bogdan
cab50b35aa Convert some selectors to Typescript 2023-07-29 03:14:47 +03:00
Weblate
eee1be784b Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Fixer <ygj59783@zslsz.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ro/
Translation: Servarr/Prowlarr
2023-07-28 12:55:09 +03:00
Bogdan
269dc5688b New: (IPTorrents) Add new base url 2023-07-27 12:18:49 +03:00
Weblate
9bed795c89 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translation: Servarr/Prowlarr
2023-07-27 12:14:26 +03:00
Bogdan
3b5f151252 New: Set default names for providers in Add Modals 2023-07-27 02:58:07 +03:00
Weblate
b3a541c9ff Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dortlix <jeremy.boely@hotmail.fr>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: aguillement <adrien.guillement@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ro/
Translation: Servarr/Prowlarr
2023-07-26 07:18:25 +03:00
Bogdan
bc90fa2d3f Add unit to history cleanup days option 2023-07-26 07:17:06 +03:00
Bogdan
4b0a896434 Fixed: (Cardigann) Improvements to automatic logins with captcha 2023-07-26 05:35:42 +03:00
Bogdan
6be0e08635 Convert Delete Indexer to Typescript 2023-07-25 05:50:45 +03:00
Bogdan
f618901048 Convert Indexer Stats to Typescript 2023-07-25 05:50:45 +03:00
Weblate
809ed940e6 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: SHUAI.W <x@ousui.org>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2023-07-25 01:05:11 +03:00
Qstick
7b14c2ee66 Bump version to 1.8.0 2023-07-23 23:25:30 -05:00
Qstick
4528d03931 Update Sentry DSN 2023-07-23 21:49:21 -05:00
Bogdan
e0b30d34b1 New: (Apps) Add Go To Application in UI 2023-07-23 22:03:49 +03:00
Bogdan
8edf483e69 Bump version to 1.7.4 2023-07-23 07:09:59 +03:00
Bogdan
cea6aae9e1 Add support for deprecated values in field select options
(cherry picked from commit d9786887f3fe30ef60ad9c50b3272bf60dfef309)
2023-07-23 05:05:41 +03:00
Bogdan
1697cee680 Add hover background color in Indexer Table Index 2023-07-22 05:34:39 +03:00
Bogdan
ce8c90a125 Added magnetUrl prop in Search Index Row 2023-07-22 05:34:39 +03:00
Weblate
c8ad3d6edd Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translation: Servarr/Prowlarr
2023-07-21 13:25:57 +03:00
Bogdan
ebe01913c2 Fix selecting guids from search results 2023-07-21 10:35:04 +03:00
Mark McDowall
07cb19f9f3 Sort available filters options in custom filters
(cherry picked from commit 9e694c7b06c6d54fd652792fa1d81cc27ec1f311)
2023-07-21 07:40:36 +03:00
Bogdan
7f51c44829 Fixed: (UI) Ensure proper parsing for size values in custom filters 2023-07-21 07:26:43 +03:00
Bogdan
07f816f9fd Fixed: (BeyondHD) Add search types option 2023-07-21 01:24:29 +03:00
Bogdan
a4a50b880c Add GetAttribute for enums 2023-07-21 01:24:29 +03:00
Bogdan
79361d92cb Ensure No search results found isn't shown without a search 2023-07-20 23:05:48 +03:00
Bogdan
ecda75152e Cache busting for CSS files 2023-07-20 19:57:45 +03:00
Bogdan
37a4e7c228 Rename decisions to releases in Search Controller 2023-07-20 19:25:04 +03:00
Bogdan
1a66d23bfe Fixed: (UI) Improved mobile search form and show indexer flags 2023-07-20 19:24:00 +03:00
Bogdan
a26aa4bd1e New: (UI) Show indexer id as hint in IndexerSelect 2023-07-20 18:46:12 +03:00
Bogdan
a5d83459e9 New: (BeyondHD) Add pagination support 2023-07-20 15:54:05 +03:00
Mark McDowall
4bfaab4b21 Typings cleanup and improvements
(cherry picked from commit b2c43fb2a67965d68d3d35b72302b0cddb5aca23)
2023-07-20 14:40:21 +03:00
Bogdan
5764950b10 Show implementation name in Application Modal's header 2023-07-20 14:40:21 +03:00
Mark McDowall
470b57316a Add type number to value prop in HintedSelectInputSelectedValue
(cherry picked from commit fea66cb7bccc7e94523614db38b157fa38f55ea5)
2023-07-20 14:40:21 +03:00
Bogdan
f546b9a3b0 Fixed: (SubsPlease) Update indexer urls 2023-07-20 02:45:07 +03:00
Bogdan
cc28c90e39 Combine cleanse rules for passkey and rsskey 2023-07-20 02:20:55 +03:00
Mark McDowall
6e21e892bc Fix chunk IDs and source map file names
(cherry picked from commit bb8fed94eb2c44040031643e8c20ff72de759535)
2023-07-20 01:03:16 +03:00
Weblate
62d868f0e9 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translation: Servarr/Prowlarr
2023-07-19 15:08:48 +03:00
Bogdan
27b36fe501 Tidy up input fields on mobile in Add Indexer Modal 2023-07-19 05:35:26 +03:00
Mark McDowall
fc80efd15f Fixed: List jump bar click issues
(cherry picked from commit 9c7378625112088d022239fdbdb90c0dc941d61d)
2023-07-18 23:37:26 +03:00
Bogdan
9b75ba6ca0 New: (BeyondHD) Add internal indexer flag 2023-07-18 04:47:05 +03:00
Bogdan
d42649c4df New: (BeyondHD) Add limited, refund and rewind search options 2023-07-18 04:47:05 +03:00
Bogdan
53adfb750c New: (Shazbat) Add scene indexer flag to all releases 2023-07-18 04:19:41 +03:00
Bogdan
ac487f9b40 Fixed: (BeyondHD) Add search by freeleech only 2023-07-17 20:25:20 +03:00
Bogdan
6dd354bf1a Fixed: (BeyondHD) Searching ImdbId has priority over TmdbId. 2023-07-17 19:40:14 +03:00
Weblate
b747d0a321 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Godwhitelight <godwhitelight1@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Qstick <qstick@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ar/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/bg/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/da/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/id/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/is/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/it/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ja/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nb_NO/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ro/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ru/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/sk/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/sv/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/th/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/uk/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_TW/
Translation: Servarr/Prowlarr
2023-07-17 06:57:53 +03:00
Weblate
0e6cec6f54 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Godwhitelight <godwhitelight1@gmail.com>
Co-authored-by: Guy Porat <guyporatmail@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/he/
Translation: Servarr/Prowlarr
2023-07-16 22:54:31 +03:00
Weblate
65cf7c1009 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/
Translation: Servarr/Prowlarr
2023-07-16 19:42:54 +03:00
Weblate
5f9c3585f4 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/
Translation: Servarr/Prowlarr
2023-07-16 19:42:19 +03:00
Weblate
a9d1d4be90 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/
Translation: Servarr/Prowlarr
2023-07-16 19:41:26 +03:00
Weblate
a94ed11b21 Translations update from Servarr Weblate
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>
Co-authored-by: victor22265 <843427709@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/el/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2023-07-16 11:32:06 -05:00
Bogdan
3fab8fb0db Fixed: (Search) Ensure TvMazeId is parsed correctly on a repeat search 2023-07-16 17:04:08 +03:00
Mark McDowall
5e52627799 Fixed: Ensure translations are fetched before loading app
(cherry picked from commit ad2721dc55f3233e4c299babe5744418bc530418)
2023-07-16 08:29:30 +03:00
Bogdan
b9a28f243e Bump version to 1.7.3 2023-07-16 08:14:24 +03:00
Bogdan
146e7ca7b6 Use HelpTexts for sync levels in applications 2023-07-16 07:03:58 +03:00
Bogdan
1488fb7570 Revert "Fixed: Ensure translations are fetched before loading app"
This reverts commit 0fc52ae16f.
2023-07-16 04:07:54 +03:00
Mark McDowall
0fc52ae16f Fixed: Ensure translations are fetched before loading app
(cherry picked from commit ad2721dc55f3233e4c299babe5744418bc530418)
2023-07-16 02:21:39 +03:00
Mark McDowall
5218bea705 Use named keys for apply tags help text
(cherry picked from commit c1f8c7b17ba5775a0f6f76cebc3173e03124d000)
2023-07-16 02:11:34 +03:00
Bogdan
ac33330c7c Fix selection input in QueryParameterModal 2023-07-15 18:20:59 +03:00
Bogdan
041a7c571f Translate url type for indexer description 2023-07-15 18:00:52 +03:00
Bogdan
5d73c6aa91 Update webpack, eslint and core-js 2023-07-15 17:34:54 +03:00
Bogdan
ef9a3a4f2a Fixed: (AvistaZ) Allow search by episode 2023-07-15 17:34:28 +03:00
Bogdan
3ce3f8acdd Fixed: (Apps) Lower the severity for common messages 2023-07-13 06:21:05 +03:00
Bogdan
9bac2992b5 Fixed: (UI) Show available indexers count in Add Indexer 2023-07-13 00:37:57 +03:00
Bogdan
4a88b70f40 Show the correct total of releases when selecting 2023-07-13 00:30:12 +03:00
Bogdan
c9b1d0d958 Fixed: (API) Prevent search failed exception when using non-interactive search 2023-07-12 04:40:17 +03:00
Bogdan
a5b5e7a3a5 Fixed: (UI) Prevent passing NaN values to search API when using invalid ids 2023-07-12 04:34:30 +03:00
Bogdan
376202e2af Fixed: (BTN) Prevent NullRef when Result.Torrents is null 2023-07-11 04:53:43 +03:00
Servarr
6b698b33be Automated API Docs update [skip ci] 2023-07-10 19:23:30 +03:00
Bogdan
1706728230 New: Bulk Manage Applications, Download Clients
Co-authored-by: Qstick <qstick@gmail.com>
2023-07-10 19:17:46 +03:00
Bogdan
cb520b2264 Bump version to 1.7.2 2023-07-09 14:59:01 +03:00
581 changed files with 13451 additions and 6175 deletions

View File

@@ -1,5 +1,5 @@
name: Bug Report
description: 'Report a new bug, if you are not 100% certain this is a bug please go to our Reddit or Discord first'
description: 'Report a new bug, if you are not 100% certain this is a bug please go to our Discord first'
labels: ['Type: Bug', 'Status: Needs Triage']
body:
- type: checkboxes

View File

@@ -6,6 +6,3 @@ contact_links:
- name: Support via Discord
url: https://prowlarr.com/discord
about: Chat with users and devs on support and setup related topics.
- name: Support via Reddit
url: https://reddit.com/r/prowlarr
about: Discuss and search thru support topics.

View File

@@ -4,8 +4,7 @@
comment: >
:wave: @{issue-author}, we use the issue tracker exclusively
for bug reports and feature requests. However, this issue appears
to be a support request. Please hop over onto our [Discord](https://prowlarr.com/discord)
or [Subreddit](https://reddit.com/r/prowlarr)
to be a support request. Please hop over onto our [Discord](https://prowlarr.com/discord).
close: true
close-reason: 'not planned'

View File

@@ -29,7 +29,6 @@ Prowlarr is an indexer manager/proxy built on the popular \*arr .net/reactjs bas
[![Wiki](https://img.shields.io/badge/servarr-wiki-181717.svg?maxAge=60)](https://wiki.servarr.com/prowlarr)
[![Discord](https://img.shields.io/badge/discord-chat-7289DA.svg?maxAge=60)](https://prowlarr.com/discord)
[![Reddit](https://img.shields.io/badge/reddit-discussion-FF4500.svg?maxAge=60)](https://www.reddit.com/r/Prowlarr)
Note: GitHub Issues are for Bugs and Feature Requests Only

View File

@@ -9,15 +9,15 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '1.7.1'
majorVersion: '1.9.3'
minorVersion: $[counter('minorVersion', 1)]
prowlarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '6.0.408'
dotnetVersion: '6.0.413'
nodeVersion: '16.X'
innoVersion: '6.2.0'
nodeVersion: '16.x'
windowsImage: 'windows-2022'
linuxImage: 'ubuntu-20.04'
macImage: 'macOS-11'
@@ -27,6 +27,10 @@ trigger:
include:
- develop
- master
paths:
exclude:
- .github
- src/Prowlarr.Api.*/openapi.json
pr:
branches:
@@ -34,8 +38,9 @@ pr:
- develop
paths:
exclude:
- .github
- src/NzbDrone.Core/Localization/Core
- src/Prowlarr.API.*/openapi.json
- src/Prowlarr.Api.*/openapi.json
stages:
- stage: Setup
@@ -349,7 +354,7 @@ stages:
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/net6.0
- task: ArchiveFiles@2
displayName: Create FreeBSD Core Core tar
displayName: Create freebsd-x64 tar
inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).freebsd-core-x64.tar.gz'
archiveType: 'tar'
@@ -528,8 +533,8 @@ stages:
testRunTitle: '$(testName) Unit Tests'
failTaskOnFailedTests: true
- job: Unit_LinuxCore_Postgres
displayName: Unit Native LinuxCore with Postgres Database
- job: Unit_LinuxCore_Postgres14
displayName: Unit Native LinuxCore with Postgres14 Database
dependsOn: Prepare
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
variables:
@@ -565,6 +570,7 @@ stages:
-e POSTGRES_PASSWORD=prowlarr \
-e POSTGRES_USER=prowlarr \
-p 5432:5432/tcp \
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
postgres:14
displayName: Start postgres
- bash: |
@@ -577,7 +583,60 @@ stages:
inputs:
testResultsFormat: 'NUnit'
testResultsFiles: '**/TestResult.xml'
testRunTitle: 'LinuxCore Postgres Unit Tests'
testRunTitle: 'LinuxCore Postgres14 Unit Tests'
failTaskOnFailedTests: true
- job: Unit_LinuxCore_Postgres15
displayName: Unit Native LinuxCore with Postgres15 Database
dependsOn: Prepare
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
variables:
pattern: 'Prowlarr.*.linux-core-x64.tar.gz'
artifactName: linux-x64-tests
Prowlarr__Postgres__Host: 'localhost'
Prowlarr__Postgres__Port: '5432'
Prowlarr__Postgres__User: 'prowlarr'
Prowlarr__Postgres__Password: 'prowlarr'
pool:
vmImage: ${{ variables.linuxImage }}
timeoutInMinutes: 10
steps:
- task: UseDotNet@2
displayName: 'Install .net core'
inputs:
version: $(dotnetVersion)
- checkout: none
- task: DownloadPipelineArtifact@2
displayName: Download Test Artifact
inputs:
buildType: 'current'
artifactName: $(artifactName)
targetPath: $(testsFolder)
- bash: find ${TESTSFOLDER} -name "Prowlarr.Test.Dummy" -exec chmod a+x {} \;
displayName: Make Test Dummy Executable
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
- bash: |
docker run -d --name=postgres15 \
-e POSTGRES_PASSWORD=prowlarr \
-e POSTGRES_USER=prowlarr \
-p 5432:5432/tcp \
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
postgres:15
displayName: Start postgres
- bash: |
chmod a+x ${TESTSFOLDER}/test.sh
ls -lR ${TESTSFOLDER}
${TESTSFOLDER}/test.sh Linux Unit Test
displayName: Run Tests
- task: PublishTestResults@2
displayName: Publish Test Results
inputs:
testResultsFormat: 'NUnit'
testResultsFiles: '**/TestResult.xml'
testRunTitle: 'LinuxCore Postgres15 Unit Tests'
failTaskOnFailedTests: true
- stage: Integration
@@ -663,8 +722,8 @@ stages:
failTaskOnFailedTests: true
displayName: Publish Test Results
- job: Integration_LinuxCore_Postgres
displayName: Integration Native LinuxCore with Postgres Database
- job: Integration_LinuxCore_Postgres14
displayName: Integration Native LinuxCore with Postgres14 Database
dependsOn: Prepare
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
variables:
@@ -710,6 +769,7 @@ stages:
-e POSTGRES_PASSWORD=prowlarr \
-e POSTGRES_USER=prowlarr \
-p 5432:5432/tcp \
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
postgres:14
displayName: Start postgres
- bash: |
@@ -720,7 +780,70 @@ stages:
inputs:
testResultsFormat: 'NUnit'
testResultsFiles: '**/TestResult.xml'
testRunTitle: 'Integration LinuxCore Postgres Database Integration Tests'
testRunTitle: 'Integration LinuxCore Postgres14 Database Integration Tests'
failTaskOnFailedTests: true
displayName: Publish Test Results
- job: Integration_LinuxCore_Postgres15
displayName: Integration Native LinuxCore with Postgres Database
dependsOn: Prepare
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
variables:
pattern: 'Prowlarr.*.linux-core-x64.tar.gz'
Prowlarr__Postgres__Host: 'localhost'
Prowlarr__Postgres__Port: '5432'
Prowlarr__Postgres__User: 'prowlarr'
Prowlarr__Postgres__Password: 'prowlarr'
pool:
vmImage: ${{ variables.linuxImage }}
steps:
- task: UseDotNet@2
displayName: 'Install .net core'
inputs:
version: $(dotnetVersion)
- checkout: none
- task: DownloadPipelineArtifact@2
displayName: Download Test Artifact
inputs:
buildType: 'current'
artifactName: 'linux-x64-tests'
targetPath: $(testsFolder)
- task: DownloadPipelineArtifact@2
displayName: Download Build Artifact
inputs:
buildType: 'current'
artifactName: Packages
itemPattern: '**/$(pattern)'
targetPath: $(Build.ArtifactStagingDirectory)
- task: ExtractFiles@1
inputs:
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
displayName: Extract Package
- bash: |
mkdir -p ./bin/
cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Prowlarr/. ./bin/
displayName: Move Package Contents
- bash: |
docker run -d --name=postgres15 \
-e POSTGRES_PASSWORD=prowlarr \
-e POSTGRES_USER=prowlarr \
-p 5432:5432/tcp \
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
postgres:15
displayName: Start postgres
- bash: |
chmod a+x ${TESTSFOLDER}/test.sh
${TESTSFOLDER}/test.sh Linux Integration Test
displayName: Run Integration Tests
- task: PublishTestResults@2
inputs:
testResultsFormat: 'NUnit'
testResultsFiles: '**/TestResult.xml'
testRunTitle: 'Integration LinuxCore Postgres15 Database Integration Tests'
failTaskOnFailedTests: true
displayName: Publish Test Results
@@ -1003,7 +1126,7 @@ stages:
git add .
if git status | grep -q modified
then
git commit -am 'Automated API Docs update [skip ci]'
git commit -am 'Automated API Docs update'
git push -f --set-upstream origin api-docs
curl -X POST -H "Authorization: token ${GITHUBTOKEN}" -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/prowlarr/prowlarr/pulls -d '{"head":"api-docs","base":"develop","title":"Update API docs"}'
else

View File

@@ -392,22 +392,21 @@ then
fi
fi
if [ "$FRONTEND" = "YES" ];
if [[ "$LINT" = "YES" || "$FRONTEND" = "YES" ]];
then
YarnInstall
RunWebpack
fi
if [ "$LINT" = "YES" ];
then
if [ -z "$FRONTEND" ];
then
YarnInstall
fi
LintUI
fi
if [ "$FRONTEND" = "YES" ];
then
RunWebpack
fi
if [ "$PACKAGES" = "YES" ];
then
UpdateVersionNumber

View File

@@ -26,7 +26,8 @@ module.exports = {
globals: {
expect: false,
chai: false,
sinon: false
sinon: false,
JSX: true
},
parserOptions: {

View File

@@ -4,14 +4,14 @@ module.exports = {
plugins: [
// Stage 1
'@babel/plugin-proposal-export-default-from',
['@babel/plugin-proposal-optional-chaining', { loose }],
['@babel/plugin-proposal-nullish-coalescing-operator', { loose }],
['@babel/plugin-transform-optional-chaining', { loose }],
['@babel/plugin-transform-nullish-coalescing-operator', { loose }],
// Stage 2
'@babel/plugin-proposal-export-namespace-from',
'@babel/plugin-transform-export-namespace-from',
// Stage 3
['@babel/plugin-proposal-class-properties', { loose }],
['@babel/plugin-transform-class-properties', { loose }],
'@babel/plugin-syntax-dynamic-import'
],
env: {

View File

@@ -35,7 +35,7 @@ module.exports = (env) => {
},
entry: {
index: 'index.js'
index: 'index.ts'
},
resolve: {
@@ -65,23 +65,23 @@ module.exports = (env) => {
output: {
path: distFolder,
publicPath: '/',
filename: '[name].js',
filename: '[name]-[contenthash].js',
sourceMapFilename: '[file].map'
},
optimization: {
moduleIds: 'deterministic',
chunkIds: 'named',
splitChunks: {
chunks: 'initial',
name: 'vendors'
}
chunkIds: isProduction ? 'deterministic' : 'named'
},
performance: {
hints: false
},
experiments: {
topLevelAwait: true
},
plugins: [
new webpack.DefinePlugin({
__DEV__: !isProduction,
@@ -89,13 +89,15 @@ module.exports = (env) => {
}),
new MiniCssExtractPlugin({
filename: 'Content/styles.css'
filename: 'Content/styles.css',
chunkFilename: 'Content/[id]-[chunkhash].css'
}),
new HtmlWebpackPlugin({
template: 'frontend/src/index.ejs',
filename: 'index.html',
publicPath: '/'
publicPath: '/',
inject: false
}),
new FileManagerPlugin({

View File

@@ -5,9 +5,9 @@ import NotFound from 'Components/NotFound';
import Switch from 'Components/Router/Switch';
import HistoryConnector from 'History/HistoryConnector';
import IndexerIndex from 'Indexer/Index/IndexerIndex';
import StatsConnector from 'Indexer/Stats/StatsConnector';
import IndexerStats from 'Indexer/Stats/IndexerStats';
import SearchIndexConnector from 'Search/SearchIndexConnector';
import ApplicationSettingsConnector from 'Settings/Applications/ApplicationSettingsConnector';
import ApplicationSettings from 'Settings/Applications/ApplicationSettings';
import DevelopmentSettingsConnector from 'Settings/Development/DevelopmentSettingsConnector';
import DownloadClientSettingsConnector from 'Settings/DownloadClients/DownloadClientSettingsConnector';
import GeneralSettingsConnector from 'Settings/General/GeneralSettingsConnector';
@@ -60,7 +60,7 @@ function AppRoutes(props) {
<Route
path="/indexers/stats"
component={StatsConnector}
component={IndexerStats}
/>
{/*
@@ -98,7 +98,7 @@ function AppRoutes(props) {
<Route
path="/settings/applications"
component={ApplicationSettingsConnector}
component={ApplicationSettings}
/>
<Route

View File

@@ -1,6 +1,7 @@
.version {
margin: 0 3px;
font-weight: bold;
font-family: var(--defaultFontFamily);
}
.maintenance {

View File

@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import Button from 'Components/Link/Button';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
@@ -64,12 +65,12 @@ function AppUpdatedModalContent(props) {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Prowlarr Updated
{translate('AppUpdated', { appName: 'Prowlarr' })}
</ModalHeader>
<ModalBody>
<div>
Version <span className={styles.version}>{version}</span> of Prowlarr has been installed, in order to get the latest changes you'll need to reload Prowlarr.
<InlineMarkdown data={translate('AppUpdatedVersion', { appName: 'Prowlarr', version })} blockClassName={styles.version} />
</div>
{
@@ -77,16 +78,14 @@ function AppUpdatedModalContent(props) {
<div>
{
!update.changes &&
<div className={styles.maintenance}>
{translate('MaintenanceRelease')}
</div>
<div className={styles.maintenance}>{translate('MaintenanceRelease')}</div>
}
{
!!update.changes &&
<div>
<div className={styles.changes}>
What's new?
{translate('WhatsNew')}
</div>
<UpdateChanges
@@ -113,14 +112,14 @@ function AppUpdatedModalContent(props) {
<Button
onPress={onSeeChangesPress}
>
Recent Changes
{translate('RecentChanges')}
</Button>
<Button
kind={kinds.PRIMARY}
onPress={onModalClose}
>
Reload
{translate('Reload')}
</Button>
</ModalFooter>
</ModalContent>

View File

@@ -28,11 +28,11 @@ function ConnectionLostModal(props) {
<ModalBody>
<div>
{translate('ConnectionLostMessage')}
{translate('ConnectionLostToBackend', { appName: 'Prowlarr' })}
</div>
<div className={styles.automatic}>
{translate('ConnectionLostAutomaticMessage')}
{translate('ConnectionLostReconnect', { appName: 'Prowlarr' })}
</div>
</ModalBody>
<ModalFooter>

View File

@@ -0,0 +1,48 @@
import SortDirection from 'Helpers/Props/SortDirection';
export interface Error {
responseJSON: {
message: string;
};
}
export interface AppSectionDeleteState {
isDeleting: boolean;
deleteError: Error;
}
export interface AppSectionSaveState {
isSaving: boolean;
saveError: Error;
}
export interface PagedAppSectionState {
pageSize: number;
}
export interface AppSectionSchemaState<T> {
isSchemaFetching: boolean;
isSchemaPopulated: boolean;
schemaError: Error;
schema: {
items: T[];
};
}
export interface AppSectionItemState<T> {
isFetching: boolean;
isPopulated: boolean;
error: Error;
item: T;
}
interface AppSectionState<T> {
isFetching: boolean;
isPopulated: boolean;
error: Error;
items: T[];
sortKey: string;
sortDirection: SortDirection;
}
export default AppSectionState;

View File

@@ -0,0 +1,58 @@
import CommandAppState from './CommandAppState';
import HistoryAppState from './HistoryAppState';
import IndexerAppState, {
IndexerHistoryAppState,
IndexerIndexAppState,
IndexerStatusAppState,
} from './IndexerAppState';
import IndexerStatsAppState from './IndexerStatsAppState';
import SettingsAppState from './SettingsAppState';
import SystemAppState from './SystemAppState';
import TagsAppState from './TagsAppState';
interface FilterBuilderPropOption {
id: string;
name: string;
}
export interface FilterBuilderProp<T> {
name: string;
label: string;
type: string;
valueType?: string;
optionsSelector?: (items: T[]) => FilterBuilderPropOption[];
}
export interface PropertyFilter {
key: string;
value: boolean | string | number | string[] | number[];
type: string;
}
export interface Filter {
key: string;
label: string;
filers: PropertyFilter[];
}
export interface CustomFilter {
id: number;
type: string;
label: string;
filers: PropertyFilter[];
}
interface AppState {
commands: CommandAppState;
history: HistoryAppState;
indexerHistory: IndexerHistoryAppState;
indexerIndex: IndexerIndexAppState;
indexerStats: IndexerStatsAppState;
indexerStatus: IndexerStatusAppState;
indexers: IndexerAppState;
settings: SettingsAppState;
system: SystemAppState;
tags: TagsAppState;
}
export default AppState;

View File

@@ -0,0 +1,8 @@
import { CustomFilter } from './AppState';
interface ClientSideCollectionAppState {
totalItems: number;
customFilters: CustomFilter[];
}
export default ClientSideCollectionAppState;

View File

@@ -0,0 +1,6 @@
import AppSectionState from 'App/State/AppSectionState';
import Command from 'Commands/Command';
export type CommandAppState = AppSectionState<Command>;
export default CommandAppState;

View File

@@ -0,0 +1,10 @@
import AppSectionState from 'App/State/AppSectionState';
import Column from 'Components/Table/Column';
import History from 'typings/History';
interface HistoryAppState extends AppSectionState<History> {
pageSize: number;
columns: Column[];
}
export default HistoryAppState;

View File

@@ -0,0 +1,40 @@
import Column from 'Components/Table/Column';
import SortDirection from 'Helpers/Props/SortDirection';
import Indexer, { IndexerStatus } from 'Indexer/Indexer';
import History from 'typings/History';
import AppSectionState, {
AppSectionDeleteState,
AppSectionSaveState,
} from './AppSectionState';
import { Filter, FilterBuilderProp } from './AppState';
export interface IndexerIndexAppState {
isTestingAll: boolean;
sortKey: string;
sortDirection: SortDirection;
secondarySortKey: string;
secondarySortDirection: SortDirection;
view: string;
tableOptions: {
showSearchAction: boolean;
};
selectedFilterKey: string;
filterBuilderProps: FilterBuilderProp<Indexer>[];
filters: Filter[];
columns: Column[];
}
interface IndexerAppState
extends AppSectionState<Indexer>,
AppSectionDeleteState,
AppSectionSaveState {
itemMap: Record<number, number>;
}
export type IndexerStatusAppState = AppSectionState<IndexerStatus>;
export type IndexerHistoryAppState = AppSectionState<History>;
export default IndexerAppState;

View File

@@ -0,0 +1,13 @@
import { AppSectionItemState } from 'App/State/AppSectionState';
import { Filter, FilterBuilderProp } from 'App/State/AppState';
import Indexer from 'Indexer/Indexer';
import { IndexerStats } from 'typings/IndexerStats';
export interface IndexerStatsAppState
extends AppSectionItemState<IndexerStats> {
filterBuilderProps: FilterBuilderProp<Indexer>[];
selectedFilterKey: string;
filters: Filter[];
}
export default IndexerStatsAppState;

View File

@@ -0,0 +1,10 @@
import AppSectionState, {
AppSectionDeleteState,
} from 'App/State/AppSectionState';
import Release from 'typings/Release';
interface ReleaseAppState
extends AppSectionState<Release>,
AppSectionDeleteState {}
export default ReleaseAppState;

View File

@@ -0,0 +1,42 @@
import AppSectionState, {
AppSectionDeleteState,
AppSectionItemState,
AppSectionSaveState,
} from 'App/State/AppSectionState';
import Application from 'typings/Application';
import DownloadClient from 'typings/DownloadClient';
import Notification from 'typings/Notification';
import { UiSettings } from 'typings/UiSettings';
export interface AppProfileAppState
extends AppSectionState<Application>,
AppSectionDeleteState,
AppSectionSaveState {}
export interface ApplicationAppState
extends AppSectionState<Application>,
AppSectionDeleteState,
AppSectionSaveState {
isTestingAll: boolean;
}
export interface DownloadClientAppState
extends AppSectionState<DownloadClient>,
AppSectionDeleteState,
AppSectionSaveState {}
export interface NotificationAppState
extends AppSectionState<Notification>,
AppSectionDeleteState {}
export type UiSettingsAppState = AppSectionItemState<UiSettings>;
interface SettingsAppState {
appProfiles: AppProfileAppState;
applications: ApplicationAppState;
downloadClients: DownloadClientAppState;
notifications: NotificationAppState;
ui: UiSettingsAppState;
}
export default SettingsAppState;

View File

@@ -0,0 +1,10 @@
import SystemStatus from 'typings/SystemStatus';
import { AppSectionItemState } from './AppSectionState';
export type SystemStatusAppState = AppSectionItemState<SystemStatus>;
interface SystemAppState {
status: SystemStatusAppState;
}
export default SystemAppState;

View File

@@ -0,0 +1,28 @@
import ModelBase from 'App/ModelBase';
import AppSectionState, {
AppSectionDeleteState,
AppSectionSaveState,
} from 'App/State/AppSectionState';
export interface Tag extends ModelBase {
label: string;
}
export interface TagDetail extends ModelBase {
label: string;
applicationIds: number[];
indexerIds: number[];
indexerProxyIds: number[];
notificationIds: number[];
}
export interface TagDetailAppState
extends AppSectionState<TagDetail>,
AppSectionDeleteState,
AppSectionSaveState {}
interface TagsAppState extends AppSectionState<Tag>, AppSectionDeleteState {
details: TagDetailAppState;
}
export default TagsAppState;

View File

@@ -0,0 +1,37 @@
import ModelBase from 'App/ModelBase';
export interface CommandBody {
sendUpdatesToClient: boolean;
updateScheduledTask: boolean;
completionMessage: string;
requiresDiskAccess: boolean;
isExclusive: boolean;
isLongRunning: boolean;
name: string;
lastExecutionTime: string;
lastStartTime: string;
trigger: string;
suppressMessages: boolean;
seriesId?: number;
}
interface Command extends ModelBase {
name: string;
commandName: string;
message: string;
body: CommandBody;
priority: string;
status: string;
result: string;
queued: string;
started: string;
ended: string;
duration: string;
trigger: string;
stateChangeTime: string;
sendUpdatesToClient: boolean;
updateScheduledTask: boolean;
lastExecutionTime: string;
}
export default Command;

View File

@@ -2,6 +2,7 @@ import Chart from 'chart.js/auto';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { kinds } from 'Helpers/Props';
import { defaultFontFamily } from 'Styles/Variables/fonts';
function getColors(kind) {
@@ -39,7 +40,15 @@ class BarChart extends Component {
plugins: {
title: {
display: true,
text: this.props.title
align: 'start',
text: this.props.title,
padding: {
bottom: 30
},
font: {
size: 14,
family: defaultFontFamily
}
},
legend: {
display: this.props.legend

View File

@@ -1,6 +1,7 @@
import Chart from 'chart.js/auto';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { defaultFontFamily } from 'Styles/Variables/fonts';
function getColors(kind) {
@@ -22,7 +23,15 @@ class DoughnutChart extends Component {
plugins: {
title: {
display: true,
text: this.props.title
align: 'start',
text: this.props.title,
padding: {
bottom: 30
},
font: {
size: 14,
family: defaultFontFamily
}
},
legend: {
position: 'bottom'

View File

@@ -1,6 +1,7 @@
import Chart from 'chart.js/auto';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { defaultFontFamily } from 'Styles/Variables/fonts';
function getColors(index) {
@@ -36,7 +37,15 @@ class StackedBarChart extends Component {
plugins: {
title: {
display: true,
text: this.props.title
align: 'start',
text: this.props.title,
padding: {
bottom: 30
},
font: {
size: 14,
family: defaultFontFamily
}
}
}
},

View File

@@ -23,7 +23,9 @@ function ErrorBoundaryError(props: ErrorBoundaryErrorProps) {
info,
} = props;
const [detailedError, setDetailedError] = useState(null);
const [detailedError, setDetailedError] = useState<
StackTrace.StackFrame[] | null
>(null);
useEffect(() => {
if (error) {

View File

@@ -20,12 +20,12 @@ import styles from './FileBrowserModalContent.css';
const columns = [
{
name: 'type',
label: translate('Type'),
label: () => translate('Type'),
isVisible: true
},
{
name: 'name',
label: translate('Name'),
label: () => translate('Name'),
isVisible: true
}
];

View File

@@ -198,11 +198,13 @@ class FilterBuilderRow extends Component {
const selectedFilterBuilderProp = this.selectedFilterBuilderProp;
const keyOptions = filterBuilderProps.map((availablePropFilter) => {
const { name, label } = availablePropFilter;
return {
key: availablePropFilter.name,
value: availablePropFilter.label
key: name,
value: typeof label === 'function' ? label() : label
};
});
}).sort((a, b) => a.value.localeCompare(b.value));
const ValueComponent = getRowValueConnector(selectedFilterBuilderProp);

View File

@@ -9,13 +9,13 @@ import FilterBuilderRowValue from './FilterBuilderRowValue';
function createMapStateToProps() {
return createSelector(
(state) => state.indexers,
(qualityProfiles) => {
(indexers) => {
const {
isFetching,
isPopulated,
error,
items
} = qualityProfiles;
} = indexers;
const tagList = items.map((item) => {
return {

View File

@@ -3,9 +3,24 @@ import translate from 'Utilities/String/translate';
import FilterBuilderRowValue from './FilterBuilderRowValue';
const privacyTypes = [
{ id: 'public', name: translate('Public') },
{ id: 'private', name: translate('Private') },
{ id: 'semiPrivate', name: translate('SemiPrivate') }
{
id: 'public',
get name() {
return translate('Public');
}
},
{
id: 'private',
get name() {
return translate('Private');
}
},
{
id: 'semiPrivate',
get name() {
return translate('SemiPrivate');
}
}
];
function PrivacyFilterBuilderRowValue(props) {

View File

@@ -9,6 +9,10 @@
&:hover {
background-color: var(--inputHoverBackgroundColor);
}
&.isDisabled {
cursor: not-allowed;
}
}
.optionCheck {

View File

@@ -270,6 +270,7 @@ FormInputGroup.propTypes = {
helpTexts: PropTypes.arrayOf(PropTypes.string),
helpTextWarning: PropTypes.string,
helpLink: PropTypes.string,
autoFocus: PropTypes.bool,
includeNoChange: PropTypes.bool,
includeNoChangeDisabled: PropTypes.bool,
selectedValueOptions: PropTypes.object,

View File

@@ -33,11 +33,11 @@ function HintedSelectInputOption(props) {
isMobile && styles.isMobile
)}
>
<div>{value}</div>
<div>{typeof value === 'function' ? value() : value}</div>
{
hint != null &&
<div className={styles.hintText}>
<div className={styles.hintText} title={hint}>
{hint}
</div>
}
@@ -48,7 +48,7 @@ function HintedSelectInputOption(props) {
HintedSelectInputOption.propTypes = {
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
value: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
hint: PropTypes.node,
depth: PropTypes.number,
isSelected: PropTypes.bool.isRequired,

View File

@@ -24,7 +24,7 @@ function HintedSelectInputSelectedValue(props) {
>
<div className={styles.valueText}>
{
isMultiSelect &&
isMultiSelect ?
value.map((key, index) => {
const v = valuesMap[key];
return (
@@ -32,26 +32,28 @@ function HintedSelectInputSelectedValue(props) {
{v ? v.value : key}
</Label>
);
})
}) :
null
}
{
!isMultiSelect && value
isMultiSelect ? null : value
}
</div>
{
hint != null && includeHint &&
hint != null && includeHint ?
<div className={styles.hintText}>
{hint}
</div>
</div> :
null
}
</EnhancedSelectInputSelectedValue>
);
}
HintedSelectInputSelectedValue.propTypes = {
value: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number]))]).isRequired,
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number]))]).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
hint: PropTypes.string,
isMultiSelect: PropTypes.bool.isRequired,

View File

@@ -1,18 +1,20 @@
import _ from 'lodash';
import { groupBy, map } from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
import sortByName from 'Utilities/Array/sortByName';
import titleCase from 'Utilities/String/titleCase';
import EnhancedSelectInput from './EnhancedSelectInput';
function createMapStateToProps() {
return createSelector(
(state, { value }) => value,
(state) => state.indexers,
createSortedSectionSelector('indexers', sortByName),
(value, indexers) => {
const values = [];
const groupedIndexers = _.map(_.groupBy(indexers.items, 'protocol'), (val, key) => ({ protocol: key, indexers: val }));
const groupedIndexers = map(groupBy(indexers.items, 'protocol'), (val, key) => ({ protocol: key, indexers: val }));
groupedIndexers.forEach((element) => {
values.push({
@@ -25,6 +27,7 @@ function createMapStateToProps() {
values.push({
key: indexer.id,
value: indexer.name,
hint: `(${indexer.id})`,
isDisabled: !indexer.enable,
parentKey: element.protocol === 'usenet' ? -1 : -2
});
@@ -50,7 +53,6 @@ class IndexersSelectInputConnector extends Component {
// Render
render() {
return (
<EnhancedSelectInput
{...this.props}

View File

@@ -10,7 +10,7 @@ function parseValue(props, value) {
} = props;
if (value == null || value === '') {
return min;
return null;
}
let newValue = isFloat ? parseFloat(value) : parseInt(value);
@@ -41,7 +41,7 @@ class NumberInput extends Component {
componentDidUpdate(prevProps, prevState) {
const { value } = this.props;
if (value !== prevProps.value && !this.state.isFocused) {
if (!isNaN(value) && value !== prevProps.value && !this.state.isFocused) {
this.setState({
value: value == null ? '' : value.toString()
});

View File

@@ -68,6 +68,7 @@ class PathInputConnector extends Component {
}
PathInputConnector.propTypes = {
...PathInput.props,
includeFiles: PropTypes.bool.isRequired,
dispatchFetchPaths: PropTypes.func.isRequired,
dispatchClearPaths: PropTypes.func.isRequired

View File

@@ -61,7 +61,7 @@ class SelectInput extends Component {
value={key}
{...otherOptionProps}
>
{optionValue}
{typeof optionValue === 'function' ? optionValue() : optionValue}
</option>
);
})
@@ -75,7 +75,7 @@ SelectInput.propTypes = {
className: PropTypes.string,
disabledClassName: PropTypes.string,
name: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.func]).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
isDisabled: PropTypes.bool,
hasError: PropTypes.bool,

View File

@@ -41,7 +41,7 @@ class Icon extends PureComponent {
return (
<span
className={containerClassName}
title={title}
title={typeof title === 'function' ? title() : title}
>
{icon}
</span>
@@ -58,7 +58,7 @@ Icon.propTypes = {
name: PropTypes.object.isRequired,
kind: PropTypes.string.isRequired,
size: PropTypes.number.isRequired,
title: PropTypes.string,
title: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
isSpinning: PropTypes.bool.isRequired,
fixedWidth: PropTypes.bool.isRequired
};

View File

@@ -1,98 +0,0 @@
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { Link as RouterLink } from 'react-router-dom';
import styles from './Link.css';
class Link extends Component {
//
// Listeners
onClick = (event) => {
const {
isDisabled,
onPress
} = this.props;
if (!isDisabled && onPress) {
onPress(event);
}
};
//
// Render
render() {
const {
className,
component,
to,
target,
isDisabled,
noRouter,
onPress,
...otherProps
} = this.props;
const linkProps = { target };
let el = component;
if (to) {
if ((/\w+?:\/\//).test(to)) {
el = 'a';
linkProps.href = to;
linkProps.target = target || '_blank';
linkProps.rel = 'noreferrer';
} else if (noRouter) {
el = 'a';
linkProps.href = to;
linkProps.target = target || '_self';
} else {
el = RouterLink;
linkProps.to = `${window.Prowlarr.urlBase}/${to.replace(/^\//, '')}`;
linkProps.target = target;
}
}
if (el === 'button' || el === 'input') {
linkProps.type = otherProps.type || 'button';
linkProps.disabled = isDisabled;
}
linkProps.className = classNames(
className,
styles.link,
to && styles.to,
isDisabled && 'isDisabled'
);
const props = {
...otherProps,
...linkProps
};
props.onClick = this.onClick;
return (
React.createElement(el, props)
);
}
}
Link.propTypes = {
className: PropTypes.string,
component: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
to: PropTypes.string,
target: PropTypes.string,
isDisabled: PropTypes.bool,
noRouter: PropTypes.bool,
onPress: PropTypes.func
};
Link.defaultProps = {
component: 'button',
noRouter: false
};
export default Link;

View File

@@ -0,0 +1,96 @@
import classNames from 'classnames';
import React, {
ComponentClass,
FunctionComponent,
SyntheticEvent,
useCallback,
} from 'react';
import { Link as RouterLink } from 'react-router-dom';
import styles from './Link.css';
interface ReactRouterLinkProps {
to?: string;
}
export interface LinkProps extends React.HTMLProps<HTMLAnchorElement> {
className?: string;
component?:
| string
| FunctionComponent<LinkProps>
| ComponentClass<LinkProps, unknown>;
to?: string;
target?: string;
isDisabled?: boolean;
noRouter?: boolean;
onPress?(event: SyntheticEvent): void;
}
function Link(props: LinkProps) {
const {
className,
component = 'button',
to,
target,
type,
isDisabled,
noRouter = false,
onPress,
...otherProps
} = props;
const onClick = useCallback(
(event: SyntheticEvent) => {
if (!isDisabled && onPress) {
onPress(event);
}
},
[isDisabled, onPress]
);
const linkProps: React.HTMLProps<HTMLAnchorElement> & ReactRouterLinkProps = {
target,
};
let el = component;
if (to) {
if (/\w+?:\/\//.test(to)) {
el = 'a';
linkProps.href = to;
linkProps.target = target || '_blank';
linkProps.rel = 'noreferrer';
} else if (noRouter) {
el = 'a';
linkProps.href = to;
linkProps.target = target || '_self';
} else {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
el = RouterLink;
linkProps.to = `${window.Prowlarr.urlBase}/${to.replace(/^\//, '')}`;
linkProps.target = target;
}
}
if (el === 'button' || el === 'input') {
linkProps.type = type || 'button';
linkProps.disabled = isDisabled;
}
linkProps.className = classNames(
className,
styles.link,
to && styles.to,
isDisabled && 'isDisabled'
);
const elementProps = {
...otherProps,
type,
...linkProps,
};
elementProps.onClick = onClick;
return React.createElement(el, elementProps);
}
export default Link;

View File

@@ -97,6 +97,7 @@ class SpinnerErrorButton extends Component {
render() {
const {
kind,
isSpinning,
error,
children,
@@ -112,7 +113,7 @@ class SpinnerErrorButton extends Component {
const showIcon = wasSuccessful || hasWarning || hasError;
let iconName = icons.CHECK;
let iconKind = kinds.SUCCESS;
let iconKind = kind === kinds.PRIMARY ? kinds.DEFAULT : kinds.SUCCESS;
if (hasWarning) {
iconName = icons.WARNING;
@@ -126,6 +127,7 @@ class SpinnerErrorButton extends Component {
return (
<SpinnerButton
kind={kind}
isSpinning={isSpinning}
{...otherProps}
>
@@ -154,6 +156,7 @@ class SpinnerErrorButton extends Component {
}
SpinnerErrorButton.propTypes = {
kind: PropTypes.oneOf(kinds.all),
isSpinning: PropTypes.bool.isRequired,
error: PropTypes.object,
children: PropTypes.node.isRequired

View File

@@ -10,27 +10,55 @@ class InlineMarkdown extends Component {
render() {
const {
className,
data
data,
blockClassName
} = this.props;
// For now only replace links
// For now only replace links or code blocks (not both)
const markdownBlocks = [];
if (data) {
const regex = RegExp(/\[(.+?)\]\((.+?)\)/g);
const linkRegex = RegExp(/\[(.+?)\]\((.+?)\)/g);
let endIndex = 0;
let match = null;
while ((match = regex.exec(data)) !== null) {
while ((match = linkRegex.exec(data)) !== null) {
if (match.index > endIndex) {
markdownBlocks.push(data.substr(endIndex, match.index - endIndex));
}
markdownBlocks.push(<Link key={match.index} to={match[2]}>{match[1]}</Link>);
endIndex = match.index + match[0].length;
}
if (endIndex !== data.length) {
if (endIndex !== data.length && markdownBlocks.length > 0) {
markdownBlocks.push(data.substr(endIndex, data.length - endIndex));
}
const codeRegex = RegExp(/(?=`)`(?!`)[^`]*(?=`)`(?!`)/g);
endIndex = 0;
match = null;
let matchedCode = false;
while ((match = codeRegex.exec(data)) !== null) {
matchedCode = true;
if (match.index > endIndex) {
markdownBlocks.push(data.substr(endIndex, match.index - endIndex));
}
markdownBlocks.push(<code key={`code-${match.index}`} className={blockClassName ?? null}>{match[0].substring(1, match[0].length - 1)}</code>);
endIndex = match.index + match[0].length;
}
if (endIndex !== data.length && markdownBlocks.length > 0 && matchedCode) {
markdownBlocks.push(data.substr(endIndex, data.length - endIndex));
}
if (markdownBlocks.length === 0) {
markdownBlocks.push(data);
}
}
return <span className={className}>{markdownBlocks}</span>;
@@ -39,7 +67,8 @@ class InlineMarkdown extends Component {
InlineMarkdown.propTypes = {
className: PropTypes.string,
data: PropTypes.string
data: PropTypes.string,
blockClassName: PropTypes.string
};
export default InlineMarkdown;

View File

@@ -33,7 +33,7 @@ class FilterMenuContent extends Component {
selectedFilterKey={selectedFilterKey}
onPress={onFilterSelect}
>
{filter.label}
{typeof filter.label === 'function' ? filter.label() : filter.label}
</FilterMenuItem>
);
})

View File

@@ -7,6 +7,7 @@ function ErrorPage(props) {
const {
version,
isLocalStorageSupported,
translationsError,
indexersError,
indexerStatusError,
indexerCategoriesError,
@@ -21,6 +22,8 @@ function ErrorPage(props) {
if (!isLocalStorageSupported) {
errorMessage = 'Local Storage is not supported or disabled. A plugin or private browsing may have disabled it.';
} else if (translationsError) {
errorMessage = getErrorMessage(translationsError, 'Failed to load translations from API');
} else if (indexersError) {
errorMessage = getErrorMessage(indexersError, 'Failed to load indexers from API');
} else if (indexerStatusError) {
@@ -55,6 +58,7 @@ function ErrorPage(props) {
ErrorPage.propTypes = {
version: PropTypes.string.isRequired,
isLocalStorageSupported: PropTypes.bool.isRequired,
translationsError: PropTypes.object,
indexersError: PropTypes.object,
indexerStatusError: PropTypes.object,
indexerCategoriesError: PropTypes.object,

View File

@@ -3,7 +3,7 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import { createSelector } from 'reselect';
import { saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions';
import { fetchTranslations, saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions';
import { fetchCustomFilters } from 'Store/Actions/customFilterActions';
import { fetchIndexers } from 'Store/Actions/indexerActions';
import { fetchIndexerStatus } from 'Store/Actions/indexerStatusActions';
@@ -54,6 +54,7 @@ const selectIsPopulated = createSelector(
(state) => state.indexerStatus.isPopulated,
(state) => state.settings.indexerCategories.isPopulated,
(state) => state.system.status.isPopulated,
(state) => state.app.translations.isPopulated,
(
customFiltersIsPopulated,
tagsIsPopulated,
@@ -63,7 +64,8 @@ const selectIsPopulated = createSelector(
indexersIsPopulated,
indexerStatusIsPopulated,
indexerCategoriesIsPopulated,
systemStatusIsPopulated
systemStatusIsPopulated,
translationsIsPopulated
) => {
return (
customFiltersIsPopulated &&
@@ -74,7 +76,8 @@ const selectIsPopulated = createSelector(
indexersIsPopulated &&
indexerStatusIsPopulated &&
indexerCategoriesIsPopulated &&
systemStatusIsPopulated
systemStatusIsPopulated &&
translationsIsPopulated
);
}
);
@@ -89,6 +92,7 @@ const selectErrors = createSelector(
(state) => state.indexerStatus.error,
(state) => state.settings.indexerCategories.error,
(state) => state.system.status.error,
(state) => state.app.translations.error,
(
customFiltersError,
tagsError,
@@ -98,7 +102,8 @@ const selectErrors = createSelector(
indexersError,
indexerStatusError,
indexerCategoriesError,
systemStatusError
systemStatusError,
translationsError
) => {
const hasError = !!(
customFiltersError ||
@@ -109,7 +114,8 @@ const selectErrors = createSelector(
indexersError ||
indexerStatusError ||
indexerCategoriesError ||
systemStatusError
systemStatusError ||
translationsError
);
return {
@@ -122,7 +128,8 @@ const selectErrors = createSelector(
indexersError,
indexerStatusError,
indexerCategoriesError,
systemStatusError
systemStatusError,
translationsError
};
}
);
@@ -184,6 +191,9 @@ function createMapDispatchToProps(dispatch, props) {
dispatchFetchStatus() {
dispatch(fetchStatus());
},
dispatchFetchTranslations() {
dispatch(fetchTranslations());
},
onResize(dimensions) {
dispatch(saveDimensions(dimensions));
},
@@ -217,6 +227,7 @@ class PageConnector extends Component {
this.props.dispatchFetchUISettings();
this.props.dispatchFetchGeneralSettings();
this.props.dispatchFetchStatus();
this.props.dispatchFetchTranslations();
}
}
@@ -242,6 +253,7 @@ class PageConnector extends Component {
dispatchFetchUISettings,
dispatchFetchGeneralSettings,
dispatchFetchStatus,
dispatchFetchTranslations,
...otherProps
} = this.props;
@@ -282,6 +294,7 @@ PageConnector.propTypes = {
dispatchFetchUISettings: PropTypes.func.isRequired,
dispatchFetchGeneralSettings: PropTypes.func.isRequired,
dispatchFetchStatus: PropTypes.func.isRequired,
dispatchFetchTranslations: PropTypes.func.isRequired,
onSidebarVisibleChange: PropTypes.func.isRequired
};

View File

@@ -1,22 +1,19 @@
import React, { forwardRef, ReactNode, useCallback } from 'react';
import Scroller from 'Components/Scroller/Scroller';
import React, { ForwardedRef, forwardRef, ReactNode, useCallback } from 'react';
import Scroller, { OnScroll } from 'Components/Scroller/Scroller';
import ScrollDirection from 'Helpers/Props/ScrollDirection';
import { isLocked } from 'Utilities/scrollLock';
import styles from './PageContentBody.css';
interface PageContentBodyProps {
className: string;
innerClassName: string;
className?: string;
innerClassName?: string;
children: ReactNode;
initialScrollTop?: number;
onScroll?: (payload) => void;
onScroll?: (payload: OnScroll) => void;
}
const PageContentBody = forwardRef(
(
props: PageContentBodyProps,
ref: React.MutableRefObject<HTMLDivElement>
) => {
(props: PageContentBodyProps, ref: ForwardedRef<HTMLDivElement>) => {
const {
className = styles.contentBody,
innerClassName = styles.innerContentBody,
@@ -26,7 +23,7 @@ const PageContentBody = forwardRef(
} = props;
const onScrollWrapper = useCallback(
(payload) => {
(payload: OnScroll) => {
if (onScroll && !isLocked()) {
onScroll(payload);
}

View File

@@ -1,4 +1,5 @@
.jumpBar {
z-index: $pageJumpBarZIndex;
display: flex;
align-content: stretch;
align-items: stretch;

View File

@@ -20,12 +20,12 @@ const SIDEBAR_WIDTH = parseInt(dimensions.sidebarWidth);
const links = [
{
iconName: icons.MOVIE_CONTINUING,
title: translate('Indexers'),
title: () => translate('Indexers'),
to: '/',
alias: '/indexers',
children: [
{
title: translate('Stats'),
title: () => translate('Stats'),
to: '/indexers/stats'
}
]
@@ -33,47 +33,47 @@ const links = [
{
iconName: icons.SEARCH,
title: translate('Search'),
title: () => translate('Search'),
to: '/search'
},
{
iconName: icons.ACTIVITY,
title: translate('History'),
title: () => translate('History'),
to: '/history'
},
{
iconName: icons.SETTINGS,
title: translate('Settings'),
title: () => translate('Settings'),
to: '/settings',
children: [
{
title: translate('Indexers'),
title: () => translate('Indexers'),
to: '/settings/indexers'
},
{
title: translate('Apps'),
title: () => translate('Apps'),
to: '/settings/applications'
},
{
title: translate('DownloadClients'),
title: () => translate('DownloadClients'),
to: '/settings/downloadclients'
},
{
title: translate('Connect'),
title: () => translate('Connect'),
to: '/settings/connect'
},
{
title: translate('Tags'),
title: () => translate('Tags'),
to: '/settings/tags'
},
{
title: translate('General'),
title: () => translate('General'),
to: '/settings/general'
},
{
title: translate('UI'),
title: () => translate('UI'),
to: '/settings/ui'
}
]
@@ -81,32 +81,32 @@ const links = [
{
iconName: icons.SYSTEM,
title: translate('System'),
title: () => translate('System'),
to: '/system/status',
children: [
{
title: translate('Status'),
title: () => translate('Status'),
to: '/system/status',
statusComponent: HealthStatusConnector
},
{
title: translate('Tasks'),
title: () => translate('Tasks'),
to: '/system/tasks'
},
{
title: translate('Backup'),
title: () => translate('Backup'),
to: '/system/backup'
},
{
title: translate('Updates'),
title: () => translate('Updates'),
to: '/system/updates'
},
{
title: translate('Events'),
title: () => translate('Events'),
to: '/system/events'
},
{
title: translate('LogFiles'),
title: () => translate('LogFiles'),
to: '/system/logs/files'
}
]

View File

@@ -64,7 +64,7 @@ class PageSidebarItem extends Component {
}
<span className={isChildItem ? styles.noIcon : null}>
{title}
{typeof title === 'function' ? title() : title}
</span>
{
@@ -88,7 +88,7 @@ class PageSidebarItem extends Component {
PageSidebarItem.propTypes = {
iconName: PropTypes.object,
title: PropTypes.string.isRequired,
title: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
to: PropTypes.string.isRequired,
isActive: PropTypes.bool,
isActiveParent: PropTypes.bool,

View File

@@ -1,9 +1,21 @@
import classNames from 'classnames';
import { throttle } from 'lodash';
import React, { forwardRef, ReactNode, useEffect, useRef } from 'react';
import React, {
ForwardedRef,
forwardRef,
MutableRefObject,
ReactNode,
useEffect,
useRef,
} from 'react';
import ScrollDirection from 'Helpers/Props/ScrollDirection';
import styles from './Scroller.css';
export interface OnScroll {
scrollLeft: number;
scrollTop: number;
}
interface ScrollerProps {
className?: string;
scrollDirection?: ScrollDirection;
@@ -12,11 +24,11 @@ interface ScrollerProps {
scrollTop?: number;
initialScrollTop?: number;
children?: ReactNode;
onScroll?: (payload) => void;
onScroll?: (payload: OnScroll) => void;
}
const Scroller = forwardRef(
(props: ScrollerProps, ref: React.MutableRefObject<HTMLDivElement>) => {
(props: ScrollerProps, ref: ForwardedRef<HTMLDivElement>) => {
const {
className,
autoFocus = false,
@@ -30,7 +42,7 @@ const Scroller = forwardRef(
} = props;
const internalRef = useRef();
const currentRef = ref ?? internalRef;
const currentRef = (ref as MutableRefObject<HTMLDivElement>) ?? internalRef;
useEffect(
() => {

View File

@@ -1,58 +1,66 @@
import PropTypes from 'prop-types';
import React, { PureComponent } from 'react';
import React from 'react';
import { useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import formatDateTime from 'Utilities/Date/formatDateTime';
import getRelativeDate from 'Utilities/Date/getRelativeDate';
import TableRowCell from './TableRowCell';
import styles from './RelativeDateCell.css';
class RelativeDateCell extends PureComponent {
function createRelativeDateCellSelector() {
return createSelector(createUISettingsSelector(), (uiSettings) => {
return {
showRelativeDates: uiSettings.showRelativeDates,
shortDateFormat: uiSettings.shortDateFormat,
longDateFormat: uiSettings.longDateFormat,
timeFormat: uiSettings.timeFormat
};
});
}
function RelativeDateCell(props) {
//
// Render
render() {
const {
className,
date,
includeSeconds,
showRelativeDates,
shortDateFormat,
longDateFormat,
timeFormat,
component: Component,
dispatch,
...otherProps
} = this.props;
const {
className,
date,
includeSeconds,
component: Component,
dispatch,
...otherProps
} = props;
if (!date) {
return (
<Component
className={className}
{...otherProps}
/>
);
}
const { showRelativeDates, shortDateFormat, longDateFormat, timeFormat } =
useSelector(createRelativeDateCellSelector());
return (
<Component
className={className}
title={formatDateTime(date, longDateFormat, timeFormat, { includeSeconds, includeRelativeDay: !showRelativeDates })}
{...otherProps}
>
{getRelativeDate(date, shortDateFormat, showRelativeDates, { timeFormat, includeSeconds, timeForToday: true })}
</Component>
);
if (!date) {
return <Component className={className} {...otherProps} />;
}
return (
<Component
className={className}
title={formatDateTime(date, longDateFormat, timeFormat, {
includeSeconds,
includeRelativeDay: !showRelativeDates
})}
{...otherProps}
>
{getRelativeDate(date, shortDateFormat, showRelativeDates, {
timeFormat,
includeSeconds,
timeForToday: true
})}
</Component>
);
}
RelativeDateCell.propTypes = {
className: PropTypes.string.isRequired,
date: PropTypes.string,
includeSeconds: PropTypes.bool.isRequired,
showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,
longDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired,
component: PropTypes.elementType,
dispatch: PropTypes.func
};

View File

@@ -1,7 +1,7 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'markAsFailedButton': string;
'cell': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

@@ -1,25 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import Link from 'Components/Link/Link';
import TableRowCell from './TableRowCell';
import styles from './TableRowCellButton.css';
function TableRowCellButton({ className, ...otherProps }) {
return (
<Link
className={className}
component={TableRowCell}
{...otherProps}
/>
);
}
TableRowCellButton.propTypes = {
className: PropTypes.string.isRequired
};
TableRowCellButton.defaultProps = {
className: styles.cell
};
export default TableRowCellButton;

View File

@@ -0,0 +1,19 @@
import React, { ReactNode } from 'react';
import Link, { LinkProps } from 'Components/Link/Link';
import TableRowCell from './TableRowCell';
import styles from './TableRowCellButton.css';
interface TableRowCellButtonProps extends LinkProps {
className?: string;
children: ReactNode;
}
function TableRowCellButton(props: TableRowCellButtonProps) {
const { className = styles.cell, ...otherProps } = props;
return (
<Link className={className} component={TableRowCell} {...otherProps} />
);
}
export default TableRowCellButton;

View File

@@ -1,8 +1,8 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'fullWidthChart': string;
'halfWidthChart': string;
'input': string;
'selectCell': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

@@ -1,8 +1,12 @@
import React from 'react';
type PropertyFunction<T> = () => T;
interface Column {
name: string;
label: string;
columnLabel: string;
isSortable: boolean;
label: string | PropertyFunction<string> | React.ReactNode;
columnLabel?: string;
isSortable?: boolean;
isVisible: boolean;
isModifiable?: boolean;
}

View File

@@ -107,7 +107,7 @@ function Table(props) {
{...getTableHeaderCellProps(otherProps)}
{...column}
>
{column.label}
{typeof column.label === 'function' ? column.label() : column.label}
</TableHeaderCell>
);
})
@@ -121,6 +121,7 @@ function Table(props) {
}
Table.propTypes = {
...TableHeaderCell.props,
className: PropTypes.string,
horizontalScroll: PropTypes.bool.isRequired,
selectAll: PropTypes.bool.isRequired,

View File

@@ -30,6 +30,7 @@ class TableHeaderCell extends Component {
const {
className,
name,
label,
columnLabel,
isSortable,
isVisible,
@@ -53,7 +54,8 @@ class TableHeaderCell extends Component {
{...otherProps}
component="th"
className={className}
title={columnLabel}
label={typeof label === 'function' ? label() : label}
title={typeof columnLabel === 'function' ? columnLabel() : columnLabel}
onPress={this.onPress}
>
{children}
@@ -77,7 +79,8 @@ class TableHeaderCell extends Component {
TableHeaderCell.propTypes = {
className: PropTypes.string,
name: PropTypes.string.isRequired,
columnLabel: PropTypes.string,
label: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.node]),
columnLabel: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
isSortable: PropTypes.bool,
isVisible: PropTypes.bool,
isModifiable: PropTypes.bool,

View File

@@ -35,7 +35,7 @@ function TableOptionsColumn(props) {
isDisabled={isModifiable === false}
onChange={onVisibleChange}
/>
{label}
{typeof label === 'function' ? label() : label}
</label>
{
@@ -56,7 +56,7 @@ function TableOptionsColumn(props) {
TableOptionsColumn.propTypes = {
name: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
label: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
isVisible: PropTypes.bool.isRequired,
isModifiable: PropTypes.bool.isRequired,
index: PropTypes.number.isRequired,

View File

@@ -112,7 +112,7 @@ class TableOptionsColumnDragSource extends Component {
<TableOptionsColumn
name={name}
label={label}
label={typeof label === 'function' ? label() : label}
isVisible={isVisible}
isModifiable={isModifiable}
index={index}
@@ -138,7 +138,7 @@ class TableOptionsColumnDragSource extends Component {
TableOptionsColumnDragSource.propTypes = {
name: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
label: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
isVisible: PropTypes.bool.isRequired,
isModifiable: PropTypes.bool.isRequired,
index: PropTypes.number.isRequired,

View File

@@ -192,7 +192,7 @@ class TableOptionsModal extends Component {
<TableOptionsColumnDragSource
key={name}
name={name}
label={label || columnLabel}
label={columnLabel || label}
isVisible={isVisible}
isModifiable={true}
index={index}
@@ -210,7 +210,7 @@ class TableOptionsModal extends Component {
<TableOptionsColumn
key={name}
name={name}
label={label || columnLabel}
label={columnLabel || label}
isVisible={isVisible}
index={index}
isModifiable={false}

View File

@@ -6,37 +6,51 @@ import translate from 'Utilities/String/translate';
export const shortcuts = {
OPEN_KEYBOARD_SHORTCUTS_MODAL: {
key: '?',
name: translate('OpenThisModal')
get name() {
return translate('OpenThisModal');
}
},
CLOSE_MODAL: {
key: 'Esc',
name: translate('CloseCurrentModal')
get name() {
return translate('CloseCurrentModal');
}
},
ACCEPT_CONFIRM_MODAL: {
key: 'Enter',
name: translate('AcceptConfirmationModal')
get name() {
return translate('AcceptConfirmationModal');
}
},
MOVIE_SEARCH_INPUT: {
key: 's',
name: translate('FocusSearchBox')
get name() {
return translate('FocusSearchBox');
}
},
SAVE_SETTINGS: {
key: 'mod+s',
name: translate('SaveSettings')
get name() {
return translate('SaveSettings');
}
},
SCROLL_TOP: {
key: 'mod+home',
name: translate('MovieIndexScrollTop')
get name() {
return translate('MovieIndexScrollTop');
}
},
SCROLL_BOTTOM: {
key: 'mod+end',
name: translate('MovieIndexScrollBottom')
get name() {
return translate('MovieIndexScrollBottom');
}
}
};

View File

@@ -1,24 +1,30 @@
import PropTypes from 'prop-types';
import React from 'react';
import { RouteComponentProps } from 'react-router-dom';
import scrollPositions from 'Store/scrollPositions';
function withScrollPosition(WrappedComponent, scrollPositionKey) {
function ScrollPosition(props) {
interface WrappedComponentProps {
initialScrollTop: number;
}
interface ScrollPositionProps {
history: RouteComponentProps['history'];
location: RouteComponentProps['location'];
match: RouteComponentProps['match'];
}
function withScrollPosition(
WrappedComponent: React.FC<WrappedComponentProps>,
scrollPositionKey: string
) {
function ScrollPosition(props: ScrollPositionProps) {
const { history } = props;
const initialScrollTop =
history.action === 'POP' ||
(history.location.state && history.location.state.restoreScrollPosition)
? scrollPositions[scrollPositionKey]
: 0;
history.action === 'POP' ? scrollPositions[scrollPositionKey] : 0;
return <WrappedComponent {...props} initialScrollTop={initialScrollTop} />;
}
ScrollPosition.propTypes = {
history: PropTypes.object.isRequired,
};
return ScrollPosition;
}

View File

@@ -11,7 +11,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 { authenticationMethodOptions, authenticationRequiredOptions, authenticationRequiredWarning } from 'Settings/General/SecuritySettings';
import { authenticationMethodOptions, authenticationRequiredOptions } from 'Settings/General/SecuritySettings';
import translate from 'Utilities/String/translate';
import styles from './AuthenticationRequiredModalContent.css';
@@ -63,71 +63,63 @@ function AuthenticationRequiredModalContent(props) {
className={styles.authRequiredAlert}
kind={kinds.WARNING}
>
{authenticationRequiredWarning}
{translate('AuthenticationRequiredWarning', { appName: 'Prowlarr' })}
</Alert>
{
isPopulated && !error ?
<div>
<FormGroup>
<FormLabel>{translate('Authentication')}</FormLabel>
<FormLabel>{translate('AuthenticationMethod')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="authenticationMethod"
values={authenticationMethodOptions}
helpText={translate('AuthenticationMethodHelpText')}
helpText={translate('AuthenticationMethodHelpText', { appName: 'Prowlarr' })}
helpTextWarning={authenticationMethod.value === 'none' ? translate('AuthenticationMethodHelpTextWarning') : undefined}
helpLink="https://wiki.servarr.com/prowlarr/faq#forced-authentication"
onChange={onInputChange}
{...authenticationMethod}
/>
</FormGroup>
{
authenticationEnabled ?
<FormGroup>
<FormLabel>{translate('AuthenticationRequired')}</FormLabel>
<FormGroup>
<FormLabel>{translate('AuthenticationRequired')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="authenticationRequired"
values={authenticationRequiredOptions}
helpText={translate('AuthenticationRequiredHelpText')}
onChange={onInputChange}
{...authenticationRequired}
/>
</FormGroup> :
null
}
<FormInputGroup
type={inputTypes.SELECT}
name="authenticationRequired"
values={authenticationRequiredOptions}
helpText={translate('AuthenticationRequiredHelpText')}
onChange={onInputChange}
{...authenticationRequired}
/>
</FormGroup>
{
authenticationEnabled ?
<FormGroup>
<FormLabel>{translate('Username')}</FormLabel>
<FormGroup>
<FormLabel>{translate('Username')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="username"
onChange={onInputChange}
{...username}
/>
</FormGroup> :
null
}
<FormInputGroup
type={inputTypes.TEXT}
name="username"
onChange={onInputChange}
helpTextWarning={username?.value ? undefined : translate('AuthenticationRequiredUsernameHelpTextWarning')}
{...username}
/>
</FormGroup>
{
authenticationEnabled ?
<FormGroup>
<FormLabel>{translate('Password')}</FormLabel>
<FormGroup>
<FormLabel>{translate('Password')}</FormLabel>
<FormInputGroup
type={inputTypes.PASSWORD}
name="password"
onChange={onInputChange}
{...password}
/>
</FormGroup> :
null
}
<FormInputGroup
type={inputTypes.PASSWORD}
name="password"
onChange={onInputChange}
helpTextWarning={password?.value ? undefined : translate('AuthenticationRequiredPasswordHelpTextWarning')}
{...password}
/>
</FormGroup>
</div> :
null
}

View File

@@ -72,8 +72,10 @@ import {
faLanguage as fasLanguage,
faLaptop as fasLaptop,
faLevelUpAlt as fasLevelUpAlt,
faListCheck as fasListCheck,
faLocationArrow as fasLocationArrow,
faLock as fasLock,
faMagnet as fasMagnet,
faMedkit as fasMedkit,
faMinus as fasMinus,
faMusic as fasMusic,
@@ -180,6 +182,8 @@ export const INTERACTIVE = fasUser;
export const KEYBOARD = farKeyboard;
export const LOCK = fasLock;
export const LOGOUT = fasSignOutAlt;
export const MAGNET = fasMagnet;
export const MANAGE = fasListCheck;
export const MEDIA_INFO = farFileInvoice;
export const MISSING = fasExclamationTriangle;
export const MONITORED = fasBookmark;

View File

@@ -10,6 +10,7 @@ export const INFO = 'info';
export const MOVIE_MONITORED_SELECT = 'movieMonitoredSelect';
export const CATEGORY_SELECT = 'newznabCategorySelect';
export const DOWNLOAD_CLIENT_SELECT = 'downloadClientSelect';
export const FLOAT = 'float';
export const NUMBER = 'number';
export const OAUTH = 'oauth';
export const PASSWORD = 'password';
@@ -35,6 +36,7 @@ export const all = [
INFO,
MOVIE_MONITORED_SELECT,
CATEGORY_SELECT,
FLOAT,
NUMBER,
OAUTH,
PASSWORD,

View File

@@ -1,5 +0,0 @@
.markAsFailedButton {
composes: button from '~Components/Link/Button.css';
margin-right: auto;
}

View File

@@ -1,16 +1,13 @@
import PropTypes from 'prop-types';
import React from 'react';
import Button from 'Components/Link/Button';
import SpinnerButton from 'Components/Link/SpinnerButton';
import Modal from 'Components/Modal/Modal';
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 { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import HistoryDetails from './HistoryDetails';
import styles from './HistoryDetailsModal.css';
function getHeaderTitle(eventType) {
switch (eventType) {
@@ -33,10 +30,8 @@ function HistoryDetailsModal(props) {
eventType,
indexer,
data,
isMarkingAsFailed,
shortDateFormat,
timeFormat,
onMarkAsFailedPress,
onModalClose
} = props;
@@ -61,18 +56,6 @@ function HistoryDetailsModal(props) {
</ModalBody>
<ModalFooter>
{
eventType === 'grabbed' &&
<SpinnerButton
className={styles.markAsFailedButton}
kind={kinds.DANGER}
isSpinning={isMarkingAsFailed}
onPress={onMarkAsFailedPress}
>
Mark as Failed
</SpinnerButton>
}
<Button
onPress={onModalClose}
>
@@ -89,10 +72,8 @@ HistoryDetailsModal.propTypes = {
eventType: PropTypes.string.isRequired,
indexer: PropTypes.object.isRequired,
data: PropTypes.object.isRequired,
isMarkingAsFailed: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired,
onMarkAsFailedPress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

View File

@@ -62,6 +62,7 @@ class HistoryOptions extends Component {
<FormInputGroup
type={inputTypes.NUMBER}
name="historyCleanupDays"
unit={translate('days')}
value={historyCleanupDays}
helpText={translate('HistoryCleanupDaysHelpText')}
helpTextWarning={translate('HistoryCleanupDaysHelpTextWarning')}

View File

@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Label from 'Components/Label';
import IconButton from 'Components/Link/IconButton';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import RelativeDateCell from 'Components/Table/Cells/RelativeDateCell';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow';
import { icons, kinds } from 'Helpers/Props';
@@ -14,24 +14,79 @@ import HistoryEventTypeCell from './HistoryEventTypeCell';
import HistoryRowParameter from './HistoryRowParameter';
import styles from './HistoryRow.css';
const historyParameters = [
export const historyParameters = [
{ key: historyDataTypes.IMDB_ID, title: 'IMDb' },
{ key: historyDataTypes.TMDB_ID, title: 'TMDb' },
{ key: historyDataTypes.TVDB_ID, title: 'TVDb' },
{ key: historyDataTypes.TRAKT_ID, title: 'Trakt' },
{ key: historyDataTypes.R_ID, title: 'TvRage' },
{ key: historyDataTypes.TVMAZE_ID, title: 'TvMaze' },
{ key: historyDataTypes.SEASON, title: translate('Season') },
{ key: historyDataTypes.EPISODE, title: translate('Episode') },
{ key: historyDataTypes.ARTIST, title: translate('Artist') },
{ key: historyDataTypes.ALBUM, title: translate('Album') },
{ key: historyDataTypes.LABEL, title: translate('Label') },
{ key: historyDataTypes.TRACK, title: translate('Track') },
{ key: historyDataTypes.YEAR, title: translate('Year') },
{ key: historyDataTypes.GENRE, title: translate('Genre') },
{ key: historyDataTypes.AUTHOR, title: translate('Author') },
{ key: historyDataTypes.TITLE, title: translate('Title') },
{ key: historyDataTypes.PUBLISHER, title: translate('Publisher') }
{
key: historyDataTypes.SEASON,
get title() {
return translate('Season');
}
},
{
key: historyDataTypes.EPISODE,
get title() {
return translate('Episode');
}
},
{
key: historyDataTypes.ARTIST,
get title() {
return translate('Artist');
}
},
{
key: historyDataTypes.ALBUM,
get title() {
return translate('Album');
}
},
{
key: historyDataTypes.LABEL,
get title() {
return translate('Label');
}
},
{
key: historyDataTypes.TRACK,
get title() {
return translate('Track');
}
},
{
key: historyDataTypes.YEAR,
get title() {
return translate('Year');
}
},
{
key: historyDataTypes.GENRE,
get title() {
return translate('Genre');
}
},
{
key: historyDataTypes.AUTHOR,
get title() {
return translate('Author');
}
},
{
key: historyDataTypes.TITLE,
get title() {
return translate('Title');
}
},
{
key: historyDataTypes.PUBLISHER,
get title() {
return translate('Publisher');
}
}
];
class HistoryRow extends Component {
@@ -298,7 +353,7 @@ class HistoryRow extends Component {
if (name === 'date') {
return (
<RelativeDateCellConnector
<RelativeDateCell
key={name}
date={date}
className={styles.date}

View File

@@ -1,43 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import styles from './HistoryRowParameter.css';
class HistoryRowParameter extends Component {
//
// Render
render() {
const {
title,
value
} = this.props;
return (
<div className={styles.parameter}>
<div className={styles.info}>
<span>
{
title
}
</span>
</div>
<div
className={styles.value}
>
{
value
}
</div>
</div>
);
}
}
HistoryRowParameter.propTypes = {
title: PropTypes.string.isRequired,
value: PropTypes.string.isRequired
};
export default HistoryRowParameter;

View File

@@ -0,0 +1,44 @@
import React from 'react';
import Link from 'Components/Link/Link';
import styles from './HistoryRowParameter.css';
interface HistoryRowParameterProps {
title: string;
value: string;
}
function HistoryRowParameter(props: HistoryRowParameterProps) {
const { title, value } = props;
const type = title.toLowerCase();
let link = null;
if (type === 'imdb') {
link = <Link to={`https://imdb.com/title/${value}/`}>{value}</Link>;
} else if (type === 'tmdb') {
link = (
<Link to={`https://www.themoviedb.org/movie/${value}`}>{value}</Link>
);
} else if (type === 'tvdb') {
link = (
<Link to={`https://www.thetvdb.com/?tab=series&id=${value}`}>
{value}
</Link>
);
} else if (type === 'tvmaze') {
link = <Link to={`https://www.tvmaze.com/shows/${value}/_`}>{value}</Link>;
}
return (
<div className={styles.parameter}>
<div className={styles.info}>
<span>{title}</span>
</div>
<div className={styles.value}>{link ? link : value}</div>
</div>
);
}
export default HistoryRowParameter;

View File

@@ -40,7 +40,6 @@
flex: 1;
flex-direction: column;
margin-right: 12px;
max-width: 50%;
}
.filterContainer:last-child {
@@ -53,17 +52,22 @@
}
@media only screen and (max-width: $breakpointSmall) {
.filterInput {
margin-bottom: 5px;
}
.alert {
display: none;
}
.filterRow {
flex-direction: column;
display: block;
margin-bottom: 10px;
}
.filterContainer {
margin-right: 0;
margin-bottom: 12px;
margin-bottom: 5px;
}
.scroller {
@@ -72,3 +76,30 @@
margin-left: -30px;
}
}
@media only screen and (min-width: $breakpointSmall) {
.filterContainer {
max-width: 50%;
}
}
.modalFooter {
composes: modalFooter from '~Components/Modal/ModalFooter.css';
justify-content: space-between;
}
.available {
font-weight: bold;
}
@media only screen and (max-width: $breakpointExtraSmall) {
.modalFooter {
flex-direction: column;
gap: 10px;
}
.available {
display: none;
}
}

View File

@@ -2,12 +2,14 @@
// Please do not change this file!
interface CssExports {
'alert': string;
'available': string;
'filterContainer': string;
'filterInput': string;
'filterLabel': string;
'filterRow': string;
'indexers': string;
'modalBody': string;
'modalFooter': string;
'scroller': string;
}
export const cssExports: CssExports;

View File

@@ -22,31 +22,31 @@ import styles from './AddIndexerModalContent.css';
const columns = [
{
name: 'protocol',
label: translate('Protocol'),
label: () => translate('Protocol'),
isSortable: true,
isVisible: true
},
{
name: 'sortName',
label: translate('Name'),
label: () => translate('Name'),
isSortable: true,
isVisible: true
},
{
name: 'language',
label: translate('Language'),
label: () => translate('Language'),
isSortable: true,
isVisible: true
},
{
name: 'description',
label: translate('Description'),
label: () => translate('Description'),
isSortable: false,
isVisible: true
},
{
name: 'privacy',
label: translate('Privacy'),
label: () => translate('Privacy'),
isSortable: true,
isVisible: true
}
@@ -66,15 +66,21 @@ const protocols = [
const privacyLevels = [
{
key: 'private',
value: translate('Private')
get value() {
return translate('Private');
}
},
{
key: 'semiPrivate',
value: translate('SemiPrivate')
get value() {
return translate('SemiPrivate');
}
},
{
key: 'public',
value: translate('Public')
get value() {
return translate('Public');
}
}
];
@@ -257,6 +263,7 @@ class AddIndexerModalContent extends Component {
<SelectIndexerRowConnector
key={`${indexer.implementation}-${indexer.name}`}
implementation={indexer.implementation}
implementationName={indexer.implementationName}
{...indexer}
onIndexerSelect={onIndexerSelect}
/>
@@ -278,12 +285,18 @@ class AddIndexerModalContent extends Component {
</Scroller>
</ModalBody>
<ModalFooter>
<Button
onPress={onModalClose}
>
{translate('Close')}
</Button>
<ModalFooter className={styles.modalFooter}>
<div className={styles.available}>
{
isPopulated ?
translate('CountIndexersAvailable', { count: filteredIndexers.length }) :
null
}
</div>
<div>
<Button onPress={onModalClose}>{translate('Close')}</Button>
</div>
</ModalFooter>
</ModalContent>
);

View File

@@ -49,8 +49,8 @@ class AddIndexerModalContentConnector extends Component {
//
// Listeners
onIndexerSelect = ({ implementation, name }) => {
this.props.selectIndexerSchema({ implementation, name });
onIndexerSelect = ({ implementation, implementationName, name }) => {
this.props.selectIndexerSchema({ implementation, implementationName, name });
this.props.onSelectIndexer();
};

View File

@@ -10,12 +10,14 @@ class AddIndexerPresetMenuItem extends Component {
onPress = () => {
const {
name,
implementation
implementation,
implementationName
} = this.props;
this.props.onPress({
name,
implementation
implementation,
implementationName
});
};
@@ -26,6 +28,7 @@ class AddIndexerPresetMenuItem extends Component {
const {
name,
implementation,
implementationName,
...otherProps
} = this.props;
@@ -43,6 +46,7 @@ class AddIndexerPresetMenuItem extends Component {
AddIndexerPresetMenuItem.propTypes = {
name: PropTypes.string.isRequired,
implementation: PropTypes.string.isRequired,
implementationName: PropTypes.string.isRequired,
onPress: PropTypes.func.isRequired
};

View File

@@ -17,10 +17,11 @@ class SelectIndexerRow extends Component {
onPress = () => {
const {
implementation,
implementationName,
name
} = this.props;
this.props.onIndexerSelect({ implementation, name });
this.props.onIndexerSelect({ implementation, implementationName, name });
};
//
@@ -81,6 +82,7 @@ SelectIndexerRow.propTypes = {
language: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
implementation: PropTypes.string.isRequired,
implementationName: PropTypes.string.isRequired,
onIndexerSelect: PropTypes.func.isRequired,
isExistingIndexer: PropTypes.bool.isRequired
};

View File

@@ -1,34 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import Modal from 'Components/Modal/Modal';
import { sizes } from 'Helpers/Props';
import DeleteIndexerModalContentConnector from './DeleteIndexerModalContentConnector';
function DeleteIndexerModal(props) {
const {
isOpen,
onModalClose,
...otherProps
} = props;
return (
<Modal
isOpen={isOpen}
size={sizes.MEDIUM}
onModalClose={onModalClose}
>
<DeleteIndexerModalContentConnector
{...otherProps}
onModalClose={onModalClose}
/>
</Modal>
);
}
DeleteIndexerModal.propTypes = {
...DeleteIndexerModalContentConnector.propTypes,
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default DeleteIndexerModal;

View File

@@ -0,0 +1,25 @@
import React from 'react';
import Modal from 'Components/Modal/Modal';
import { sizes } from 'Helpers/Props';
import DeleteIndexerModalContent from './DeleteIndexerModalContent';
interface DeleteIndexerModalProps {
isOpen: boolean;
indexerId: number;
onModalClose(): void;
}
function DeleteIndexerModal(props: DeleteIndexerModalProps) {
const { isOpen, indexerId, onModalClose } = props;
return (
<Modal isOpen={isOpen} size={sizes.MEDIUM} onModalClose={onModalClose}>
<DeleteIndexerModalContent
indexerId={indexerId}
onModalClose={onModalClose}
/>
</Modal>
);
}
export default DeleteIndexerModal;

View File

@@ -1,88 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Button from 'Components/Link/Button';
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 { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
class DeleteIndexerModalContent extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
deleteFiles: false,
addImportExclusion: false
};
}
//
// Listeners
onDeleteFilesChange = ({ value }) => {
this.setState({ deleteFiles: value });
};
onAddImportExclusionChange = ({ value }) => {
this.setState({ addImportExclusion: value });
};
onDeleteMovieConfirmed = () => {
const deleteFiles = this.state.deleteFiles;
const addImportExclusion = this.state.addImportExclusion;
this.setState({ deleteFiles: false, addImportExclusion: false });
this.props.onDeletePress(deleteFiles, addImportExclusion);
};
//
// Render
render() {
const {
name,
onModalClose
} = this.props;
return (
<ModalContent
onModalClose={onModalClose}
>
<ModalHeader>
Delete - {name}
</ModalHeader>
<ModalBody>
{`Are you sure you want to delete ${name} from Prowlarr`}
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>
{translate('Close')}
</Button>
<Button
kind={kinds.DANGER}
onPress={this.onDeleteMovieConfirmed}
>
{translate('Delete')}
</Button>
</ModalFooter>
</ModalContent>
);
}
}
DeleteIndexerModalContent.propTypes = {
name: PropTypes.string.isRequired,
onDeletePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default DeleteIndexerModalContent;

View File

@@ -0,0 +1,54 @@
import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import Button from 'Components/Link/Button';
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 { kinds } from 'Helpers/Props';
import Indexer from 'Indexer/Indexer';
import { deleteIndexer } from 'Store/Actions/indexerActions';
import { createIndexerSelectorForHook } from 'Store/Selectors/createIndexerSelector';
import translate from 'Utilities/String/translate';
interface DeleteIndexerModalContentProps {
indexerId: number;
onModalClose(): void;
}
function DeleteIndexerModalContent(props: DeleteIndexerModalContentProps) {
const { indexerId, onModalClose } = props;
const { name } = useSelector(
createIndexerSelectorForHook(indexerId)
) as Indexer;
const dispatch = useDispatch();
const onConfirmDelete = useCallback(() => {
dispatch(deleteIndexer({ id: indexerId }));
onModalClose();
}, [indexerId, dispatch, onModalClose]);
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{translate('Delete')} - {name}
</ModalHeader>
<ModalBody>
{translate('AreYouSureYouWantToDeleteIndexer', { name })}
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>{translate('Close')}</Button>
<Button kind={kinds.DANGER} onPress={onConfirmDelete}>
{translate('Delete')}
</Button>
</ModalFooter>
</ModalContent>
);
}
export default DeleteIndexerModalContent;

View File

@@ -1,57 +0,0 @@
import { push } from 'connected-react-router';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { deleteIndexer } from 'Store/Actions/indexerActions';
import createIndexerSelector from 'Store/Selectors/createIndexerSelector';
import DeleteIndexerModalContent from './DeleteIndexerModalContent';
function createMapStateToProps() {
return createSelector(
createIndexerSelector(),
(indexer) => {
return indexer;
}
);
}
const mapDispatchToProps = {
deleteIndexer,
push
};
class DeleteIndexerModalContentConnector extends Component {
//
// Listeners
onDeletePress = () => {
this.props.deleteIndexer({
id: this.props.indexerId
});
this.props.onModalClose(true);
};
//
// Render
render() {
return (
<DeleteIndexerModalContent
{...this.props}
onDeletePress={this.onDeletePress}
/>
);
}
}
DeleteIndexerModalContentConnector.propTypes = {
indexerId: PropTypes.number.isRequired,
onModalClose: PropTypes.func.isRequired,
deleteIndexer: PropTypes.func.isRequired,
push: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(DeleteIndexerModalContentConnector);

View File

@@ -61,7 +61,7 @@ function EditIndexerModalContent(props) {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{`${id ? translate('EditIndexer') : translate('AddIndexer')} - ${indexerDisplayName}`}
{id ? translate('EditIndexerImplementation', { implementationName: indexerDisplayName }) : translate('AddIndexerImplementation', { implementationName: indexerDisplayName })}
</ModalHeader>
<ModalBody>
@@ -187,6 +187,7 @@ function EditIndexerModalContent(props) {
type={inputTypes.TAG}
name="tags"
helpText={translate('IndexerTagsHelpText')}
helpTextWarning={translate('IndexerTagsHelpTextWarning')}
{...tags}
onChange={onInputChange}
/>

View File

@@ -1,6 +1,10 @@
import React, { useCallback, useMemo, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { SelectProvider } from 'App/SelectContext';
import ClientSideCollectionAppState from 'App/State/ClientSideCollectionAppState';
import IndexerAppState, {
IndexerIndexAppState,
} from 'App/State/IndexerAppState';
import { APP_INDEXER_SYNC } from 'Commands/commandNames';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent';
@@ -18,7 +22,7 @@ import AddIndexerModal from 'Indexer/Add/AddIndexerModal';
import EditIndexerModalConnector from 'Indexer/Edit/EditIndexerModalConnector';
import NoIndexer from 'Indexer/NoIndexer';
import { executeCommand } from 'Store/Actions/commandActions';
import { testAllIndexers } from 'Store/Actions/indexerActions';
import { cloneIndexer, testAllIndexers } from 'Store/Actions/indexerActions';
import {
setIndexerFilter,
setIndexerSort,
@@ -41,9 +45,7 @@ import IndexerIndexTable from './Table/IndexerIndexTable';
import IndexerIndexTableOptions from './Table/IndexerIndexTableOptions';
import styles from './IndexerIndex.css';
function getViewComponent() {
return IndexerIndexTable;
}
const getViewComponent = () => IndexerIndexTable;
interface IndexerIndexProps {
initialScrollTop?: number;
@@ -64,29 +66,22 @@ const IndexerIndex = withScrollPosition((props: IndexerIndexProps) => {
sortKey,
sortDirection,
view,
} = useSelector(
createIndexerClientSideCollectionItemsSelector('indexerIndex')
);
}: IndexerAppState & IndexerIndexAppState & ClientSideCollectionAppState =
useSelector(createIndexerClientSideCollectionItemsSelector('indexerIndex'));
const isSyncingIndexers = useSelector(
createCommandExecutingSelector(APP_INDEXER_SYNC)
);
const { isSmallScreen } = useSelector(createDimensionsSelector());
const dispatch = useDispatch();
const scrollerRef = useRef<HTMLDivElement>();
const scrollerRef = useRef<HTMLDivElement>(null);
const [isAddIndexerModalOpen, setIsAddIndexerModalOpen] = useState(false);
const [isEditIndexerModalOpen, setIsEditIndexerModalOpen] = useState(false);
const [jumpToCharacter, setJumpToCharacter] = useState<string | null>(null);
const [jumpToCharacter, setJumpToCharacter] = useState<string | undefined>(
undefined
);
const [isSelectMode, setIsSelectMode] = useState(false);
const onAppIndexerSyncPress = useCallback(() => {
dispatch(
executeCommand({
name: APP_INDEXER_SYNC,
})
);
}, [dispatch]);
const onAddIndexerPress = useCallback(() => {
setIsAddIndexerModalOpen(true);
}, [setIsAddIndexerModalOpen]);
@@ -103,6 +98,24 @@ const IndexerIndex = withScrollPosition((props: IndexerIndexProps) => {
setIsEditIndexerModalOpen(false);
}, [setIsEditIndexerModalOpen]);
const onCloneIndexerPress = useCallback(
(id: number) => {
dispatch(cloneIndexer({ id }));
setIsEditIndexerModalOpen(true);
},
[dispatch, setIsEditIndexerModalOpen]
);
const onAppIndexerSyncPress = useCallback(() => {
dispatch(
executeCommand({
name: APP_INDEXER_SYNC,
forceSync: true,
})
);
}, [dispatch]);
const onTestAllPress = useCallback(() => {
dispatch(testAllIndexers());
}, [dispatch]);
@@ -112,37 +125,37 @@ const IndexerIndex = withScrollPosition((props: IndexerIndexProps) => {
}, [isSelectMode, setIsSelectMode]);
const onTableOptionChange = useCallback(
(payload) => {
(payload: unknown) => {
dispatch(setIndexerTableOption(payload));
},
[dispatch]
);
const onSortSelect = useCallback(
(value) => {
(value: string) => {
dispatch(setIndexerSort({ sortKey: value }));
},
[dispatch]
);
const onFilterSelect = useCallback(
(value) => {
(value: string) => {
dispatch(setIndexerFilter({ selectedFilterKey: value }));
},
[dispatch]
);
const onJumpBarItemPress = useCallback(
(character) => {
(character: string) => {
setJumpToCharacter(character);
},
[setJumpToCharacter]
);
const onScroll = useCallback(
({ scrollTop }) => {
setJumpToCharacter(null);
scrollPositions.seriesIndex = scrollTop;
({ scrollTop }: { scrollTop: number }) => {
setJumpToCharacter(undefined);
scrollPositions.indexerIndex = scrollTop;
},
[setJumpToCharacter]
);
@@ -155,7 +168,7 @@ const IndexerIndex = withScrollPosition((props: IndexerIndexProps) => {
};
}
const characters = items.reduce((acc, item) => {
const characters = items.reduce((acc: Record<string, number>, item) => {
let char = item.sortName.charAt(0);
if (!isNaN(Number(char))) {
@@ -190,7 +203,7 @@ const IndexerIndex = withScrollPosition((props: IndexerIndexProps) => {
return (
<SelectProvider items={items}>
<PageContent>
<PageContent title={translate('Indexers')}>
<PageToolbar>
<PageToolbarSection>
<PageToolbarButton
@@ -277,6 +290,8 @@ const IndexerIndex = withScrollPosition((props: IndexerIndexProps) => {
<PageContentBody
ref={scrollerRef}
className={styles.contentBody}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
innerClassName={styles[`${view}InnerContentBody`]}
initialScrollTop={props.initialScrollTop}
onScroll={onScroll}
@@ -297,6 +312,7 @@ const IndexerIndex = withScrollPosition((props: IndexerIndexProps) => {
jumpToCharacter={jumpToCharacter}
isSelectMode={isSelectMode}
isSmallScreen={isSmallScreen}
onCloneIndexerPress={onCloneIndexerPress}
/>
<IndexerIndexFooter />

View File

@@ -1,12 +1,13 @@
import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
import FilterModal from 'Components/Filter/FilterModal';
import { setIndexerFilter } from 'Store/Actions/indexerIndexActions';
function createIndexerSelector() {
return createSelector(
(state) => state.indexers.items,
(state: AppState) => state.indexers.items,
(indexers) => {
return indexers;
}
@@ -15,14 +16,20 @@ function createIndexerSelector() {
function createFilterBuilderPropsSelector() {
return createSelector(
(state) => state.indexerIndex.filterBuilderProps,
(state: AppState) => state.indexerIndex.filterBuilderProps,
(filterBuilderProps) => {
return filterBuilderProps;
}
);
}
export default function IndexerIndexFilterModal(props) {
interface IndexerIndexFilterModalProps {
isOpen: boolean;
}
export default function IndexerIndexFilterModal(
props: IndexerIndexFilterModalProps
) {
const sectionItems = useSelector(createIndexerSelector());
const filterBuilderProps = useSelector(createFilterBuilderPropsSelector());
const customFilterType = 'indexerIndex';
@@ -30,7 +37,7 @@ export default function IndexerIndexFilterModal(props) {
const dispatch = useDispatch();
const dispatchSetFilter = useCallback(
(payload) => {
(payload: unknown) => {
dispatch(setIndexerFilter(payload));
},
[dispatch]
@@ -38,6 +45,7 @@ export default function IndexerIndexFilterModal(props) {
return (
<FilterModal
// TODO: Don't spread all the props
{...props}
sectionItems={sectionItems}
filterBuilderProps={filterBuilderProps}

View File

@@ -3,6 +3,7 @@ import React from 'react';
import { useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import { ColorImpairedConsumer } from 'App/ColorImpairedContext';
import IndexerAppState from 'App/State/IndexerAppState';
import DescriptionList from 'Components/DescriptionList/DescriptionList';
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
@@ -13,7 +14,7 @@ import styles from './IndexerIndexFooter.css';
function createUnoptimizedSelector() {
return createSelector(
createClientSideCollectionSelector('indexers', 'indexerIndex'),
(indexers) => {
(indexers: IndexerAppState) => {
return indexers.items.map((s) => {
const { protocol, privacy, enable } = s;

View File

@@ -1,10 +1,18 @@
import PropTypes from 'prop-types';
import React from 'react';
import { CustomFilter } from 'App/State/AppState';
import FilterMenu from 'Components/Menu/FilterMenu';
import { align } from 'Helpers/Props';
import IndexerIndexFilterModal from 'Indexer/Index/IndexerIndexFilterModal';
function IndexerIndexFilterMenu(props) {
interface IndexerIndexFilterMenuProps {
selectedFilterKey: string | number;
filters: object[];
customFilters: CustomFilter[];
isDisabled: boolean;
onFilterSelect(filterName: string): unknown;
}
function IndexerIndexFilterMenu(props: IndexerIndexFilterMenuProps) {
const {
selectedFilterKey,
filters,
@@ -26,15 +34,6 @@ function IndexerIndexFilterMenu(props) {
);
}
IndexerIndexFilterMenu.propTypes = {
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
.isRequired,
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
isDisabled: PropTypes.bool.isRequired,
onFilterSelect: PropTypes.func.isRequired,
};
IndexerIndexFilterMenu.defaultProps = {
showCustomFilters: false,
};

View File

@@ -1,12 +1,19 @@
import PropTypes from 'prop-types';
import React from 'react';
import MenuContent from 'Components/Menu/MenuContent';
import SortMenu from 'Components/Menu/SortMenu';
import SortMenuItem from 'Components/Menu/SortMenuItem';
import { align, sortDirections } from 'Helpers/Props';
import { align } from 'Helpers/Props';
import SortDirection from 'Helpers/Props/SortDirection';
import translate from 'Utilities/String/translate';
function IndexerIndexSortMenu(props) {
interface IndexerIndexSortMenuProps {
sortKey?: string;
sortDirection?: SortDirection;
isDisabled: boolean;
onSortSelect(sortKey: string): unknown;
}
function IndexerIndexSortMenu(props: IndexerIndexSortMenuProps) {
const { sortKey, sortDirection, isDisabled, onSortSelect } = props;
return (
@@ -79,11 +86,4 @@ function IndexerIndexSortMenu(props) {
);
}
IndexerIndexSortMenu.propTypes = {
sortKey: PropTypes.string,
sortDirection: PropTypes.oneOf(sortDirections.all),
isDisabled: PropTypes.bool.isRequired,
onSortSelect: PropTypes.func.isRequired,
};
export default IndexerIndexSortMenu;

View File

@@ -7,7 +7,8 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { kinds } from 'Helpers/Props';
import { bulkDeleteIndexers } from 'Store/Actions/indexerIndexActions';
import Indexer from 'Indexer/Indexer';
import { bulkDeleteIndexers } from 'Store/Actions/indexerActions';
import createAllIndexersSelector from 'Store/Selectors/createAllIndexersSelector';
import translate from 'Utilities/String/translate';
import styles from './DeleteIndexerModalContent.css';
@@ -20,21 +21,21 @@ interface DeleteIndexerModalContentProps {
function DeleteIndexerModalContent(props: DeleteIndexerModalContentProps) {
const { indexerIds, onModalClose } = props;
const allIndexers = useSelector(createAllIndexersSelector());
const allIndexers: Indexer[] = useSelector(createAllIndexersSelector());
const dispatch = useDispatch();
const selectedIndexers = useMemo(() => {
const indexers = indexerIds.map((id) => {
const indexers = useMemo((): Indexer[] => {
const indexerList = indexerIds.map((id) => {
return allIndexers.find((s) => s.id === id);
});
}) as Indexer[];
return orderBy(indexers, ['sortName']);
return orderBy(indexerList, ['sortName']);
}, [indexerIds, allIndexers]);
const onDeleteIndexerConfirmed = useCallback(() => {
dispatch(
bulkDeleteIndexers({
indexerIds,
ids: indexerIds,
})
);
@@ -47,13 +48,13 @@ function DeleteIndexerModalContent(props: DeleteIndexerModalContentProps) {
<ModalBody>
<div className={styles.message}>
{translate('DeleteSelectedIndexersMessageText', [
selectedIndexers.length,
])}
{translate('DeleteSelectedIndexersMessageText', {
count: indexers.length,
})}
</div>
<ul>
{selectedIndexers.map((s) => {
{indexers.map((s) => {
return (
<li key={s.id}>
<span>{s.name}</span>

View File

@@ -7,7 +7,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 } from 'Helpers/Props';
import { inputTypes, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './EditIndexerModalContent.css';
@@ -15,6 +15,10 @@ interface SavePayload {
enable?: boolean;
appProfileId?: number;
priority?: number;
minimumSeeders?: number;
seedRatio?: number;
seedTime?: number;
packSeedTime?: number;
}
interface EditIndexerModalContentProps {
@@ -26,9 +30,25 @@ interface EditIndexerModalContentProps {
const NO_CHANGE = 'noChange';
const enableOptions = [
{ key: NO_CHANGE, value: translate('NoChange'), disabled: true },
{ key: 'true', value: translate('Enabled') },
{ key: 'false', value: translate('Disabled') },
{
key: NO_CHANGE,
get value() {
return translate('NoChange');
},
disabled: true,
},
{
key: 'true',
get value() {
return translate('Enabled');
},
},
{
key: 'false',
get value() {
return translate('Disabled');
},
},
];
function EditIndexerModalContent(props: EditIndexerModalContentProps) {
@@ -37,6 +57,14 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
const [enable, setEnable] = useState(NO_CHANGE);
const [appProfileId, setAppProfileId] = useState<string | number>(NO_CHANGE);
const [priority, setPriority] = useState<null | string | number>(null);
const [minimumSeeders, setMinimumSeeders] = useState<null | string | number>(
null
);
const [seedRatio, setSeedRatio] = useState<null | string | number>(null);
const [seedTime, setSeedTime] = useState<null | string | number>(null);
const [packSeedTime, setPackSeedTime] = useState<null | string | number>(
null
);
const save = useCallback(() => {
let hasChanges = false;
@@ -57,15 +85,45 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
payload.priority = priority as number;
}
if (minimumSeeders !== null) {
hasChanges = true;
payload.minimumSeeders = minimumSeeders as number;
}
if (seedRatio !== null) {
hasChanges = true;
payload.seedRatio = seedRatio as number;
}
if (seedTime !== null) {
hasChanges = true;
payload.seedTime = seedTime as number;
}
if (packSeedTime !== null) {
hasChanges = true;
payload.packSeedTime = packSeedTime as number;
}
if (hasChanges) {
onSavePress(payload);
}
onModalClose();
}, [enable, appProfileId, priority, onSavePress, onModalClose]);
}, [
enable,
appProfileId,
priority,
minimumSeeders,
seedRatio,
seedTime,
packSeedTime,
onSavePress,
onModalClose,
]);
const onInputChange = useCallback(
({ name, value }) => {
({ name, value }: { name: string; value: string }) => {
switch (name) {
case 'enable':
setEnable(value);
@@ -76,6 +134,18 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
case 'priority':
setPriority(value);
break;
case 'minimumSeeders':
setMinimumSeeders(value);
break;
case 'seedRatio':
setSeedRatio(value);
break;
case 'seedTime':
setSeedTime(value);
break;
case 'packSeedTime':
setPackSeedTime(value);
break;
default:
console.warn(`EditIndexersModalContent Unknown Input: '${name}'`);
}
@@ -94,7 +164,7 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
<ModalHeader>{translate('EditSelectedIndexers')}</ModalHeader>
<ModalBody>
<FormGroup>
<FormGroup size={sizes.MEDIUM}>
<FormLabel>{translate('Enable')}</FormLabel>
<FormInputGroup
@@ -106,21 +176,22 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
/>
</FormGroup>
<FormGroup>
<FormGroup size={sizes.MEDIUM}>
<FormLabel>{translate('SyncProfile')}</FormLabel>
<FormInputGroup
type={inputTypes.APP_PROFILE_SELECT}
name="appProfileId"
value={appProfileId}
helpText={translate('AppProfileSelectHelpText')}
includeNoChange={true}
includeNoChangeDisabled={false}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Priority')}</FormLabel>
<FormGroup size={sizes.MEDIUM}>
<FormLabel>{translate('IndexerPriority')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
@@ -128,6 +199,57 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
value={priority}
min={1}
max={50}
helpText={translate('IndexerPriorityHelpText')}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup size={sizes.MEDIUM}>
<FormLabel>{translate('AppsMinimumSeeders')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="minimumSeeders"
value={minimumSeeders}
helpText={translate('AppsMinimumSeedersHelpText')}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup size={sizes.MEDIUM}>
<FormLabel>{translate('SeedRatio')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="seedRatio"
value={seedRatio}
helpText={translate('SeedRatioHelpText')}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup size={sizes.MEDIUM}>
<FormLabel>{translate('SeedTime')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="seedTime"
value={seedTime}
unit={translate('minutes')}
helpText={translate('SeedTimeHelpText')}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup size={sizes.MEDIUM}>
<FormLabel>{translate('PackSeedTime')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="packSeedTime"
value={packSeedTime}
unit={translate('minutes')}
helpText={translate('PackSeedTimeHelpText')}
onChange={onInputChange}
/>
</FormGroup>
@@ -135,7 +257,7 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
<ModalFooter className={styles.modalFooter}>
<div className={styles.selected}>
{translate('CountIndexersSelected', [selectedCount])}
{translate('CountIndexersSelected', { count: selectedCount })}
</div>
<div>

View File

@@ -7,7 +7,7 @@ import translate from 'Utilities/String/translate';
interface IndexerIndexSelectAllButtonProps {
label: string;
isSelectMode: boolean;
overflowComponent: React.FunctionComponent;
overflowComponent: React.FunctionComponent<never>;
}
function IndexerIndexSelectAllButton(props: IndexerIndexSelectAllButtonProps) {

View File

@@ -2,11 +2,12 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import { useSelect } from 'App/SelectContext';
import AppState from 'App/State/AppState';
import SpinnerButton from 'Components/Link/SpinnerButton';
import PageContentFooter from 'Components/Page/PageContentFooter';
import usePrevious from 'Helpers/Hooks/usePrevious';
import { kinds } from 'Helpers/Props';
import { saveIndexerEditor } from 'Store/Actions/indexerIndexActions';
import { bulkEditIndexers } from 'Store/Actions/indexerActions';
import translate from 'Utilities/String/translate';
import getSelectedIds from 'Utilities/Table/getSelectedIds';
import DeleteIndexerModal from './Delete/DeleteIndexerModal';
@@ -14,8 +15,18 @@ import EditIndexerModal from './Edit/EditIndexerModal';
import TagsModal from './Tags/TagsModal';
import styles from './IndexerIndexSelectFooter.css';
interface SavePayload {
enable?: boolean;
appProfileId?: number;
priority?: number;
minimumSeeders?: number;
seedRatio?: number;
seedTime?: number;
packSeedTime?: number;
}
const indexersEditorSelector = createSelector(
(state) => state.indexers,
(state: AppState) => state.indexers,
(indexers) => {
const { isSaving, isDeleting, deleteError } = indexers;
@@ -59,14 +70,14 @@ function IndexerIndexSelectFooter() {
}, [setIsEditModalOpen]);
const onSavePress = useCallback(
(payload) => {
(payload: SavePayload) => {
setIsSavingIndexer(true);
setIsEditModalOpen(false);
dispatch(
saveIndexerEditor({
bulkEditIndexers({
...payload,
indexerIds,
ids: indexerIds,
})
);
},
@@ -82,13 +93,13 @@ function IndexerIndexSelectFooter() {
}, [setIsTagsModalOpen]);
const onApplyTagsPress = useCallback(
(tags, applyTags) => {
(tags: number[], applyTags: string) => {
setIsSavingTags(true);
setIsTagsModalOpen(false);
dispatch(
saveIndexerEditor({
indexerIds,
bulkEditIndexers({
ids: indexerIds,
tags,
applyTags,
})
@@ -154,7 +165,7 @@ function IndexerIndexSelectFooter() {
</div>
<div className={styles.selected}>
{translate('CountIndexersSelected', [selectedCount])}
{translate('CountIndexersSelected', { count: selectedCount })}
</div>
<EditIndexerModal

View File

@@ -7,7 +7,7 @@ interface IndexerIndexSelectModeButtonProps {
label: string;
iconName: IconDefinition;
isSelectMode: boolean;
overflowComponent: React.FunctionComponent;
overflowComponent: React.FunctionComponent<never>;
onPress: () => void;
}

View File

@@ -1,6 +1,7 @@
import { concat, uniq } from 'lodash';
import { uniq } from 'lodash';
import React, { useCallback, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { Tag } from 'App/State/TagsAppState';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
@@ -12,6 +13,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds, sizes } from 'Helpers/Props';
import Indexer from 'Indexer/Indexer';
import createAllIndexersSelector from 'Store/Selectors/createAllIndexersSelector';
import createTagsSelector from 'Store/Selectors/createTagsSelector';
import translate from 'Utilities/String/translate';
@@ -26,29 +28,35 @@ interface TagsModalContentProps {
function TagsModalContent(props: TagsModalContentProps) {
const { indexerIds, onModalClose, onApplyTagsPress } = props;
const allIndexers = useSelector(createAllIndexersSelector());
const tagList = useSelector(createTagsSelector());
const allIndexers: Indexer[] = useSelector(createAllIndexersSelector());
const tagList: Tag[] = useSelector(createTagsSelector());
const [tags, setTags] = useState<number[]>([]);
const [applyTags, setApplyTags] = useState('add');
const indexerTags = useMemo(() => {
const indexers = indexerIds.map((id) => {
return allIndexers.find((s) => s.id === id);
});
const tags = indexerIds.reduce((acc: number[], id) => {
const s = allIndexers.find((s) => s.id === id);
return uniq(concat(...indexers.map((s) => s.tags)));
if (s) {
acc.push(...s.tags);
}
return acc;
}, []);
return uniq(tags);
}, [indexerIds, allIndexers]);
const onTagsChange = useCallback(
({ value }) => {
({ value }: { value: number[] }) => {
setTags(value);
},
[setTags]
);
const onApplyTagsChange = useCallback(
({ value }) => {
({ value }: { value: string }) => {
setApplyTags(value);
},
[setApplyTags]
@@ -90,10 +98,10 @@ function TagsModalContent(props: TagsModalContentProps) {
value={applyTags}
values={applyTagsOptions}
helpTexts={[
translate('ApplyTagsHelpTexts1'),
translate('ApplyTagsHelpTexts2'),
translate('ApplyTagsHelpTexts3'),
translate('ApplyTagsHelpTexts4'),
translate('ApplyTagsHelpTextHowToApplyIndexers'),
translate('ApplyTagsHelpTextAdd'),
translate('ApplyTagsHelpTextRemove'),
translate('ApplyTagsHelpTextReplace'),
]}
onChange={onApplyTagsChange}
/>

View File

@@ -19,7 +19,11 @@
.priority,
.protocol,
.privacy {
.privacy,
.minimumSeeders,
.seedRatio,
.seedTime,
.packSeedTime {
composes: cell;
flex: 0 0 90px;

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