Compare commits

...

287 Commits

Author SHA1 Message Date
abcasada
f7bf21df68 Update help text for Tags usage in Applications 2024-01-26 19:13:12 +02:00
Bogdan
d764e3405d New: (Torznab) Internal and Scene tags 2024-01-26 09:58:22 +02:00
Bogdan
16baceb784 New: Option to disable Email encryption
* New: Option to disable Email encryption

(cherry picked from commit 7be5732a3a6679120b0f01bd1eb1207194f57f5e)

* Fix possible NullRef in Email Encryption migration

(cherry picked from commit 271266b10ac51ee6dd7a7024d346b631bd5397c2)
2024-01-24 11:37:01 +02:00
Bogdan
5d2b80d15a Fixed: (SubsPlease) Parse release size from magnet links 2024-01-23 10:59:59 +02:00
Bogdan
a20a81f424 New: Category filter for Indexers 2024-01-23 09:00:49 +02:00
Bogdan
ebb66e9086 Update database migration version translation token 2024-01-23 08:50:05 +02:00
Bogdan
cb8797693e Fixed: Sorting by name in Manage Applications and Download Client modals 2024-01-23 08:40:32 +02:00
Stevie Robinson
255c6335ae New: Add sorting to Manage Applications and Download Client modals
(cherry picked from commit 91053ca51ded804739f94ee936c1376a755dbe11)
2024-01-23 08:38:47 +02:00
Bogdan
155cd53dcd Fix translations for manage applications and download clients 2024-01-23 08:29:59 +02:00
Weblate
ae70a96c10 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Julian Baquero <julian-baquero@upc.edu.co>
Co-authored-by: MaddionMax <kovacs.tamas@ius.hu>
Co-authored-by: Magyar <kochnorbert@icloud.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: blabla <romcrack56@gmail.com>
Co-authored-by: brn <barantsenkul@gmail.com>
Co-authored-by: fordas <fordas15@gmail.com>
Co-authored-by: horvi28 <horvi28@gmail.com>
Co-authored-by: wilfriedarma <wilfriedarma.collet@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/tr/
Translation: Servarr/Prowlarr
2024-01-23 08:15:51 +02:00
Bogdan
16c0daf090 Fix value types for EnhancedSelectInputConnector 2024-01-22 13:24:00 +02:00
Bogdan
34c78c5a9d Fixed: (BTN) Disable ID-based searches for episodes with absolute episode number
Release name searches are not supported, so it's better to prevent an useless request.
2024-01-22 11:48:32 +02:00
Bogdan
dd5b108ffd Fixed: (BTN) Allow ID-based with text search query 2024-01-21 17:13:12 +02:00
Servarr
0b83986255 Automated API Docs update 2024-01-21 07:54:16 +02:00
Bogdan
2bd25fb6f3 Reorder sync fields for App Profiles 2024-01-21 07:47:48 +02:00
Bogdan
0f5eb5d3a3 Bump version to 1.13.1 2024-01-21 07:47:48 +02:00
Bogdan
c9434c61e3 Transpile logical assignment operators with babel 2024-01-21 03:58:55 +02:00
Mark McDowall
ee969b7a06 New: Log warning if less than 1 GB free space during update
(cherry picked from commit e66ba84fc0b5b120dd4e87f6b8ae1b3c038ee72b)
2024-01-21 03:58:28 +02:00
Bogdan
cfdf88a1e2 Add exceptions to logs for indexers and apps 2024-01-20 04:49:16 +02:00
bakerboy448
70a5dae293 Fixed: Don't lowercase UrlBase in ConfigFileProvider
UrlBase should honour the case it is given.

(cherry picked from commit e1de523c89f7649e64f520b090bbdb2f56cc4b85)
(cherry picked from commit 9ccefe00951d2959ef79bdaa5731d95f97162d46)

Co-authored-by: Marty Zalega <marty@zalega.me>
2024-01-20 01:34:20 +02:00
Bogdan
b53f8d4552 Bump version to 1.13.0 2024-01-18 05:23:29 +02:00
Weblate
9668e91b21 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: Deleted User <noreply+2593@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pl/
Translation: Servarr/Prowlarr
2024-01-17 23:01:59 +02:00
Bogdan
49857693c0 Fix count in GGn fixture after allowing only torrents 2024-01-17 11:14:00 +02:00
Bogdan
850315ad1c Fixed: (GGn) Improve titles and allow only torrents 2024-01-17 10:45:02 +02:00
Mark McDowall
86124d4319 New: Optional directory setting for Aria2
(cherry picked from commit fd17df0dd03a5feb088c3241a247eac20f0e8c6c)
2024-01-17 07:49:51 +02:00
Bogdan
4f28d583d7 Throw download as failed for invalid magnet links 2024-01-17 07:40:17 +02:00
Stevie Robinson
01f3930211 Sort Custom Filters
(cherry picked from commit e4b5d559df2d5f3d55e16aae5922509e84f31e64)
2024-01-17 07:38:23 +02:00
Weblate
7c7114c87a Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: Daniele Prevedello <dprevedello86@gmail.com>
Co-authored-by: DimitriDR <dimitridroeck@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Trooper57 <igorkcs@gmail.com>
Co-authored-by: Watashi <drazy24@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: crayon3shawn <crayon3shawn@gmail.com>
Co-authored-by: hansaudun <hans@n5.no>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ca/
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/it/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nb_NO/
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/zh_TW/
Translation: Servarr/Prowlarr
2024-01-16 05:11:12 +02:00
Bogdan
ef8e6d774b Fix typo 2024-01-16 05:10:20 +02:00
Bogdan
2960fc37d9 Fixed: (FL) Improve error message for service unavailable 2024-01-16 04:58:17 +02:00
Qstick
8bddf753bb Bump Inno version to 6.2.2
(cherry picked from commit c5992ed944b2d4f2f41a08ac407c0f013da61ea6)

Build report can get sent before installer finished

(cherry picked from commit 60d9aacac64b39fd22d43e8b1ca4641bd60a9b48)
2024-01-14 21:44:58 +02:00
Bogdan
cff24b3fd4 Bump version to 1.12.2 2024-01-14 07:13:34 +02:00
Bogdan
031d81330d Fixed: Filter history by multiple event types in PG 2024-01-12 22:11:45 +02:00
Bogdan
6201b42fbd New: Refresh button for indexer stats 2024-01-12 15:26:32 +02:00
Bogdan
7022054dd7 New: Indexer ID as column
Fixes #1948
2024-01-12 14:58:27 +02:00
ilike2burnthing
c9b663247c Empty Torznab ApiKeyWhiteList 2024-01-12 05:55:09 +02:00
Bogdan
0b0a0cfa5b New: (HDBits) Filter by origins 2024-01-10 22:39:43 +02:00
Weblate
3c0fea8b7c Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Aitzol Garmendia <aitzolgarmendia@gmail.com>
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Bradley BARBIER <bradley.barbier@outlook.fr>
Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: DimitriDR <dimitridroeck@gmail.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: RicardoVelaC <ricardovelac@gmail.com>
Co-authored-by: reloxx <reloxx@interia.pl>
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/de/
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/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/id/
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/lv/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ru/
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/zh_TW/
Translation: Servarr/Prowlarr
2024-01-09 20:59:04 +02:00
Bogdan
ac97952fd7 Fixed: (HDBits) Strip non-word chars from search query
Co-authored-by: Kyle Sanderson <kyle.leet@gmail.com>
2024-01-08 22:24:35 +02:00
Bogdan
c3e40c0564 Log web exceptions in test connection 2024-01-08 21:31:08 +02:00
Servarr
ce615a77c2 Automated API Docs update 2024-01-08 01:29:38 +02:00
Bogdan
0f6dfe389c Fix CS in History 2024-01-08 01:21:44 +02:00
Bogdan
25d94a9286 New: History custom filters
Co-authored-by: Mark McDowall <mark@mcdowall.ca>
2024-01-08 00:53:40 +02:00
Bogdan
52a690b41a Fixed: Filter history by multiple event types 2024-01-07 19:04:22 +02:00
Bogdan
56c8c3d6c6 Bump version to 1.12.1 2024-01-07 11:10:04 +02:00
Weblate
5cee8990b3 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Aitzol Garmendia <aitzolgarmendia@gmail.com>
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Dani Talens <databio@gmail.com>
Co-authored-by: HuaBing <admin@hbcraft.cn>
Co-authored-by: JJonttuu <oikeaihminen@protonmail.com>
Co-authored-by: Juan Lores <juan.lores@gmail.com>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Piotr Komborski <piotr+github@kombor.ski>
Co-authored-by: RicardoVelaC <ricardovelac@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: boan51204 <je.991707@gmail.com>
Co-authored-by: 饶志华 <879467666@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
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/pl/
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
2024-01-05 18:38:00 -06:00
Bogdan
9679e88717 Update end date for RED freeload event 2024-01-04 23:32:37 +02:00
Bogdan
f4203993ba New: (MAM) Book title and author returned in response 2024-01-03 11:29:06 +02:00
Bogdan
4be0715fe3 New: (MAM) Filter releases by Min/Max size 2024-01-03 11:21:40 +02:00
Bogdan
f9c9d4a0e0 Fixed: (GGn) Improve title, timezone, MST, min/max size filters
Hiding torrents of type link
And some minor refactoring around the passkey fetching and parsing.
2024-01-03 10:49:25 +02:00
Weblate
bf47380f7b 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: Mario Rodriguez <mario2423@gmail.com>
Co-authored-by: Norbi <kovinor123@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translation: Servarr/Prowlarr
2024-01-01 14:10:25 +02:00
Mark McDowall
e27a46f578 Fixed: Disable SSL on start if certificate path is not set
(cherry picked from commit 4e19fec123900b8ba1252b640f26f2a4983683ff)
2024-01-01 08:07:46 +02:00
Weblate
af4e69f8fb Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dimitri <dimitridroeck@gmail.com>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Koch Norbert <kochnorbert@icloud.com>
Co-authored-by: Pietro Ribeiro <xxb1exuv6@mozmail.com>
Co-authored-by: SunStorm <me@sunstorm.rocks>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: chiral-lab <jan.eltner@googlemail.com>
Co-authored-by: chrizl <chrizl@gmail.com>
Co-authored-by: resi23 <x-resistant-x@gmx.de>
Co-authored-by: slammingdeath <sebastianbrudny97@gmail.com>
Co-authored-by: ube <ube@alienautopsy.net>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
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/sv/
Translation: Servarr/Prowlarr
2023-12-31 10:48:13 +02:00
Weblate
1b2106d4f0 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dimitri <dimitridroeck@gmail.com>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Koch Norbert <kochnorbert@icloud.com>
Co-authored-by: Pietro Ribeiro <xxb1exuv6@mozmail.com>
Co-authored-by: SunStorm <me@sunstorm.rocks>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: chiral-lab <jan.eltner@googlemail.com>
Co-authored-by: chrizl <chrizl@gmail.com>
Co-authored-by: resi23 <x-resistant-x@gmx.de>
Co-authored-by: slammingdeath <sebastianbrudny97@gmail.com>
Co-authored-by: ube <ube@alienautopsy.net>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
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/sv/
Translation: Servarr/Prowlarr
2023-12-31 10:39:03 +02:00
Weblate
bdfbda3805 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dimitri <dimitridroeck@gmail.com>
Co-authored-by: Koch Norbert <kochnorbert@icloud.com>
Co-authored-by: Pietro Ribeiro <xxb1exuv6@mozmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: chrizl <chrizl@gmail.com>
Co-authored-by: resi23 <x-resistant-x@gmx.de>
Co-authored-by: slammingdeath <sebastianbrudny97@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
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/
Translation: Servarr/Prowlarr
2023-12-31 07:26:54 +02:00
Stevie Robinson
cb98b10468 Translate fields on the backend
(cherry picked from commit 48b12f5b00429a7cd218d23f0544641b0da62a06)
2023-12-31 07:16:38 +02:00
Mark McDowall
ae1bc8366c New: Add qBittorrent option for Content Layout
(cherry picked from commit 4b22200708ca120cfdcf9cb796be92183adb95d1)
2023-12-31 06:46:16 +02:00
Bogdan
67eeb4373c Fixed: Ignore empty tags when adding items to Flood
(cherry picked from commit 0a5200766ea80fc1c97bfa497cdfed31b9af687f)
2023-12-31 06:39:20 +02:00
Bogdan
9d40a64be4 Bump version to 1.12.0 2023-12-31 06:36:27 +02:00
Bogdan
20cc146d82 Fixed: Don't die when grabbing releases in bulk 2023-12-30 14:40:04 +02:00
Bogdan
8beff32117 Minor cleanup in AudioBookBay 2023-12-25 14:14:22 +02:00
Bogdan
8b7c488173 New: (Discord) Remove size grab field 2023-12-25 14:14:15 +02:00
Bogdan
1dabbc94f8 Fixed: BinSearch removed 2023-12-24 22:44:34 +02:00
Bogdan
52667b979b Bump version to 1.11.4 2023-12-24 09:13:43 +02:00
Weblate
14f9a75a73 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Aitzol Garmendia <aitzolgarmendia@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translation: Servarr/Prowlarr
2023-12-21 02:34:36 +02:00
Weblate
ed17d91a7b Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: ηg <jonas.konrath@icloud.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translation: Servarr/Prowlarr
2023-12-19 20:02:09 +02:00
Bogdan
f54280b888 Bump version to 1.11.3 2023-12-17 15:53:28 +02:00
Bogdan
7890ef6f9d Remove invalid Turkish translations 2023-12-17 04:01:35 +02:00
Weblate
df8e4e5acb Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: lifeisfreedom048 <koyuncu.ozgur@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/tr/
Translation: Servarr/Prowlarr
2023-12-17 04:00:13 +02:00
Agneev Mukherjee
1b36951879 Enable browser navigation buttons for PWA
(cherry picked from commit da9a60691f363323565a293ed9eaeb6349ceccb6)
2023-12-16 15:44:25 +02:00
Weblate
d8d5170ab8 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Menno Liefstingh <mennoliefstingh@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
Translation: Servarr/Prowlarr
2023-12-15 16:43:07 +02:00
Bogdan
a443a87603 Fixed: (AvistaZ) Disable pagination and implement MST formula 2023-12-14 20:37:31 +02:00
Weblate
df18ee77e7 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: ROSERAT Ugo <roserat.ugo@gmail.com>
Co-authored-by: RicardoVelaC <ricardovelac@gmail.com>
Co-authored-by: SHUAI.W <x@ousui.org>
Co-authored-by: matiasba <matiasabarros@hotmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/lv/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2023-12-14 14:20:36 +02:00
Bogdan
426159b452 Fixed: (RetroFlix) Remove unrelated results 2023-12-13 21:41:13 +02:00
Bogdan
8704bef69a Update year format in titles for RED/OPS 2023-12-13 19:49:54 +02:00
Bogdan
0f1b01adab Log invalid torrent files contents as info 2023-12-12 16:53:06 +02:00
Qstick
4dbf5aa9f4 Fixed: Correctly handle Migration when PG Host has ".db"
(cherry picked from commit 97ee24507f4306e3b62c3d00cd3ade6a09d1b957)
2023-12-12 15:49:54 +02:00
Bogdan
e1264d7cda Implement DatabaseConnectionInfo
Co-authored-by: Qstick <qstick@gmail.com>
2023-12-12 15:49:50 +02:00
Bogdan
d4bbb2e14a Fixed: (FL/BHD/IPT) Filter releases by query 2023-12-10 20:06:33 +02:00
Servarr
a2395dccb5 Automated API Docs update 2023-12-10 16:08:08 +02:00
Bogdan
df89450428 New: External links for search results 2023-12-10 16:02:24 +02:00
Bogdan
7b5e1f40ba Bump version to 1.11.2 2023-12-10 13:44:44 +02:00
Bogdan
fe142b8a1c Fixed: (IPTorrents) Parse size column using index by name 2023-12-09 23:22:33 +02:00
Weblate
8c7f73ed16 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Jurriaan Den Toonder <jur.den.toonder@gmail.com>
Co-authored-by: RicardoVelaC <ricardovelac@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
Translation: Servarr/Prowlarr
2023-12-08 16:02:46 +02:00
Weblate
a4ae800603 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Augusto Poletti <augustpolet@gmail.com>
Co-authored-by: Dominika Matějková <dominika.matejkova@outlook.cz>
Co-authored-by: VisoTC <szlytlyt@outlook.com>
Co-authored-by: 米大饭 <1246333567@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2023-12-06 16:01:59 +02:00
Bogdan
e5d7a21714 New: (Discord) Add size grab field and improve field selection 2023-12-06 10:24:08 +02:00
Bogdan
67355ec07b Fixed: (aro.lol) Removed, site has shutdown 2023-12-05 14:00:02 +02:00
Bogdan
43d56f6817 New: (NorBits) Add FreeLeech Only option 2023-12-04 15:58:10 +02:00
Bogdan
515f4dff8b New: (NorBits) Login with 2FA support 2023-12-04 15:46:15 +02:00
Bogdan
c3aa377ed5 Fixed: (PTP) Parse half leech 2023-12-04 10:41:29 +02:00
Mark McDowall
fbc3c09094 Always validate Custom Script path
(cherry picked from commit c922cc5dc617dd776d4523cbf62376821c5a4ad9)
2023-12-03 20:38:54 +02:00
Maksym Ivanchenko
b88f8e5fde Fixed: (Toloka) Support RAW search (#1944) 2023-12-03 09:37:39 +02:00
Bogdan
aaf4c73c83 Bump version to 1.11.1 2023-12-03 08:46:25 +02:00
Bogdan
7c38ec2baa Fixed: (AvistaZ) Improve message for failed logins 2023-12-02 12:07:40 +02:00
Bogdan
dcfdd49119 Rollback YamlDotNet 2023-12-02 11:30:59 +02:00
Weblate
badb9dee61 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Appoxo <appoxo@appoxo.de>
Co-authored-by: David Molero <contact@dolvem.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Patatra <patrice.chevreau@gmail.com>
Co-authored-by: Zalhera <tobias.bechen@gmail.com>
Co-authored-by: liimee <git.taaa@fedora.email>
Co-authored-by: resi23 <x-resistant-x@gmx.de>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/id/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translation: Servarr/Prowlarr
2023-12-01 04:03:11 +02:00
Bogdan
12ca705556 Fixed: (AvistaZ) Increase rate limit and use workaround for Retry-After in auth 2023-11-30 01:57:32 +02:00
Bogdan
a0d0e3e232 Fixed: (OPS) Use canUseToken for download links when using Use Freeleech Tokens 2023-11-29 08:22:17 +02:00
Bogdan
e12cd68772 Fixed: (RED/OPS) Add log score to release titles 2023-11-29 08:18:24 +02:00
Bogdan
9dc117191e Minor cleanup for BTN 2023-11-28 09:22:13 +02:00
Bogdan
fdaca64d60 Fixed: (BTN) Add MST, indexer flags and parse IMDb ID 2023-11-28 03:54:47 +02:00
Bogdan
6d134750ff New: (Redacted) Add Freeload Only option 2023-11-27 12:06:18 +02:00
Bogdan
bbf9945b49 Add missing CSS typing 2023-11-27 05:34:03 +02:00
Bogdan
b66a85269f Add color to links in info inputs 2023-11-27 03:02:03 +02:00
Bogdan
81e9c29d54 Bump version to 1.11.0 2023-11-26 22:46:36 +02:00
Weblate
3df33e1a86 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Appoxo <appoxo@appoxo.de>
Co-authored-by: Charlie <zola@zipmail.pw>
Co-authored-by: Dimitri <dimitridroeck@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: RicardoVelaC <ricardovelac@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/pt_BR/
Translation: Servarr/Prowlarr
2023-11-25 22:32:05 +02:00
Stevie Robinson
c19802c471 New: Remove defunct Boxcar notifications
(cherry picked from commit c6ad2396bb98dc8eb1ad47bf5d066b227a47f8b5)
2023-11-25 22:27:12 +02:00
Bogdan
36c8806f88 New: Add Freeleech Only for HDS, iS and XS 2023-11-25 22:22:56 +02:00
Bogdan
0d69a42ae0 Fix Localization test 2023-11-25 05:59:03 +02:00
Stevie Robinson
fc482d4808 Fixed: Fallback to English translations if invalid UI language in config
(cherry picked from commit 4c7201741276eccaea2fb1f33daecc31e8b2d54e)
2023-11-25 05:30:23 +02:00
Mark McDowall
fab4bd5ead Initial support to use named tokens for backend translations
(cherry picked from commit 11f96c31048c2d1aafca0c91736d439f7f9a95a8)
2023-11-25 05:18:48 +02:00
Bogdan
9c243c7e0d Fixed: (Redacted) Treat freeload torrents as neutral leech
Towards #1933
2023-11-24 22:36:40 +02:00
Bogdan
c107011659 Use album year in Redacted/Orpheus 2023-11-24 22:35:56 +02:00
Bogdan
c690e9a50a New: Add NeutralLeech to indexer flags 2023-11-24 22:31:00 +02:00
Bogdan
c4b0ecdafe Bump dotnet to 6.0.25 2023-11-23 23:44:05 +02:00
Bogdan
f2a709f725 Remove OZnzb from default definitions
Fixes #1932
2023-11-23 23:09:04 +02:00
Bogdan
01a9799ce8 BB has shutdown 2023-11-23 05:53:52 +02:00
Bogdan
8ac68e8946 Fixed: (Cardigann) Use of the default value for new select settings
Fixes #1929
2023-11-23 04:11:56 +02:00
Bogdan
6cb787c17b Fix title in tests 2023-11-22 07:57:37 +02:00
Bogdan
51259ceb30 Fixed: (HDBits) Enable Use Filenames by default 2023-11-22 07:26:43 +02:00
Weblate
92cf329174 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/el/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/es/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/he/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/is/
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/th/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/tr/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/vi/
Translation: Servarr/Prowlarr
2023-11-21 17:49:53 +02:00
Bogdan
257c9ab248 Return 429 right away after release search 2023-11-20 02:38:26 +02:00
Servarr
cad42fd005 Automated API Docs update 2023-11-19 22:39:39 +02:00
Bogdan
d7927f60fe Cleanup appName tokens
Co-authored-by: Stevie Robinson <stevie.robinson@gmail.com>
2023-11-19 22:32:43 +02:00
Mark McDowall
26a657fa77 New: Require password confirmation when setting or changing password
(cherry picked from commit b248163df598dc611ee919d525eb7357256d73d5)
2023-11-19 22:20:29 +02:00
Mark McDowall
7fa4daae9b Fixed: Disable SSL when using the removed SslCertHash configuration
(cherry picked from commit d95660d3c78d1ee11a7966d58e78a82a8df01393)
2023-11-19 22:18:32 +02:00
Stevie Robinson
e1b063eaa5 Fixed: Replacing 'appName' translation token
(cherry picked from commit 2e51b8792db0d3ec402672dc92c95f3cb886ef44)
2023-11-19 22:17:28 +02:00
Bogdan
75f00436ec Bump version to 1.10.5 2023-11-19 07:05:56 +02:00
Bogdan
343d7088c9 Fixed: Don't die on info indexer when the definition is missing 2023-11-19 01:36:50 +02:00
Bogdan
709dfe453b Fix AB tests 2023-11-18 06:08:12 +02:00
Bogdan
3130fac106 New: (AnimeBytes) Filter old releases on RSS 2023-11-18 05:07:10 +02:00
Weblate
28004dfae1 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Dlgeri123 <bornemiszageri@gmail.com>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Jhonata da Rocha <jhonata182@gmail.com>
Co-authored-by: Jordy <prive@jordyhoebergen.nl>
Co-authored-by: LandonLi <lxx4work@gmail.com>
Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: bai0012 <baicongrui@gmail.com>
Co-authored-by: hpoon <henry.yh.poon@gmail.com>
Co-authored-by: jianl <jianjianfengyun@126.com>
Co-authored-by: 宿命 <331874545@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/cs/
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/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/ko/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/nl/
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/vi/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2023-11-18 03:13:20 +02:00
Bogdan
9b34c89bc8 Fixed: Enforce validation warnings when testing providers
(cherry picked from commit c3b4126d0c4f449a41e2cf7ea438b20e25370995)
2023-11-17 05:26:04 +02:00
Mark McDowall
28e90acd0d Rename 'ReturnUrl' to 'returnUrl' for forms auth redirection
(cherry picked from commit 812712e2843a738054c065a6d5c1b7c81c5f8e7b)
2023-11-17 05:25:41 +02:00
Erik P
9d11d7e17f Fixed: (PTP) Add IMDb ID to TV Search capabilities (#1920)
Co-authored-by: Erik Persson <erik@erikpersson.me>
2023-11-16 19:33:53 +02:00
Bogdan
2cbdb5bcba New: (HDBits) Add Use Filenames option 2023-11-16 18:50:24 +02:00
Bogdan
118bfb8c28 Fixed: (AvistaZ) Increase rate limit to 5 seconds 2023-11-16 18:50:20 +02:00
Bogdan
942477ecf9 Fixed: (HDBits) Add labels for codecs and mediums 2023-11-14 17:21:07 +02:00
Bogdan
4b4589ed27 Improvements to download factor and show Freeleech only for HDB 2023-11-13 16:23:10 +02:00
Bogdan
bd0609639e New: (HDBits) Add pagination support 2023-11-13 02:54:28 +02:00
Bogdan
ccdad3a44c Bump version to 1.10.4 2023-11-12 16:49:35 +02:00
Bogdan
d99da0481b Fix AvistaZ tests 2023-11-12 15:22:35 +02:00
Bogdan
da1965b18e Fixed: (AvistaZ) Fix PublishDate timezone
Fixes #1917
2023-11-12 14:58:56 +02:00
Bogdan
493114f4e8 Fixed: Record status for notifications on tests 2023-11-10 05:34:15 +02:00
Mark McDowall
6969326092 Don't store status results for invalid providers
(cherry picked from commit de23182d593e2284972103d505e66dd8d812dfdb)
(cherry picked from commit 44d8dbaac81706691124ae5f8317289f0a3e5d73)
2023-11-10 04:01:04 +02:00
Bogdan
95f899131d Fix count in OrpheusFixture 2023-11-07 15:38:49 +02:00
Bogdan
0ba4f3e692 Fixed: (Orpheus) Filter old releases on RSS 2023-11-07 15:03:50 +02:00
Bogdan
a7c00a0fd7 Fixed: (Redacted) Filter old releases on RSS 2023-11-07 15:03:49 +02:00
Bogdan
c84ff60ec9 Fixed: (PTP) Add TV search capabilities 2023-11-05 21:00:24 +02:00
Bogdan
b3f6f54e6e Fixed: (PTP) Add support for TV searches 2023-11-05 20:53:21 +02:00
Bogdan
ed272aaf74 Increase the timeout for CheckHealth command 2023-11-05 20:17:02 +02:00
Bogdan
c0b10f889b Prevent NullRef on header assert 2023-11-05 19:58:30 +02:00
Bogdan
bbfb92bbd8 Bump version to 1.10.3 2023-11-05 11:10:44 +02:00
Bogdan
793de05e3d Fixed: (Beyond-HD) Types filtering 2023-11-04 17:36:01 +02:00
Bogdan
1b1f9d16be Remove default definitions for dead indexers 2023-11-04 16:30:40 +02:00
Bogdan
051dea30c2 Fixed: (Beyond-HD) Category filtering 2023-11-04 16:27:39 +02:00
Bogdan
75d8a3d1d0 Fixed: (FileList) Change TZ to account DST 2023-11-02 19:40:38 +02:00
Bogdan
edf41e2ead Bump version to 1.10.2 2023-10-29 10:35:43 +02:00
Bogdan
c15c71386d New: Set busy timeout for SQLite
(cherry picked from commit 192eb7b62ae60f300a9371ce3ed2e0056b5a1f4d)
2023-10-29 01:28:13 +03:00
Bogdan
71a19efd9a Improve appearance of info fields 2023-10-27 20:54:50 +03:00
Bogdan
2c6c0fcc81 Allow 0 as value in download client Id validation 2023-10-25 17:11:48 +03:00
Bogdan
203e2dbb10 Remove mandatory validation for download client in indexers 2023-10-25 16:24:30 +03:00
Bogdan
6169fc2fa3 New: Add Download Client validation for indexers 2023-10-25 15:55:38 +03:00
Bogdan
768ce14afb Fix integration tests for indexers 2023-10-25 14:17:22 +03:00
Servarr
31d32e8c30 Automated API Docs update 2023-10-25 13:38:07 +03:00
Bogdan
7a61761b2b Add schema endpoint for app profiles 2023-10-25 13:25:16 +03:00
Bogdan
3963807c96 New: Add App Profile validation for indexers
Fixes #1903
2023-10-25 13:04:02 +03:00
Bogdan
e0f6726a3d Fixed: Detect Raw search in Generic Torznab/Newznab feeds
Fixes #1895
2023-10-25 11:51:31 +03:00
Weblate
dd25bff3d6 Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Dlgeri123 <bornemiszageri@gmail.com>
Co-authored-by: Fixer <ygj59783@zslsz.com>
Co-authored-by: Jhonata da Rocha <jhonata182@gmail.com>
Co-authored-by: LandonLi <lxx4work@gmail.com>
Co-authored-by: Lizandra Candido da Silva <lizandra.c.s@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: hpoon <henry.yh.poon@gmail.com>
Co-authored-by: jianl <jianjianfengyun@126.com>
Co-authored-by: 宿命 <331874545@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/hu/
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/zh_CN/
Translation: Servarr/Prowlarr
2023-10-25 11:26:16 +03:00
Jordy
d834c4292e Update link to Docker instructions in readme (#1905) 2023-10-25 11:16:13 +03:00
Bogdan
7e8272ec2b Bump version to 1.10.1 2023-10-22 09:32:32 +03:00
Bogdan
62548f32fe Fixed: (FileList) Skip ID searches for daily episodes 2023-10-21 12:46:50 +03:00
Bogdan
db9f061564 Return TV category if season/episode detected in title for PTP 2023-10-20 00:13:55 +03:00
Bogdan
b37d8799a0 Add acronym for PrivateHD 2023-10-16 18:45:06 +03:00
DaftFuzz
4366530409 Fixed: Calculating value for peers filter (#1900)
Co-authored-by: Bogdan <mynameisbogdan@users.noreply.github.com>
2023-10-16 18:21:40 +03:00
Bogdan
c7959f735e Bump version to 1.10.0 2023-10-15 21:46:33 +03:00
Gabe
be3ee00e1f Fixed: Remove Defunct Tracker PirateTheNet
PTN closed down for good.

They had an official announcement to close down on the 14th October.

Aither offered to accept refugees. See /forums/topics/2535 on aither for more context.
Description

The definition is no longer needed as the tracker ceased all operation.
2023-10-15 19:22:06 +03:00
Bogdan
dace1982d6 Fixed: (FileList) Use air date in search query 2023-10-15 17:44:50 +03:00
Bogdan
980bd35f95 Revert dependencies update 2023-10-14 15:34:41 +03:00
Bogdan
4b2f81bee8 Update postcss, webpack and core-js 2023-10-14 15:19:56 +03:00
Bogdan
30eb481c65 Add year to releases for AnimeBytes 2023-10-14 10:17:54 +03:00
Bogdan
29f1c36f54 Prevent NullRef in Nebulance with null TvMazeIds 2023-10-13 18:55:04 +03:00
Bogdan
f1c01343bf New: (Nebulance) Parse TvMazeId and scene attributes 2023-10-13 14:27:32 +03:00
Weblate
bae79b22ad Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: DavidHenryThoreau <sorau@protonmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/fr/
Translation: Servarr/Prowlarr
2023-10-13 11:55:11 +03:00
Bogdan
229d879f86 New: Show indexer categories in Add Indexer modal 2023-10-13 09:39:51 +03:00
bakerboy448
d1cee950a4 Fixed: New Indexer URL for DICMusic 2023-10-13 04:21:09 +03:00
Bogdan
7e32b54547 Fixed: Prevent NullRef in NewznabRequestGenerator for missing definition 2023-10-12 06:50:13 +03:00
Bogdan
b1f7d30021 Fixed: Ignore case when cleansing announce URLs 2023-10-12 05:10:26 +03:00
Mark McDowall
c41a7e0ccc Fixed: Duplicate notifications for failed health checks
(cherry picked from commit c0e54773e213f526a5046fa46aa7b57532471128)

Mock debouncer for testing

(cherry picked from commit bb7b2808e2f70389157408809ec47cc8860b4938)
2023-10-11 03:34:18 +03:00
Weblate
42c533386b Multiple Translations updated by Weblate
ignore-downstream

Co-authored-by: Timo <Tclemens@live.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: blankhang <blankhang@gmail.com>
Co-authored-by: 宿命 <331874545@qq.com>
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/prowlarr/zh_CN/
Translation: Servarr/Prowlarr
2023-10-11 02:52:16 +03:00
Servarr
bdae7a2cdc Automated API Docs update 2023-10-11 02:51:54 +03:00
Mark McDowall
5e8d3542f4 Log executing health check
(cherry picked from commit 78b39bd2fecda60e04a1fef17ae17f62bd2b6914)
2023-10-11 02:33:38 +03:00
Mark McDowall
d9d2aa8493 Sync label styling for FormLabel
(cherry picked from commit 87e0a7983a437a4d166aa8b9c9eaf78ea5431969)
2023-10-11 02:30:18 +03:00
Stevie Robinson
09bf1500d6 New: Additional tooltips for icon buttons
(cherry picked from commit 8c07f0d3d19a48ed96d1ded54399c66bf2977b2a)
2023-10-11 02:24:32 +03:00
Mark McDowall
34464160cb Paging params in API docs
(cherry picked from commit bfaa7291e14a8d3847ef2154a52c363944560803)
2023-10-11 02:17:27 +03:00
Bogdan
bada5fe309 New: Add TorrentNetwork 2023-10-10 13:29:26 +03:00
Bogdan
b088febbc4 Use async for http client test 2023-10-10 03:05:16 +03:00
Bogdan
1a307b8e21 Fix test for http client 2023-10-10 03:02:35 +03:00
Bogdan
32db2af0ea Improved http timeout handling
Co-authored-by: Qstick <qstick@gmail.com>
2023-10-10 02:41:05 +03:00
Bogdan
e602862102 Fixed: (FileList) Remove dead domain 2023-10-08 21:31:38 +03:00
Bogdan
bd5336e4c4 Bump version to 1.9.4 2023-10-08 07:08:25 +03:00
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
370 changed files with 8221 additions and 4387 deletions

View File

@@ -2,7 +2,7 @@
[![Build Status](https://dev.azure.com/Prowlarr/Prowlarr/_apis/build/status/Prowlarr.Prowlarr?branchName=develop)](https://dev.azure.com/Prowlarr/Prowlarr/_build/latest?definitionId=1&branchName=develop)
[![Translated](https://translate.servarr.com/widgets/servarr/-/prowlarr/svg-badge.svg)](https://translate.servarr.com/engage/prowlarr/?utm_source=widget)
[![Docker Pulls](https://img.shields.io/docker/pulls/hotio/prowlarr.svg)](https://wiki.servarr.com/prowlarr/installation#docker)
[![Docker Pulls](https://img.shields.io/docker/pulls/hotio/prowlarr.svg)](https://wiki.servarr.com/prowlarr/installation/docker)
![Github Downloads](https://img.shields.io/github/downloads/Prowlarr/Prowlarr/total.svg)
[![Backers on Open Collective](https://opencollective.com/Prowlarr/backers/badge.svg)](#backers)
[![Sponsors on Open Collective](https://opencollective.com/Prowlarr/sponsors/badge.svg)](#sponsors)

View File

@@ -9,15 +9,15 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '1.8.5'
majorVersion: '1.13.1'
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.417'
nodeVersion: '16.X'
innoVersion: '6.2.0'
innoVersion: '6.2.2'
windowsImage: 'windows-2022'
linuxImage: 'ubuntu-20.04'
macImage: 'macOS-11'
@@ -1206,6 +1206,7 @@ stages:
- stage: Report_Out
dependsOn:
- Analyze
- Installer
- Unit_Test
- Integration
- Automation

View File

@@ -254,7 +254,7 @@ InstallInno()
ProgressStart "Installing portable Inno Setup"
rm -rf _inno
curl -s --output innosetup.exe "https://files.jrsoftware.org/is/6/innosetup-${INNOVERSION:-6.2.0}.exe"
curl -s --output innosetup.exe "https://files.jrsoftware.org/is/6/innosetup-${INNOVERSION:-6.2.2}.exe"
mkdir _inno
./innosetup.exe //portable=1 //silent //currentuser //dir=.\\_inno
rm innosetup.exe

View File

@@ -2,16 +2,18 @@ const loose = true;
module.exports = {
plugins: [
'@babel/plugin-transform-logical-assignment-operators',
// 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

@@ -65,12 +65,12 @@ function AppUpdatedModalContent(props) {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{translate('AppUpdated', { appName: 'Prowlarr' })}
{translate('AppUpdated')}
</ModalHeader>
<ModalBody>
<div>
<InlineMarkdown data={translate('AppUpdatedVersion', { appName: 'Prowlarr', version })} blockClassName={styles.version} />
<InlineMarkdown data={translate('AppUpdatedVersion', { version })} blockClassName={styles.version} />
</div>
{

View File

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

View File

@@ -1,4 +1,5 @@
import SortDirection from 'Helpers/Props/SortDirection';
import { FilterBuilderProp } from './AppState';
export interface Error {
responseJSON: {
@@ -20,6 +21,10 @@ export interface PagedAppSectionState {
pageSize: number;
}
export interface AppSectionFilterState<T> {
filterBuilderProps: FilterBuilderProp<T>[];
}
export interface AppSectionSchemaState<T> {
isSchemaFetching: boolean;
isSchemaPopulated: boolean;

View File

@@ -1,5 +1,7 @@
import CommandAppState from './CommandAppState';
import HistoryAppState from './HistoryAppState';
import IndexerAppState, {
IndexerHistoryAppState,
IndexerIndexAppState,
IndexerStatusAppState,
} from './IndexerAppState';
@@ -42,6 +44,8 @@ export interface CustomFilter {
interface AppState {
commands: CommandAppState;
history: HistoryAppState;
indexerHistory: IndexerHistoryAppState;
indexerIndex: IndexerIndexAppState;
indexerStats: IndexerStatsAppState;
indexerStatus: IndexerStatusAppState;

View File

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

View File

@@ -1,6 +1,7 @@
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,
@@ -34,4 +35,6 @@ interface IndexerAppState
export type IndexerStatusAppState = AppSectionState<IndexerStatus>;
export type IndexerHistoryAppState = AppSectionState<History>;
export default IndexerAppState;

View File

@@ -1,9 +1,11 @@
import { AppSectionItemState } from 'App/State/AppSectionState';
import { Filter } from 'App/State/AppState';
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[];
}

View File

@@ -3,6 +3,7 @@ import AppSectionState, {
AppSectionItemState,
AppSectionSaveState,
} from 'App/State/AppSectionState';
import { IndexerCategory } from 'Indexer/Indexer';
import Application from 'typings/Application';
import DownloadClient from 'typings/DownloadClient';
import Notification from 'typings/Notification';
@@ -25,6 +26,11 @@ export interface DownloadClientAppState
AppSectionDeleteState,
AppSectionSaveState {}
export interface IndexerCategoryAppState
extends AppSectionState<IndexerCategory>,
AppSectionDeleteState,
AppSectionSaveState {}
export interface NotificationAppState
extends AppSectionState<Notification>,
AppSectionDeleteState {}
@@ -35,6 +41,7 @@ interface SettingsAppState {
appProfiles: AppProfileAppState;
applications: ApplicationAppState;
downloadClients: DownloadClientAppState;
indexerCategories: IndexerCategoryAppState;
notifications: NotificationAppState;
ui: UiSettingsAppState;
}

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

@@ -0,0 +1,41 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
import { IndexerCategory } from 'Indexer/Indexer';
import FilterBuilderRowValue from './FilterBuilderRowValue';
import FilterBuilderRowValueProps from './FilterBuilderRowValueProps';
const indexerCategoriesSelector = createSelector(
(state: AppState) => state.settings.indexerCategories,
(categories) => categories.items
);
function CategoryFilterBuilderRowValue(props: FilterBuilderRowValueProps) {
const categories: IndexerCategory[] = useSelector(indexerCategoriesSelector);
const tagList = categories.reduce(
(acc: { id: number; name: string }[], element) => {
acc.push({
id: element.id,
name: `${element.name} (${element.id})`,
});
if (element.subCategories && element.subCategories.length > 0) {
element.subCategories.forEach((subCat) => {
acc.push({
id: subCat.id,
name: `${subCat.name} (${subCat.id})`,
});
});
}
return acc;
},
[]
);
return <FilterBuilderRowValue {...props} tagList={tagList} />;
}
export default CategoryFilterBuilderRowValue;

View File

@@ -5,8 +5,10 @@ import IconButton from 'Components/Link/IconButton';
import { filterBuilderTypes, filterBuilderValueTypes, icons } from 'Helpers/Props';
import AppProfileFilterBuilderRowValueConnector from './AppProfileFilterBuilderRowValueConnector';
import BoolFilterBuilderRowValue from './BoolFilterBuilderRowValue';
import CategoryFilterBuilderRowValue from './CategoryFilterBuilderRowValue';
import DateFilterBuilderRowValue from './DateFilterBuilderRowValue';
import FilterBuilderRowValueConnector from './FilterBuilderRowValueConnector';
import HistoryEventTypeFilterBuilderRowValue from './HistoryEventTypeFilterBuilderRowValue';
import IndexerFilterBuilderRowValueConnector from './IndexerFilterBuilderRowValueConnector';
import PrivacyFilterBuilderRowValue from './PrivacyFilterBuilderRowValue';
import ProtocolFilterBuilderRowValue from './ProtocolFilterBuilderRowValue';
@@ -55,9 +57,15 @@ function getRowValueConnector(selectedFilterBuilderProp) {
case filterBuilderValueTypes.BOOL:
return BoolFilterBuilderRowValue;
case filterBuilderValueTypes.CATEGORY:
return CategoryFilterBuilderRowValue;
case filterBuilderValueTypes.DATE:
return DateFilterBuilderRowValue;
case filterBuilderValueTypes.HISTORY_EVENT_TYPE:
return HistoryEventTypeFilterBuilderRowValue;
case filterBuilderValueTypes.INDEXER:
return IndexerFilterBuilderRowValueConnector;

View File

@@ -0,0 +1,16 @@
import { FilterBuilderProp } from 'App/State/AppState';
interface FilterBuilderRowOnChangeProps {
name: string;
value: unknown[];
}
interface FilterBuilderRowValueProps {
filterType?: string;
filterValue: string | number | object | string[] | number[] | object[];
selectedFilterBuilderProp: FilterBuilderProp<unknown>;
sectionItem: unknown[];
onChange: (payload: FilterBuilderRowOnChangeProps) => void;
}
export default FilterBuilderRowValueProps;

View File

@@ -0,0 +1,39 @@
import React from 'react';
import translate from 'Utilities/String/translate';
import FilterBuilderRowValue from './FilterBuilderRowValue';
import FilterBuilderRowValueProps from './FilterBuilderRowValueProps';
const EVENT_TYPE_OPTIONS = [
{
id: 1,
get name() {
return translate('Grabbed');
},
},
{
id: 3,
get name() {
return translate('IndexerRss');
},
},
{
id: 2,
get name() {
return translate('IndexerQuery');
},
},
{
id: 4,
get name() {
return translate('IndexerAuth');
},
},
];
function HistoryEventTypeFilterBuilderRowValue(
props: FilterBuilderRowValueProps
) {
return <FilterBuilderRowValue {...props} tagList={EVENT_TYPE_OPTIONS} />;
}
export default HistoryEventTypeFilterBuilderRowValue;

View File

@@ -30,22 +30,24 @@ function CustomFiltersModalContent(props) {
<ModalBody>
{
customFilters.map((customFilter) => {
return (
<CustomFilter
key={customFilter.id}
id={customFilter.id}
label={customFilter.label}
filters={customFilter.filters}
selectedFilterKey={selectedFilterKey}
isDeleting={isDeleting}
deleteError={deleteError}
dispatchSetFilter={dispatchSetFilter}
dispatchDeleteCustomFilter={dispatchDeleteCustomFilter}
onEditPress={onEditCustomFilter}
/>
);
})
customFilters
.sort((a, b) => a.label.localeCompare(b.label))
.map((customFilter) => {
return (
<CustomFilter
key={customFilter.id}
id={customFilter.id}
label={customFilter.label}
filters={customFilter.filters}
selectedFilterKey={selectedFilterKey}
isDeleting={isDeleting}
deleteError={deleteError}
dispatchSetFilter={dispatchSetFilter}
dispatchDeleteCustomFilter={dispatchDeleteCustomFilter}
onEditPress={onEditCustomFilter}
/>
);
})
}
<div className={styles.addButtonContainer}>

View File

@@ -147,7 +147,7 @@ EnhancedSelectInputConnector.propTypes = {
provider: PropTypes.string.isRequired,
providerData: PropTypes.object.isRequired,
name: PropTypes.string.isRequired,
value: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])).isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.arrayOf(PropTypes.string), PropTypes.arrayOf(PropTypes.number)]).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
selectOptionsProviderAction: PropTypes.string,
onChange: PropTypes.func.isRequired,

View File

@@ -2,8 +2,10 @@
display: flex;
justify-content: flex-end;
margin-right: $formLabelRightMarginWidth;
padding-top: 8px;
min-height: 35px;
text-align: end;
font-weight: bold;
line-height: 35px;
}
.hasError {

View File

@@ -0,0 +1,13 @@
.message {
composes: alert from '~Components/Alert.css';
a {
color: var(--linkColor);
text-decoration: none;
&:hover {
color: var(--linkHoverColor);
text-decoration: underline;
}
}
}

View File

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

View File

@@ -1,5 +1,8 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import { kinds } from 'Helpers/Props';
import styles from './InfoInput.css';
class InfoInput extends Component {
@@ -7,12 +10,15 @@ class InfoInput extends Component {
// Render
render() {
const {
value
} = this.props;
const { value } = this.props;
return (
<span dangerouslySetInnerHTML={{ __html: value }} />
<Alert
kind={kinds.INFO}
className={styles.message}
>
<span dangerouslySetInnerHTML={{ __html: value }} />
</Alert>
);
}
}

View File

@@ -40,18 +40,26 @@ class FilterMenuContent extends Component {
}
{
customFilters.map((filter) => {
return (
<FilterMenuItem
key={filter.id}
filterKey={filter.id}
selectedFilterKey={selectedFilterKey}
onPress={onFilterSelect}
>
{filter.label}
</FilterMenuItem>
);
})
customFilters.length > 0 ?
<MenuItemSeparator /> :
null
}
{
customFilters
.sort((a, b) => a.label.localeCompare(b.label))
.map((filter) => {
return (
<FilterMenuItem
key={filter.id}
filterKey={filter.id}
selectedFilterKey={selectedFilterKey}
onPress={onFilterSelect}
>
{filter.label}
</FilterMenuItem>
);
})
}
{

View File

@@ -3,6 +3,7 @@ import React from 'react';
import Icon from 'Components/Icon';
import Link from 'Components/Link/Link';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './ModalContent.css';
function ModalContent(props) {
@@ -28,6 +29,7 @@ function ModalContent(props) {
<Icon
name={icons.CLOSE}
size={18}
title={translate('Close')}
/>
</Link>
}

View File

@@ -78,6 +78,7 @@ class PageHeader extends Component {
aria-label="Donate"
to="https://prowlarr.com/donate"
size={14}
title={translate('Donate')}
/>
<IconButton
className={styles.translate}

View File

@@ -24,6 +24,7 @@ function PageHeaderActionsMenu(props) {
<MenuButton className={styles.menuButton} aria-label="Menu Button">
<Icon
name={icons.INTERACTIVE}
title={translate('Menu')}
/>
</MenuButton>

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

@@ -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

@@ -15,5 +15,5 @@
"start_url": "../../../../",
"theme_color": "#3a3f51",
"background_color": "#3a3f51",
"display": "standalone"
"display": "minimal-ui"
}

View File

@@ -34,7 +34,8 @@ function AuthenticationRequiredModalContent(props) {
authenticationMethod,
authenticationRequired,
username,
password
password,
passwordConfirmation
} = settings;
const authenticationEnabled = authenticationMethod && authenticationMethod.value !== 'none';
@@ -63,7 +64,7 @@ function AuthenticationRequiredModalContent(props) {
className={styles.authRequiredAlert}
kind={kinds.WARNING}
>
{translate('AuthenticationRequiredWarning', { appName: 'Prowlarr' })}
{translate('AuthenticationRequiredWarning')}
</Alert>
{
@@ -76,7 +77,7 @@ function AuthenticationRequiredModalContent(props) {
type={inputTypes.SELECT}
name="authenticationMethod"
values={authenticationMethodOptions}
helpText={translate('AuthenticationMethodHelpText', { appName: 'Prowlarr' })}
helpText={translate('AuthenticationMethodHelpText')}
helpTextWarning={authenticationMethod.value === 'none' ? translate('AuthenticationMethodHelpTextWarning') : undefined}
helpLink="https://wiki.servarr.com/prowlarr/faq#forced-authentication"
onChange={onInputChange}
@@ -120,6 +121,18 @@ function AuthenticationRequiredModalContent(props) {
{...password}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('PasswordConfirmation')}</FormLabel>
<FormInputGroup
type={inputTypes.PASSWORD}
name="passwordConfirmation"
onChange={onInputChange}
helpTextWarning={passwordConfirmation?.value ? undefined : translate('AuthenticationRequiredPasswordConfirmationHelpTextWarning')}
{...passwordConfirmation}
/>
</FormGroup>
</div> :
null
}

View File

@@ -2,9 +2,10 @@ export const BOOL = 'bool';
export const BYTES = 'bytes';
export const DATE = 'date';
export const DEFAULT = 'default';
export const HISTORY_EVENT_TYPE = 'historyEventType';
export const INDEXER = 'indexer';
export const PROTOCOL = 'protocol';
export const PRIVACY = 'privacy';
export const APP_PROFILE = 'appProfile';
export const MOVIE_STATUS = 'movieStatus';
export const CATEGORY = 'category';
export const TAG = 'tag';

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

@@ -15,6 +15,7 @@ import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptions
import TablePager from 'Components/Table/TablePager';
import { align, icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import HistoryFilterModal from './HistoryFilterModal';
import HistoryOptionsConnector from './HistoryOptionsConnector';
import HistoryRowConnector from './HistoryRowConnector';
@@ -63,6 +64,7 @@ class History extends Component {
columns,
selectedFilterKey,
filters,
customFilters,
totalRecords,
onFilterSelect,
onFirstPagePress,
@@ -108,7 +110,8 @@ class History extends Component {
alignMenu={align.RIGHT}
selectedFilterKey={selectedFilterKey}
filters={filters}
customFilters={[]}
customFilters={customFilters}
filterModalConnectorComponent={HistoryFilterModal}
onFilterSelect={onFilterSelect}
/>
</PageToolbarSection>
@@ -193,8 +196,9 @@ History.propTypes = {
indexersError: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
selectedFilterKey: PropTypes.string.isRequired,
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
totalRecords: PropTypes.number,
onFilterSelect: PropTypes.func.isRequired,
onFirstPagePress: PropTypes.func.isRequired,

View File

@@ -6,6 +6,7 @@ import * as commandNames from 'Commands/commandNames';
import withCurrentPage from 'Components/withCurrentPage';
import { executeCommand } from 'Store/Actions/commandActions';
import * as historyActions from 'Store/Actions/historyActions';
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
import History from './History';
@@ -14,13 +15,15 @@ function createMapStateToProps() {
return createSelector(
(state) => state.history,
(state) => state.indexers,
createCustomFiltersSelector('history'),
createCommandExecutingSelector(commandNames.CLEAR_HISTORY),
(history, indexers, isHistoryClearing) => {
(history, indexers, customFilters, isHistoryClearing) => {
return {
isIndexersFetching: indexers.isFetching,
isIndexersPopulated: indexers.isPopulated,
indexersError: indexers.error,
isHistoryClearing,
customFilters,
...history
};
}

View File

@@ -0,0 +1,54 @@
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 { setHistoryFilter } from 'Store/Actions/historyActions';
function createHistorySelector() {
return createSelector(
(state: AppState) => state.history.items,
(queueItems) => {
return queueItems;
}
);
}
function createFilterBuilderPropsSelector() {
return createSelector(
(state: AppState) => state.history.filterBuilderProps,
(filterBuilderProps) => {
return filterBuilderProps;
}
);
}
interface HistoryFilterModalProps {
isOpen: boolean;
}
export default function HistoryFilterModal(props: HistoryFilterModalProps) {
const sectionItems = useSelector(createHistorySelector());
const filterBuilderProps = useSelector(createFilterBuilderPropsSelector());
const customFilterType = 'history';
const dispatch = useDispatch();
const dispatchSetFilter = useCallback(
(payload: unknown) => {
dispatch(setHistoryFilter(payload));
},
[dispatch]
);
return (
<FilterModal
// TODO: Don't spread all the props
{...props}
sectionItems={sectionItems}
filterBuilderProps={filterBuilderProps}
customFilterType={customFilterType}
dispatchSetFilter={dispatchSetFilter}
/>
);
}

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,7 +14,7 @@ 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' },
@@ -353,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

@@ -1,6 +1,7 @@
import PropTypes from 'prop-types';
import React from 'react';
import Modal from 'Components/Modal/Modal';
import { sizes } from 'Helpers/Props';
import AddIndexerModalContentConnector from './AddIndexerModalContentConnector';
import styles from './AddIndexerModal.css';
@@ -8,6 +9,7 @@ function AddIndexerModal({ isOpen, onModalClose, onSelectIndexer, ...otherProps
return (
<Modal
isOpen={isOpen}
size={sizes.EXTRA_LARGE}
onModalClose={onModalClose}
className={styles.modal}
>

View File

@@ -16,7 +16,7 @@ import TableBody from 'Components/Table/TableBody';
import { kinds, scrollDirections } from 'Helpers/Props';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
import translate from 'Utilities/String/translate';
import SelectIndexerRowConnector from './SelectIndexerRowConnector';
import SelectIndexerRow from './SelectIndexerRow';
import styles from './AddIndexerModalContent.css';
const columns = [
@@ -49,6 +49,12 @@ const columns = [
label: () => translate('Privacy'),
isSortable: true,
isVisible: true
},
{
name: 'categories',
label: () => translate('Categories'),
isSortable: false,
isVisible: true
}
];
@@ -260,7 +266,7 @@ class AddIndexerModalContent extends Component {
<TableBody>
{
filteredIndexers.map((indexer) => (
<SelectIndexerRowConnector
<SelectIndexerRow
key={`${indexer.implementation}-${indexer.name}`}
implementation={indexer.implementation}
implementationName={indexer.implementationName}

View File

@@ -1,15 +1,18 @@
import { some } from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchIndexerSchema, selectIndexerSchema, setIndexerSchemaSort } from 'Store/Actions/indexerActions';
import createAllIndexersSelector from 'Store/Selectors/createAllIndexersSelector';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import AddIndexerModalContent from './AddIndexerModalContent';
function createMapStateToProps() {
return createSelector(
createClientSideCollectionSelector('indexers.schema'),
(indexers) => {
createAllIndexersSelector(),
(indexers, allIndexers) => {
const {
isFetching,
isPopulated,
@@ -19,11 +22,19 @@ function createMapStateToProps() {
sortKey
} = indexers;
const indexerList = items.map((item) => {
const { definitionName } = item;
return {
...item,
isExistingIndexer: some(allIndexers, { definitionName })
};
});
return {
isFetching,
isPopulated,
error,
indexers: items,
indexers: indexerList,
sortKey,
sortDirection
};

View File

@@ -1,90 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Icon from 'Components/Icon';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRowButton from 'Components/Table/TableRowButton';
import { icons } from 'Helpers/Props';
import ProtocolLabel from 'Indexer/Index/Table/ProtocolLabel';
import firstCharToUpper from 'Utilities/String/firstCharToUpper';
import translate from 'Utilities/String/translate';
import styles from './SelectIndexerRow.css';
class SelectIndexerRow extends Component {
//
// Listeners
onPress = () => {
const {
implementation,
implementationName,
name
} = this.props;
this.props.onIndexerSelect({ implementation, implementationName, name });
};
//
// Render
render() {
const {
protocol,
privacy,
name,
language,
description,
isExistingIndexer
} = this.props;
return (
<TableRowButton onPress={this.onPress}>
<TableRowCell className={styles.protocol}>
<ProtocolLabel
protocol={protocol}
/>
</TableRowCell>
<TableRowCell>
{name}
{
isExistingIndexer ?
<Icon
className={styles.alreadyExistsIcon}
name={icons.CHECK_CIRCLE}
size={15}
title={translate('IndexerAlreadySetup')}
/> :
null
}
</TableRowCell>
<TableRowCell>
{language}
</TableRowCell>
<TableRowCell>
{description}
</TableRowCell>
<TableRowCell>
{translate(firstCharToUpper(privacy))}
</TableRowCell>
</TableRowButton>
);
}
}
SelectIndexerRow.propTypes = {
name: PropTypes.string.isRequired,
protocol: PropTypes.string.isRequired,
privacy: PropTypes.string.isRequired,
language: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
implementation: PropTypes.string.isRequired,
implementationName: PropTypes.string.isRequired,
onIndexerSelect: PropTypes.func.isRequired,
isExistingIndexer: PropTypes.bool.isRequired
};
export default SelectIndexerRow;

View File

@@ -0,0 +1,75 @@
import React, { useCallback } from 'react';
import Icon from 'Components/Icon';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRowButton from 'Components/Table/TableRowButton';
import { icons } from 'Helpers/Props';
import CapabilitiesLabel from 'Indexer/Index/Table/CapabilitiesLabel';
import ProtocolLabel from 'Indexer/Index/Table/ProtocolLabel';
import { IndexerCapabilities } from 'Indexer/Indexer';
import firstCharToUpper from 'Utilities/String/firstCharToUpper';
import translate from 'Utilities/String/translate';
import styles from './SelectIndexerRow.css';
interface SelectIndexerRowProps {
name: string;
protocol: string;
privacy: string;
language: string;
description: string;
capabilities: IndexerCapabilities;
implementation: string;
implementationName: string;
isExistingIndexer: boolean;
onIndexerSelect(...args: unknown[]): void;
}
function SelectIndexerRow(props: SelectIndexerRowProps) {
const {
name,
protocol,
privacy,
language,
description,
capabilities,
implementation,
implementationName,
isExistingIndexer,
onIndexerSelect,
} = props;
const onPress = useCallback(() => {
onIndexerSelect({ implementation, implementationName, name });
}, [implementation, implementationName, name, onIndexerSelect]);
return (
<TableRowButton onPress={onPress}>
<TableRowCell className={styles.protocol}>
<ProtocolLabel protocol={protocol} />
</TableRowCell>
<TableRowCell>
{name}
{isExistingIndexer ? (
<Icon
className={styles.alreadyExistsIcon}
name={icons.CHECK_CIRCLE}
size={15}
title={translate('IndexerAlreadySetup')}
/>
) : null}
</TableRowCell>
<TableRowCell>{language}</TableRowCell>
<TableRowCell>{description}</TableRowCell>
<TableRowCell>{translate(firstCharToUpper(privacy))}</TableRowCell>
<TableRowCell>
<CapabilitiesLabel capabilities={capabilities} />
</TableRowCell>
</TableRowButton>
);
}
export default SelectIndexerRow;

View File

@@ -1,18 +0,0 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createExistingIndexerSelector from 'Store/Selectors/createExistingIndexerSelector';
import SelectIndexerRow from './SelectIndexerRow';
function createMapStateToProps() {
return createSelector(
createExistingIndexerSelector(),
(isExistingIndexer, dimensions) => {
return {
isExistingIndexer
};
}
);
}
export default connect(createMapStateToProps)(SelectIndexerRow);

View File

@@ -187,6 +187,7 @@ function EditIndexerModalContent(props) {
type={inputTypes.TAG}
name="tags"
helpText={translate('IndexerTagsHelpText')}
helpTextWarning={translate('IndexerTagsHelpTextWarning')}
{...tags}
onChange={onInputChange}
/>

View File

@@ -22,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,
@@ -98,6 +98,15 @@ 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({
@@ -303,6 +312,7 @@ const IndexerIndex = withScrollPosition((props: IndexerIndexProps) => {
jumpToCharacter={jumpToCharacter}
isSelectMode={isSelectMode}
isSmallScreen={isSmallScreen}
onCloneIndexerPress={onCloneIndexerPress}
/>
<IndexerIndexFooter />

View File

@@ -1,3 +1,4 @@
import { uniqBy } from 'lodash';
import React from 'react';
import Label from 'Components/Label';
import { IndexerCapabilities } from 'Indexer/Indexer';
@@ -23,14 +24,18 @@ function CapabilitiesLabel(props: CapabilitiesLabelProps) {
);
}
const nameList = Array.from(
new Set(filteredList.map((item) => item.name).sort())
const indexerCategories = uniqBy(filteredList, 'id').sort(
(a, b) => a.id - b.id
);
return (
<span>
{nameList.map((category) => {
return <Label key={category}>{category}</Label>;
{indexerCategories.map((category) => {
return (
<Label key={category.id} title={`${category.id}`}>
{category.name}
</Label>
);
})}
{filteredList.length === 0 ? <Label>{'None'}</Label> : null}

View File

@@ -11,6 +11,12 @@
flex: 0 0 60px;
}
.id {
composes: cell;
flex: 0 0 60px;
}
.sortName {
composes: cell;

View File

@@ -8,6 +8,7 @@ interface CssExports {
'cell': string;
'checkInput': string;
'externalLink': string;
'id': string;
'minimumSeeders': string;
'packSeedTime': string;
'priority': string;

View File

@@ -3,7 +3,7 @@ import { useSelector } from 'react-redux';
import { useSelect } from 'App/SelectContext';
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 VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
import Column from 'Components/Table/Column';
@@ -27,13 +27,14 @@ interface IndexerIndexRowProps {
sortKey: string;
columns: Column[];
isSelectMode: boolean;
onCloneIndexerPress(id: number): void;
}
function IndexerIndexRow(props: IndexerIndexRowProps) {
const { indexerId, columns, isSelectMode } = props;
const { indexerId, columns, isSelectMode, onCloneIndexerPress } = props;
const { indexer, appProfile, status, longDateFormat, timeFormat } =
useSelector(createIndexerIndexItemSelector(props.indexerId));
useSelector(createIndexerIndexItemSelector(indexerId));
const {
id,
@@ -147,12 +148,25 @@ function IndexerIndexRow(props: IndexerIndexRowProps) {
);
}
if (name === 'id') {
return (
<VirtualTableRowCell key={name} className={styles[name]}>
<IndexerTitleLink
indexerId={indexerId}
title={`${indexerId}`}
onCloneIndexerPress={onCloneIndexerPress}
/>
</VirtualTableRowCell>
);
}
if (name === 'sortName') {
return (
<VirtualTableRowCell key={name} className={styles[name]}>
<IndexerTitleLink
indexerId={indexerId}
indexerName={indexerName}
title={indexerName}
onCloneIndexerPress={onCloneIndexerPress}
/>
</VirtualTableRowCell>
);
@@ -202,7 +216,7 @@ function IndexerIndexRow(props: IndexerIndexRowProps) {
return (
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore ts(2739)
<RelativeDateCellConnector
<RelativeDateCell
key={name}
className={styles[name]}
date={added.toString()}
@@ -215,7 +229,7 @@ function IndexerIndexRow(props: IndexerIndexRowProps) {
return (
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore ts(2739)
<RelativeDateCellConnector
<RelativeDateCell
key={name}
className={styles[name]}
date={vipExpiration}

View File

@@ -26,6 +26,7 @@ interface RowItemData {
sortKey: string;
columns: Column[];
isSelectMode: boolean;
onCloneIndexerPress(id: number): void;
}
interface IndexerIndexTableProps {
@@ -37,6 +38,7 @@ interface IndexerIndexTableProps {
scrollerRef: RefObject<HTMLElement>;
isSelectMode: boolean;
isSmallScreen: boolean;
onCloneIndexerPress(id: number): void;
}
const columnsSelector = createSelector(
@@ -49,7 +51,7 @@ const Row: React.FC<ListChildComponentProps<RowItemData>> = ({
style,
data,
}) => {
const { items, sortKey, columns, isSelectMode } = data;
const { items, sortKey, columns, isSelectMode, onCloneIndexerPress } = data;
if (index >= items.length) {
return null;
@@ -71,6 +73,7 @@ const Row: React.FC<ListChildComponentProps<RowItemData>> = ({
sortKey={sortKey}
columns={columns}
isSelectMode={isSelectMode}
onCloneIndexerPress={onCloneIndexerPress}
/>
</div>
);
@@ -89,6 +92,7 @@ function IndexerIndexTable(props: IndexerIndexTableProps) {
isSelectMode,
isSmallScreen,
scrollerRef,
onCloneIndexerPress,
} = props;
const columns = useSelector(columnsSelector);
@@ -198,6 +202,7 @@ function IndexerIndexTable(props: IndexerIndexTableProps) {
sortKey,
columns,
isSelectMode,
onCloneIndexerPress,
}}
>
{Row}

View File

@@ -4,6 +4,12 @@
flex: 0 0 60px;
}
.id {
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
flex: 0 0 60px;
}
.sortName {
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';

View File

@@ -5,6 +5,7 @@ interface CssExports {
'added': string;
'appProfileId': string;
'capabilities': string;
'id': string;
'minimumSeeders': string;
'packSeedTime': string;
'priority': string;

View File

@@ -1,16 +1,16 @@
import PropTypes from 'prop-types';
import React, { useCallback, useState } from 'react';
import Link from 'Components/Link/Link';
import IndexerInfoModal from './Info/IndexerInfoModal';
import styles from './IndexerTitleLink.css';
interface IndexerTitleLinkProps {
indexerName: string;
indexerId: number;
title: string;
onCloneIndexerPress(id: number): void;
}
function IndexerTitleLink(props: IndexerTitleLinkProps) {
const { indexerName, indexerId } = props;
const { title, indexerId, onCloneIndexerPress } = props;
const [isIndexerInfoModalOpen, setIsIndexerInfoModalOpen] = useState(false);
@@ -25,20 +25,17 @@ function IndexerTitleLink(props: IndexerTitleLinkProps) {
return (
<div>
<Link className={styles.link} onPress={onIndexerInfoPress}>
{indexerName}
{title}
</Link>
<IndexerInfoModal
indexerId={indexerId}
isOpen={isIndexerInfoModalOpen}
onModalClose={onIndexerInfoModalClose}
onCloneIndexerPress={onCloneIndexerPress}
/>
</div>
);
}
IndexerTitleLink.propTypes = {
indexerName: PropTypes.string.isRequired,
};
export default IndexerTitleLink;

View File

@@ -0,0 +1,139 @@
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
import { IndexerHistoryAppState } from 'App/State/IndexerAppState';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import { kinds } from 'Helpers/Props';
import Indexer from 'Indexer/Indexer';
import {
clearIndexerHistory,
fetchIndexerHistory,
} from 'Store/Actions/indexerHistoryActions';
import { createIndexerSelectorForHook } from 'Store/Selectors/createIndexerSelector';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import translate from 'Utilities/String/translate';
import IndexerHistoryRow from './IndexerHistoryRow';
const columns = [
{
name: 'eventType',
isVisible: true,
},
{
name: 'query',
label: () => translate('Query'),
isVisible: true,
},
{
name: 'parameters',
label: () => translate('Parameters'),
isVisible: true,
},
{
name: 'date',
label: () => translate('Date'),
isVisible: true,
},
{
name: 'source',
label: () => translate('Source'),
isVisible: true,
},
{
name: 'details',
label: () => translate('Details'),
isVisible: true,
},
];
function createIndexerHistorySelector() {
return createSelector(
(state: AppState) => state.indexerHistory,
createUISettingsSelector(),
(state: AppState) => state.history.pageSize,
(indexerHistory: IndexerHistoryAppState, uiSettings, pageSize) => {
return {
...indexerHistory,
shortDateFormat: uiSettings.shortDateFormat,
timeFormat: uiSettings.timeFormat,
pageSize,
};
}
);
}
interface IndexerHistoryProps {
indexerId: number;
}
function IndexerHistory(props: IndexerHistoryProps) {
const {
isFetching,
isPopulated,
error,
items,
shortDateFormat,
timeFormat,
pageSize,
} = useSelector(createIndexerHistorySelector());
const indexer = useSelector(
createIndexerSelectorForHook(props.indexerId)
) as Indexer;
const dispatch = useDispatch();
useEffect(() => {
dispatch(
fetchIndexerHistory({ indexerId: props.indexerId, limit: pageSize })
);
return () => {
dispatch(clearIndexerHistory());
};
}, [props, pageSize, dispatch]);
const hasItems = !!items.length;
if (isFetching) {
return <LoadingIndicator />;
}
if (!isFetching && !!error) {
return (
<Alert kind={kinds.DANGER}>{translate('IndexerHistoryLoadError')}</Alert>
);
}
if (isPopulated && !hasItems && !error) {
return <Alert kind={kinds.INFO}>{translate('NoIndexerHistory')}</Alert>;
}
if (isPopulated && hasItems && !error) {
return (
<Table columns={columns}>
<TableBody>
{items.map((item) => {
return (
<IndexerHistoryRow
key={item.id}
indexer={indexer}
shortDateFormat={shortDateFormat}
timeFormat={timeFormat}
{...item}
/>
);
})}
</TableBody>
</Table>
);
}
return null;
}
export default IndexerHistory;

View File

@@ -0,0 +1,23 @@
.query {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 120px;
}
.elapsedTime,
.source {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 150px;
}
.details {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 70px;
}
.parametersContent {
display: flex;
flex-wrap: wrap;
}

View File

@@ -0,0 +1,11 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'details': string;
'elapsedTime': string;
'parametersContent': string;
'query': string;
'source': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

@@ -0,0 +1,104 @@
import React, { useCallback, useState } from 'react';
import IconButton from 'Components/Link/IconButton';
import RelativeDateCell from 'Components/Table/Cells/RelativeDateCell';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow';
import { icons } from 'Helpers/Props';
import HistoryDetailsModal from 'History/Details/HistoryDetailsModal';
import HistoryEventTypeCell from 'History/HistoryEventTypeCell';
import { historyParameters } from 'History/HistoryRow';
import HistoryRowParameter from 'History/HistoryRowParameter';
import Indexer from 'Indexer/Indexer';
import { HistoryData } from 'typings/History';
import translate from 'Utilities/String/translate';
import styles from './IndexerHistoryRow.css';
interface IndexerHistoryRowProps {
data: HistoryData;
date: string;
eventType: string;
successful: boolean;
indexer: Indexer;
shortDateFormat: string;
timeFormat: string;
}
function IndexerHistoryRow(props: IndexerHistoryRowProps) {
const {
data,
date,
eventType,
successful,
indexer,
shortDateFormat,
timeFormat,
} = props;
const [isDetailsModalOpen, setIsDetailsModalOpen] = useState(false);
const onDetailsModalPress = useCallback(() => {
setIsDetailsModalOpen(true);
}, [setIsDetailsModalOpen]);
const onDetailsModalClose = useCallback(() => {
setIsDetailsModalOpen(false);
}, [setIsDetailsModalOpen]);
const parameters = historyParameters.filter(
(parameter) =>
parameter.key in data && data[parameter.key as keyof HistoryData]
);
return (
<TableRow>
<HistoryEventTypeCell
indexer={indexer}
eventType={eventType}
data={data}
successful={successful}
/>
<TableRowCell className={styles.query}>{data.query}</TableRowCell>
<TableRowCell>
<div className={styles.parametersContent}>
{parameters.map((parameter) => {
return (
<HistoryRowParameter
key={parameter.key}
title={parameter.title}
value={data[parameter.key as keyof HistoryData].toString()}
/>
);
})}
</div>
</TableRowCell>
<RelativeDateCell date={date} />
<TableRowCell className={styles.source}>
{data.source ? data.source : null}
</TableRowCell>
<TableRowCell className={styles.details}>
<IconButton
name={icons.INFO}
onPress={onDetailsModalPress}
title={translate('HistoryDetails')}
/>
</TableRowCell>
<HistoryDetailsModal
isOpen={isDetailsModalOpen}
eventType={eventType}
data={data}
indexer={indexer}
shortDateFormat={shortDateFormat}
timeFormat={timeFormat}
onModalClose={onDetailsModalClose}
/>
</TableRow>
);
}
export default IndexerHistoryRow;

View File

@@ -7,16 +7,18 @@ interface IndexerInfoModalProps {
isOpen: boolean;
indexerId: number;
onModalClose(): void;
onCloneIndexerPress(id: number): void;
}
function IndexerInfoModal(props: IndexerInfoModalProps) {
const { isOpen, onModalClose, indexerId } = props;
const { isOpen, indexerId, onModalClose, onCloneIndexerPress } = props;
return (
<Modal size={sizes.MEDIUM} isOpen={isOpen} onModalClose={onModalClose}>
<Modal size={sizes.LARGE} isOpen={isOpen} onModalClose={onModalClose}>
<IndexerInfoModalContent
indexerId={indexerId}
onModalClose={onModalClose}
onCloneIndexerPress={onCloneIndexerPress}
/>
</Modal>
);

View File

@@ -9,3 +9,47 @@
margin-right: auto;
}
.tabs {
margin-top: -32px;
}
.tabList {
margin: 0;
padding: 0;
}
.tab {
position: relative;
bottom: -1px;
display: inline-block;
padding: 6px 12px;
border: 1px solid transparent;
border-top: none;
list-style: none;
cursor: pointer;
}
.selectedTab {
border-color: var(--borderColor);
border-radius: 0 0 5px 5px;
background-color: rgba(239, 239, 239, 0.4);
color: var(--black);
}
.tabContent {
margin-top: 20px;
}
.modalFooter {
composes: modalFooter from '~Components/Modal/ModalFooter.css';
justify-content: space-between;
}
@media only screen and (max-width: $breakpointExtraSmall) {
.modalFooter {
flex-direction: column;
gap: 10px;
}
}

View File

@@ -3,6 +3,12 @@
interface CssExports {
'deleteButton': string;
'description': string;
'modalFooter': string;
'selectedTab': string;
'tab': string;
'tabContent': string;
'tabList': string;
'tabs': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

@@ -1,7 +1,9 @@
import { uniqBy } from 'lodash';
import React, { useCallback, useState } from 'react';
import { useSelector } from 'react-redux';
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
import { createSelector } from 'reselect';
import Alert from 'Components/Alert';
import DescriptionList from 'Components/DescriptionList/DescriptionList';
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
import DescriptionListItemDescription from 'Components/DescriptionList/DescriptionListItemDescription';
@@ -22,9 +24,10 @@ import TagListConnector from 'Components/TagListConnector';
import { kinds } from 'Helpers/Props';
import DeleteIndexerModal from 'Indexer/Delete/DeleteIndexerModal';
import EditIndexerModalConnector from 'Indexer/Edit/EditIndexerModalConnector';
import Indexer from 'Indexer/Indexer';
import Indexer, { IndexerCapabilities } from 'Indexer/Indexer';
import { createIndexerSelectorForHook } from 'Store/Selectors/createIndexerSelector';
import translate from 'Utilities/String/translate';
import IndexerHistory from './History/IndexerHistory';
import styles from './IndexerInfoModalContent.css';
function createIndexerInfoItemSelector(indexerId: number) {
@@ -38,15 +41,18 @@ function createIndexerInfoItemSelector(indexerId: number) {
);
}
const tabs = ['details', 'categories', 'history', 'stats'];
interface IndexerInfoModalContentProps {
indexerId: number;
onModalClose(): void;
onCloneIndexerPress(id: number): void;
}
function IndexerInfoModalContent(props: IndexerInfoModalContentProps) {
const { indexer } = useSelector(
createIndexerInfoItemSelector(props.indexerId)
);
const { indexerId, onCloneIndexerPress } = props;
const { indexer } = useSelector(createIndexerInfoItemSelector(indexerId));
const {
id,
@@ -58,7 +64,7 @@ function IndexerInfoModalContent(props: IndexerInfoModalContentProps) {
fields,
tags,
protocol,
capabilities,
capabilities = {} as IndexerCapabilities,
} = indexer as Indexer;
const { onModalClose } = props;
@@ -70,10 +76,19 @@ function IndexerInfoModalContent(props: IndexerInfoModalContentProps) {
const vipExpiration =
fields.find((field) => field.name === 'vipExpiration')?.value ?? undefined;
const [selectedTab, setSelectedTab] = useState(tabs[0]);
const [isEditIndexerModalOpen, setIsEditIndexerModalOpen] = useState(false);
const [isDeleteIndexerModalOpen, setIsDeleteIndexerModalOpen] =
useState(false);
const onTabSelect = useCallback(
(index: number) => {
const selectedTab = tabs[index];
setSelectedTab(selectedTab);
},
[setSelectedTab]
);
const onEditIndexerPress = useCallback(() => {
setIsEditIndexerModalOpen(true);
}, [setIsEditIndexerModalOpen]);
@@ -92,220 +107,269 @@ function IndexerInfoModalContent(props: IndexerInfoModalContentProps) {
onModalClose();
}, [setIsDeleteIndexerModalOpen, onModalClose]);
const onCloneIndexerPressWrapper = useCallback(() => {
onCloneIndexerPress(id);
onModalClose();
}, [id, onCloneIndexerPress, onModalClose]);
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>{`${name}`}</ModalHeader>
<ModalBody>
<FieldSet legend={translate('IndexerDetails')}>
<div>
<DescriptionList>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('Id')}
data={id}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('Description')}
data={description ? description : '-'}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('Encoding')}
data={encoding ? encoding : '-'}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('Language')}
data={language ?? '-'}
/>
{vipExpiration ? (
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('VipExpiration')}
data={vipExpiration}
/>
) : null}
<DescriptionListItemTitle>
{translate('IndexerSite')}
</DescriptionListItemTitle>
<DescriptionListItemDescription>
{baseUrl ? (
<Link to={baseUrl}>
{baseUrl.replace(/(:\/\/)api\./, '$1')}
</Link>
) : (
'-'
)}
</DescriptionListItemDescription>
<DescriptionListItemTitle>
{protocol === 'usenet'
? translate('NewznabUrl')
: translate('TorznabUrl')}
</DescriptionListItemTitle>
<DescriptionListItemDescription>
{`${window.location.origin}${window.Prowlarr.urlBase}/${id}/api`}
</DescriptionListItemDescription>
{tags.length > 0 ? (
<>
<DescriptionListItemTitle>
{translate('Tags')}
</DescriptionListItemTitle>
<DescriptionListItemDescription>
<TagListConnector tags={tags} />
</DescriptionListItemDescription>
</>
) : null}
</DescriptionList>
</div>
</FieldSet>
<Tabs
className={styles.tabs}
selectedIndex={tabs.indexOf(selectedTab)}
onSelect={onTabSelect}
>
<TabList className={styles.tabList}>
<Tab className={styles.tab} selectedClassName={styles.selectedTab}>
{translate('Details')}
</Tab>
<FieldSet legend={translate('SearchCapabilities')}>
<div>
<DescriptionList>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('RawSearchSupported')}
data={
capabilities.supportsRawSearch
? translate('Yes')
: translate('No')
}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('SearchTypes')}
data={
capabilities.searchParams.length === 0 ? (
translate('NotSupported')
) : (
<Label kind={kinds.PRIMARY}>
{capabilities.searchParams[0]}
</Label>
)
}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('TVSearchTypes')}
data={
capabilities.tvSearchParams.length === 0
? translate('NotSupported')
: capabilities.tvSearchParams.map((p) => {
return (
<Label key={p} kind={kinds.PRIMARY}>
{p}
</Label>
);
})
}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('MovieSearchTypes')}
data={
capabilities.movieSearchParams.length === 0
? translate('NotSupported')
: capabilities.movieSearchParams.map((p) => {
return (
<Label key={p} kind={kinds.PRIMARY}>
{p}
</Label>
);
})
}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('BookSearchTypes')}
data={
capabilities.bookSearchParams.length === 0
? translate('NotSupported')
: capabilities.bookSearchParams.map((p) => {
return (
<Label key={p} kind={kinds.PRIMARY}>
{p}
</Label>
);
})
}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('MusicSearchTypes')}
data={
capabilities.musicSearchParams.length === 0
? translate('NotSupported')
: capabilities.musicSearchParams.map((p) => {
return (
<Label key={p} kind={kinds.PRIMARY}>
{p}
</Label>
);
})
}
/>
</DescriptionList>
</div>
</FieldSet>
<Tab className={styles.tab} selectedClassName={styles.selectedTab}>
{translate('Categories')}
</Tab>
{capabilities?.categories?.length > 0 ? (
<FieldSet legend={translate('IndexerCategories')}>
<Table
columns={[
{
name: 'id',
label: translate('Id'),
isVisible: true,
},
{
name: 'name',
label: translate('Name'),
isVisible: true,
},
]}
>
{uniqBy(capabilities.categories, 'id')
.sort((a, b) => a.id - b.id)
.map((category) => {
return (
<TableBody key={category.id}>
<TableRow key={category.id}>
<TableRowCell>{category.id}</TableRowCell>
<TableRowCell>{category.name}</TableRowCell>
</TableRow>
{category?.subCategories?.length > 0
? uniqBy(category.subCategories, 'id')
.sort((a, b) => a.id - b.id)
.map((subCategory) => {
<Tab className={styles.tab} selectedClassName={styles.selectedTab}>
{translate('History')}
</Tab>
</TabList>
<TabPanel>
<div className={styles.tabContent}>
<FieldSet legend={translate('IndexerDetails')}>
<div>
<DescriptionList>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('Id')}
data={id}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('Description')}
data={description ? description : '-'}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('Encoding')}
data={encoding ? encoding : '-'}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('Language')}
data={language ?? '-'}
/>
{vipExpiration ? (
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('VipExpiration')}
data={vipExpiration}
/>
) : null}
<DescriptionListItemTitle>
{translate('IndexerSite')}
</DescriptionListItemTitle>
<DescriptionListItemDescription>
{baseUrl ? (
<Link to={baseUrl}>
{baseUrl.replace(/(:\/\/)api\./, '$1')}
</Link>
) : (
'-'
)}
</DescriptionListItemDescription>
<DescriptionListItemTitle>
{protocol === 'usenet'
? translate('NewznabUrl')
: translate('TorznabUrl')}
</DescriptionListItemTitle>
<DescriptionListItemDescription>
{`${window.location.origin}${window.Prowlarr.urlBase}/${id}/api`}
</DescriptionListItemDescription>
{tags.length > 0 ? (
<>
<DescriptionListItemTitle>
{translate('Tags')}
</DescriptionListItemTitle>
<DescriptionListItemDescription>
<TagListConnector tags={tags} />
</DescriptionListItemDescription>
</>
) : null}
</DescriptionList>
</div>
</FieldSet>
<FieldSet legend={translate('SearchCapabilities')}>
<div>
<DescriptionList>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('RawSearchSupported')}
data={
capabilities?.supportsRawSearch
? translate('Yes')
: translate('No')
}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('SearchTypes')}
data={
capabilities?.searchParams?.length > 0 ? (
<Label kind={kinds.PRIMARY}>
{capabilities.searchParams[0]}
</Label>
) : (
translate('NotSupported')
)
}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('TVSearchTypes')}
data={
capabilities?.tvSearchParams?.length > 0
? capabilities.tvSearchParams.map((p) => {
return (
<TableRow key={subCategory.id}>
<TableRowCell>{subCategory.id}</TableRowCell>
<TableRowCell>
{subCategory.name}
</TableRowCell>
</TableRow>
<Label key={p} kind={kinds.PRIMARY}>
{p}
</Label>
);
})
: null}
</TableBody>
);
})}
</Table>
</FieldSet>
) : null}
: translate('NotSupported')
}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('MovieSearchTypes')}
data={
capabilities?.movieSearchParams?.length > 0
? capabilities.movieSearchParams.map((p) => {
return (
<Label key={p} kind={kinds.PRIMARY}>
{p}
</Label>
);
})
: translate('NotSupported')
}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('BookSearchTypes')}
data={
capabilities?.bookSearchParams?.length > 0
? capabilities.bookSearchParams.map((p) => {
return (
<Label key={p} kind={kinds.PRIMARY}>
{p}
</Label>
);
})
: translate('NotSupported')
}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('MusicSearchTypes')}
data={
capabilities?.musicSearchParams?.length > 0
? capabilities.musicSearchParams.map((p) => {
return (
<Label key={p} kind={kinds.PRIMARY}>
{p}
</Label>
);
})
: translate('NotSupported')
}
/>
</DescriptionList>
</div>
</FieldSet>
</div>
</TabPanel>
<TabPanel>
<div className={styles.tabContent}>
{capabilities?.categories?.length > 0 ? (
<FieldSet legend={translate('IndexerCategories')}>
<Table
columns={[
{
name: 'id',
label: translate('Id'),
isVisible: true,
},
{
name: 'name',
label: translate('Name'),
isVisible: true,
},
]}
>
{uniqBy(capabilities.categories, 'id')
.sort((a, b) => a.id - b.id)
.map((category) => {
return (
<TableBody key={category.id}>
<TableRow key={category.id}>
<TableRowCell>{category.id}</TableRowCell>
<TableRowCell>{category.name}</TableRowCell>
</TableRow>
{category?.subCategories?.length > 0
? uniqBy(category.subCategories, 'id')
.sort((a, b) => a.id - b.id)
.map((subCategory) => {
return (
<TableRow key={subCategory.id}>
<TableRowCell>
{subCategory.id}
</TableRowCell>
<TableRowCell>
{subCategory.name}
</TableRowCell>
</TableRow>
);
})
: null}
</TableBody>
);
})}
</Table>
</FieldSet>
) : (
<Alert kind={kinds.INFO}>
{translate('NoIndexerCategories')}
</Alert>
)}
</div>
</TabPanel>
<TabPanel>
<div className={styles.tabContent}>
<IndexerHistory indexerId={id} />
</div>
</TabPanel>
</Tabs>
</ModalBody>
<ModalFooter>
<Button
className={styles.deleteButton}
kind={kinds.DANGER}
onPress={onDeleteIndexerPress}
>
{translate('Delete')}
</Button>
<Button onPress={onEditIndexerPress}>{translate('Edit')}</Button>
<Button onPress={onModalClose}>{translate('Close')}</Button>
<ModalFooter className={styles.modalFooter}>
<div>
<Button
className={styles.deleteButton}
kind={kinds.DANGER}
onPress={onDeleteIndexerPress}
>
{translate('Delete')}
</Button>
<Button onPress={onCloneIndexerPressWrapper}>
{translate('Clone')}
</Button>
</div>
<div>
<Button onPress={onEditIndexerPress}>{translate('Edit')}</Button>
<Button onPress={onModalClose}>{translate('Close')}</Button>
</div>
</ModalFooter>
<EditIndexerModalConnector

View File

@@ -1,22 +1,52 @@
.fullWidthChart {
display: inline-block;
padding: 15px 25px;
width: 100%;
height: 300px;
}
.halfWidthChart {
display: inline-block;
padding: 15px 25px;
width: 50%;
}
.quarterWidthChart {
display: inline-block;
width: 25%;
}
.chartContainer {
margin: 5px;
padding: 15px 25px;
height: 300px;
border-radius: 10px;
background-color: var(--chartBackgroundColor);
}
.statContainer {
margin: 5px;
padding: 15px 25px;
height: 150px;
border-radius: 10px;
background-color: var(--chartBackgroundColor);
}
.statTitle {
font-weight: bold;
font-size: 14px;
}
.stat {
font-weight: bold;
font-size: 60px;
}
@media only screen and (max-width: $breakpointSmall) {
.halfWidthChart {
display: inline-block;
padding: 15px 25px;
width: 100%;
height: 300px;
}
.quarterWidthChart {
display: inline-block;
width: 100%;
}
}

View File

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

View File

@@ -8,23 +8,27 @@ import BarChart from 'Components/Chart/BarChart';
import DoughnutChart from 'Components/Chart/DoughnutChart';
import StackedBarChart from 'Components/Chart/StackedBarChart';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import FilterMenu from 'Components/Menu/FilterMenu';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import { align, kinds } from 'Helpers/Props';
import { align, icons, kinds } from 'Helpers/Props';
import {
fetchIndexerStats,
setIndexerStatsFilter,
} from 'Store/Actions/indexerStatsActions';
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
import {
IndexerStatsHost,
IndexerStatsIndexer,
IndexerStatsUserAgent,
} from 'typings/IndexerStats';
import abbreviateNumber from 'Utilities/Number/abbreviateNumber';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
import translate from 'Utilities/String/translate';
import IndexerStatsFilterMenu from './IndexerStatsFilterMenu';
import IndexerStatsFilterModal from './IndexerStatsFilterModal';
import styles from './IndexerStats.css';
function getAverageResponseTimeData(indexerStats: IndexerStatsIndexer[]) {
@@ -165,21 +169,36 @@ function getHostQueryData(indexerStats: IndexerStatsHost[]) {
const indexerStatsSelector = () => {
return createSelector(
(state: AppState) => state.indexerStats,
(indexerStats: IndexerStatsAppState) => {
return indexerStats;
createCustomFiltersSelector('indexerStats'),
(indexerStats: IndexerStatsAppState, customFilters) => {
return {
...indexerStats,
customFilters,
};
}
);
};
function IndexerStats() {
const { isFetching, isPopulated, item, error, filters, selectedFilterKey } =
useSelector(indexerStatsSelector());
const {
isFetching,
isPopulated,
item,
error,
filters,
customFilters,
selectedFilterKey,
} = useSelector(indexerStatsSelector());
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchIndexerStats());
}, [dispatch]);
const onRefreshPress = useCallback(() => {
dispatch(fetchIndexerStats());
}, [dispatch]);
const onFilterSelect = useCallback(
(value: string) => {
dispatch(setIndexerStatsFilter({ selectedFilterKey: value }));
@@ -188,15 +207,42 @@ function IndexerStats() {
);
const isLoaded = !error && isPopulated;
const indexerCount = item.indexers?.length ?? 0;
const userAgentCount = item.userAgents?.length ?? 0;
const queryCount =
item.indexers?.reduce((total, indexer) => {
return (
total +
indexer.numberOfQueries +
indexer.numberOfRssQueries +
indexer.numberOfAuthQueries
);
}, 0) ?? 0;
const grabCount =
item.indexers?.reduce((total, indexer) => {
return total + indexer.numberOfGrabs;
}, 0) ?? 0;
return (
<PageContent>
<PageContent title={translate('Stats')}>
<PageToolbar>
<PageToolbarSection>
<PageToolbarButton
label={translate('Refresh')}
iconName={icons.REFRESH}
isSpinning={isFetching}
onPress={onRefreshPress}
/>
</PageToolbarSection>
<PageToolbarSection alignContent={align.RIGHT} collapseButtons={false}>
<IndexerStatsFilterMenu
<FilterMenu
alignMenu={align.RIGHT}
selectedFilterKey={selectedFilterKey}
filters={filters}
customFilters={customFilters}
onFilterSelect={onFilterSelect}
filterModalConnectorComponent={IndexerStatsFilterModal}
isDisabled={false}
/>
</PageToolbarSection>
@@ -212,58 +258,110 @@ function IndexerStats() {
{isLoaded && (
<div>
<div className={styles.fullWidthChart}>
<BarChart
data={getAverageResponseTimeData(item.indexers)}
title={translate('AverageResponseTimesMs')}
/>
<div className={styles.quarterWidthChart}>
<div className={styles.statContainer}>
<div className={styles.statTitle}>
{translate('ActiveIndexers')}
</div>
<div className={styles.stat}>{indexerCount}</div>
</div>
</div>
<div className={styles.quarterWidthChart}>
<div className={styles.statContainer}>
<div className={styles.statTitle}>
{translate('TotalQueries')}
</div>
<div className={styles.stat}>
{abbreviateNumber(queryCount)}
</div>
</div>
</div>
<div className={styles.quarterWidthChart}>
<div className={styles.statContainer}>
<div className={styles.statTitle}>
{translate('TotalGrabs')}
</div>
<div className={styles.stat}>{abbreviateNumber(grabCount)}</div>
</div>
</div>
<div className={styles.quarterWidthChart}>
<div className={styles.statContainer}>
<div className={styles.statTitle}>
{translate('ActiveApps')}
</div>
<div className={styles.stat}>{userAgentCount}</div>
</div>
</div>
<div className={styles.fullWidthChart}>
<BarChart
data={getFailureRateData(item.indexers)}
title={translate('IndexerFailureRate')}
kind={kinds.WARNING}
/>
<div className={styles.chartContainer}>
<BarChart
data={getAverageResponseTimeData(item.indexers)}
title={translate('AverageResponseTimesMs')}
stepSize={100}
/>
</div>
</div>
<div className={styles.fullWidthChart}>
<div className={styles.chartContainer}>
<BarChart
data={getFailureRateData(item.indexers)}
title={translate('IndexerFailureRate')}
stepSize={0.1}
kind={kinds.WARNING}
/>
</div>
</div>
<div className={styles.halfWidthChart}>
<StackedBarChart
data={getTotalRequestsData(item.indexers)}
title={translate('TotalIndexerQueries')}
/>
<div className={styles.chartContainer}>
<StackedBarChart
data={getTotalRequestsData(item.indexers)}
title={translate('TotalIndexerQueries')}
/>
</div>
</div>
<div className={styles.halfWidthChart}>
<BarChart
data={getNumberGrabsData(item.indexers)}
title={translate('TotalIndexerSuccessfulGrabs')}
/>
<div className={styles.chartContainer}>
<BarChart
data={getNumberGrabsData(item.indexers)}
title={translate('TotalIndexerSuccessfulGrabs')}
/>
</div>
</div>
<div className={styles.halfWidthChart}>
<BarChart
data={getUserAgentQueryData(item.userAgents)}
title={translate('TotalUserAgentQueries')}
horizontal={true}
/>
<div className={styles.chartContainer}>
<BarChart
data={getUserAgentQueryData(item.userAgents)}
title={translate('TotalUserAgentQueries')}
horizontal={true}
/>
</div>
</div>
<div className={styles.halfWidthChart}>
<BarChart
data={getUserAgentGrabsData(item.userAgents)}
title={translate('TotalUserAgentGrabs')}
horizontal={true}
/>
<div className={styles.chartContainer}>
<BarChart
data={getUserAgentGrabsData(item.userAgents)}
title={translate('TotalUserAgentGrabs')}
horizontal={true}
/>
</div>
</div>
<div className={styles.halfWidthChart}>
<DoughnutChart
data={getHostQueryData(item.hosts)}
title={translate('TotalHostQueries')}
horizontal={true}
/>
<div className={styles.chartContainer}>
<DoughnutChart
data={getHostQueryData(item.hosts)}
title={translate('TotalHostQueries')}
horizontal={true}
/>
</div>
</div>
<div className={styles.halfWidthChart}>
<DoughnutChart
data={getHostGrabsData(item.hosts)}
title={translate('TotalHostGrabs')}
horizontal={true}
/>
<div className={styles.chartContainer}>
<DoughnutChart
data={getHostGrabsData(item.hosts)}
title={translate('TotalHostGrabs')}
horizontal={true}
/>
</div>
</div>
</div>
)}

View File

@@ -1,27 +0,0 @@
import React from 'react';
import FilterMenu from 'Components/Menu/FilterMenu';
import { align } from 'Helpers/Props';
interface IndexerStatsFilterMenuProps {
selectedFilterKey: string | number;
filters: object[];
isDisabled: boolean;
onFilterSelect(filterName: string): unknown;
}
function IndexerStatsFilterMenu(props: IndexerStatsFilterMenuProps) {
const { selectedFilterKey, filters, isDisabled, onFilterSelect } = props;
return (
<FilterMenu
alignMenu={align.RIGHT}
isDisabled={isDisabled}
selectedFilterKey={selectedFilterKey}
filters={filters}
customFilters={[]}
onFilterSelect={onFilterSelect}
/>
);
}
export default IndexerStatsFilterMenu;

View File

@@ -0,0 +1,56 @@
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 { setIndexerStatsFilter } from 'Store/Actions/indexerStatsActions';
function createIndexerStatsSelector() {
return createSelector(
(state: AppState) => state.indexerStats.item,
(indexerStats) => {
return indexerStats;
}
);
}
function createFilterBuilderPropsSelector() {
return createSelector(
(state: AppState) => state.indexerStats.filterBuilderProps,
(filterBuilderProps) => {
return filterBuilderProps;
}
);
}
interface IndexerStatsFilterModalProps {
isOpen: boolean;
}
export default function IndexerStatsFilterModal(
props: IndexerStatsFilterModalProps
) {
const sectionItems = [useSelector(createIndexerStatsSelector())];
const filterBuilderProps = useSelector(createFilterBuilderPropsSelector());
const customFilterType = 'indexerStats';
const dispatch = useDispatch();
const dispatchSetFilter = useCallback(
(payload: unknown) => {
dispatch(setIndexerStatsFilter(payload));
},
[dispatch]
);
return (
<FilterModal
// TODO: Don't spread all the props
{...props}
sectionItems={sectionItems}
filterBuilderProps={filterBuilderProps}
customFilterType={customFilterType}
dispatchSetFilter={dispatchSetFilter}
/>
);
}

View File

@@ -285,7 +285,7 @@ class SearchIndex extends Component {
const hasNoIndexer = !totalItems;
return (
<PageContent>
<PageContent title={translate('Search')}>
<PageToolbar>
<PageToolbarSection
alignContent={align.RIGHT}

View File

@@ -1,43 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import Label from 'Components/Label';
import Tooltip from 'Components/Tooltip/Tooltip';
import { kinds, tooltipPositions } from 'Helpers/Props';
function CategoryLabel({ categories }) {
const sortedCategories = categories.filter((cat) => cat.name !== undefined).sort((c) => c.id);
if (categories?.length === 0) {
return (
<Tooltip
anchor={<Label kind={kinds.DANGER}>Unknown</Label>}
tooltip="Please report this issue to the GitHub as this shouldn't be happening"
position={tooltipPositions.LEFT}
/>
);
}
return (
<span>
{
sortedCategories.map((category) => {
return (
<Label key={category.name}>
{category.name}
</Label>
);
})
}
</span>
);
}
CategoryLabel.defaultProps = {
categories: []
};
CategoryLabel.propTypes = {
categories: PropTypes.arrayOf(PropTypes.object).isRequired
};
export default CategoryLabel;

View File

@@ -0,0 +1,36 @@
import React from 'react';
import Label from 'Components/Label';
import Tooltip from 'Components/Tooltip/Tooltip';
import { kinds, tooltipPositions } from 'Helpers/Props';
import { IndexerCategory } from 'Indexer/Indexer';
import translate from 'Utilities/String/translate';
interface CategoryLabelProps {
categories: IndexerCategory[];
}
function CategoryLabel({ categories = [] }: CategoryLabelProps) {
if (categories?.length === 0) {
return (
<Tooltip
anchor={<Label kind={kinds.DANGER}>{translate('Unknown')}</Label>}
tooltip="Please report this issue to the GitHub as this shouldn't be happening"
position={tooltipPositions.LEFT}
/>
);
}
const sortedCategories = categories
.filter((cat) => cat.name !== undefined)
.sort((a, b) => a.id - b.id);
return (
<span>
{sortedCategories.map((category) => {
return <Label key={category.id}>{category.name}</Label>;
})}
</span>
);
}
export default CategoryLabel;

View File

@@ -0,0 +1,13 @@
.links {
margin: 0;
}
.link {
white-space: nowrap;
}
.linkLabel {
composes: label from '~Components/Label.css';
cursor: pointer;
}

View File

@@ -0,0 +1,9 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'link': string;
'linkLabel': string;
'links': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

@@ -0,0 +1,90 @@
import React from 'react';
import Label from 'Components/Label';
import Link from 'Components/Link/Link';
import { kinds, sizes } from 'Helpers/Props';
import { IndexerCategory } from 'Indexer/Indexer';
import styles from './ReleaseLinks.css';
interface ReleaseLinksProps {
categories: IndexerCategory[];
imdbId?: string;
tmdbId?: number;
tvdbId?: number;
tvMazeId?: number;
}
function ReleaseLinks(props: ReleaseLinksProps) {
const { categories = [], imdbId, tmdbId, tvdbId, tvMazeId } = props;
const categoryNames = categories
.filter((item) => item.id < 100000)
.map((c) => c.name);
return (
<div className={styles.links}>
{imdbId ? (
<Link
className={styles.link}
to={`https://imdb.com/title/tt${imdbId.toString().padStart(7, '0')}/`}
>
<Label
className={styles.linkLabel}
kind={kinds.INFO}
size={sizes.LARGE}
>
IMDb
</Label>
</Link>
) : null}
{tmdbId ? (
<Link
className={styles.link}
to={`https://www.themoviedb.org/${
categoryNames.includes('Movies') ? 'movie' : 'tv'
}/${tmdbId}`}
>
<Label
className={styles.linkLabel}
kind={kinds.INFO}
size={sizes.LARGE}
>
TMDb
</Label>
</Link>
) : null}
{tvdbId ? (
<Link
className={styles.link}
to={`https://www.thetvdb.com/?tab=series&id=${tvdbId}`}
>
<Label
className={styles.linkLabel}
kind={kinds.INFO}
size={sizes.LARGE}
>
TVDb
</Label>
</Link>
) : null}
{tvMazeId ? (
<Link
className={styles.link}
to={`https://www.tvmaze.com/shows/${tvMazeId}/_`}
>
<Label
className={styles.linkLabel}
kind={kinds.INFO}
size={sizes.LARGE}
>
TV Maze
</Label>
</Link>
) : null}
</div>
);
}
export default ReleaseLinks;

View File

@@ -63,7 +63,7 @@
}
.externalLinks {
margin: 0 2px;
width: 22px;
text-align: center;
composes: button from '~Components/Link/IconButton.css';
color: var(--textColor);
}

View File

@@ -16,6 +16,7 @@ import titleCase from 'Utilities/String/titleCase';
import translate from 'Utilities/String/translate';
import CategoryLabel from './CategoryLabel';
import Peers from './Peers';
import ReleaseLinks from './ReleaseLinks';
import styles from './SearchIndexRow.css';
function getDownloadIcon(isGrabbing, isGrabbed, grabError) {
@@ -118,6 +119,10 @@ class SearchIndexRow extends Component {
grabs,
seeders,
leechers,
imdbId,
tmdbId,
tvdbId,
tvMazeId,
indexerFlags,
columns,
isGrabbing,
@@ -343,6 +348,32 @@ class SearchIndexRow extends Component {
/> :
null
}
{
imdbId || tmdbId || tvdbId || tvMazeId ? (
<Popover
anchor={
<Icon
className={styles.externalLinks}
name={icons.EXTERNAL_LINK}
size={12}
/>
}
title={translate('Links')}
body={
<ReleaseLinks
categories={categories}
imdbId={imdbId}
tmdbId={tmdbId}
tvdbId={tvdbId}
tvMazeId={tvMazeId}
/>
}
kind={kinds.INVERSE}
position={tooltipPositions.TOP}
/>
) : null
}
</VirtualTableRowCell>
);
}
@@ -375,6 +406,10 @@ SearchIndexRow.propTypes = {
grabs: PropTypes.number,
seeders: PropTypes.number,
leechers: PropTypes.number,
imdbId: PropTypes.number,
tmdbId: PropTypes.number,
tvdbId: PropTypes.number,
tvMazeId: PropTypes.number,
indexerFlags: PropTypes.arrayOf(PropTypes.string).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
onGrabPress: PropTypes.func.isRequired,

View File

@@ -62,7 +62,7 @@ class Applications extends Component {
return (
<FieldSet legend={translate('Applications')}>
<PageSectionContent
errorMessage={translate('UnableToLoadApplicationList')}
errorMessage={translate('ApplicationsLoadError')}
{...otherProps}
>
<div className={styles.applications}>

View File

@@ -133,7 +133,8 @@ function EditApplicationModalContent(props) {
<FormInputGroup
type={inputTypes.TAG}
name="tags"
helpText={translate('TagsHelpText')}
helpText={translate('ApplicationTagsHelpText')}
helpTextWarning={translate('ApplicationTagsHelpTextWarning')}
{...tags}
onChange={onInputChange}
/>

View File

@@ -14,9 +14,11 @@ import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import useSelectState from 'Helpers/Hooks/useSelectState';
import { kinds } from 'Helpers/Props';
import SortDirection from 'Helpers/Props/SortDirection';
import {
bulkDeleteApplications,
bulkEditApplications,
setManageApplicationsSort,
} from 'Store/Actions/settingsActions';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import { SelectStateInputProps } from 'typings/props';
@@ -62,6 +64,8 @@ const COLUMNS = [
interface ManageApplicationsModalContentProps {
onModalClose(): void;
sortKey?: string;
sortDirection?: SortDirection;
}
function ManageApplicationsModalContent(
@@ -76,6 +80,8 @@ function ManageApplicationsModalContent(
isSaving,
error,
items,
sortKey,
sortDirection,
}: ApplicationAppState = useSelector(
createClientSideCollectionSelector('settings.applications')
);
@@ -96,6 +102,13 @@ function ManageApplicationsModalContent(
const selectedCount = selectedIds.length;
const onSortPress = useCallback(
(value: string) => {
dispatch(setManageApplicationsSort({ sortKey: value }));
},
[dispatch]
);
const onDeletePress = useCallback(() => {
setIsDeleteModalOpen(true);
}, [setIsDeleteModalOpen]);
@@ -201,6 +214,9 @@ function ManageApplicationsModalContent(
allSelected={allSelected}
allUnselected={allUnselected}
onSelectAllChange={onSelectAllChange}
sortKey={sortKey}
sortDirection={sortDirection}
onSortPress={onSortPress}
>
<TableBody>
{items.map((item) => {

View File

@@ -84,7 +84,7 @@ class DownloadClientSettings extends Component {
/>
<PageToolbarButton
label={translate('ManageDownloadClients')}
label={translate('ManageClients')}
iconName={icons.MANAGE}
onPress={this.onManageDownloadClientsPress}
/>

View File

@@ -61,7 +61,7 @@ class DownloadClients extends Component {
return (
<FieldSet legend={translate('DownloadClients')}>
<PageSectionContent
errorMessage={translate('UnableToLoadDownloadClients')}
errorMessage={translate('DownloadClientsLoadError')}
{...otherProps}
>
<div className={styles.downloadClients}>

View File

@@ -14,9 +14,11 @@ import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import useSelectState from 'Helpers/Hooks/useSelectState';
import { kinds } from 'Helpers/Props';
import SortDirection from 'Helpers/Props/SortDirection';
import {
bulkDeleteDownloadClients,
bulkEditDownloadClients,
setManageDownloadClientsSort,
} from 'Store/Actions/settingsActions';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import { SelectStateInputProps } from 'typings/props';
@@ -61,6 +63,8 @@ const COLUMNS = [
interface ManageDownloadClientsModalContentProps {
onModalClose(): void;
sortKey?: string;
sortDirection?: SortDirection;
}
function ManageDownloadClientsModalContent(
@@ -75,6 +79,8 @@ function ManageDownloadClientsModalContent(
isSaving,
error,
items,
sortKey,
sortDirection,
}: DownloadClientAppState = useSelector(
createClientSideCollectionSelector('settings.downloadClients')
);
@@ -93,6 +99,13 @@ function ManageDownloadClientsModalContent(
const selectedCount = selectedIds.length;
const onSortPress = useCallback(
(value: string) => {
dispatch(setManageDownloadClientsSort({ sortKey: value }));
},
[dispatch]
);
const onDeletePress = useCallback(() => {
setIsDeleteModalOpen(true);
}, [setIsDeleteModalOpen]);
@@ -174,6 +187,9 @@ function ManageDownloadClientsModalContent(
allSelected={allSelected}
allUnselected={allUnselected}
onSelectAllChange={onSelectAllChange}
sortKey={sortKey}
sortDirection={sortDirection}
onSortPress={onSortPress}
>
<TableBody>
{items.map((item) => {

View File

@@ -124,6 +124,7 @@ class SecuritySettings extends Component {
authenticationRequired,
username,
password,
passwordConfirmation,
apiKey,
certificateValidation
} = settings;
@@ -139,8 +140,8 @@ class SecuritySettings extends Component {
type={inputTypes.SELECT}
name="authenticationMethod"
values={authenticationMethodOptions}
helpText={translate('AuthenticationMethodHelpText', { appName: 'Prowlarr' })}
helpTextWarning={translate('AuthenticationRequiredWarning', { appName: 'Prowlarr' })}
helpText={translate('AuthenticationMethodHelpText')}
helpTextWarning={translate('AuthenticationRequiredWarning')}
onChange={onInputChange}
{...authenticationMethod}
/>
@@ -193,6 +194,21 @@ class SecuritySettings extends Component {
null
}
{
authenticationEnabled ?
<FormGroup>
<FormLabel>{translate('PasswordConfirmation')}</FormLabel>
<FormInputGroup
type={inputTypes.PASSWORD}
name="passwordConfirmation"
onChange={onInputChange}
{...passwordConfirmation}
/>
</FormGroup> :
null
}
<FormGroup>
<FormLabel>{translate('ApiKey')}</FormLabel>

View File

@@ -97,20 +97,6 @@ class EditAppProfileModalContent extends Component {
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('EnableInteractiveSearch')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="enableInteractiveSearch"
{...enableInteractiveSearch}
helpText={translate('EnableInteractiveSearchHelpText')}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('EnableAutomaticSearch')}
@@ -125,6 +111,20 @@ class EditAppProfileModalContent extends Component {
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('EnableInteractiveSearch')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="enableInteractiveSearch"
{...enableInteractiveSearch}
helpText={translate('EnableInteractiveSearchHelpText')}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('MinimumSeeders')}

View File

@@ -176,6 +176,13 @@ class UISettings extends Component {
helpTextWarning={translate('UILanguageHelpTextWarning')}
onChange={onInputChange}
{...settings.uiLanguage}
errors={
languages.some((language) => language.key === settings.uiLanguage.value) ?
settings.uiLanguage.errors :
[
...settings.uiLanguage.errors,
{ message: translate('InvalidUILanguage') }
]}
/>
</FormGroup>
</FieldSet>

View File

@@ -6,6 +6,8 @@ import getSectionState from 'Utilities/State/getSectionState';
import { set, updateServerSideCollection } from '../baseActions';
function createFetchServerSideCollectionHandler(section, url, fetchDataAugmenter) {
const [baseSection] = section.split('.');
return function(getState, payload, dispatch) {
dispatch(set({ section, isFetching: true }));
@@ -25,10 +27,13 @@ function createFetchServerSideCollectionHandler(section, url, fetchDataAugmenter
const {
selectedFilterKey,
filters,
customFilters
filters
} = sectionState;
const customFilters = getState().customFilters.items.filter((customFilter) => {
return customFilter.type === section || customFilter.type === baseSection;
});
const selectedFilters = findSelectedFilters(selectedFilterKey, filters, customFilters);
selectedFilters.forEach((filter) => {
@@ -37,7 +42,8 @@ function createFetchServerSideCollectionHandler(section, url, fetchDataAugmenter
const promise = createAjaxRequest({
url,
data
data,
traditional: true
}).request;
promise.done((response) => {

View File

@@ -7,6 +7,7 @@ import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/create
import { createThunk } from 'Store/thunks';
import getSectionState from 'Utilities/State/getSectionState';
import updateSectionState from 'Utilities/State/updateSectionState';
import translate from 'Utilities/String/translate';
//
// Variables
@@ -87,7 +88,7 @@ export default {
const pendingChanges = { ...item, id: 0 };
delete pendingChanges.id;
pendingChanges.name = `${pendingChanges.name} - Copy`;
pendingChanges.name = translate('DefaultNameCopiedProfile', { name: pendingChanges.name });
newState.pendingChanges = pendingChanges;
return updateSectionState(state, section, newState);

View File

@@ -1,4 +1,5 @@
import { createAction } from 'redux-actions';
import { sortDirections } from 'Helpers/Props';
import createBulkEditItemHandler from 'Store/Actions/Creators/createBulkEditItemHandler';
import createBulkRemoveItemHandler from 'Store/Actions/Creators/createBulkRemoveItemHandler';
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
@@ -7,6 +8,7 @@ import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHand
import createSaveProviderHandler, { createCancelSaveProviderHandler } from 'Store/Actions/Creators/createSaveProviderHandler';
import createTestAllProvidersHandler from 'Store/Actions/Creators/createTestAllProvidersHandler';
import createTestProviderHandler, { createCancelTestProviderHandler } from 'Store/Actions/Creators/createTestProviderHandler';
import createSetClientSideCollectionSortReducer from 'Store/Actions/Creators/Reducers/createSetClientSideCollectionSortReducer';
import createSetProviderFieldValueReducer from 'Store/Actions/Creators/Reducers/createSetProviderFieldValueReducer';
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
import { createThunk } from 'Store/thunks';
@@ -30,9 +32,10 @@ export const CANCEL_SAVE_APPLICATION = 'settings/applications/cancelSaveApplicat
export const DELETE_APPLICATION = 'settings/applications/deleteApplication';
export const TEST_APPLICATION = 'settings/applications/testApplication';
export const CANCEL_TEST_APPLICATION = 'settings/applications/cancelTestApplication';
export const TEST_ALL_APPLICATIONS = 'indexers/testAllApplications';
export const TEST_ALL_APPLICATIONS = 'settings/applications/testAllApplications';
export const BULK_EDIT_APPLICATIONS = 'settings/applications/bulkEditApplications';
export const BULK_DELETE_APPLICATIONS = 'settings/applications/bulkDeleteApplications';
export const SET_MANAGE_APPLICATIONS_SORT = 'settings/applications/setManageApplicationsSort';
//
// Action Creators
@@ -49,6 +52,7 @@ export const cancelTestApplication = createThunk(CANCEL_TEST_APPLICATION);
export const testAllApplications = createThunk(TEST_ALL_APPLICATIONS);
export const bulkEditApplications = createThunk(BULK_EDIT_APPLICATIONS);
export const bulkDeleteApplications = createThunk(BULK_DELETE_APPLICATIONS);
export const setManageApplicationsSort = createAction(SET_MANAGE_APPLICATIONS_SORT);
export const setApplicationValue = createAction(SET_APPLICATION_VALUE, (payload) => {
return {
@@ -88,7 +92,14 @@ export default {
isTesting: false,
isTestingAll: false,
items: [],
pendingChanges: {}
pendingChanges: {},
sortKey: 'name',
sortDirection: sortDirections.ASCENDING,
sortPredicates: {
name: function(item) {
return item.name.toLowerCase();
}
}
},
//
@@ -121,7 +132,10 @@ export default {
return selectedSchema;
});
}
},
[SET_MANAGE_APPLICATIONS_SORT]: createSetClientSideCollectionSortReducer(section)
}
};

View File

@@ -1,4 +1,5 @@
import { createAction } from 'redux-actions';
import { sortDirections } from 'Helpers/Props';
import createBulkEditItemHandler from 'Store/Actions/Creators/createBulkEditItemHandler';
import createBulkRemoveItemHandler from 'Store/Actions/Creators/createBulkRemoveItemHandler';
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
@@ -7,6 +8,7 @@ import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHand
import createSaveProviderHandler, { createCancelSaveProviderHandler } from 'Store/Actions/Creators/createSaveProviderHandler';
import createTestAllProvidersHandler from 'Store/Actions/Creators/createTestAllProvidersHandler';
import createTestProviderHandler, { createCancelTestProviderHandler } from 'Store/Actions/Creators/createTestProviderHandler';
import createSetClientSideCollectionSortReducer from 'Store/Actions/Creators/Reducers/createSetClientSideCollectionSortReducer';
import createSetProviderFieldValueReducer from 'Store/Actions/Creators/Reducers/createSetProviderFieldValueReducer';
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
import { createThunk } from 'Store/thunks';
@@ -34,6 +36,7 @@ export const CANCEL_TEST_DOWNLOAD_CLIENT = 'settings/downloadClients/cancelTestD
export const TEST_ALL_DOWNLOAD_CLIENTS = 'settings/downloadClients/testAllDownloadClients';
export const BULK_EDIT_DOWNLOAD_CLIENTS = 'settings/downloadClients/bulkEditDownloadClients';
export const BULK_DELETE_DOWNLOAD_CLIENTS = 'settings/downloadClients/bulkDeleteDownloadClients';
export const SET_MANAGE_DOWNLOAD_CLIENTS_SORT = 'settings/downloadClients/setManageDownloadClientsSort';
//
// Action Creators
@@ -50,6 +53,7 @@ export const cancelTestDownloadClient = createThunk(CANCEL_TEST_DOWNLOAD_CLIENT)
export const testAllDownloadClients = createThunk(TEST_ALL_DOWNLOAD_CLIENTS);
export const bulkEditDownloadClients = createThunk(BULK_EDIT_DOWNLOAD_CLIENTS);
export const bulkDeleteDownloadClients = createThunk(BULK_DELETE_DOWNLOAD_CLIENTS);
export const setManageDownloadClientsSort = createAction(SET_MANAGE_DOWNLOAD_CLIENTS_SORT);
export const setDownloadClientValue = createAction(SET_DOWNLOAD_CLIENT_VALUE, (payload) => {
return {
@@ -89,7 +93,14 @@ export default {
isTesting: false,
isTestingAll: false,
items: [],
pendingChanges: {}
pendingChanges: {},
sortKey: 'name',
sortDirection: sortDirections.ASCENDING,
sortPredicates: {
name: function(item) {
return item.name.toLowerCase();
}
}
},
//
@@ -147,7 +158,10 @@ export default {
return selectedSchema;
});
}
},
[SET_MANAGE_DOWNLOAD_CLIENTS_SORT]: createSetClientSideCollectionSortReducer(section)
}
};

View File

@@ -1,5 +1,5 @@
import { createAction } from 'redux-actions';
import { filterTypes, sortDirections } from 'Helpers/Props';
import { filterBuilderTypes, filterBuilderValueTypes, filterTypes, sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers';
@@ -159,6 +159,27 @@ export const defaultState = {
}
]
}
],
filterBuilderProps: [
{
name: 'eventType',
label: () => translate('EventType'),
type: filterBuilderTypes.EQUAL,
valueType: filterBuilderValueTypes.HISTORY_EVENT_TYPE
},
{
name: 'indexerIds',
label: () => translate('Indexer'),
type: filterBuilderTypes.EQUAL,
valueType: filterBuilderValueTypes.INDEXER
},
{
name: 'successful',
label: () => translate('Successful'),
type: filterBuilderTypes.EQUAL,
valueType: filterBuilderValueTypes.BOOL
}
]
};

View File

@@ -4,6 +4,7 @@ import * as commands from './commandActions';
import * as customFilters from './customFilterActions';
import * as history from './historyActions';
import * as indexers from './indexerActions';
import * as indexerHistory from './indexerHistoryActions';
import * as indexerIndex from './indexerIndexActions';
import * as indexerStats from './indexerStatsActions';
import * as indexerStatus from './indexerStatusActions';
@@ -28,6 +29,7 @@ export default [
releases,
localization,
indexers,
indexerHistory,
indexerIndex,
indexerStats,
indexerStatus,

View File

@@ -1,6 +1,6 @@
import _ from 'lodash';
import { createAction } from 'redux-actions';
import { sortDirections } from 'Helpers/Props';
import { filterTypePredicates, sortDirections } from 'Helpers/Props';
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHandler';
import createSaveProviderHandler, { createCancelSaveProviderHandler } from 'Store/Actions/Creators/createSaveProviderHandler';
@@ -69,6 +69,28 @@ export const filterPredicates = {
item.fields.find((field) => field.name === 'vipExpiration')?.value ?? null;
return dateFilterPredicate(vipExpiration, filterValue, type);
},
categories: function(item, filterValue, type) {
const predicate = filterTypePredicates[type];
const { categories = [] } = item.capabilities || {};
const categoryList = categories
.filter((category) => category.id < 100000)
.reduce((acc, element) => {
acc.push(element.id);
if (element.subCategories && element.subCategories.length > 0) {
element.subCategories.forEach((subCat) => {
acc.push(subCat.id);
});
}
return acc;
}, []);
return predicate(categoryList, filterValue);
}
};
@@ -210,7 +232,7 @@ export const reducers = createHandleActions({
// Set the name in pendingChanges
newState.pendingChanges = {
name: `${item.name} - Copy`
name: translate('DefaultNameCopiedProfile', { name: item.name })
};
return updateSectionState(state, section, newState);

View File

@@ -0,0 +1,81 @@
import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
import { createThunk, handleThunks } from 'Store/thunks';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import { set, update } from './baseActions';
import createHandleActions from './Creators/createHandleActions';
//
// Variables
export const section = 'indexerHistory';
//
// State
export const defaultState = {
isFetching: false,
isPopulated: false,
error: null,
items: []
};
//
// Actions Types
export const FETCH_INDEXER_HISTORY = 'indexerHistory/fetchIndexerHistory';
export const CLEAR_INDEXER_HISTORY = 'indexerHistory/clearIndexerHistory';
//
// Action Creators
export const fetchIndexerHistory = createThunk(FETCH_INDEXER_HISTORY);
export const clearIndexerHistory = createAction(CLEAR_INDEXER_HISTORY);
//
// Action Handlers
export const actionHandlers = handleThunks({
[FETCH_INDEXER_HISTORY]: function(getState, payload, dispatch) {
dispatch(set({ section, isFetching: true }));
const promise = createAjaxRequest({
url: '/history/indexer',
data: payload
}).request;
promise.done((data) => {
dispatch(batchActions([
update({ section, data }),
set({
section,
isFetching: false,
isPopulated: true,
error: null
})
]));
});
promise.fail((xhr) => {
dispatch(set({
section,
isFetching: false,
isPopulated: false,
error: xhr
}));
});
}
});
//
// Reducers
export const reducers = createHandleActions({
[CLEAR_INDEXER_HISTORY]: (state) => {
return Object.assign({}, state, defaultState);
}
}, defaultState, section);

View File

@@ -37,12 +37,18 @@ export const defaultState = {
isVisible: true,
isModifiable: false
},
{
name: 'id',
columnLabel: () => translate('IndexerId'),
label: () => translate('Id'),
isSortable: true,
isVisible: false
},
{
name: 'sortName',
label: () => translate('IndexerName'),
isSortable: true,
isVisible: true,
isModifiable: false
isVisible: true
},
{
name: 'protocol',
@@ -180,6 +186,12 @@ export const defaultState = {
type: filterBuilderTypes.EXACT,
valueType: filterBuilderValueTypes.APP_PROFILE
},
{
name: 'categories',
label: () => translate('Categories'),
type: filterBuilderTypes.ARRAY,
valueType: filterBuilderValueTypes.CATEGORY
},
{
name: 'tags',
label: () => translate('Tags'),

View File

@@ -3,6 +3,7 @@ import { batchActions } from 'redux-batched-actions';
import { filterBuilderTypes, filterBuilderValueTypes } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import findSelectedFilters from 'Utilities/Filter/findSelectedFilters';
import translate from 'Utilities/String/translate';
import { set, update } from './baseActions';
import createHandleActions from './Creators/createHandleActions';
@@ -55,19 +56,27 @@ export const defaultState = {
filterBuilderProps: [
{
name: 'startDate',
label: 'Start Date',
type: filterBuilderTypes.EXACT,
valueType: filterBuilderValueTypes.DATE
name: 'indexers',
label: () => translate('Indexers'),
type: filterBuilderTypes.CONTAINS,
valueType: filterBuilderValueTypes.INDEXER
},
{
name: 'endDate',
label: 'End Date',
name: 'protocols',
label: () => translate('Protocols'),
type: filterBuilderTypes.EXACT,
valueType: filterBuilderValueTypes.DATE
valueType: filterBuilderValueTypes.PROTOCOL
},
{
name: 'tags',
label: () => translate('Tags'),
type: filterBuilderTypes.CONTAINS,
valueType: filterBuilderValueTypes.TAG
}
],
selectedFilterKey: 'all'
};
export const persistState = [
@@ -81,6 +90,10 @@ export const persistState = [
export const FETCH_INDEXER_STATS = 'indexerStats/fetchIndexerStats';
export const SET_INDEXER_STATS_FILTER = 'indexerStats/setIndexerStatsFilter';
function getCustomFilters(state, type) {
return state.customFilters.items.filter((customFilter) => customFilter.type === type);
}
//
// Action Creators
@@ -94,23 +107,39 @@ export const actionHandlers = handleThunks({
[FETCH_INDEXER_STATS]: function(getState, payload, dispatch) {
const state = getState();
const indexerStats = state.indexerStats;
const customFilters = getCustomFilters(state, section);
const selectedFilters = findSelectedFilters(indexerStats.selectedFilterKey, indexerStats.filters, customFilters);
const requestParams = {
endDate: moment().toISOString()
};
selectedFilters.forEach((selectedFilter) => {
if (selectedFilter.key === 'indexers') {
requestParams.indexers = selectedFilter.value.join(',');
}
if (selectedFilter.key === 'protocols') {
requestParams.protocols = selectedFilter.value.join(',');
}
if (selectedFilter.key === 'tags') {
requestParams.tags = selectedFilter.value.join(',');
}
});
if (indexerStats.selectedFilterKey !== 'all') {
let dayCount = 7;
if (indexerStats.selectedFilterKey === 'lastSeven') {
requestParams.startDate = moment().add(-7, 'days').endOf('day').toISOString();
}
if (indexerStats.selectedFilterKey === 'lastThirty') {
dayCount = 30;
requestParams.startDate = moment().add(-30, 'days').endOf('day').toISOString();
}
if (indexerStats.selectedFilterKey === 'lastNinety') {
dayCount = 90;
requestParams.startDate = moment().add(-90, 'days').endOf('day').toISOString();
}
requestParams.startDate = moment().add(-dayCount, 'days').endOf('day').toISOString();
}
const basesAttrs = {

View File

@@ -1,7 +1,9 @@
import $ from 'jquery';
import React from 'react';
import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props';
import Icon from 'Components/Icon';
import { filterBuilderTypes, filterBuilderValueTypes, filterTypePredicates, icons, sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import getSectionState from 'Utilities/State/getSectionState';
@@ -110,7 +112,11 @@ export const defaultState = {
},
{
name: 'indexerFlags',
columnLabel: 'Indexer Flags',
columnLabel: () => translate('IndexerFlags'),
label: React.createElement(Icon, {
name: icons.FLAG,
title: () => translate('IndexerFlags')
}),
isSortable: true,
isVisible: true
},
@@ -163,6 +169,18 @@ export const defaultState = {
}
],
filterPredicates: {
peers: function(item, filterValue, type) {
const predicate = filterTypePredicates[type];
const seeders = item.seeders || 0;
const leechers = item.leechers || 0;
const peers = seeders + leechers;
return predicate(peers, filterValue);
}
},
filterBuilderProps: [
{
name: 'title',
@@ -351,8 +369,9 @@ export const actionHandlers = handleThunks({
promise.done((data) => {
dispatch(batchActions([
...data.map((release) => {
...data.map(({ guid }) => {
return updateRelease({
guid,
isGrabbing: false,
isGrabbed: true,
grabError: null

View File

@@ -187,6 +187,7 @@ module.exports = {
//
// Charts
chartBackgroundColor: '#262626',
failedColors: ['#ffbeb2', '#feb4a6', '#fdab9b', '#fca290', '#fb9984', '#fa8f79', '#f9856e', '#f77b66', '#f5715d', '#f36754', '#f05c4d', '#ec5049', '#e74545', '#e13b42', '#da323f', '#d3293d', '#ca223c', '#c11a3b', '#b8163a', '#ae123a'],
chartColorsDiversified: ['#90caf9', '#f4d166', '#ff8a65', '#ce93d8', '#80cba9', '#ffab91', '#8097ea', '#bcaaa4', '#a57583', '#e4e498', '#9e96af', '#c6ab81', '#6972c6', '#619fc6', '#81ad81', '#f48fb1', '#82afca', '#b5b071', '#8b959b', '#7ec0b4'],
chartColors: ['#f4d166', '#f6c760', '#f8bc58', '#f8b252', '#f7a84a', '#f69e41', '#f49538', '#f38b2f', '#f28026', '#f0751e', '#eb6c1c', '#e4641e', '#de5d1f', '#d75521', '#cf4f22', '#c64a22', '#bc4623', '#b24223', '#a83e24', '#9e3a26']

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