mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2026-03-26 18:04:07 -04:00
Compare commits
447 Commits
instance-n
...
paging
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ca6f83a4d | ||
|
|
fb5b325271 | ||
|
|
ec8025c3dc | ||
|
|
b42bf2cf20 | ||
|
|
712d95e6ce | ||
|
|
24f6c937da | ||
|
|
e94aa7c499 | ||
|
|
201bc1944b | ||
|
|
09e40e0060 | ||
|
|
348d90a37e | ||
|
|
726dc34424 | ||
|
|
2e9f6cd94b | ||
|
|
495f61f412 | ||
|
|
0f11f414b6 | ||
|
|
d397cdf5fb | ||
|
|
888b514dd8 | ||
|
|
caab337379 | ||
|
|
26bea14137 | ||
|
|
5f26287234 | ||
|
|
6ec761c217 | ||
|
|
b85679de56 | ||
|
|
71775b97a3 | ||
|
|
5bb3dbfbf5 | ||
|
|
b608a7a904 | ||
|
|
4ad992f76a | ||
|
|
95497480a2 | ||
|
|
cc57866ab0 | ||
|
|
dbc4989a95 | ||
|
|
af4961e3e6 | ||
|
|
0ec54906c6 | ||
|
|
35f85fc986 | ||
|
|
0aedafb278 | ||
|
|
54dce448a8 | ||
|
|
3c915002c6 | ||
|
|
e32f8f4330 | ||
|
|
5abb5ada49 | ||
|
|
6579385110 | ||
|
|
1c6e5543df | ||
|
|
85737aacbe | ||
|
|
30c3aedeb1 | ||
|
|
1640980e2b | ||
|
|
99bc56efb6 | ||
|
|
04276eb587 | ||
|
|
34c560fd3a | ||
|
|
caa8bb05a7 | ||
|
|
773e8ff1f4 | ||
|
|
0984976378 | ||
|
|
fcb3c96455 | ||
|
|
acf7a425b5 | ||
|
|
da898fe958 | ||
|
|
5bb3ea0806 | ||
|
|
b41cb80e33 | ||
|
|
a39341be4b | ||
|
|
27b3d8618a | ||
|
|
550b9b58df | ||
|
|
035ad33b72 | ||
|
|
85f8e0c451 | ||
|
|
ea2061a7d3 | ||
|
|
ea6d01a49b | ||
|
|
252cd97e35 | ||
|
|
a8ea05af07 | ||
|
|
24d6a0cb06 | ||
|
|
8e1771b5a9 | ||
|
|
d767a82e84 | ||
|
|
76bfd29f23 | ||
|
|
c923982711 | ||
|
|
f03a64f9ac | ||
|
|
e713e58e83 | ||
|
|
4fb5d3432b | ||
|
|
a31b107a90 | ||
|
|
f91ffb8328 | ||
|
|
a3ba070296 | ||
|
|
bccb0bd5c8 | ||
|
|
4517f271c4 | ||
|
|
2ae2a0b184 | ||
|
|
b5e43e7a1a | ||
|
|
3a52048dc2 | ||
|
|
8b898733ab | ||
|
|
f99a2e1164 | ||
|
|
306209fcc2 | ||
|
|
5d09c2b5fa | ||
|
|
41a9d2d732 | ||
|
|
49b120ba55 | ||
|
|
a88fc34a78 | ||
|
|
c46b7c5e4b | ||
|
|
94c45541ae | ||
|
|
f8082047a5 | ||
|
|
011fd57f7d | ||
|
|
6c35c3fc6f | ||
|
|
5da02c49eb | ||
|
|
1a339b9ab2 | ||
|
|
94edd7538e | ||
|
|
9b2274805e | ||
|
|
dbf86efb0a | ||
|
|
529fbfd9bd | ||
|
|
0ed5bfe0d0 | ||
|
|
6a43eb0031 | ||
|
|
a12001a5ef | ||
|
|
b57014762d | ||
|
|
a51a8bf921 | ||
|
|
e8dc5b3206 | ||
|
|
d4f22f3596 | ||
|
|
b6018a4cd7 | ||
|
|
ec389987df | ||
|
|
6b62504916 | ||
|
|
626d777d3c | ||
|
|
234707b291 | ||
|
|
15734ca0da | ||
|
|
19913e5b01 | ||
|
|
156f6505be | ||
|
|
e383287972 | ||
|
|
0c0cbdac2f | ||
|
|
0685c2eb04 | ||
|
|
e8c132e908 | ||
|
|
bea9bd39ff | ||
|
|
077e4727f2 | ||
|
|
5f7bc82eb5 | ||
|
|
0dd5c56175 | ||
|
|
409a218379 | ||
|
|
07cc1e03c8 | ||
|
|
560cda8ba0 | ||
|
|
934f566359 | ||
|
|
89ae5ceaa6 | ||
|
|
c7d5889e59 | ||
|
|
bea3c051b9 | ||
|
|
c0b1675627 | ||
|
|
906d09e162 | ||
|
|
8cd9ad01c2 | ||
|
|
ce2f322478 | ||
|
|
0487309ee8 | ||
|
|
9862584611 | ||
|
|
6a00e0db90 | ||
|
|
c93831dd8b | ||
|
|
6546ba773c | ||
|
|
4c3484a898 | ||
|
|
8561b862f9 | ||
|
|
e1032fb0f5 | ||
|
|
4063219430 | ||
|
|
e008be8581 | ||
|
|
d6b379df64 | ||
|
|
27094ccf62 | ||
|
|
edf9473e9a | ||
|
|
a0d11e7e33 | ||
|
|
7729eb398a | ||
|
|
989564dbce | ||
|
|
c1f917f1ac | ||
|
|
4b7e47c397 | ||
|
|
1529527af9 | ||
|
|
a11bd1c3c7 | ||
|
|
915b320a4a | ||
|
|
155f72cc45 | ||
|
|
3f73fec5c3 | ||
|
|
8515623ceb | ||
|
|
963cddb582 | ||
|
|
ede323b8ed | ||
|
|
07d7fc98b0 | ||
|
|
1b78fd38db | ||
|
|
5a9d4d6280 | ||
|
|
70685de5d2 | ||
|
|
9860183433 | ||
|
|
50331c61ae | ||
|
|
bd3408f170 | ||
|
|
c043bf8da9 | ||
|
|
ea3fa6f28d | ||
|
|
8917347c0b | ||
|
|
2cebdf4a06 | ||
|
|
985110cfb9 | ||
|
|
de876247a3 | ||
|
|
bad6c301f8 | ||
|
|
fc3b23394a | ||
|
|
92c3656bad | ||
|
|
1acbee2a57 | ||
|
|
c28f9b6bcd | ||
|
|
aa8048968c | ||
|
|
6646734510 | ||
|
|
71dd8b6d04 | ||
|
|
6d87bd9f8c | ||
|
|
551d969680 | ||
|
|
57dac6afdd | ||
|
|
3dfbfd07dd | ||
|
|
842df6913c | ||
|
|
599eeb4c61 | ||
|
|
da371dd921 | ||
|
|
fc25ba7ac0 | ||
|
|
6e1bef13e2 | ||
|
|
72ee413411 | ||
|
|
e87b45b47e | ||
|
|
cc841fe3d1 | ||
|
|
264ffdcc26 | ||
|
|
5cc044aa8f | ||
|
|
de2fd92b6f | ||
|
|
eff09c1f72 | ||
|
|
9db888c9a3 | ||
|
|
bf78396164 | ||
|
|
0e7eaa9221 | ||
|
|
5b82decc31 | ||
|
|
38ab533272 | ||
|
|
4914fcd5df | ||
|
|
858415b037 | ||
|
|
43f4899324 | ||
|
|
c60a94adfb | ||
|
|
f386ddb806 | ||
|
|
4175c2577e | ||
|
|
6ce9e5ceb9 | ||
|
|
c15643be39 | ||
|
|
a58380031d | ||
|
|
73af5c9a72 | ||
|
|
d556545e7f | ||
|
|
affde5d7b7 | ||
|
|
518c85dee2 | ||
|
|
ba3a240707 | ||
|
|
587a73f3d6 | ||
|
|
ae8f017ca8 | ||
|
|
d9098b612e | ||
|
|
29e7cc06a1 | ||
|
|
387fb0bd15 | ||
|
|
2d33560d89 | ||
|
|
94a797fc1e | ||
|
|
2e851b0588 | ||
|
|
7303cdf555 | ||
|
|
6636cbc4ae | ||
|
|
a5a4f62f25 | ||
|
|
05a7465a07 | ||
|
|
c35f1212fb | ||
|
|
ad95d73e9d | ||
|
|
30f53c20ed | ||
|
|
0199a37a0c | ||
|
|
e9764820c0 | ||
|
|
d285cbb021 | ||
|
|
8afaa3386d | ||
|
|
c94beb6814 | ||
|
|
c7eb08a0f0 | ||
|
|
2a2e859420 | ||
|
|
31f0e8212e | ||
|
|
1cbb9b1724 | ||
|
|
45dbcc6b89 | ||
|
|
3b26613394 | ||
|
|
6bb8c09fcf | ||
|
|
810b3612aa | ||
|
|
57dcd861a9 | ||
|
|
dfe132cda2 | ||
|
|
a635820b48 | ||
|
|
d959e81efb | ||
|
|
ac89cd636f | ||
|
|
50616f5c9e | ||
|
|
3f9cb2c6ea | ||
|
|
b5aa85a548 | ||
|
|
0fa5127c83 | ||
|
|
4d137886bc | ||
|
|
9dde041c99 | ||
|
|
a8234c9ce0 | ||
|
|
9227efdb65 | ||
|
|
fa923e658f | ||
|
|
364a5564ae | ||
|
|
9efd0b391e | ||
|
|
320161e051 | ||
|
|
38ba810ae8 | ||
|
|
4e3f460a24 | ||
|
|
0d918a0aa9 | ||
|
|
a110412665 | ||
|
|
6c97f1b6ee | ||
|
|
470779ead2 | ||
|
|
b371f2d913 | ||
|
|
3ff3452e2d | ||
|
|
df13537e29 | ||
|
|
5d2fefde8f | ||
|
|
ffb3f83324 | ||
|
|
1c125733b2 | ||
|
|
2af7fac15e | ||
|
|
f172d17ecc | ||
|
|
c69843931e | ||
|
|
cd3e99ad87 | ||
|
|
1cce39b404 | ||
|
|
9b46ab73e4 | ||
|
|
a352c053ab | ||
|
|
b33e45d266 | ||
|
|
817d61de91 | ||
|
|
c7e5cc6462 | ||
|
|
25596fc2e8 | ||
|
|
9ff0b90626 | ||
|
|
4f4c011436 | ||
|
|
bd0115931f | ||
|
|
a0d18c546e | ||
|
|
d935b0df82 | ||
|
|
9e37f69224 | ||
|
|
2805c4f18b | ||
|
|
dae21f22b9 | ||
|
|
7ddbe09eca | ||
|
|
90e3c809c3 | ||
|
|
ec8cf5f57a | ||
|
|
f4bbf2f8af | ||
|
|
ea98d41472 | ||
|
|
b8cb0fd291 | ||
|
|
d3dfa620ac | ||
|
|
049668f307 | ||
|
|
c400575aac | ||
|
|
6f122fb2e4 | ||
|
|
a9c210f8e7 | ||
|
|
1068ba8915 | ||
|
|
635335d876 | ||
|
|
2ed51cd933 | ||
|
|
b74c46c554 | ||
|
|
7029e0d6ee | ||
|
|
438ea380f5 | ||
|
|
4eec675d61 | ||
|
|
0a9bd8287f | ||
|
|
b583ac3a97 | ||
|
|
4be41ff3fb | ||
|
|
b911f8cc08 | ||
|
|
22face385f | ||
|
|
3e700b63c2 | ||
|
|
df0b8fc660 | ||
|
|
f96dbbfc21 | ||
|
|
4a75f92cb5 | ||
|
|
dd05a9dbd4 | ||
|
|
e78b8d5346 | ||
|
|
74a1d95ab7 | ||
|
|
f929a7e62f | ||
|
|
e9e4248af4 | ||
|
|
9e3b43ef12 | ||
|
|
738a690aac | ||
|
|
3b7c59e9bb | ||
|
|
b8ca28d955 | ||
|
|
8797bb7d1c | ||
|
|
be430732f5 | ||
|
|
e7b1380b85 | ||
|
|
c29735741c | ||
|
|
f56a13a375 | ||
|
|
148d8ee249 | ||
|
|
3547028b96 | ||
|
|
e4ffa1873e | ||
|
|
2e85a21576 | ||
|
|
0a111e7572 | ||
|
|
25217c0ee8 | ||
|
|
791592927c | ||
|
|
4137193a60 | ||
|
|
99816bfd36 | ||
|
|
59e5b5bd52 | ||
|
|
7fa0a2b33c | ||
|
|
0593ca6b9e | ||
|
|
06a26b5c87 | ||
|
|
dcae6dc151 | ||
|
|
04e3ed0ffe | ||
|
|
1ed5ed9179 | ||
|
|
d292d086ee | ||
|
|
f68915c5dd | ||
|
|
01e970e1a7 | ||
|
|
68df439498 | ||
|
|
33de7ca7ab | ||
|
|
ae2d9b795b | ||
|
|
eadea745f8 | ||
|
|
f958c4aefa | ||
|
|
4cf9fb0e79 | ||
|
|
bfa68347e6 | ||
|
|
f97b35403d | ||
|
|
bf2e057247 | ||
|
|
5a278f4e9d | ||
|
|
232a6efd0d | ||
|
|
7e01c93b2c | ||
|
|
d58f6551e6 | ||
|
|
6446528022 | ||
|
|
7f63757e06 | ||
|
|
b5d789df3a | ||
|
|
4473551182 | ||
|
|
fd88f44865 | ||
|
|
69b8be5b67 | ||
|
|
fbde3fe2cd | ||
|
|
f9e2c5b673 | ||
|
|
5c5dfbb66b | ||
|
|
2db24d454e | ||
|
|
cb35a3948e | ||
|
|
8c314439cd | ||
|
|
ee6467073f | ||
|
|
6412048eb9 | ||
|
|
efffeebe7c | ||
|
|
1d25a643f9 | ||
|
|
60f48e3a94 | ||
|
|
60f8778305 | ||
|
|
d5088cf472 | ||
|
|
215c87a099 | ||
|
|
32ca2d1720 | ||
|
|
8baf1b533b | ||
|
|
970f80b155 | ||
|
|
b8dd8b1880 | ||
|
|
f607347bd7 | ||
|
|
9959a1b5ed | ||
|
|
8c10f8b55c | ||
|
|
cad4f3740b | ||
|
|
f26b0474f5 | ||
|
|
8b8d0b24ae | ||
|
|
4dee1d65d1 | ||
|
|
09ed132fe6 | ||
|
|
e85ccd5808 | ||
|
|
37914fb90e | ||
|
|
f2f6a82cf0 | ||
|
|
812cf8135a | ||
|
|
e4284d47b0 | ||
|
|
c53e0054ee | ||
|
|
ddcef3a99c | ||
|
|
b7b5a6e7e1 | ||
|
|
593a649045 | ||
|
|
cec304a0be | ||
|
|
06f3c8e151 | ||
|
|
91eb65bd6c | ||
|
|
832080cb36 | ||
|
|
f9c731627f | ||
|
|
83344fb6f4 | ||
|
|
59b7435820 | ||
|
|
d2c1ffa761 | ||
|
|
5436d4f800 | ||
|
|
86fe19a5dd | ||
|
|
473405ceeb | ||
|
|
cac2729230 | ||
|
|
9b1f9abfac | ||
|
|
76285a8ccd | ||
|
|
a6b499e4a5 | ||
|
|
5ee95e3cc2 | ||
|
|
654d2dbad3 | ||
|
|
67ae7e32df | ||
|
|
d52516b700 | ||
|
|
326a7b5e16 | ||
|
|
313a0b459a | ||
|
|
2ffe88bf6a | ||
|
|
a311d13805 | ||
|
|
0e2d15cb73 | ||
|
|
d1949d24e0 | ||
|
|
cdb1f163f8 | ||
|
|
580fc528e5 | ||
|
|
dfed229a1d | ||
|
|
e76a255229 | ||
|
|
a0b650e7a5 | ||
|
|
7cf9fc6a4f | ||
|
|
86f5768461 | ||
|
|
479e29dde7 | ||
|
|
761e15a476 | ||
|
|
d3ffb7b490 | ||
|
|
0db804b647 | ||
|
|
4334e7eef1 | ||
|
|
fbfb70a1bb | ||
|
|
59e990227d | ||
|
|
f0abfae978 | ||
|
|
40e7f80be9 | ||
|
|
3c913eac24 | ||
|
|
95a2bd3d03 | ||
|
|
d5abde98e1 | ||
|
|
5d14d2c134 | ||
|
|
ed46c5c86d |
@@ -117,7 +117,6 @@ dotnet_diagnostic.CA1003.severity = suggestion
|
|||||||
dotnet_diagnostic.CA1008.severity = suggestion
|
dotnet_diagnostic.CA1008.severity = suggestion
|
||||||
dotnet_diagnostic.CA1010.severity = suggestion
|
dotnet_diagnostic.CA1010.severity = suggestion
|
||||||
dotnet_diagnostic.CA1012.severity = suggestion
|
dotnet_diagnostic.CA1012.severity = suggestion
|
||||||
dotnet_diagnostic.CA1014.severity = suggestion
|
|
||||||
dotnet_diagnostic.CA1016.severity = suggestion
|
dotnet_diagnostic.CA1016.severity = suggestion
|
||||||
dotnet_diagnostic.CA1017.severity = suggestion
|
dotnet_diagnostic.CA1017.severity = suggestion
|
||||||
dotnet_diagnostic.CA1018.severity = suggestion
|
dotnet_diagnostic.CA1018.severity = suggestion
|
||||||
@@ -163,6 +162,7 @@ dotnet_diagnostic.CA1309.severity = suggestion
|
|||||||
dotnet_diagnostic.CA1310.severity = suggestion
|
dotnet_diagnostic.CA1310.severity = suggestion
|
||||||
dotnet_diagnostic.CA1401.severity = suggestion
|
dotnet_diagnostic.CA1401.severity = suggestion
|
||||||
dotnet_diagnostic.CA1416.severity = suggestion
|
dotnet_diagnostic.CA1416.severity = suggestion
|
||||||
|
dotnet_diagnostic.CA1419.severity = suggestion
|
||||||
dotnet_diagnostic.CA1507.severity = suggestion
|
dotnet_diagnostic.CA1507.severity = suggestion
|
||||||
dotnet_diagnostic.CA1508.severity = suggestion
|
dotnet_diagnostic.CA1508.severity = suggestion
|
||||||
dotnet_diagnostic.CA1707.severity = suggestion
|
dotnet_diagnostic.CA1707.severity = suggestion
|
||||||
@@ -178,9 +178,6 @@ dotnet_diagnostic.CA1720.severity = suggestion
|
|||||||
dotnet_diagnostic.CA1721.severity = suggestion
|
dotnet_diagnostic.CA1721.severity = suggestion
|
||||||
dotnet_diagnostic.CA1724.severity = suggestion
|
dotnet_diagnostic.CA1724.severity = suggestion
|
||||||
dotnet_diagnostic.CA1725.severity = suggestion
|
dotnet_diagnostic.CA1725.severity = suggestion
|
||||||
dotnet_diagnostic.CA1801.severity = suggestion
|
|
||||||
dotnet_diagnostic.CA1802.severity = suggestion
|
|
||||||
dotnet_diagnostic.CA1805.severity = suggestion
|
|
||||||
dotnet_diagnostic.CA1806.severity = suggestion
|
dotnet_diagnostic.CA1806.severity = suggestion
|
||||||
dotnet_diagnostic.CA1810.severity = suggestion
|
dotnet_diagnostic.CA1810.severity = suggestion
|
||||||
dotnet_diagnostic.CA1812.severity = suggestion
|
dotnet_diagnostic.CA1812.severity = suggestion
|
||||||
@@ -192,13 +189,14 @@ dotnet_diagnostic.CA1819.severity = suggestion
|
|||||||
dotnet_diagnostic.CA1822.severity = suggestion
|
dotnet_diagnostic.CA1822.severity = suggestion
|
||||||
dotnet_diagnostic.CA1823.severity = suggestion
|
dotnet_diagnostic.CA1823.severity = suggestion
|
||||||
dotnet_diagnostic.CA1824.severity = suggestion
|
dotnet_diagnostic.CA1824.severity = suggestion
|
||||||
|
dotnet_diagnostic.CA1835.severity = suggestion
|
||||||
|
dotnet_diagnostic.CA1845.severity = suggestion
|
||||||
|
dotnet_diagnostic.CA1848.severity = suggestion
|
||||||
|
dotnet_diagnostic.CA1849.severity = suggestion
|
||||||
dotnet_diagnostic.CA2000.severity = suggestion
|
dotnet_diagnostic.CA2000.severity = suggestion
|
||||||
dotnet_diagnostic.CA2002.severity = suggestion
|
dotnet_diagnostic.CA2002.severity = suggestion
|
||||||
dotnet_diagnostic.CA2007.severity = suggestion
|
dotnet_diagnostic.CA2007.severity = suggestion
|
||||||
dotnet_diagnostic.CA2008.severity = suggestion
|
dotnet_diagnostic.CA2008.severity = suggestion
|
||||||
dotnet_diagnostic.CA2009.severity = suggestion
|
|
||||||
dotnet_diagnostic.CA2010.severity = suggestion
|
|
||||||
dotnet_diagnostic.CA2011.severity = suggestion
|
|
||||||
dotnet_diagnostic.CA2012.severity = suggestion
|
dotnet_diagnostic.CA2012.severity = suggestion
|
||||||
dotnet_diagnostic.CA2013.severity = suggestion
|
dotnet_diagnostic.CA2013.severity = suggestion
|
||||||
dotnet_diagnostic.CA2100.severity = suggestion
|
dotnet_diagnostic.CA2100.severity = suggestion
|
||||||
@@ -229,6 +227,7 @@ dotnet_diagnostic.CA2243.severity = suggestion
|
|||||||
dotnet_diagnostic.CA2244.severity = suggestion
|
dotnet_diagnostic.CA2244.severity = suggestion
|
||||||
dotnet_diagnostic.CA2245.severity = suggestion
|
dotnet_diagnostic.CA2245.severity = suggestion
|
||||||
dotnet_diagnostic.CA2246.severity = suggestion
|
dotnet_diagnostic.CA2246.severity = suggestion
|
||||||
|
dotnet_diagnostic.CA2254.severity = suggestion
|
||||||
dotnet_diagnostic.CA3061.severity = suggestion
|
dotnet_diagnostic.CA3061.severity = suggestion
|
||||||
dotnet_diagnostic.CA3075.severity = suggestion
|
dotnet_diagnostic.CA3075.severity = suggestion
|
||||||
dotnet_diagnostic.CA3076.severity = suggestion
|
dotnet_diagnostic.CA3076.severity = suggestion
|
||||||
@@ -255,6 +254,7 @@ dotnet_diagnostic.CA5385.severity = suggestion
|
|||||||
dotnet_diagnostic.CA5392.severity = suggestion
|
dotnet_diagnostic.CA5392.severity = suggestion
|
||||||
dotnet_diagnostic.CA5394.severity = suggestion
|
dotnet_diagnostic.CA5394.severity = suggestion
|
||||||
dotnet_diagnostic.CA5397.severity = suggestion
|
dotnet_diagnostic.CA5397.severity = suggestion
|
||||||
|
dotnet_diagnostic.CA5401.severity = suggestion
|
||||||
|
|
||||||
dotnet_diagnostic.SYSLIB0014.severity = none
|
dotnet_diagnostic.SYSLIB0014.severity = none
|
||||||
|
|
||||||
|
|||||||
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -5,9 +5,9 @@ body:
|
|||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
label: Is there an existing issue for this?
|
label: Is there an existing issue for this?
|
||||||
description: Please search to see if an issue already exists for the bug you encountered.
|
description: Please search to see if an open or closed issue already exists for the bug you encountered. If a bug exists and is closed note that it may only be fixed in an unstable branch.
|
||||||
options:
|
options:
|
||||||
- label: I have searched the existing issues
|
- label: I have searched the existing open and closed issues
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
4
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
4
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -5,9 +5,9 @@ body:
|
|||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
label: Is there an existing issue for this?
|
label: Is there an existing issue for this?
|
||||||
description: Please search to see if an issue already exists for the feature you are requesting.
|
description: Please search to see if an open or closed issue already exists for the feature you are requesting. If a request exists and is closed note that it may only be fixed in an unstable branch.
|
||||||
options:
|
options:
|
||||||
- label: I have searched the existing issues
|
- label: I have searched the existing open and closed issues
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
41
.github/workflows/azuresync.yml
vendored
41
.github/workflows/azuresync.yml
vendored
@@ -1,41 +0,0 @@
|
|||||||
name: Sync issue to Azure DevOps work item
|
|
||||||
|
|
||||||
on:
|
|
||||||
issues:
|
|
||||||
types:
|
|
||||||
[opened, edited, deleted, closed, reopened, labeled, unlabeled, assigned]
|
|
||||||
|
|
||||||
concurrency: azuresync-${{ github.event.issue.number }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
alert:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: danhellem/github-actions-issue-to-work-item@master
|
|
||||||
if: "${{ contains(github.event.issue.labels.*.name, 'Type: Bug') == true }}"
|
|
||||||
env:
|
|
||||||
ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}"
|
|
||||||
github_token: "${{ github.token }}"
|
|
||||||
ado_organization: "Servarr"
|
|
||||||
ado_project: "Servarr"
|
|
||||||
ado_area_path: "Servarr\\Prowlarr"
|
|
||||||
ado_wit: "Bug"
|
|
||||||
ado_new_state: "New"
|
|
||||||
ado_active_state: "Active"
|
|
||||||
ado_close_state: "Closed"
|
|
||||||
ado_bypassrules: true
|
|
||||||
log_level: 100
|
|
||||||
- uses: danhellem/github-actions-issue-to-work-item@master
|
|
||||||
if: "${{ contains(github.event.issue.labels.*.name, 'Type: Bug') == false }}"
|
|
||||||
env:
|
|
||||||
ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}"
|
|
||||||
github_token: "${{ github.token }}"
|
|
||||||
ado_organization: "Servarr"
|
|
||||||
ado_project: "Servarr"
|
|
||||||
ado_area_path: "Servarr\\Prowlarr"
|
|
||||||
ado_wit: "User Story"
|
|
||||||
ado_new_state: "New"
|
|
||||||
ado_active_state: "Active"
|
|
||||||
ado_close_state: "Closed"
|
|
||||||
ado_bypassrules: true
|
|
||||||
log_level: 100
|
|
||||||
132
CODE_OF_CONDUCT.md
Normal file
132
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and leaders pledge to make participation in our
|
||||||
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
|
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||||
|
identity and orientation.
|
||||||
|
|
||||||
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
|
diverse, inclusive, and healthy community.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to a positive environment for our
|
||||||
|
community include:
|
||||||
|
|
||||||
|
* Demonstrating empathy and kindness toward other people
|
||||||
|
* Being respectful of differing opinions, viewpoints, and experiences
|
||||||
|
* Giving and gracefully accepting constructive feedback
|
||||||
|
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
|
and learning from the experience
|
||||||
|
* Focusing on what is best not just for us as individuals, but for the overall
|
||||||
|
community
|
||||||
|
|
||||||
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||||
|
any kind
|
||||||
|
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or email address,
|
||||||
|
without their explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Enforcement Responsibilities
|
||||||
|
|
||||||
|
Community leaders are responsible for clarifying and enforcing our standards of
|
||||||
|
acceptable behavior and will take appropriate and fair corrective action in
|
||||||
|
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||||
|
or harmful.
|
||||||
|
|
||||||
|
Community leaders have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||||
|
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||||
|
decisions when appropriate.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all community spaces, and also applies when
|
||||||
|
an individual is officially representing the community in public spaces.
|
||||||
|
Examples of representing our community include using an official e-mail address,
|
||||||
|
posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported to the community leaders responsible for enforcement at
|
||||||
|
<development@prowlarr.com>.
|
||||||
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
reporter of any incident.
|
||||||
|
|
||||||
|
## Enforcement Guidelines
|
||||||
|
|
||||||
|
Community leaders will follow these Community Impact Guidelines in determining
|
||||||
|
the consequences for any action they deem in violation of this Code of Conduct:
|
||||||
|
|
||||||
|
### 1. Correction
|
||||||
|
|
||||||
|
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||||
|
unprofessional or unwelcome in the community.
|
||||||
|
|
||||||
|
**Consequence**: A private, written warning from community leaders, providing
|
||||||
|
clarity around the nature of the violation and an explanation of why the
|
||||||
|
behavior was inappropriate. A public apology may be requested.
|
||||||
|
|
||||||
|
### 2. Warning
|
||||||
|
|
||||||
|
**Community Impact**: A violation through a single incident or series of
|
||||||
|
actions.
|
||||||
|
|
||||||
|
**Consequence**: A warning with consequences for continued behavior. No
|
||||||
|
interaction with the people involved, including unsolicited interaction with
|
||||||
|
those enforcing the Code of Conduct, for a specified period of time. This
|
||||||
|
includes avoiding interactions in community spaces as well as external channels
|
||||||
|
like social media. Violating these terms may lead to a temporary or permanent
|
||||||
|
ban.
|
||||||
|
|
||||||
|
### 3. Temporary Ban
|
||||||
|
|
||||||
|
**Community Impact**: A serious violation of community standards, including
|
||||||
|
sustained inappropriate behavior.
|
||||||
|
|
||||||
|
**Consequence**: A temporary ban from any sort of interaction or public
|
||||||
|
communication with the community for a specified period of time. No public or
|
||||||
|
private interaction with the people involved, including unsolicited interaction
|
||||||
|
with those enforcing the Code of Conduct, is allowed during this period.
|
||||||
|
Violating these terms may lead to a permanent ban.
|
||||||
|
|
||||||
|
### 4. Permanent Ban
|
||||||
|
|
||||||
|
**Community Impact**: Demonstrating a pattern of violation of community
|
||||||
|
standards, including sustained inappropriate behavior, harassment of an
|
||||||
|
individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
|
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||||
|
community.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
|
version 2.1, available at
|
||||||
|
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||||
|
|
||||||
|
Community Impact Guidelines were inspired by
|
||||||
|
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see the FAQ at
|
||||||
|
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||||
|
[https://www.contributor-covenant.org/translations][translations].
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||||
|
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||||
|
[FAQ]: https://www.contributor-covenant.org/faq
|
||||||
|
[translations]: https://www.contributor-covenant.org/translations
|
||||||
@@ -27,10 +27,7 @@ Prowlarr is an indexer manager/proxy built on the popular \*arr .net/reactjs bas
|
|||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
Note: Prowlarr is currently early in life, thus bugs should be expected
|
|
||||||
|
|
||||||
[](https://wiki.servarr.com/prowlarr)
|
[](https://wiki.servarr.com/prowlarr)
|
||||||
|
|
||||||
[](https://prowlarr.com/discord)
|
[](https://prowlarr.com/discord)
|
||||||
[](https://www.reddit.com/r/Prowlarr)
|
[](https://www.reddit.com/r/Prowlarr)
|
||||||
|
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ variables:
|
|||||||
testsFolder: './_tests'
|
testsFolder: './_tests'
|
||||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||||
majorVersion: '0.4.0'
|
majorVersion: '1.3.1'
|
||||||
minorVersion: $[counter('minorVersion', 1)]
|
minorVersion: $[counter('minorVersion', 1)]
|
||||||
prowlarrVersion: '$(majorVersion).$(minorVersion)'
|
prowlarrVersion: '$(majorVersion).$(minorVersion)'
|
||||||
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
|
buildName: '$(Build.SourceBranchName).$(prowlarrVersion)'
|
||||||
sentryOrg: 'servarr'
|
sentryOrg: 'servarr'
|
||||||
sentryUrl: 'https://sentry.servarr.com'
|
sentryUrl: 'https://sentry.servarr.com'
|
||||||
dotnetVersion: '6.0.201'
|
dotnetVersion: '6.0.405'
|
||||||
innoVersion: '6.2.0'
|
innoVersion: '6.2.0'
|
||||||
nodeVersion: '16.x'
|
nodeVersion: '16.x'
|
||||||
windowsImage: 'windows-2022'
|
windowsImage: 'windows-2022'
|
||||||
@@ -97,15 +97,14 @@ stages:
|
|||||||
- bash: |
|
- bash: |
|
||||||
BUNDLEDVERSIONS=${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}/Microsoft.NETCoreSdk.BundledVersions.props
|
BUNDLEDVERSIONS=${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}/Microsoft.NETCoreSdk.BundledVersions.props
|
||||||
echo $BUNDLEDVERSIONS
|
echo $BUNDLEDVERSIONS
|
||||||
grep osx-x64 $BUNDLEDVERSIONS
|
|
||||||
if grep -q freebsd-x64 $BUNDLEDVERSIONS; then
|
if grep -q freebsd-x64 $BUNDLEDVERSIONS; then
|
||||||
echo "BSD already enabled"
|
echo "Extra platforms already enabled"
|
||||||
else
|
else
|
||||||
echo "Enabling BSD support"
|
echo "Enabling extra platform support"
|
||||||
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64/' $BUNDLEDVERSIONS
|
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS
|
||||||
fi
|
fi
|
||||||
displayName: Enable FreeBSD Support
|
displayName: Enable Extra Platform Support
|
||||||
- bash: ./build.sh --backend --enable-bsd
|
- bash: ./build.sh --backend --enable-extra-platforms
|
||||||
displayName: Build Prowlarr Backend
|
displayName: Build Prowlarr Backend
|
||||||
- bash: |
|
- bash: |
|
||||||
find ${OUTPUTFOLDER} -type f ! -path "*/publish/*" -exec rm -rf {} \;
|
find ${OUTPUTFOLDER} -type f ! -path "*/publish/*" -exec rm -rf {} \;
|
||||||
@@ -119,24 +118,28 @@ stages:
|
|||||||
displayName: Publish Backend
|
displayName: Publish Backend
|
||||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
- publish: '$(testsFolder)/net6.0/win-x64/publish'
|
- publish: '$(testsFolder)/net6.0/win-x64/publish'
|
||||||
artifact: WindowsCoreTests
|
artifact: win-x64-tests
|
||||||
displayName: Publish Windows Test Package
|
displayName: Publish win-x64 Test Package
|
||||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
- publish: '$(testsFolder)/net6.0/linux-x64/publish'
|
- publish: '$(testsFolder)/net6.0/linux-x64/publish'
|
||||||
artifact: LinuxCoreTests
|
artifact: linux-x64-tests
|
||||||
displayName: Publish Linux Test Package
|
displayName: Publish linux-x64 Test Package
|
||||||
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
|
- publish: '$(testsFolder)/net6.0/linux-x86/publish'
|
||||||
|
artifact: linux-x86-tests
|
||||||
|
displayName: Publish linux-x86 Test Package
|
||||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
- publish: '$(testsFolder)/net6.0/linux-musl-x64/publish'
|
- publish: '$(testsFolder)/net6.0/linux-musl-x64/publish'
|
||||||
artifact: LinuxMuslCoreTests
|
artifact: linux-musl-x64-tests
|
||||||
displayName: Publish Linux Musl Test Package
|
displayName: Publish linux-musl-x64 Test Package
|
||||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
- publish: '$(testsFolder)/net6.0/freebsd-x64/publish'
|
- publish: '$(testsFolder)/net6.0/freebsd-x64/publish'
|
||||||
artifact: FreebsdCoreTests
|
artifact: freebsd-x64-tests
|
||||||
displayName: Publish FreeBSD Test Package
|
displayName: Publish freebsd-x64 Test Package
|
||||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
- publish: '$(testsFolder)/net6.0/osx-x64/publish'
|
- publish: '$(testsFolder)/net6.0/osx-x64/publish'
|
||||||
artifact: MacCoreTests
|
artifact: osx-x64-tests
|
||||||
displayName: Publish MacOS Test Package
|
displayName: Publish osx-x64 Test Package
|
||||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
|
|
||||||
- stage: Build_Frontend
|
- stage: Build_Frontend
|
||||||
@@ -239,35 +242,35 @@ stages:
|
|||||||
artifactName: WindowsFrontend
|
artifactName: WindowsFrontend
|
||||||
targetPath: _output
|
targetPath: _output
|
||||||
displayName: Fetch Frontend
|
displayName: Fetch Frontend
|
||||||
- bash: ./build.sh --packages --enable-bsd
|
- bash: ./build.sh --packages --enable-extra-platforms
|
||||||
displayName: Create Packages
|
displayName: Create Packages
|
||||||
- bash: |
|
- bash: |
|
||||||
find . -name "Prowlarr" -exec chmod a+x {} \;
|
find . -name "Prowlarr" -exec chmod a+x {} \;
|
||||||
find . -name "Prowlarr.Update" -exec chmod a+x {} \;
|
find . -name "Prowlarr.Update" -exec chmod a+x {} \;
|
||||||
displayName: Set executable bits
|
displayName: Set executable bits
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create Windows Core zip
|
displayName: Create win-x64 zip
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).windows-core-x64.zip'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).windows-core-x64.zip'
|
||||||
archiveType: 'zip'
|
archiveType: 'zip'
|
||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/win-x64/net6.0
|
rootFolderOrFile: $(artifactsFolder)/win-x64/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create Windows x86 Core zip
|
displayName: Create win-x86 zip
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).windows-core-x86.zip'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).windows-core-x86.zip'
|
||||||
archiveType: 'zip'
|
archiveType: 'zip'
|
||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/win-x86/net6.0
|
rootFolderOrFile: $(artifactsFolder)/win-x86/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create MacOS x64 Core app
|
displayName: Create osx-x64 app
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).osx-app-core-x64.zip'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).osx-app-core-x64.zip'
|
||||||
archiveType: 'zip'
|
archiveType: 'zip'
|
||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/osx-x64-app/net6.0
|
rootFolderOrFile: $(artifactsFolder)/osx-x64-app/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create MacOS x64 Core tar
|
displayName: Create osx-x64 tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).osx-core-x64.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).osx-core-x64.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -275,14 +278,14 @@ stages:
|
|||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/osx-x64/net6.0
|
rootFolderOrFile: $(artifactsFolder)/osx-x64/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create MacOS arm64 Core app
|
displayName: Create osx-arm64 app
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).osx-app-core-arm64.zip'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).osx-app-core-arm64.zip'
|
||||||
archiveType: 'zip'
|
archiveType: 'zip'
|
||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/osx-arm64-app/net6.0
|
rootFolderOrFile: $(artifactsFolder)/osx-arm64-app/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create MacOS arm64 Core tar
|
displayName: Create osx-arm64 tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).osx-core-arm64.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).osx-core-arm64.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -290,7 +293,7 @@ stages:
|
|||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/osx-arm64/net6.0
|
rootFolderOrFile: $(artifactsFolder)/osx-arm64/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create Linux Core tar
|
displayName: Create linux-x64 tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-core-x64.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-core-x64.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -298,7 +301,7 @@ stages:
|
|||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/linux-x64/net6.0
|
rootFolderOrFile: $(artifactsFolder)/linux-x64/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create Linux Musl Core tar
|
displayName: Create linux-musl-x64 tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-musl-core-x64.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-musl-core-x64.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -306,7 +309,15 @@ stages:
|
|||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/net6.0
|
rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create ARM32 Linux Core tar
|
displayName: Create linux-x86 tar
|
||||||
|
inputs:
|
||||||
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-core-x86.tar.gz'
|
||||||
|
archiveType: 'tar'
|
||||||
|
tarCompression: 'gz'
|
||||||
|
includeRootFolder: false
|
||||||
|
rootFolderOrFile: $(artifactsFolder)/linux-x86/net6.0
|
||||||
|
- task: ArchiveFiles@2
|
||||||
|
displayName: Create linux-arm tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-core-arm.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-core-arm.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -314,7 +325,7 @@ stages:
|
|||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/linux-arm/net6.0
|
rootFolderOrFile: $(artifactsFolder)/linux-arm/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create ARM32 Linux Musl Core tar
|
displayName: Create linux-musl-arm tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-musl-core-arm.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-musl-core-arm.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -322,7 +333,7 @@ stages:
|
|||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm/net6.0
|
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create ARM64 Linux Core tar
|
displayName: Create linux-arm64 tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-core-arm64.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-core-arm64.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -330,7 +341,7 @@ stages:
|
|||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/linux-arm64/net6.0
|
rootFolderOrFile: $(artifactsFolder)/linux-arm64/net6.0
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create ARM64 Linux Musl Core tar
|
displayName: Create linux-musl-arm64 tar
|
||||||
inputs:
|
inputs:
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-musl-core-arm64.tar.gz'
|
archiveFile: '$(Build.ArtifactStagingDirectory)/Prowlarr.$(buildName).linux-musl-core-arm64.tar.gz'
|
||||||
archiveType: 'tar'
|
archiveType: 'tar'
|
||||||
@@ -405,22 +416,22 @@ stages:
|
|||||||
matrix:
|
matrix:
|
||||||
MacCore:
|
MacCore:
|
||||||
osName: 'Mac'
|
osName: 'Mac'
|
||||||
testName: 'MacCore'
|
testName: 'osx-x64'
|
||||||
poolName: 'Azure Pipelines'
|
poolName: 'Azure Pipelines'
|
||||||
imageName: ${{ variables.macImage }}
|
imageName: ${{ variables.macImage }}
|
||||||
WindowsCore:
|
WindowsCore:
|
||||||
osName: 'Windows'
|
osName: 'Windows'
|
||||||
testName: 'WindowsCore'
|
testName: 'win-x64'
|
||||||
poolName: 'Azure Pipelines'
|
poolName: 'Azure Pipelines'
|
||||||
imageName: ${{ variables.windowsImage }}
|
imageName: ${{ variables.windowsImage }}
|
||||||
LinuxCore:
|
LinuxCore:
|
||||||
osName: 'Linux'
|
osName: 'Linux'
|
||||||
testName: 'LinuxCore'
|
testName: 'linux-x64'
|
||||||
poolName: 'Azure Pipelines'
|
poolName: 'Azure Pipelines'
|
||||||
imageName: ${{ variables.linuxImage }}
|
imageName: ${{ variables.linuxImage }}
|
||||||
FreebsdCore:
|
FreebsdCore:
|
||||||
osName: 'Linux'
|
osName: 'Linux'
|
||||||
testName: 'FreebsdCore'
|
testName: 'freebsd-x64'
|
||||||
poolName: 'FreeBSD'
|
poolName: 'FreeBSD'
|
||||||
imageName:
|
imageName:
|
||||||
|
|
||||||
@@ -439,7 +450,7 @@ stages:
|
|||||||
displayName: Download Test Artifact
|
displayName: Download Test Artifact
|
||||||
inputs:
|
inputs:
|
||||||
buildType: 'current'
|
buildType: 'current'
|
||||||
artifactName: '$(testName)Tests'
|
artifactName: '$(testName)-tests'
|
||||||
targetPath: $(testsFolder)
|
targetPath: $(testsFolder)
|
||||||
- powershell: Set-Service SCardSvr -StartupType Manual
|
- powershell: Set-Service SCardSvr -StartupType Manual
|
||||||
displayName: Enable Windows Test Service
|
displayName: Enable Windows Test Service
|
||||||
@@ -469,8 +480,12 @@ stages:
|
|||||||
matrix:
|
matrix:
|
||||||
alpine:
|
alpine:
|
||||||
testName: 'Musl Net Core'
|
testName: 'Musl Net Core'
|
||||||
artifactName: LinuxMuslCoreTests
|
artifactName: linux-musl-x64-tests
|
||||||
containerImage: ghcr.io/servarr/testimages:alpine
|
containerImage: ghcr.io/servarr/testimages:alpine
|
||||||
|
linux-x86:
|
||||||
|
testName: 'linux-x86'
|
||||||
|
artifactName: linux-x86-tests
|
||||||
|
containerImage: ghcr.io/servarr/testimages:linux-x86
|
||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: ${{ variables.linuxImage }}
|
vmImage: ${{ variables.linuxImage }}
|
||||||
@@ -481,9 +496,15 @@ stages:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
displayName: 'Install .net core'
|
displayName: 'Install .NET'
|
||||||
inputs:
|
inputs:
|
||||||
version: $(dotnetVersion)
|
version: $(dotnetVersion)
|
||||||
|
condition: and(succeeded(), ne(variables['testName'], 'linux-x86'))
|
||||||
|
- bash: |
|
||||||
|
SDKURL=$(curl -s https://api.github.com/repos/Servarr/dotnet-linux-x86/releases | jq -rc '.[].assets[].browser_download_url' | grep sdk-${DOTNETVERSION}.*gz$)
|
||||||
|
curl -fsSL $SDKURL | tar xzf - -C /opt/dotnet
|
||||||
|
displayName: 'Install .NET'
|
||||||
|
condition: and(succeeded(), eq(variables['testName'], 'linux-x86'))
|
||||||
- checkout: none
|
- checkout: none
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: Download Test Artifact
|
displayName: Download Test Artifact
|
||||||
@@ -506,6 +527,58 @@ stages:
|
|||||||
testResultsFiles: '**/TestResult.xml'
|
testResultsFiles: '**/TestResult.xml'
|
||||||
testRunTitle: '$(testName) Unit Tests'
|
testRunTitle: '$(testName) Unit Tests'
|
||||||
failTaskOnFailedTests: true
|
failTaskOnFailedTests: true
|
||||||
|
|
||||||
|
- job: Unit_LinuxCore_Postgres
|
||||||
|
displayName: Unit Native LinuxCore with Postgres Database
|
||||||
|
dependsOn: Prepare
|
||||||
|
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
|
variables:
|
||||||
|
pattern: 'Prowlarr.*.linux-core-x64.tar.gz'
|
||||||
|
artifactName: linux-x64-tests
|
||||||
|
Prowlarr__Postgres__Host: 'localhost'
|
||||||
|
Prowlarr__Postgres__Port: '5432'
|
||||||
|
Prowlarr__Postgres__User: 'prowlarr'
|
||||||
|
Prowlarr__Postgres__Password: 'prowlarr'
|
||||||
|
|
||||||
|
pool:
|
||||||
|
vmImage: ${{ variables.linuxImage }}
|
||||||
|
|
||||||
|
timeoutInMinutes: 10
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- task: UseDotNet@2
|
||||||
|
displayName: 'Install .net core'
|
||||||
|
inputs:
|
||||||
|
version: $(dotnetVersion)
|
||||||
|
- checkout: none
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
displayName: Download Test Artifact
|
||||||
|
inputs:
|
||||||
|
buildType: 'current'
|
||||||
|
artifactName: $(artifactName)
|
||||||
|
targetPath: $(testsFolder)
|
||||||
|
- bash: find ${TESTSFOLDER} -name "Prowlarr.Test.Dummy" -exec chmod a+x {} \;
|
||||||
|
displayName: Make Test Dummy Executable
|
||||||
|
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
|
||||||
|
- bash: |
|
||||||
|
docker run -d --name=postgres14 \
|
||||||
|
-e POSTGRES_PASSWORD=prowlarr \
|
||||||
|
-e POSTGRES_USER=prowlarr \
|
||||||
|
-p 5432:5432/tcp \
|
||||||
|
postgres:14
|
||||||
|
displayName: Start postgres
|
||||||
|
- bash: |
|
||||||
|
chmod a+x ${TESTSFOLDER}/test.sh
|
||||||
|
ls -lR ${TESTSFOLDER}
|
||||||
|
${TESTSFOLDER}/test.sh Linux Unit Test
|
||||||
|
displayName: Run Tests
|
||||||
|
- task: PublishTestResults@2
|
||||||
|
displayName: Publish Test Results
|
||||||
|
inputs:
|
||||||
|
testResultsFormat: 'NUnit'
|
||||||
|
testResultsFiles: '**/TestResult.xml'
|
||||||
|
testRunTitle: 'LinuxCore Postgres Unit Tests'
|
||||||
|
failTaskOnFailedTests: true
|
||||||
|
|
||||||
- stage: Integration
|
- stage: Integration
|
||||||
displayName: Integration
|
displayName: Integration
|
||||||
@@ -533,17 +606,17 @@ stages:
|
|||||||
matrix:
|
matrix:
|
||||||
MacCore:
|
MacCore:
|
||||||
osName: 'Mac'
|
osName: 'Mac'
|
||||||
testName: 'MacCore'
|
testName: 'osx-x64'
|
||||||
imageName: ${{ variables.macImage }}
|
imageName: ${{ variables.macImage }}
|
||||||
pattern: 'Prowlarr.*.osx-core-x64.tar.gz'
|
pattern: 'Prowlarr.*.osx-core-x64.tar.gz'
|
||||||
WindowsCore:
|
WindowsCore:
|
||||||
osName: 'Windows'
|
osName: 'Windows'
|
||||||
testName: 'WindowsCore'
|
testName: 'win-x64'
|
||||||
imageName: ${{ variables.windowsImage }}
|
imageName: ${{ variables.windowsImage }}
|
||||||
pattern: 'Prowlarr.*.windows-core-x64.zip'
|
pattern: 'Prowlarr.*.windows-core-x64.zip'
|
||||||
LinuxCore:
|
LinuxCore:
|
||||||
osName: 'Linux'
|
osName: 'Linux'
|
||||||
testName: 'LinuxCore'
|
testName: 'linux-x64'
|
||||||
imageName: ${{ variables.linuxImage }}
|
imageName: ${{ variables.linuxImage }}
|
||||||
pattern: 'Prowlarr.*.linux-core-x64.tar.gz'
|
pattern: 'Prowlarr.*.linux-core-x64.tar.gz'
|
||||||
|
|
||||||
@@ -560,7 +633,7 @@ stages:
|
|||||||
displayName: Download Test Artifact
|
displayName: Download Test Artifact
|
||||||
inputs:
|
inputs:
|
||||||
buildType: 'current'
|
buildType: 'current'
|
||||||
artifactName: '$(testName)Tests'
|
artifactName: '$(testName)-tests'
|
||||||
targetPath: $(testsFolder)
|
targetPath: $(testsFolder)
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: Download Build Artifact
|
displayName: Download Build Artifact
|
||||||
@@ -590,6 +663,67 @@ stages:
|
|||||||
failTaskOnFailedTests: true
|
failTaskOnFailedTests: true
|
||||||
displayName: Publish Test Results
|
displayName: Publish Test Results
|
||||||
|
|
||||||
|
- job: Integration_LinuxCore_Postgres
|
||||||
|
displayName: Integration Native LinuxCore with Postgres Database
|
||||||
|
dependsOn: Prepare
|
||||||
|
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
|
variables:
|
||||||
|
pattern: 'Prowlarr.*.linux-core-x64.tar.gz'
|
||||||
|
Prowlarr__Postgres__Host: 'localhost'
|
||||||
|
Prowlarr__Postgres__Port: '5432'
|
||||||
|
Prowlarr__Postgres__User: 'prowlarr'
|
||||||
|
Prowlarr__Postgres__Password: 'prowlarr'
|
||||||
|
|
||||||
|
pool:
|
||||||
|
vmImage: ${{ variables.linuxImage }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- task: UseDotNet@2
|
||||||
|
displayName: 'Install .net core'
|
||||||
|
inputs:
|
||||||
|
version: $(dotnetVersion)
|
||||||
|
- checkout: none
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
displayName: Download Test Artifact
|
||||||
|
inputs:
|
||||||
|
buildType: 'current'
|
||||||
|
artifactName: 'linux-x64-tests'
|
||||||
|
targetPath: $(testsFolder)
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
displayName: Download Build Artifact
|
||||||
|
inputs:
|
||||||
|
buildType: 'current'
|
||||||
|
artifactName: Packages
|
||||||
|
itemPattern: '**/$(pattern)'
|
||||||
|
targetPath: $(Build.ArtifactStagingDirectory)
|
||||||
|
- task: ExtractFiles@1
|
||||||
|
inputs:
|
||||||
|
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
||||||
|
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
||||||
|
displayName: Extract Package
|
||||||
|
- bash: |
|
||||||
|
mkdir -p ./bin/
|
||||||
|
cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Prowlarr/. ./bin/
|
||||||
|
displayName: Move Package Contents
|
||||||
|
- bash: |
|
||||||
|
docker run -d --name=postgres14 \
|
||||||
|
-e POSTGRES_PASSWORD=prowlarr \
|
||||||
|
-e POSTGRES_USER=prowlarr \
|
||||||
|
-p 5432:5432/tcp \
|
||||||
|
postgres:14
|
||||||
|
displayName: Start postgres
|
||||||
|
- bash: |
|
||||||
|
chmod a+x ${TESTSFOLDER}/test.sh
|
||||||
|
${TESTSFOLDER}/test.sh Linux Integration Test
|
||||||
|
displayName: Run Integration Tests
|
||||||
|
- task: PublishTestResults@2
|
||||||
|
inputs:
|
||||||
|
testResultsFormat: 'NUnit'
|
||||||
|
testResultsFiles: '**/TestResult.xml'
|
||||||
|
testRunTitle: 'Integration LinuxCore Postgres Database Integration Tests'
|
||||||
|
failTaskOnFailedTests: true
|
||||||
|
displayName: Publish Test Results
|
||||||
|
|
||||||
- job: Integration_FreeBSD
|
- job: Integration_FreeBSD
|
||||||
displayName: Integration Native FreeBSD
|
displayName: Integration Native FreeBSD
|
||||||
dependsOn: Prepare
|
dependsOn: Prepare
|
||||||
@@ -607,14 +741,14 @@ stages:
|
|||||||
displayName: Download Test Artifact
|
displayName: Download Test Artifact
|
||||||
inputs:
|
inputs:
|
||||||
buildType: 'current'
|
buildType: 'current'
|
||||||
artifactName: 'FreebsdCoreTests'
|
artifactName: 'freebsd-x64-tests'
|
||||||
targetPath: $(testsFolder)
|
targetPath: $(testsFolder)
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: Download Build Artifact
|
displayName: Download Build Artifact
|
||||||
inputs:
|
inputs:
|
||||||
buildType: 'current'
|
buildType: 'current'
|
||||||
artifactName: Packages
|
artifactName: Packages
|
||||||
itemPattern: '/$(pattern)'
|
itemPattern: '**/$(pattern)'
|
||||||
targetPath: $(Build.ArtifactStagingDirectory)
|
targetPath: $(Build.ArtifactStagingDirectory)
|
||||||
- bash: |
|
- bash: |
|
||||||
mkdir -p ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin
|
mkdir -p ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin
|
||||||
@@ -643,11 +777,15 @@ stages:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
alpine:
|
alpine:
|
||||||
testName: 'Musl Net Core'
|
testName: 'linux-musl-x64'
|
||||||
artifactName: LinuxMuslCoreTests
|
artifactName: linux-musl-x64-tests
|
||||||
containerImage: ghcr.io/servarr/testimages:alpine
|
containerImage: ghcr.io/servarr/testimages:alpine
|
||||||
pattern: 'Prowlarr.*.linux-musl-core-x64.tar.gz'
|
pattern: 'Prowlarr.*.linux-musl-core-x64.tar.gz'
|
||||||
|
linux-x86:
|
||||||
|
testName: 'linux-x86'
|
||||||
|
artifactName: linux-x86-tests
|
||||||
|
containerImage: ghcr.io/servarr/testimages:linux-x86
|
||||||
|
pattern: 'Prowlarr.*.linux-core-x86.tar.gz'
|
||||||
pool:
|
pool:
|
||||||
vmImage: ${{ variables.linuxImage }}
|
vmImage: ${{ variables.linuxImage }}
|
||||||
|
|
||||||
@@ -657,9 +795,15 @@ stages:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
displayName: 'Install .net core'
|
displayName: 'Install .NET'
|
||||||
inputs:
|
inputs:
|
||||||
version: $(dotnetVersion)
|
version: $(dotnetVersion)
|
||||||
|
condition: and(succeeded(), ne(variables['testName'], 'linux-x86'))
|
||||||
|
- bash: |
|
||||||
|
SDKURL=$(curl -s https://api.github.com/repos/Servarr/dotnet-linux-x86/releases | jq -rc '.[].assets[].browser_download_url' | grep sdk-${DOTNETVERSION}.*gz$)
|
||||||
|
curl -fsSL $SDKURL | tar xzf - -C /opt/dotnet
|
||||||
|
displayName: 'Install .NET'
|
||||||
|
condition: and(succeeded(), eq(variables['testName'], 'linux-x86'))
|
||||||
- checkout: none
|
- checkout: none
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: Download Test Artifact
|
displayName: Download Test Artifact
|
||||||
@@ -705,16 +849,19 @@ stages:
|
|||||||
matrix:
|
matrix:
|
||||||
Linux:
|
Linux:
|
||||||
osName: 'Linux'
|
osName: 'Linux'
|
||||||
|
artifactName: 'linux-x64'
|
||||||
imageName: ${{ variables.linuxImage }}
|
imageName: ${{ variables.linuxImage }}
|
||||||
pattern: 'Prowlarr.*.linux-core-x64.tar.gz'
|
pattern: 'Prowlarr.*.linux-core-x64.tar.gz'
|
||||||
failBuild: true
|
failBuild: true
|
||||||
Mac:
|
Mac:
|
||||||
osName: 'Mac'
|
osName: 'Mac'
|
||||||
|
artifactName: 'osx-x64'
|
||||||
imageName: ${{ variables.macImage }}
|
imageName: ${{ variables.macImage }}
|
||||||
pattern: 'Prowlarr.*.osx-core-x64.tar.gz'
|
pattern: 'Prowlarr.*.osx-core-x64.tar.gz'
|
||||||
failBuild: true
|
failBuild: true
|
||||||
Windows:
|
Windows:
|
||||||
osName: 'Windows'
|
osName: 'Windows'
|
||||||
|
artifactName: 'win-x64'
|
||||||
imageName: ${{ variables.windowsImage }}
|
imageName: ${{ variables.windowsImage }}
|
||||||
pattern: 'Prowlarr.*.windows-core-x64.zip'
|
pattern: 'Prowlarr.*.windows-core-x64.zip'
|
||||||
failBuild: true
|
failBuild: true
|
||||||
@@ -732,7 +879,7 @@ stages:
|
|||||||
displayName: Download Test Artifact
|
displayName: Download Test Artifact
|
||||||
inputs:
|
inputs:
|
||||||
buildType: 'current'
|
buildType: 'current'
|
||||||
artifactName: '$(osName)CoreTests'
|
artifactName: '$(artifactName)-tests'
|
||||||
targetPath: $(testsFolder)
|
targetPath: $(testsFolder)
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: Download Build Artifact
|
displayName: Download Build Artifact
|
||||||
@@ -961,4 +1108,5 @@ stages:
|
|||||||
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
||||||
DISCORDCHANNELID: $(discordChannelId)
|
DISCORDCHANNELID: $(discordChannelId)
|
||||||
DISCORDWEBHOOKKEY: $(discordWebhookKey)
|
DISCORDWEBHOOKKEY: $(discordWebhookKey)
|
||||||
|
DISCORDTHREADID: $(discordThreadId)
|
||||||
|
|
||||||
|
|||||||
45
build.sh
45
build.sh
@@ -25,15 +25,22 @@ UpdateVersionNumber()
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
EnableBsdSupport()
|
EnableExtraPlatformsInSDK()
|
||||||
{
|
{
|
||||||
#todo enable sdk with
|
SDK_PATH=$(dotnet --list-sdks | grep -P '6\.\d\.\d+' | head -1 | sed 's/\(6\.[0-9]*\.[0-9]*\).*\[\(.*\)\]/\2\/\1/g')
|
||||||
#SDK_PATH=$(dotnet --list-sdks | grep -P '5\.\d\.\d+' | head -1 | sed 's/\(5\.[0-9]*\.[0-9]*\).*\[\(.*\)\]/\2\/\1/g')
|
BUNDLEDVERSIONS="${SDK_PATH}/Microsoft.NETCoreSdk.BundledVersions.props"
|
||||||
# BUNDLED_VERSIONS="${SDK_PATH}/Microsoft.NETCoreSdk.BundledVersions.props"
|
if grep -q freebsd-x64 $BUNDLEDVERSIONS; then
|
||||||
|
echo "Extra platforms already enabled"
|
||||||
|
else
|
||||||
|
echo "Enabling extra platform support"
|
||||||
|
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
EnableExtraPlatforms()
|
||||||
|
{
|
||||||
if grep -qv freebsd-x64 src/Directory.Build.props; then
|
if grep -qv freebsd-x64 src/Directory.Build.props; then
|
||||||
sed -i'' -e "s^<RuntimeIdentifiers>\(.*\)</RuntimeIdentifiers>^<RuntimeIdentifiers>\1;freebsd-x64</RuntimeIdentifiers>^g" src/Directory.Build.props
|
sed -i'' -e "s^<RuntimeIdentifiers>\(.*\)</RuntimeIdentifiers>^<RuntimeIdentifiers>\1;freebsd-x64;linux-x86</RuntimeIdentifiers>^g" src/Directory.Build.props
|
||||||
sed -i'' -e "s^<ExcludedRuntimeFrameworkPairs>\(.*\)</ExcludedRuntimeFrameworkPairs>^<ExcludedRuntimeFrameworkPairs>\1;freebsd-x64:net472</ExcludedRuntimeFrameworkPairs>^g" src/Directory.Build.props
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,7 +300,8 @@ if [ $# -eq 0 ]; then
|
|||||||
PACKAGES=YES
|
PACKAGES=YES
|
||||||
INSTALLER=NO
|
INSTALLER=NO
|
||||||
LINT=YES
|
LINT=YES
|
||||||
ENABLE_BSD=NO
|
ENABLE_EXTRA_PLATFORMS=NO
|
||||||
|
ENABLE_EXTRA_PLATFORMS_IN_SDK=NO
|
||||||
fi
|
fi
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]
|
while [[ $# -gt 0 ]]
|
||||||
@@ -305,8 +313,12 @@ case $key in
|
|||||||
BACKEND=YES
|
BACKEND=YES
|
||||||
shift # past argument
|
shift # past argument
|
||||||
;;
|
;;
|
||||||
--enable-bsd)
|
--enable-bsd|--enable-extra-platforms)
|
||||||
ENABLE_BSD=YES
|
ENABLE_EXTRA_PLATFORMS=YES
|
||||||
|
shift # past argument
|
||||||
|
;;
|
||||||
|
--enable-extra-platforms-in-sdk)
|
||||||
|
ENABLE_EXTRA_PLATFORMS_IN_SDK=YES
|
||||||
shift # past argument
|
shift # past argument
|
||||||
;;
|
;;
|
||||||
-r|--runtime)
|
-r|--runtime)
|
||||||
@@ -350,12 +362,17 @@ esac
|
|||||||
done
|
done
|
||||||
set -- "${POSITIONAL[@]}" # restore positional parameters
|
set -- "${POSITIONAL[@]}" # restore positional parameters
|
||||||
|
|
||||||
|
if [ "$ENABLE_EXTRA_PLATFORMS_IN_SDK" = "YES" ];
|
||||||
|
then
|
||||||
|
EnableExtraPlatformsInSDK
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$BACKEND" = "YES" ];
|
if [ "$BACKEND" = "YES" ];
|
||||||
then
|
then
|
||||||
UpdateVersionNumber
|
UpdateVersionNumber
|
||||||
if [ "$ENABLE_BSD" = "YES" ];
|
if [ "$ENABLE_EXTRA_PLATFORMS" = "YES" ];
|
||||||
then
|
then
|
||||||
EnableBsdSupport
|
EnableExtraPlatforms
|
||||||
fi
|
fi
|
||||||
Build
|
Build
|
||||||
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
|
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
|
||||||
@@ -365,9 +382,10 @@ then
|
|||||||
PackageTests "net6.0" "linux-x64"
|
PackageTests "net6.0" "linux-x64"
|
||||||
PackageTests "net6.0" "linux-musl-x64"
|
PackageTests "net6.0" "linux-musl-x64"
|
||||||
PackageTests "net6.0" "osx-x64"
|
PackageTests "net6.0" "osx-x64"
|
||||||
if [ "$ENABLE_BSD" = "YES" ];
|
if [ "$ENABLE_EXTRA_PLATFORMS" = "YES" ];
|
||||||
then
|
then
|
||||||
PackageTests "net6.0" "freebsd-x64"
|
PackageTests "net6.0" "freebsd-x64"
|
||||||
|
PackageTests "net6.0" "linux-x86"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
PackageTests "$FRAMEWORK" "$RID"
|
PackageTests "$FRAMEWORK" "$RID"
|
||||||
@@ -406,9 +424,10 @@ then
|
|||||||
Package "net6.0" "linux-musl-arm"
|
Package "net6.0" "linux-musl-arm"
|
||||||
Package "net6.0" "osx-x64"
|
Package "net6.0" "osx-x64"
|
||||||
Package "net6.0" "osx-arm64"
|
Package "net6.0" "osx-arm64"
|
||||||
if [ "$ENABLE_BSD" = "YES" ];
|
if [ "$ENABLE_EXTRA_PLATFORMS" = "YES" ];
|
||||||
then
|
then
|
||||||
Package "net6.0" "freebsd-x64"
|
Package "net6.0" "freebsd-x64"
|
||||||
|
Package "net6.0" "linux-x86"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
Package "$FRAMEWORK" "$RID"
|
Package "$FRAMEWORK" "$RID"
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ module.exports = {
|
|||||||
plugins: [
|
plugins: [
|
||||||
'filenames',
|
'filenames',
|
||||||
'react',
|
'react',
|
||||||
|
'react-hooks',
|
||||||
'simple-import-sort',
|
'simple-import-sort',
|
||||||
'import'
|
'import'
|
||||||
],
|
],
|
||||||
@@ -308,7 +309,9 @@ module.exports = {
|
|||||||
'react/react-in-jsx-scope': 2,
|
'react/react-in-jsx-scope': 2,
|
||||||
'react/self-closing-comp': 2,
|
'react/self-closing-comp': 2,
|
||||||
'react/sort-comp': 2,
|
'react/sort-comp': 2,
|
||||||
'react/jsx-wrap-multilines': 2
|
'react/jsx-wrap-multilines': 2,
|
||||||
|
'react-hooks/rules-of-hooks': 'error',
|
||||||
|
'react-hooks/exhaustive-deps': 'error'
|
||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -142,8 +142,8 @@ module.exports = (env) => {
|
|||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.js?$/,
|
test: /\.jsx?$/,
|
||||||
exclude: /(node_modules|JsLibraries)/,
|
exclude: /[\\/]node_modules[\\/](?!(@sentry\/browser|@sentry\/integrations|chart.js|filesize|normalize.css)[\\/])/,
|
||||||
use: [
|
use: [
|
||||||
{
|
{
|
||||||
loader: 'babel-loader',
|
loader: 'babel-loader',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Fragment, useEffect } from 'react';
|
import React, { Fragment, useCallback, useEffect } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import themes from 'Styles/Themes';
|
import themes from 'Styles/Themes';
|
||||||
@@ -19,7 +19,8 @@ function createMapStateToProps() {
|
|||||||
|
|
||||||
function ApplyTheme({ theme, children }) {
|
function ApplyTheme({ theme, children }) {
|
||||||
// Update the CSS Variables
|
// Update the CSS Variables
|
||||||
function updateCSSVariables() {
|
|
||||||
|
const updateCSSVariables = useCallback(() => {
|
||||||
const arrayOfVariableKeys = Object.keys(themes[theme]);
|
const arrayOfVariableKeys = Object.keys(themes[theme]);
|
||||||
const arrayOfVariableValues = Object.values(themes[theme]);
|
const arrayOfVariableValues = Object.values(themes[theme]);
|
||||||
|
|
||||||
@@ -31,12 +32,12 @@ function ApplyTheme({ theme, children }) {
|
|||||||
arrayOfVariableValues[index]
|
arrayOfVariableValues[index]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}, [theme]);
|
||||||
|
|
||||||
// On Component Mount and Component Update
|
// On Component Mount and Component Update
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateCSSVariables(theme);
|
updateCSSVariables(theme);
|
||||||
}, [theme]);
|
}, [updateCSSVariables, theme]);
|
||||||
|
|
||||||
return <Fragment>{children}</Fragment>;
|
return <Fragment>{children}</Fragment>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ function CustomFiltersModalContent(props) {
|
|||||||
|
|
||||||
<div className={styles.addButtonContainer}>
|
<div className={styles.addButtonContainer}>
|
||||||
<Button onPress={onAddCustomFilter}>
|
<Button onPress={onAddCustomFilter}>
|
||||||
Add Custom Filter
|
{translate('AddCustomFilter')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
|||||||
@@ -78,7 +78,7 @@
|
|||||||
|
|
||||||
border: 1px solid var(--inputBorderColor);
|
border: 1px solid var(--inputBorderColor);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-color: var(--white);
|
background-color: var(--inputBackgroundColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading {
|
.loading {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import FormInputHelpText from './FormInputHelpText';
|
|||||||
import IndexerFlagsSelectInputConnector from './IndexerFlagsSelectInputConnector';
|
import IndexerFlagsSelectInputConnector from './IndexerFlagsSelectInputConnector';
|
||||||
import InfoInput from './InfoInput';
|
import InfoInput from './InfoInput';
|
||||||
import KeyValueListInput from './KeyValueListInput';
|
import KeyValueListInput from './KeyValueListInput';
|
||||||
|
import NewznabCategorySelectInputConnector from './NewznabCategorySelectInputConnector';
|
||||||
import NumberInput from './NumberInput';
|
import NumberInput from './NumberInput';
|
||||||
import OAuthInputConnector from './OAuthInputConnector';
|
import OAuthInputConnector from './OAuthInputConnector';
|
||||||
import PasswordInput from './PasswordInput';
|
import PasswordInput from './PasswordInput';
|
||||||
@@ -68,6 +69,9 @@ function getComponent(type) {
|
|||||||
case inputTypes.PATH:
|
case inputTypes.PATH:
|
||||||
return PathInputConnector;
|
return PathInputConnector;
|
||||||
|
|
||||||
|
case inputTypes.CATEGORY_SELECT:
|
||||||
|
return NewznabCategorySelectInputConnector;
|
||||||
|
|
||||||
case inputTypes.INDEXER_FLAGS_SELECT:
|
case inputTypes.INDEXER_FLAGS_SELECT:
|
||||||
return IndexerFlagsSelectInputConnector;
|
return IndexerFlagsSelectInputConnector;
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ function createMapStateToProps() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
value,
|
value: value || [],
|
||||||
values
|
values
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ class TagInputInput extends Component {
|
|||||||
<div
|
<div
|
||||||
ref={forwardedRef}
|
ref={forwardedRef}
|
||||||
className={className}
|
className={className}
|
||||||
component="div"
|
|
||||||
onMouseDown={this.onMouseDown}
|
onMouseDown={this.onMouseDown}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -36,5 +36,5 @@
|
|||||||
/** Outline **/
|
/** Outline **/
|
||||||
|
|
||||||
.outline {
|
.outline {
|
||||||
background-color: var(--white);
|
background-color: var(--cardBackgroundColor);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,5 +108,5 @@
|
|||||||
/** Outline **/
|
/** Outline **/
|
||||||
|
|
||||||
.outline {
|
.outline {
|
||||||
background-color: var(--white);
|
background-color: var(--cardBackgroundColor);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,10 +30,10 @@ function ConfirmModal(props) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
bindShortcut('enter', onConfirm);
|
bindShortcut('enter', onConfirm);
|
||||||
} else {
|
|
||||||
unbindShortcut('enter', onConfirm);
|
return () => unbindShortcut('enter', onConfirm);
|
||||||
}
|
}
|
||||||
}, [onConfirm]);
|
}, [bindShortcut, unbindShortcut, isOpen, onConfirm]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ class PageHeader extends Component {
|
|||||||
<IconButton
|
<IconButton
|
||||||
className={styles.donate}
|
className={styles.donate}
|
||||||
name={icons.HEART}
|
name={icons.HEART}
|
||||||
to="https://opencollective.com/prowlarr"
|
to="https://prowlarr.com/donate"
|
||||||
size={14}
|
size={14}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--toobarButtonHoverColor);
|
color: #515253;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import AppUpdatedModalConnector from 'App/AppUpdatedModalConnector';
|
|||||||
import ColorImpairedContext from 'App/ColorImpairedContext';
|
import ColorImpairedContext from 'App/ColorImpairedContext';
|
||||||
import ConnectionLostModalConnector from 'App/ConnectionLostModalConnector';
|
import ConnectionLostModalConnector from 'App/ConnectionLostModalConnector';
|
||||||
import SignalRConnector from 'Components/SignalRConnector';
|
import SignalRConnector from 'Components/SignalRConnector';
|
||||||
|
import AuthenticationRequiredModal from 'FirstRun/AuthenticationRequiredModal';
|
||||||
import locationShape from 'Helpers/Props/Shapes/locationShape';
|
import locationShape from 'Helpers/Props/Shapes/locationShape';
|
||||||
import PageHeader from './Header/PageHeader';
|
import PageHeader from './Header/PageHeader';
|
||||||
import PageSidebar from './Sidebar/PageSidebar';
|
import PageSidebar from './Sidebar/PageSidebar';
|
||||||
@@ -75,6 +76,7 @@ class Page extends Component {
|
|||||||
isSmallScreen,
|
isSmallScreen,
|
||||||
isSidebarVisible,
|
isSidebarVisible,
|
||||||
enableColorImpairedMode,
|
enableColorImpairedMode,
|
||||||
|
authenticationEnabled,
|
||||||
onSidebarToggle,
|
onSidebarToggle,
|
||||||
onSidebarVisibleChange
|
onSidebarVisibleChange
|
||||||
} = this.props;
|
} = this.props;
|
||||||
@@ -109,6 +111,10 @@ class Page extends Component {
|
|||||||
isOpen={this.state.isConnectionLostModalOpen}
|
isOpen={this.state.isConnectionLostModalOpen}
|
||||||
onModalClose={this.onConnectionLostModalClose}
|
onModalClose={this.onConnectionLostModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<AuthenticationRequiredModal
|
||||||
|
isOpen={!authenticationEnabled}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</ColorImpairedContext.Provider>
|
</ColorImpairedContext.Provider>
|
||||||
);
|
);
|
||||||
@@ -124,6 +130,7 @@ Page.propTypes = {
|
|||||||
isUpdated: PropTypes.bool.isRequired,
|
isUpdated: PropTypes.bool.isRequired,
|
||||||
isDisconnected: PropTypes.bool.isRequired,
|
isDisconnected: PropTypes.bool.isRequired,
|
||||||
enableColorImpairedMode: PropTypes.bool.isRequired,
|
enableColorImpairedMode: PropTypes.bool.isRequired,
|
||||||
|
authenticationEnabled: PropTypes.bool.isRequired,
|
||||||
onResize: PropTypes.func.isRequired,
|
onResize: PropTypes.func.isRequired,
|
||||||
onSidebarToggle: PropTypes.func.isRequired,
|
onSidebarToggle: PropTypes.func.isRequired,
|
||||||
onSidebarVisibleChange: PropTypes.func.isRequired
|
onSidebarVisibleChange: PropTypes.func.isRequired
|
||||||
|
|||||||
@@ -7,10 +7,11 @@ import { saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions';
|
|||||||
import { fetchCustomFilters } from 'Store/Actions/customFilterActions';
|
import { fetchCustomFilters } from 'Store/Actions/customFilterActions';
|
||||||
import { fetchIndexers } from 'Store/Actions/indexerActions';
|
import { fetchIndexers } from 'Store/Actions/indexerActions';
|
||||||
import { fetchIndexerStatus } from 'Store/Actions/indexerStatusActions';
|
import { fetchIndexerStatus } from 'Store/Actions/indexerStatusActions';
|
||||||
import { fetchAppProfiles, fetchGeneralSettings, fetchIndexerCategories, fetchLanguages, fetchUISettings } from 'Store/Actions/settingsActions';
|
import { fetchAppProfiles, fetchGeneralSettings, fetchIndexerCategories, fetchUISettings } from 'Store/Actions/settingsActions';
|
||||||
import { fetchStatus } from 'Store/Actions/systemActions';
|
import { fetchStatus } from 'Store/Actions/systemActions';
|
||||||
import { fetchTags } from 'Store/Actions/tagActions';
|
import { fetchTags } from 'Store/Actions/tagActions';
|
||||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||||
|
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
|
||||||
import ErrorPage from './ErrorPage';
|
import ErrorPage from './ErrorPage';
|
||||||
import LoadingPage from './LoadingPage';
|
import LoadingPage from './LoadingPage';
|
||||||
import Page from './Page';
|
import Page from './Page';
|
||||||
@@ -48,7 +49,6 @@ const selectIsPopulated = createSelector(
|
|||||||
(state) => state.tags.isPopulated,
|
(state) => state.tags.isPopulated,
|
||||||
(state) => state.settings.ui.isPopulated,
|
(state) => state.settings.ui.isPopulated,
|
||||||
(state) => state.settings.general.isPopulated,
|
(state) => state.settings.general.isPopulated,
|
||||||
(state) => state.settings.languages.isPopulated,
|
|
||||||
(state) => state.settings.appProfiles.isPopulated,
|
(state) => state.settings.appProfiles.isPopulated,
|
||||||
(state) => state.indexers.isPopulated,
|
(state) => state.indexers.isPopulated,
|
||||||
(state) => state.indexerStatus.isPopulated,
|
(state) => state.indexerStatus.isPopulated,
|
||||||
@@ -59,7 +59,6 @@ const selectIsPopulated = createSelector(
|
|||||||
tagsIsPopulated,
|
tagsIsPopulated,
|
||||||
uiSettingsIsPopulated,
|
uiSettingsIsPopulated,
|
||||||
generalSettingsIsPopulated,
|
generalSettingsIsPopulated,
|
||||||
languagesIsPopulated,
|
|
||||||
appProfilesIsPopulated,
|
appProfilesIsPopulated,
|
||||||
indexersIsPopulated,
|
indexersIsPopulated,
|
||||||
indexerStatusIsPopulated,
|
indexerStatusIsPopulated,
|
||||||
@@ -71,7 +70,6 @@ const selectIsPopulated = createSelector(
|
|||||||
tagsIsPopulated &&
|
tagsIsPopulated &&
|
||||||
uiSettingsIsPopulated &&
|
uiSettingsIsPopulated &&
|
||||||
generalSettingsIsPopulated &&
|
generalSettingsIsPopulated &&
|
||||||
languagesIsPopulated &&
|
|
||||||
appProfilesIsPopulated &&
|
appProfilesIsPopulated &&
|
||||||
indexersIsPopulated &&
|
indexersIsPopulated &&
|
||||||
indexerStatusIsPopulated &&
|
indexerStatusIsPopulated &&
|
||||||
@@ -86,7 +84,6 @@ const selectErrors = createSelector(
|
|||||||
(state) => state.tags.error,
|
(state) => state.tags.error,
|
||||||
(state) => state.settings.ui.error,
|
(state) => state.settings.ui.error,
|
||||||
(state) => state.settings.general.error,
|
(state) => state.settings.general.error,
|
||||||
(state) => state.settings.languages.error,
|
|
||||||
(state) => state.settings.appProfiles.error,
|
(state) => state.settings.appProfiles.error,
|
||||||
(state) => state.indexers.error,
|
(state) => state.indexers.error,
|
||||||
(state) => state.indexerStatus.error,
|
(state) => state.indexerStatus.error,
|
||||||
@@ -97,7 +94,6 @@ const selectErrors = createSelector(
|
|||||||
tagsError,
|
tagsError,
|
||||||
uiSettingsError,
|
uiSettingsError,
|
||||||
generalSettingsError,
|
generalSettingsError,
|
||||||
languagesError,
|
|
||||||
appProfilesError,
|
appProfilesError,
|
||||||
indexersError,
|
indexersError,
|
||||||
indexerStatusError,
|
indexerStatusError,
|
||||||
@@ -109,7 +105,6 @@ const selectErrors = createSelector(
|
|||||||
tagsError ||
|
tagsError ||
|
||||||
uiSettingsError ||
|
uiSettingsError ||
|
||||||
generalSettingsError ||
|
generalSettingsError ||
|
||||||
languagesError ||
|
|
||||||
appProfilesError ||
|
appProfilesError ||
|
||||||
indexersError ||
|
indexersError ||
|
||||||
indexerStatusError ||
|
indexerStatusError ||
|
||||||
@@ -123,7 +118,6 @@ const selectErrors = createSelector(
|
|||||||
tagsError,
|
tagsError,
|
||||||
uiSettingsError,
|
uiSettingsError,
|
||||||
generalSettingsError,
|
generalSettingsError,
|
||||||
languagesError,
|
|
||||||
appProfilesError,
|
appProfilesError,
|
||||||
indexersError,
|
indexersError,
|
||||||
indexerStatusError,
|
indexerStatusError,
|
||||||
@@ -140,18 +134,21 @@ function createMapStateToProps() {
|
|||||||
selectErrors,
|
selectErrors,
|
||||||
selectAppProps,
|
selectAppProps,
|
||||||
createDimensionsSelector(),
|
createDimensionsSelector(),
|
||||||
|
createSystemStatusSelector(),
|
||||||
(
|
(
|
||||||
enableColorImpairedMode,
|
enableColorImpairedMode,
|
||||||
isPopulated,
|
isPopulated,
|
||||||
errors,
|
errors,
|
||||||
app,
|
app,
|
||||||
dimensions
|
dimensions,
|
||||||
|
systemStatus
|
||||||
) => {
|
) => {
|
||||||
return {
|
return {
|
||||||
...app,
|
...app,
|
||||||
...errors,
|
...errors,
|
||||||
isPopulated,
|
isPopulated,
|
||||||
isSmallScreen: dimensions.isSmallScreen,
|
isSmallScreen: dimensions.isSmallScreen,
|
||||||
|
authenticationEnabled: systemStatus.authentication !== 'none',
|
||||||
enableColorImpairedMode
|
enableColorImpairedMode
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -166,9 +163,6 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
dispatchFetchTags() {
|
dispatchFetchTags() {
|
||||||
dispatch(fetchTags());
|
dispatch(fetchTags());
|
||||||
},
|
},
|
||||||
dispatchFetchLanguages() {
|
|
||||||
dispatch(fetchLanguages());
|
|
||||||
},
|
|
||||||
dispatchFetchIndexers() {
|
dispatchFetchIndexers() {
|
||||||
dispatch(fetchIndexers());
|
dispatch(fetchIndexers());
|
||||||
},
|
},
|
||||||
@@ -216,7 +210,6 @@ class PageConnector extends Component {
|
|||||||
if (!this.props.isPopulated) {
|
if (!this.props.isPopulated) {
|
||||||
this.props.dispatchFetchCustomFilters();
|
this.props.dispatchFetchCustomFilters();
|
||||||
this.props.dispatchFetchTags();
|
this.props.dispatchFetchTags();
|
||||||
this.props.dispatchFetchLanguages();
|
|
||||||
this.props.dispatchFetchAppProfiles();
|
this.props.dispatchFetchAppProfiles();
|
||||||
this.props.dispatchFetchIndexers();
|
this.props.dispatchFetchIndexers();
|
||||||
this.props.dispatchFetchIndexerStatus();
|
this.props.dispatchFetchIndexerStatus();
|
||||||
@@ -242,7 +235,6 @@ class PageConnector extends Component {
|
|||||||
isPopulated,
|
isPopulated,
|
||||||
hasError,
|
hasError,
|
||||||
dispatchFetchTags,
|
dispatchFetchTags,
|
||||||
dispatchFetchLanguages,
|
|
||||||
dispatchFetchAppProfiles,
|
dispatchFetchAppProfiles,
|
||||||
dispatchFetchIndexers,
|
dispatchFetchIndexers,
|
||||||
dispatchFetchIndexerStatus,
|
dispatchFetchIndexerStatus,
|
||||||
@@ -283,7 +275,6 @@ PageConnector.propTypes = {
|
|||||||
isSidebarVisible: PropTypes.bool.isRequired,
|
isSidebarVisible: PropTypes.bool.isRequired,
|
||||||
dispatchFetchCustomFilters: PropTypes.func.isRequired,
|
dispatchFetchCustomFilters: PropTypes.func.isRequired,
|
||||||
dispatchFetchTags: PropTypes.func.isRequired,
|
dispatchFetchTags: PropTypes.func.isRequired,
|
||||||
dispatchFetchLanguages: PropTypes.func.isRequired,
|
|
||||||
dispatchFetchAppProfiles: PropTypes.func.isRequired,
|
dispatchFetchAppProfiles: PropTypes.func.isRequired,
|
||||||
dispatchFetchIndexers: PropTypes.func.isRequired,
|
dispatchFetchIndexers: PropTypes.func.isRequired,
|
||||||
dispatchFetchIndexerStatus: PropTypes.func.isRequired,
|
dispatchFetchIndexerStatus: PropTypes.func.isRequired,
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ const SIDEBAR_WIDTH = parseInt(dimensions.sidebarWidth);
|
|||||||
const links = [
|
const links = [
|
||||||
{
|
{
|
||||||
iconName: icons.MOVIE_CONTINUING,
|
iconName: icons.MOVIE_CONTINUING,
|
||||||
title: 'Indexers',
|
title: translate('Indexers'),
|
||||||
to: '/',
|
to: '/',
|
||||||
alias: '/movies',
|
alias: '/indexers',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
title: translate('Stats'),
|
title: translate('Stats'),
|
||||||
@@ -33,13 +33,13 @@ const links = [
|
|||||||
|
|
||||||
{
|
{
|
||||||
iconName: icons.SEARCH,
|
iconName: icons.SEARCH,
|
||||||
title: 'Search',
|
title: translate('Search'),
|
||||||
to: '/search'
|
to: '/search'
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
iconName: icons.ACTIVITY,
|
iconName: icons.ACTIVITY,
|
||||||
title: 'History',
|
title: translate('History'),
|
||||||
to: '/history'
|
to: '/history'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&.default {
|
&.default {
|
||||||
background-color: var(--white);
|
background-color: var(--popoverBodyBackgroundColor);
|
||||||
box-shadow: 0 5px 10px var(--popoverShadowColor);
|
box-shadow: 0 5px 10px var(--popoverShadowColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
34
frontend/src/FirstRun/AuthenticationRequiredModal.js
Normal file
34
frontend/src/FirstRun/AuthenticationRequiredModal.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import Modal from 'Components/Modal/Modal';
|
||||||
|
import { sizes } from 'Helpers/Props';
|
||||||
|
import AuthenticationRequiredModalContentConnector from './AuthenticationRequiredModalContentConnector';
|
||||||
|
|
||||||
|
function onModalClose() {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
|
||||||
|
function AuthenticationRequiredModal(props) {
|
||||||
|
const {
|
||||||
|
isOpen
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
size={sizes.MEDIUM}
|
||||||
|
isOpen={isOpen}
|
||||||
|
closeOnBackgroundClick={false}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
>
|
||||||
|
<AuthenticationRequiredModalContentConnector
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthenticationRequiredModal.propTypes = {
|
||||||
|
isOpen: PropTypes.bool.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthenticationRequiredModal;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
.authRequiredAlert {
|
||||||
|
composes: alert from '~Components/Alert.css';
|
||||||
|
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
165
frontend/src/FirstRun/AuthenticationRequiredModalContent.js
Normal file
165
frontend/src/FirstRun/AuthenticationRequiredModalContent.js
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
import Alert from 'Components/Alert';
|
||||||
|
import FormGroup from 'Components/Form/FormGroup';
|
||||||
|
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||||
|
import FormLabel from 'Components/Form/FormLabel';
|
||||||
|
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||||
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
import ModalBody from 'Components/Modal/ModalBody';
|
||||||
|
import ModalContent from 'Components/Modal/ModalContent';
|
||||||
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
|
import { inputTypes, kinds } from 'Helpers/Props';
|
||||||
|
import { authenticationMethodOptions, authenticationRequiredOptions, authenticationRequiredWarning } from 'Settings/General/SecuritySettings';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import styles from './AuthenticationRequiredModalContent.css';
|
||||||
|
|
||||||
|
function onModalClose() {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
|
||||||
|
function AuthenticationRequiredModalContent(props) {
|
||||||
|
const {
|
||||||
|
isPopulated,
|
||||||
|
error,
|
||||||
|
isSaving,
|
||||||
|
settings,
|
||||||
|
onInputChange,
|
||||||
|
onSavePress,
|
||||||
|
dispatchFetchStatus
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
authenticationMethod,
|
||||||
|
authenticationRequired,
|
||||||
|
username,
|
||||||
|
password
|
||||||
|
} = settings;
|
||||||
|
|
||||||
|
const authenticationEnabled = authenticationMethod && authenticationMethod.value !== 'none';
|
||||||
|
|
||||||
|
const didMount = useRef(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isSaving && didMount.current) {
|
||||||
|
dispatchFetchStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
didMount.current = true;
|
||||||
|
}, [isSaving, dispatchFetchStatus]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalContent
|
||||||
|
showCloseButton={false}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
>
|
||||||
|
<ModalHeader>
|
||||||
|
{translate('AuthenticationRequired')}
|
||||||
|
</ModalHeader>
|
||||||
|
|
||||||
|
<ModalBody>
|
||||||
|
<Alert
|
||||||
|
className={styles.authRequiredAlert}
|
||||||
|
kind={kinds.WARNING}
|
||||||
|
>
|
||||||
|
{authenticationRequiredWarning}
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
{
|
||||||
|
isPopulated && !error ?
|
||||||
|
<div>
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('Authentication')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.SELECT}
|
||||||
|
name="authenticationMethod"
|
||||||
|
values={authenticationMethodOptions}
|
||||||
|
helpText={translate('AuthenticationMethodHelpText')}
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...authenticationMethod}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
{
|
||||||
|
authenticationEnabled ?
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('AuthenticationRequired')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.SELECT}
|
||||||
|
name="authenticationRequired"
|
||||||
|
values={authenticationRequiredOptions}
|
||||||
|
helpText={translate('AuthenticationRequiredHelpText')}
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...authenticationRequired}
|
||||||
|
/>
|
||||||
|
</FormGroup> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
authenticationEnabled ?
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('Username')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.TEXT}
|
||||||
|
name="username"
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...username}
|
||||||
|
/>
|
||||||
|
</FormGroup> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
authenticationEnabled ?
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('Password')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.PASSWORD}
|
||||||
|
name="password"
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...password}
|
||||||
|
/>
|
||||||
|
</FormGroup> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
</div> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
!isPopulated && !error ? <LoadingIndicator /> : null
|
||||||
|
}
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<SpinnerButton
|
||||||
|
kind={kinds.PRIMARY}
|
||||||
|
isSpinning={isSaving}
|
||||||
|
isDisabled={!authenticationEnabled}
|
||||||
|
onPress={onSavePress}
|
||||||
|
>
|
||||||
|
{translate('Save')}
|
||||||
|
</SpinnerButton>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthenticationRequiredModalContent.propTypes = {
|
||||||
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
|
error: PropTypes.object,
|
||||||
|
isSaving: PropTypes.bool.isRequired,
|
||||||
|
saveError: PropTypes.object,
|
||||||
|
settings: PropTypes.object.isRequired,
|
||||||
|
onInputChange: PropTypes.func.isRequired,
|
||||||
|
onSavePress: PropTypes.func.isRequired,
|
||||||
|
dispatchFetchStatus: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthenticationRequiredModalContent;
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||||
|
import { fetchGeneralSettings, saveGeneralSettings, setGeneralSettingsValue } from 'Store/Actions/settingsActions';
|
||||||
|
import { fetchStatus } from 'Store/Actions/systemActions';
|
||||||
|
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
|
||||||
|
import AuthenticationRequiredModalContent from './AuthenticationRequiredModalContent';
|
||||||
|
|
||||||
|
const SECTION = 'general';
|
||||||
|
|
||||||
|
function createMapStateToProps() {
|
||||||
|
return createSelector(
|
||||||
|
createSettingsSectionSelector(SECTION),
|
||||||
|
(sectionSettings) => {
|
||||||
|
return {
|
||||||
|
...sectionSettings
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
dispatchClearPendingChanges: clearPendingChanges,
|
||||||
|
dispatchSetGeneralSettingsValue: setGeneralSettingsValue,
|
||||||
|
dispatchSaveGeneralSettings: saveGeneralSettings,
|
||||||
|
dispatchFetchGeneralSettings: fetchGeneralSettings,
|
||||||
|
dispatchFetchStatus: fetchStatus
|
||||||
|
};
|
||||||
|
|
||||||
|
class AuthenticationRequiredModalContentConnector extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.dispatchFetchGeneralSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.props.dispatchClearPendingChanges({ section: `settings.${SECTION}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onInputChange = ({ name, value }) => {
|
||||||
|
this.props.dispatchSetGeneralSettingsValue({ name, value });
|
||||||
|
};
|
||||||
|
|
||||||
|
onSavePress = () => {
|
||||||
|
this.props.dispatchSaveGeneralSettings();
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
dispatchClearPendingChanges,
|
||||||
|
dispatchFetchGeneralSettings,
|
||||||
|
dispatchSetGeneralSettingsValue,
|
||||||
|
dispatchSaveGeneralSettings,
|
||||||
|
...otherProps
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthenticationRequiredModalContent
|
||||||
|
{...otherProps}
|
||||||
|
onInputChange={this.onInputChange}
|
||||||
|
onSavePress={this.onSavePress}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthenticationRequiredModalContentConnector.propTypes = {
|
||||||
|
dispatchClearPendingChanges: PropTypes.func.isRequired,
|
||||||
|
dispatchFetchGeneralSettings: PropTypes.func.isRequired,
|
||||||
|
dispatchSetGeneralSettingsValue: PropTypes.func.isRequired,
|
||||||
|
dispatchSaveGeneralSettings: PropTypes.func.isRequired,
|
||||||
|
dispatchFetchStatus: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(createMapStateToProps, mapDispatchToProps)(AuthenticationRequiredModalContentConnector);
|
||||||
@@ -8,6 +8,7 @@ export const DEVICE = 'device';
|
|||||||
export const KEY_VALUE_LIST = 'keyValueList';
|
export const KEY_VALUE_LIST = 'keyValueList';
|
||||||
export const INFO = 'info';
|
export const INFO = 'info';
|
||||||
export const MOVIE_MONITORED_SELECT = 'movieMonitoredSelect';
|
export const MOVIE_MONITORED_SELECT = 'movieMonitoredSelect';
|
||||||
|
export const CATEGORY_SELECT = 'newznabCategorySelect';
|
||||||
export const NUMBER = 'number';
|
export const NUMBER = 'number';
|
||||||
export const OAUTH = 'oauth';
|
export const OAUTH = 'oauth';
|
||||||
export const PASSWORD = 'password';
|
export const PASSWORD = 'password';
|
||||||
@@ -32,6 +33,7 @@ export const all = [
|
|||||||
KEY_VALUE_LIST,
|
KEY_VALUE_LIST,
|
||||||
INFO,
|
INFO,
|
||||||
MOVIE_MONITORED_SELECT,
|
MOVIE_MONITORED_SELECT,
|
||||||
|
CATEGORY_SELECT,
|
||||||
NUMBER,
|
NUMBER,
|
||||||
OAUTH,
|
OAUTH,
|
||||||
PASSWORD,
|
PASSWORD,
|
||||||
|
|||||||
@@ -226,6 +226,42 @@ class HistoryRow extends Component {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
data.label ?
|
||||||
|
<HistoryRowParameter
|
||||||
|
title='Label'
|
||||||
|
value={data.label}
|
||||||
|
/> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
data.track ?
|
||||||
|
<HistoryRowParameter
|
||||||
|
title='Track'
|
||||||
|
value={data.track}
|
||||||
|
/> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
data.year ?
|
||||||
|
<HistoryRowParameter
|
||||||
|
title='Year'
|
||||||
|
value={data.year}
|
||||||
|
/> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
data.genre ?
|
||||||
|
<HistoryRowParameter
|
||||||
|
title='Genre'
|
||||||
|
value={data.genre}
|
||||||
|
/> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
data.author ?
|
data.author ?
|
||||||
<HistoryRowParameter
|
<HistoryRowParameter
|
||||||
@@ -243,6 +279,15 @@ class HistoryRow extends Component {
|
|||||||
/> :
|
/> :
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
data.publisher ?
|
||||||
|
<HistoryRowParameter
|
||||||
|
title='Publisher'
|
||||||
|
value={data.publisher}
|
||||||
|
/> :
|
||||||
|
null
|
||||||
|
}
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ class AddIndexerModalContent extends Component {
|
|||||||
const filteredIndexers = indexers.filter((indexer) => {
|
const filteredIndexers = indexers.filter((indexer) => {
|
||||||
const { filter, filterProtocols, filterLanguages, filterPrivacyLevels } = this.state;
|
const { filter, filterProtocols, filterLanguages, filterPrivacyLevels } = this.state;
|
||||||
|
|
||||||
if (!indexer.name.toLowerCase().includes(filter.toLocaleLowerCase())) {
|
if (!indexer.name.toLowerCase().includes(filter.toLocaleLowerCase()) && !indexer.description.toLowerCase().includes(filter.toLocaleLowerCase())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,15 +61,15 @@ class TagsModalContent extends Component {
|
|||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const applyTagsOptions = [
|
const applyTagsOptions = [
|
||||||
{ key: 'add', value: 'Add' },
|
{ key: 'add', value: translate('Add') },
|
||||||
{ key: 'remove', value: 'Remove' },
|
{ key: 'remove', value: translate('Remove') },
|
||||||
{ key: 'replace', value: 'Replace' }
|
{ key: 'replace', value: translate('Replace') }
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
Tags
|
{translate('Tags')}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ class IndexerIndex extends Component {
|
|||||||
|
|
||||||
onKeyUp = (event) => {
|
onKeyUp = (event) => {
|
||||||
const jumpBarItems = this.state.jumpBarItems.order;
|
const jumpBarItems = this.state.jumpBarItems.order;
|
||||||
if (event.path.length === 4) {
|
if (event.composedPath && event.composedPath().length === 4) {
|
||||||
if (event.keyCode === keyCodes.HOME && event.ctrlKey) {
|
if (event.keyCode === keyCodes.HOME && event.ctrlKey) {
|
||||||
this.setState({ jumpToCharacter: jumpBarItems[0] });
|
this.setState({ jumpToCharacter: jumpBarItems[0] });
|
||||||
}
|
}
|
||||||
@@ -272,6 +272,7 @@ class IndexerIndex extends Component {
|
|||||||
saveError,
|
saveError,
|
||||||
isDeleting,
|
isDeleting,
|
||||||
isTestingAll,
|
isTestingAll,
|
||||||
|
isSyncingIndexers,
|
||||||
deleteError,
|
deleteError,
|
||||||
onScroll,
|
onScroll,
|
||||||
onSortSelect,
|
onSortSelect,
|
||||||
@@ -309,6 +310,15 @@ class IndexerIndex extends Component {
|
|||||||
onPress={this.onAddIndexerPress}
|
onPress={this.onAddIndexerPress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<PageToolbarSeparator />
|
||||||
|
|
||||||
|
<PageToolbarButton
|
||||||
|
label={translate('SyncAppIndexers')}
|
||||||
|
iconName={icons.REFRESH}
|
||||||
|
isSpinning={isSyncingIndexers}
|
||||||
|
onPress={this.props.onAppIndexerSyncPress}
|
||||||
|
/>
|
||||||
|
|
||||||
<PageToolbarButton
|
<PageToolbarButton
|
||||||
label={translate('TestAllIndexers')}
|
label={translate('TestAllIndexers')}
|
||||||
iconName={icons.TEST}
|
iconName={icons.TEST}
|
||||||
@@ -493,10 +503,12 @@ IndexerIndex.propTypes = {
|
|||||||
saveError: PropTypes.object,
|
saveError: PropTypes.object,
|
||||||
isDeleting: PropTypes.bool.isRequired,
|
isDeleting: PropTypes.bool.isRequired,
|
||||||
isTestingAll: PropTypes.bool.isRequired,
|
isTestingAll: PropTypes.bool.isRequired,
|
||||||
|
isSyncingIndexers: PropTypes.bool.isRequired,
|
||||||
deleteError: PropTypes.object,
|
deleteError: PropTypes.object,
|
||||||
onSortSelect: PropTypes.func.isRequired,
|
onSortSelect: PropTypes.func.isRequired,
|
||||||
onFilterSelect: PropTypes.func.isRequired,
|
onFilterSelect: PropTypes.func.isRequired,
|
||||||
onTestAllPress: PropTypes.func.isRequired,
|
onTestAllPress: PropTypes.func.isRequired,
|
||||||
|
onAppIndexerSyncPress: PropTypes.func.isRequired,
|
||||||
onScroll: PropTypes.func.isRequired,
|
onScroll: PropTypes.func.isRequired,
|
||||||
onSaveSelected: PropTypes.func.isRequired
|
onSaveSelected: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ import PropTypes from 'prop-types';
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
import * as commandNames from 'Commands/commandNames';
|
||||||
import withScrollPosition from 'Components/withScrollPosition';
|
import withScrollPosition from 'Components/withScrollPosition';
|
||||||
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
import { testAllIndexers } from 'Store/Actions/indexerActions';
|
import { testAllIndexers } from 'Store/Actions/indexerActions';
|
||||||
import { saveIndexerEditor, setMovieFilter, setMovieSort, setMovieTableOption } from 'Store/Actions/indexerIndexActions';
|
import { saveIndexerEditor, setMovieFilter, setMovieSort, setMovieTableOption } from 'Store/Actions/indexerIndexActions';
|
||||||
import scrollPositions from 'Store/scrollPositions';
|
import scrollPositions from 'Store/scrollPositions';
|
||||||
|
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||||
import createIndexerClientSideCollectionItemsSelector from 'Store/Selectors/createIndexerClientSideCollectionItemsSelector';
|
import createIndexerClientSideCollectionItemsSelector from 'Store/Selectors/createIndexerClientSideCollectionItemsSelector';
|
||||||
import IndexerIndex from './IndexerIndex';
|
import IndexerIndex from './IndexerIndex';
|
||||||
@@ -13,13 +16,16 @@ import IndexerIndex from './IndexerIndex';
|
|||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createIndexerClientSideCollectionItemsSelector('indexerIndex'),
|
createIndexerClientSideCollectionItemsSelector('indexerIndex'),
|
||||||
|
createCommandExecutingSelector(commandNames.APP_INDEXER_SYNC),
|
||||||
createDimensionsSelector(),
|
createDimensionsSelector(),
|
||||||
(
|
(
|
||||||
indexers,
|
indexers,
|
||||||
|
isSyncingIndexers,
|
||||||
dimensionsState
|
dimensionsState
|
||||||
) => {
|
) => {
|
||||||
return {
|
return {
|
||||||
...indexers,
|
...indexers,
|
||||||
|
isSyncingIndexers,
|
||||||
isSmallScreen: dimensionsState.isSmallScreen
|
isSmallScreen: dimensionsState.isSmallScreen
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -46,6 +52,12 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
|
|
||||||
onTestAllPress() {
|
onTestAllPress() {
|
||||||
dispatch(testAllIndexers());
|
dispatch(testAllIndexers());
|
||||||
|
},
|
||||||
|
|
||||||
|
onAppIndexerSyncPress() {
|
||||||
|
dispatch(executeCommand({
|
||||||
|
name: commandNames.APP_INDEXER_SYNC
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,21 +35,21 @@ class IndexerIndexFooter extends PureComponent {
|
|||||||
<div className={styles.legendItem}>
|
<div className={styles.legendItem}>
|
||||||
<div className={styles.enabled} />
|
<div className={styles.enabled} />
|
||||||
<div>
|
<div>
|
||||||
Enabled
|
{translate('Enabled')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.legendItem}>
|
<div className={styles.legendItem}>
|
||||||
<div className={styles.redirected} />
|
<div className={styles.redirected} />
|
||||||
<div>
|
<div>
|
||||||
Enabled, Redirected
|
{translate('EnabledRedirected')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.legendItem}>
|
<div className={styles.legendItem}>
|
||||||
<div className={styles.disabled} />
|
<div className={styles.disabled} />
|
||||||
<div>
|
<div>
|
||||||
Disabled
|
{translate('Disabled')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ class IndexerIndexFooter extends PureComponent {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
Error
|
{translate('Error')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ function IndexerIndexSortMenu(props) {
|
|||||||
sortDirection={sortDirection}
|
sortDirection={sortDirection}
|
||||||
onPress={onSortSelect}
|
onPress={onSortSelect}
|
||||||
>
|
>
|
||||||
Status
|
{translate('Status')}
|
||||||
</SortMenuItem>
|
</SortMenuItem>
|
||||||
|
|
||||||
<SortMenuItem
|
<SortMenuItem
|
||||||
@@ -62,7 +62,7 @@ function IndexerIndexSortMenu(props) {
|
|||||||
sortDirection={sortDirection}
|
sortDirection={sortDirection}
|
||||||
onPress={onSortSelect}
|
onPress={onSortSelect}
|
||||||
>
|
>
|
||||||
{'Priority'}
|
{translate('Priority')}
|
||||||
</SortMenuItem>
|
</SortMenuItem>
|
||||||
|
|
||||||
<SortMenuItem
|
<SortMenuItem
|
||||||
@@ -71,7 +71,7 @@ function IndexerIndexSortMenu(props) {
|
|||||||
sortDirection={sortDirection}
|
sortDirection={sortDirection}
|
||||||
onPress={onSortSelect}
|
onPress={onSortSelect}
|
||||||
>
|
>
|
||||||
{'Protocol'}
|
{translate('Protocol')}
|
||||||
</SortMenuItem>
|
</SortMenuItem>
|
||||||
|
|
||||||
<SortMenuItem
|
<SortMenuItem
|
||||||
@@ -80,7 +80,7 @@ function IndexerIndexSortMenu(props) {
|
|||||||
sortDirection={sortDirection}
|
sortDirection={sortDirection}
|
||||||
onPress={onSortSelect}
|
onPress={onSortSelect}
|
||||||
>
|
>
|
||||||
{'Privacy'}
|
{translate('Privacy')}
|
||||||
</SortMenuItem>
|
</SortMenuItem>
|
||||||
</MenuContent>
|
</MenuContent>
|
||||||
</SortMenu>
|
</SortMenu>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ function CapabilitiesLabel(props) {
|
|||||||
let filteredList = categories.filter((item) => item.id < 100000);
|
let filteredList = categories.filter((item) => item.id < 100000);
|
||||||
|
|
||||||
if (categoryFilter.length > 0) {
|
if (categoryFilter.length > 0) {
|
||||||
filteredList = filteredList.filter((item) => categoryFilter.includes(item.id));
|
filteredList = filteredList.filter((item) => categoryFilter.includes(item.id) || (item.subCategories && item.subCategories.some((r) => categoryFilter.includes(r.id))));
|
||||||
}
|
}
|
||||||
|
|
||||||
const nameList = filteredList.map((item) => item.name).sort();
|
const nameList = filteredList.map((item) => item.name).sort();
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ class IndexerIndexRow extends Component {
|
|||||||
privacy,
|
privacy,
|
||||||
priority,
|
priority,
|
||||||
status,
|
status,
|
||||||
|
fields,
|
||||||
appProfile,
|
appProfile,
|
||||||
added,
|
added,
|
||||||
capabilities,
|
capabilities,
|
||||||
@@ -96,6 +97,8 @@ class IndexerIndexRow extends Component {
|
|||||||
isIndexerInfoModalOpen
|
isIndexerInfoModalOpen
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
|
const baseUrl = fields.find((field) => field.name === 'baseUrl')?.value ?? (Array.isArray(indexerUrls) ? indexerUrls[0] : undefined);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{
|
{
|
||||||
@@ -245,12 +248,12 @@ class IndexerIndexRow extends Component {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{
|
{
|
||||||
indexerUrls ?
|
baseUrl ?
|
||||||
<IconButton
|
<IconButton
|
||||||
className={styles.externalLink}
|
className={styles.externalLink}
|
||||||
name={icons.EXTERNAL_LINK}
|
name={icons.EXTERNAL_LINK}
|
||||||
title={translate('Website')}
|
title={translate('Website')}
|
||||||
to={indexerUrls[0].replace('api.', '')}
|
to={baseUrl.replace(/(:\/\/)api\./, '$1')}
|
||||||
/> : null
|
/> : null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,6 +302,7 @@ IndexerIndexRow.propTypes = {
|
|||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
enable: PropTypes.bool.isRequired,
|
enable: PropTypes.bool.isRequired,
|
||||||
redirect: PropTypes.bool.isRequired,
|
redirect: PropTypes.bool.isRequired,
|
||||||
|
fields: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
appProfile: PropTypes.object.isRequired,
|
appProfile: PropTypes.object.isRequired,
|
||||||
status: PropTypes.object,
|
status: PropTypes.object,
|
||||||
capabilities: PropTypes.object,
|
capabilities: PropTypes.object,
|
||||||
|
|||||||
@@ -20,11 +20,14 @@ function IndexerInfoModalContent(props) {
|
|||||||
encoding,
|
encoding,
|
||||||
language,
|
language,
|
||||||
indexerUrls,
|
indexerUrls,
|
||||||
|
fields,
|
||||||
protocol,
|
protocol,
|
||||||
capabilities,
|
capabilities,
|
||||||
onModalClose
|
onModalClose
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const baseUrl = fields.find((field) => field.name === 'baseUrl')?.value ?? indexerUrls[0];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
@@ -57,7 +60,7 @@ function IndexerInfoModalContent(props) {
|
|||||||
/>
|
/>
|
||||||
<DescriptionListItemTitle>{translate('IndexerSite')}</DescriptionListItemTitle>
|
<DescriptionListItemTitle>{translate('IndexerSite')}</DescriptionListItemTitle>
|
||||||
<DescriptionListItemDescription>
|
<DescriptionListItemDescription>
|
||||||
<Link to={indexerUrls[0]}>{indexerUrls[0]}</Link>
|
<Link to={baseUrl}>{baseUrl}</Link>
|
||||||
</DescriptionListItemDescription>
|
</DescriptionListItemDescription>
|
||||||
<DescriptionListItemTitle>{`${protocol === 'usenet' ? 'Newznab' : 'Torznab'} Url`}</DescriptionListItemTitle>
|
<DescriptionListItemTitle>{`${protocol === 'usenet' ? 'Newznab' : 'Torznab'} Url`}</DescriptionListItemTitle>
|
||||||
<DescriptionListItemDescription>
|
<DescriptionListItemDescription>
|
||||||
@@ -114,6 +117,7 @@ IndexerInfoModalContent.propTypes = {
|
|||||||
encoding: PropTypes.string.isRequired,
|
encoding: PropTypes.string.isRequired,
|
||||||
language: PropTypes.string.isRequired,
|
language: PropTypes.string.isRequired,
|
||||||
indexerUrls: PropTypes.arrayOf(PropTypes.string).isRequired,
|
indexerUrls: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||||
|
fields: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
protocol: PropTypes.string.isRequired,
|
protocol: PropTypes.string.isRequired,
|
||||||
capabilities: PropTypes.object.isRequired,
|
capabilities: PropTypes.object.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
|||||||
49
frontend/src/Search/Mobile/SearchIndexOverview.css
Normal file
49
frontend/src/Search/Mobile/SearchIndexOverview.css
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
$hoverScale: 1.05;
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 0;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--cardBackgroundColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.titleRow {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
height: 38px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.indexerRow {
|
||||||
|
color: var(--disabledColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
.infoRow {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
overflow: hidden;
|
||||||
|
width: 85%;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14px;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
202
frontend/src/Search/Mobile/SearchIndexOverview.js
Normal file
202
frontend/src/Search/Mobile/SearchIndexOverview.js
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import TextTruncate from 'react-text-truncate';
|
||||||
|
import Label from 'Components/Label';
|
||||||
|
import IconButton from 'Components/Link/IconButton';
|
||||||
|
import Link from 'Components/Link/Link';
|
||||||
|
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
||||||
|
import { icons, kinds } from 'Helpers/Props';
|
||||||
|
import CategoryLabel from 'Search/Table/CategoryLabel';
|
||||||
|
import Peers from 'Search/Table/Peers';
|
||||||
|
import ProtocolLabel from 'Search/Table/ProtocolLabel';
|
||||||
|
import dimensions from 'Styles/Variables/dimensions';
|
||||||
|
import formatAge from 'Utilities/Number/formatAge';
|
||||||
|
import formatBytes from 'Utilities/Number/formatBytes';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import styles from './SearchIndexOverview.css';
|
||||||
|
|
||||||
|
const columnPadding = parseInt(dimensions.movieIndexColumnPadding);
|
||||||
|
const columnPaddingSmallScreen = parseInt(dimensions.movieIndexColumnPaddingSmallScreen);
|
||||||
|
|
||||||
|
function getContentHeight(rowHeight, isSmallScreen) {
|
||||||
|
const padding = isSmallScreen ? columnPaddingSmallScreen : columnPadding;
|
||||||
|
|
||||||
|
return rowHeight - padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDownloadIcon(isGrabbing, isGrabbed, grabError) {
|
||||||
|
if (isGrabbing) {
|
||||||
|
return icons.SPINNER;
|
||||||
|
} else if (isGrabbed) {
|
||||||
|
return icons.DOWNLOADING;
|
||||||
|
} else if (grabError) {
|
||||||
|
return icons.DOWNLOADING;
|
||||||
|
}
|
||||||
|
|
||||||
|
return icons.DOWNLOAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDownloadTooltip(isGrabbing, isGrabbed, grabError) {
|
||||||
|
if (isGrabbing) {
|
||||||
|
return '';
|
||||||
|
} else if (isGrabbed) {
|
||||||
|
return translate('AddedToDownloadClient');
|
||||||
|
} else if (grabError) {
|
||||||
|
return grabError;
|
||||||
|
}
|
||||||
|
|
||||||
|
return translate('AddToDownloadClient');
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchIndexOverview extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onGrabPress = () => {
|
||||||
|
const {
|
||||||
|
guid,
|
||||||
|
indexerId,
|
||||||
|
onGrabPress
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
onGrabPress({
|
||||||
|
guid,
|
||||||
|
indexerId
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
title,
|
||||||
|
infoUrl,
|
||||||
|
protocol,
|
||||||
|
downloadUrl,
|
||||||
|
categories,
|
||||||
|
seeders,
|
||||||
|
leechers,
|
||||||
|
size,
|
||||||
|
age,
|
||||||
|
ageHours,
|
||||||
|
ageMinutes,
|
||||||
|
indexer,
|
||||||
|
rowHeight,
|
||||||
|
isSmallScreen,
|
||||||
|
isGrabbed,
|
||||||
|
isGrabbing,
|
||||||
|
grabError
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const contentHeight = getContentHeight(rowHeight, isSmallScreen);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<div className={styles.content}>
|
||||||
|
<div className={styles.info} style={{ height: contentHeight }}>
|
||||||
|
<div className={styles.titleRow}>
|
||||||
|
<div className={styles.title}>
|
||||||
|
<Link
|
||||||
|
to={infoUrl}
|
||||||
|
title={title}
|
||||||
|
>
|
||||||
|
<TextTruncate
|
||||||
|
line={2}
|
||||||
|
text={title}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.actions}>
|
||||||
|
<SpinnerIconButton
|
||||||
|
name={getDownloadIcon(isGrabbing, isGrabbed, grabError)}
|
||||||
|
kind={grabError ? kinds.DANGER : kinds.DEFAULT}
|
||||||
|
title={getDownloadTooltip(isGrabbing, isGrabbed, grabError)}
|
||||||
|
isDisabled={isGrabbed}
|
||||||
|
isSpinning={isGrabbing}
|
||||||
|
onPress={this.onGrabPress}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
className={styles.downloadLink}
|
||||||
|
name={icons.SAVE}
|
||||||
|
title={translate('Save')}
|
||||||
|
to={downloadUrl}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.indexerRow}>
|
||||||
|
{indexer}
|
||||||
|
</div>
|
||||||
|
<div className={styles.infoRow}>
|
||||||
|
<ProtocolLabel
|
||||||
|
protocol={protocol}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{
|
||||||
|
protocol === 'torrent' &&
|
||||||
|
<Peers
|
||||||
|
seeders={seeders}
|
||||||
|
leechers={leechers}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
<Label>
|
||||||
|
{formatBytes(size)}
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<Label>
|
||||||
|
{formatAge(age, ageHours, ageMinutes)}
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<CategoryLabel
|
||||||
|
categories={categories}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchIndexOverview.propTypes = {
|
||||||
|
guid: PropTypes.string.isRequired,
|
||||||
|
categories: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
protocol: PropTypes.string.isRequired,
|
||||||
|
age: PropTypes.number.isRequired,
|
||||||
|
ageHours: PropTypes.number.isRequired,
|
||||||
|
ageMinutes: PropTypes.number.isRequired,
|
||||||
|
publishDate: PropTypes.string.isRequired,
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
infoUrl: PropTypes.string.isRequired,
|
||||||
|
downloadUrl: PropTypes.string.isRequired,
|
||||||
|
indexerId: PropTypes.number.isRequired,
|
||||||
|
indexer: PropTypes.string.isRequired,
|
||||||
|
size: PropTypes.number.isRequired,
|
||||||
|
files: PropTypes.number,
|
||||||
|
grabs: PropTypes.number,
|
||||||
|
seeders: PropTypes.number,
|
||||||
|
leechers: PropTypes.number,
|
||||||
|
indexerFlags: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||||
|
rowHeight: PropTypes.number.isRequired,
|
||||||
|
showRelativeDates: PropTypes.bool.isRequired,
|
||||||
|
shortDateFormat: PropTypes.string.isRequired,
|
||||||
|
longDateFormat: PropTypes.string.isRequired,
|
||||||
|
timeFormat: PropTypes.string.isRequired,
|
||||||
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
|
onGrabPress: PropTypes.func.isRequired,
|
||||||
|
isGrabbing: PropTypes.bool.isRequired,
|
||||||
|
isGrabbed: PropTypes.bool.isRequired,
|
||||||
|
grabError: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
SearchIndexOverview.defaultProps = {
|
||||||
|
isGrabbing: false,
|
||||||
|
isGrabbed: false
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SearchIndexOverview;
|
||||||
11
frontend/src/Search/Mobile/SearchIndexOverviews.css
Normal file
11
frontend/src/Search/Mobile/SearchIndexOverviews.css
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
.grid {
|
||||||
|
flex: 1 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
&:hover {
|
||||||
|
.content {
|
||||||
|
background-color: var(--tableRowHoverBackgroundColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
209
frontend/src/Search/Mobile/SearchIndexOverviews.js
Normal file
209
frontend/src/Search/Mobile/SearchIndexOverviews.js
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { Grid, WindowScroller } from 'react-virtualized';
|
||||||
|
import Measure from 'Components/Measure';
|
||||||
|
import SearchIndexItemConnector from 'Search/Table/SearchIndexItemConnector';
|
||||||
|
import getIndexOfFirstCharacter from 'Utilities/Array/getIndexOfFirstCharacter';
|
||||||
|
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
|
||||||
|
import SearchIndexOverview from './SearchIndexOverview';
|
||||||
|
import styles from './SearchIndexOverviews.css';
|
||||||
|
|
||||||
|
class SearchIndexOverviews extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
width: 0,
|
||||||
|
columnCount: 1,
|
||||||
|
rowHeight: 100,
|
||||||
|
scrollRestored: false
|
||||||
|
};
|
||||||
|
|
||||||
|
this._grid = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps, prevState) {
|
||||||
|
const {
|
||||||
|
items,
|
||||||
|
sortKey,
|
||||||
|
jumpToCharacter,
|
||||||
|
scrollTop,
|
||||||
|
isSmallScreen
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
width,
|
||||||
|
rowHeight,
|
||||||
|
scrollRestored
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
if (prevProps.sortKey !== sortKey) {
|
||||||
|
this.calculateGrid(this.state.width, isSmallScreen);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this._grid &&
|
||||||
|
(prevState.width !== width ||
|
||||||
|
prevState.rowHeight !== rowHeight ||
|
||||||
|
hasDifferentItemsOrOrder(prevProps.items, items, 'guid')
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// recomputeGridSize also forces Grid to discard its cache of rendered cells
|
||||||
|
this._grid.recomputeGridSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._grid && scrollTop !== 0 && !scrollRestored) {
|
||||||
|
this.setState({ scrollRestored: true });
|
||||||
|
this._grid.scrollToPosition({ scrollTop });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
|
||||||
|
const index = getIndexOfFirstCharacter(items, jumpToCharacter);
|
||||||
|
|
||||||
|
if (this._grid && index != null) {
|
||||||
|
|
||||||
|
this._grid.scrollToCell({
|
||||||
|
rowIndex: index,
|
||||||
|
columnIndex: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Control
|
||||||
|
|
||||||
|
setGridRef = (ref) => {
|
||||||
|
this._grid = ref;
|
||||||
|
};
|
||||||
|
|
||||||
|
calculateGrid = (width = this.state.width, isSmallScreen) => {
|
||||||
|
const rowHeight = 100;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
width,
|
||||||
|
rowHeight
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
cellRenderer = ({ key, rowIndex, style }) => {
|
||||||
|
const {
|
||||||
|
items,
|
||||||
|
showRelativeDates,
|
||||||
|
shortDateFormat,
|
||||||
|
longDateFormat,
|
||||||
|
timeFormat,
|
||||||
|
isSmallScreen,
|
||||||
|
onGrabPress
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
rowHeight
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
const release = items[rowIndex];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={styles.container}
|
||||||
|
key={key}
|
||||||
|
style={style}
|
||||||
|
>
|
||||||
|
<SearchIndexItemConnector
|
||||||
|
key={release.guid}
|
||||||
|
component={SearchIndexOverview}
|
||||||
|
rowHeight={rowHeight}
|
||||||
|
showRelativeDates={showRelativeDates}
|
||||||
|
shortDateFormat={shortDateFormat}
|
||||||
|
longDateFormat={longDateFormat}
|
||||||
|
timeFormat={timeFormat}
|
||||||
|
isSmallScreen={isSmallScreen}
|
||||||
|
style={style}
|
||||||
|
guid={release.guid}
|
||||||
|
onGrabPress={onGrabPress}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onMeasure = ({ width }) => {
|
||||||
|
this.calculateGrid(width, this.props.isSmallScreen);
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
items
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
width,
|
||||||
|
rowHeight
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Measure
|
||||||
|
whitelist={['width']}
|
||||||
|
onMeasure={this.onMeasure}
|
||||||
|
>
|
||||||
|
<WindowScroller
|
||||||
|
scrollElement={undefined}
|
||||||
|
>
|
||||||
|
{({ height, registerChild, onChildScroll, scrollTop }) => {
|
||||||
|
if (!height) {
|
||||||
|
return <div />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={registerChild}>
|
||||||
|
<Grid
|
||||||
|
ref={this.setGridRef}
|
||||||
|
className={styles.grid}
|
||||||
|
autoHeight={true}
|
||||||
|
height={height}
|
||||||
|
columnCount={1}
|
||||||
|
columnWidth={width}
|
||||||
|
rowCount={items.length}
|
||||||
|
rowHeight={rowHeight}
|
||||||
|
width={width}
|
||||||
|
onScroll={onChildScroll}
|
||||||
|
scrollTop={scrollTop}
|
||||||
|
overscanRowCount={2}
|
||||||
|
cellRenderer={this.cellRenderer}
|
||||||
|
scrollToAlignment={'start'}
|
||||||
|
isScrollingOptOut={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</WindowScroller>
|
||||||
|
</Measure>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchIndexOverviews.propTypes = {
|
||||||
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
sortKey: PropTypes.string,
|
||||||
|
scrollTop: PropTypes.number.isRequired,
|
||||||
|
jumpToCharacter: PropTypes.string,
|
||||||
|
scroller: PropTypes.instanceOf(Element).isRequired,
|
||||||
|
showRelativeDates: PropTypes.bool.isRequired,
|
||||||
|
shortDateFormat: PropTypes.string.isRequired,
|
||||||
|
longDateFormat: PropTypes.string.isRequired,
|
||||||
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
|
timeFormat: PropTypes.string.isRequired,
|
||||||
|
onGrabPress: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SearchIndexOverviews;
|
||||||
32
frontend/src/Search/Mobile/SearchIndexOverviewsConnector.js
Normal file
32
frontend/src/Search/Mobile/SearchIndexOverviewsConnector.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import { grabRelease } from 'Store/Actions/releaseActions';
|
||||||
|
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||||
|
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||||
|
import SearchIndexOverviews from './SearchIndexOverviews';
|
||||||
|
|
||||||
|
function createMapStateToProps() {
|
||||||
|
return createSelector(
|
||||||
|
createUISettingsSelector(),
|
||||||
|
createDimensionsSelector(),
|
||||||
|
(uiSettings, dimensions) => {
|
||||||
|
return {
|
||||||
|
showRelativeDates: uiSettings.showRelativeDates,
|
||||||
|
shortDateFormat: uiSettings.shortDateFormat,
|
||||||
|
longDateFormat: uiSettings.longDateFormat,
|
||||||
|
timeFormat: uiSettings.timeFormat,
|
||||||
|
isSmallScreen: dimensions.isSmallScreen
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMapDispatchToProps(dispatch, props) {
|
||||||
|
return {
|
||||||
|
onGrabPress(payload) {
|
||||||
|
dispatch(grabRelease(payload));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(createMapStateToProps, createMapDispatchToProps)(SearchIndexOverviews);
|
||||||
@@ -24,6 +24,7 @@ const searchOptions = [
|
|||||||
const seriesTokens = [
|
const seriesTokens = [
|
||||||
{ token: '{ImdbId:tt1234567}', example: 'tt12345' },
|
{ token: '{ImdbId:tt1234567}', example: 'tt12345' },
|
||||||
{ token: '{TvdbId:12345}', example: '12345' },
|
{ token: '{TvdbId:12345}', example: '12345' },
|
||||||
|
{ token: '{TmdbId:12345}', example: '12345' },
|
||||||
{ token: '{TvMazeId:12345}', example: '54321' },
|
{ token: '{TvMazeId:12345}', example: '54321' },
|
||||||
{ token: '{Season:00}', example: '01' },
|
{ token: '{Season:00}', example: '01' },
|
||||||
{ token: '{Episode:00}', example: '01' }
|
{ token: '{Episode:00}', example: '01' }
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
|||||||
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||||
import { align, icons, sortDirections } from 'Helpers/Props';
|
import { align, icons, sortDirections } from 'Helpers/Props';
|
||||||
import AddIndexerModal from 'Indexer/Add/AddIndexerModal';
|
|
||||||
import EditIndexerModalConnector from 'Indexer/Edit/EditIndexerModalConnector';
|
|
||||||
import NoIndexer from 'Indexer/NoIndexer';
|
import NoIndexer from 'Indexer/NoIndexer';
|
||||||
import * as keyCodes from 'Utilities/Constants/keyCodes';
|
import * as keyCodes from 'Utilities/Constants/keyCodes';
|
||||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||||
@@ -23,12 +21,17 @@ import selectAll from 'Utilities/Table/selectAll';
|
|||||||
import toggleSelected from 'Utilities/Table/toggleSelected';
|
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||||
import SearchIndexFilterMenu from './Menus/SearchIndexFilterMenu';
|
import SearchIndexFilterMenu from './Menus/SearchIndexFilterMenu';
|
||||||
import SearchIndexSortMenu from './Menus/SearchIndexSortMenu';
|
import SearchIndexSortMenu from './Menus/SearchIndexSortMenu';
|
||||||
|
import SearchIndexOverviewsConnector from './Mobile/SearchIndexOverviewsConnector';
|
||||||
import NoSearchResults from './NoSearchResults';
|
import NoSearchResults from './NoSearchResults';
|
||||||
import SearchFooterConnector from './SearchFooterConnector';
|
import SearchFooterConnector from './SearchFooterConnector';
|
||||||
import SearchIndexTableConnector from './Table/SearchIndexTableConnector';
|
import SearchIndexTableConnector from './Table/SearchIndexTableConnector';
|
||||||
import styles from './SearchIndex.css';
|
import styles from './SearchIndex.css';
|
||||||
|
|
||||||
function getViewComponent() {
|
function getViewComponent(isSmallScreen) {
|
||||||
|
if (isSmallScreen) {
|
||||||
|
return SearchIndexOverviewsConnector;
|
||||||
|
}
|
||||||
|
|
||||||
return SearchIndexTableConnector;
|
return SearchIndexTableConnector;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,8 +47,6 @@ class SearchIndex extends Component {
|
|||||||
scroller: null,
|
scroller: null,
|
||||||
jumpBarItems: { order: [] },
|
jumpBarItems: { order: [] },
|
||||||
jumpToCharacter: null,
|
jumpToCharacter: null,
|
||||||
isAddIndexerModalOpen: false,
|
|
||||||
isEditIndexerModalOpen: false,
|
|
||||||
searchType: null,
|
searchType: null,
|
||||||
lastToggled: null,
|
lastToggled: null,
|
||||||
allSelected: false,
|
allSelected: false,
|
||||||
@@ -177,21 +178,6 @@ class SearchIndex extends Component {
|
|||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onAddIndexerPress = () => {
|
|
||||||
this.setState({ isAddIndexerModalOpen: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
onAddIndexerModalClose = ({ indexerSelected = false } = {}) => {
|
|
||||||
this.setState({
|
|
||||||
isAddIndexerModalOpen: false,
|
|
||||||
isEditIndexerModalOpen: indexerSelected
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onEditIndexerModalClose = () => {
|
|
||||||
this.setState({ isEditIndexerModalOpen: false });
|
|
||||||
};
|
|
||||||
|
|
||||||
onJumpBarItemPress = (jumpToCharacter) => {
|
onJumpBarItemPress = (jumpToCharacter) => {
|
||||||
this.setState({ jumpToCharacter });
|
this.setState({ jumpToCharacter });
|
||||||
};
|
};
|
||||||
@@ -253,6 +239,7 @@ class SearchIndex extends Component {
|
|||||||
onScroll,
|
onScroll,
|
||||||
onSortSelect,
|
onSortSelect,
|
||||||
onFilterSelect,
|
onFilterSelect,
|
||||||
|
isSmallScreen,
|
||||||
hasIndexers,
|
hasIndexers,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
@@ -260,8 +247,6 @@ class SearchIndex extends Component {
|
|||||||
const {
|
const {
|
||||||
scroller,
|
scroller,
|
||||||
jumpBarItems,
|
jumpBarItems,
|
||||||
isAddIndexerModalOpen,
|
|
||||||
isEditIndexerModalOpen,
|
|
||||||
jumpToCharacter,
|
jumpToCharacter,
|
||||||
selectedState,
|
selectedState,
|
||||||
allSelected,
|
allSelected,
|
||||||
@@ -270,7 +255,7 @@ class SearchIndex extends Component {
|
|||||||
|
|
||||||
const selectedIndexerIds = this.getSelectedIds();
|
const selectedIndexerIds = this.getSelectedIds();
|
||||||
|
|
||||||
const ViewComponent = getViewComponent();
|
const ViewComponent = getViewComponent(isSmallScreen);
|
||||||
const isLoaded = !!(!error && isPopulated && items.length && scroller);
|
const isLoaded = !!(!error && isPopulated && items.length && scroller);
|
||||||
const hasNoIndexer = !totalItems;
|
const hasNoIndexer = !totalItems;
|
||||||
|
|
||||||
@@ -384,16 +369,6 @@ class SearchIndex extends Component {
|
|||||||
onSearchPress={this.onSearchPress}
|
onSearchPress={this.onSearchPress}
|
||||||
onBulkGrabPress={this.onBulkGrabPress}
|
onBulkGrabPress={this.onBulkGrabPress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AddIndexerModal
|
|
||||||
isOpen={isAddIndexerModalOpen}
|
|
||||||
onModalClose={this.onAddIndexerModalClose}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<EditIndexerModalConnector
|
|
||||||
isOpen={isEditIndexerModalOpen}
|
|
||||||
onModalClose={this.onEditIndexerModalClose}
|
|
||||||
/>
|
|
||||||
</PageContent>
|
</PageContent>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
.category {
|
.category {
|
||||||
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||||
|
|
||||||
flex: 0 0 110px;
|
flex: 0 0 130px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.age,
|
.age,
|
||||||
|
|||||||
@@ -18,20 +18,20 @@ function createMapStateToProps() {
|
|||||||
return createSelector(
|
return createSelector(
|
||||||
createReleaseSelector(),
|
createReleaseSelector(),
|
||||||
(
|
(
|
||||||
movie
|
release
|
||||||
) => {
|
) => {
|
||||||
|
|
||||||
// If a movie is deleted this selector may fire before the parent
|
// If a release is deleted this selector may fire before the parent
|
||||||
// selecors, which will result in an undefined movie, if that happens
|
// selecors, which will result in an undefined release, if that happens
|
||||||
// we want to return early here and again in the render function to avoid
|
// we want to return early here and again in the render function to avoid
|
||||||
// trying to show a movie that has no information available.
|
// trying to show a release that has no information available.
|
||||||
|
|
||||||
if (!movie) {
|
if (!release) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...movie
|
...release
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -41,7 +41,7 @@ const mapDispatchToProps = {
|
|||||||
dispatchExecuteCommand: executeCommand
|
dispatchExecuteCommand: executeCommand
|
||||||
};
|
};
|
||||||
|
|
||||||
class MovieIndexItemConnector extends Component {
|
class SearchIndexItemConnector extends Component {
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
@@ -66,9 +66,9 @@ class MovieIndexItemConnector extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MovieIndexItemConnector.propTypes = {
|
SearchIndexItemConnector.propTypes = {
|
||||||
guid: PropTypes.string,
|
guid: PropTypes.string,
|
||||||
component: PropTypes.elementType.isRequired
|
component: PropTypes.elementType.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(createMapStateToProps, mapDispatchToProps)(MovieIndexItemConnector);
|
export default connect(createMapStateToProps, mapDispatchToProps)(SearchIndexItemConnector);
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
.category {
|
.category {
|
||||||
composes: cell;
|
composes: cell;
|
||||||
|
|
||||||
flex: 0 0 110px;
|
flex: 0 0 130px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.age,
|
.age,
|
||||||
|
|||||||
@@ -71,6 +71,19 @@ class SearchIndexRow extends Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onSavePress = () => {
|
||||||
|
const {
|
||||||
|
downloadUrl,
|
||||||
|
fileName,
|
||||||
|
onSavePress
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
onSavePress({
|
||||||
|
downloadUrl,
|
||||||
|
fileName
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
@@ -85,7 +98,6 @@ class SearchIndexRow extends Component {
|
|||||||
publishDate,
|
publishDate,
|
||||||
title,
|
title,
|
||||||
infoUrl,
|
infoUrl,
|
||||||
downloadUrl,
|
|
||||||
indexer,
|
indexer,
|
||||||
size,
|
size,
|
||||||
files,
|
files,
|
||||||
@@ -300,7 +312,7 @@ class SearchIndexRow extends Component {
|
|||||||
className={styles.downloadLink}
|
className={styles.downloadLink}
|
||||||
name={icons.SAVE}
|
name={icons.SAVE}
|
||||||
title={translate('Save')}
|
title={translate('Save')}
|
||||||
to={downloadUrl}
|
onPress={this.onSavePress}
|
||||||
/>
|
/>
|
||||||
</VirtualTableRowCell>
|
</VirtualTableRowCell>
|
||||||
);
|
);
|
||||||
@@ -323,6 +335,7 @@ SearchIndexRow.propTypes = {
|
|||||||
ageMinutes: PropTypes.number.isRequired,
|
ageMinutes: PropTypes.number.isRequired,
|
||||||
publishDate: PropTypes.string.isRequired,
|
publishDate: PropTypes.string.isRequired,
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
|
fileName: PropTypes.string.isRequired,
|
||||||
infoUrl: PropTypes.string.isRequired,
|
infoUrl: PropTypes.string.isRequired,
|
||||||
downloadUrl: PropTypes.string.isRequired,
|
downloadUrl: PropTypes.string.isRequired,
|
||||||
indexerId: PropTypes.number.isRequired,
|
indexerId: PropTypes.number.isRequired,
|
||||||
@@ -335,6 +348,7 @@ SearchIndexRow.propTypes = {
|
|||||||
indexerFlags: PropTypes.arrayOf(PropTypes.string).isRequired,
|
indexerFlags: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
onGrabPress: PropTypes.func.isRequired,
|
onGrabPress: PropTypes.func.isRequired,
|
||||||
|
onSavePress: PropTypes.func.isRequired,
|
||||||
isGrabbing: PropTypes.bool.isRequired,
|
isGrabbing: PropTypes.bool.isRequired,
|
||||||
isGrabbed: PropTypes.bool.isRequired,
|
isGrabbed: PropTypes.bool.isRequired,
|
||||||
grabError: PropTypes.string,
|
grabError: PropTypes.string,
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ class SearchIndexTable extends Component {
|
|||||||
timeFormat,
|
timeFormat,
|
||||||
selectedState,
|
selectedState,
|
||||||
onSelectedChange,
|
onSelectedChange,
|
||||||
onGrabPress
|
onGrabPress,
|
||||||
|
onSavePress
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const release = items[rowIndex];
|
const release = items[rowIndex];
|
||||||
@@ -71,6 +72,7 @@ class SearchIndexTable extends Component {
|
|||||||
longDateFormat={longDateFormat}
|
longDateFormat={longDateFormat}
|
||||||
timeFormat={timeFormat}
|
timeFormat={timeFormat}
|
||||||
onGrabPress={onGrabPress}
|
onGrabPress={onGrabPress}
|
||||||
|
onSavePress={onSavePress}
|
||||||
/>
|
/>
|
||||||
</VirtualTableRow>
|
</VirtualTableRow>
|
||||||
);
|
);
|
||||||
@@ -134,6 +136,7 @@ SearchIndexTable.propTypes = {
|
|||||||
timeFormat: PropTypes.string.isRequired,
|
timeFormat: PropTypes.string.isRequired,
|
||||||
onSortPress: PropTypes.func.isRequired,
|
onSortPress: PropTypes.func.isRequired,
|
||||||
onGrabPress: PropTypes.func.isRequired,
|
onGrabPress: PropTypes.func.isRequired,
|
||||||
|
onSavePress: PropTypes.func.isRequired,
|
||||||
allSelected: PropTypes.bool.isRequired,
|
allSelected: PropTypes.bool.isRequired,
|
||||||
allUnselected: PropTypes.bool.isRequired,
|
allUnselected: PropTypes.bool.isRequired,
|
||||||
selectedState: PropTypes.object.isRequired,
|
selectedState: PropTypes.object.isRequired,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { grabRelease, setReleasesSort } from 'Store/Actions/releaseActions';
|
import { grabRelease, saveRelease, setReleasesSort } from 'Store/Actions/releaseActions';
|
||||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||||
import SearchIndexTable from './SearchIndexTable';
|
import SearchIndexTable from './SearchIndexTable';
|
||||||
|
|
||||||
@@ -25,6 +25,9 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
},
|
},
|
||||||
onGrabPress(payload) {
|
onGrabPress(payload) {
|
||||||
dispatch(grabRelease(payload));
|
dispatch(grabRelease(payload));
|
||||||
|
},
|
||||||
|
onSavePress(payload) {
|
||||||
|
dispatch(saveRelease(payload));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class AddApplicationModalContent extends Component {
|
|||||||
return (
|
return (
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
Add Application
|
{translate('AddApplication')}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
|
|||||||
@@ -71,14 +71,14 @@ class Application extends Component {
|
|||||||
{
|
{
|
||||||
syncLevel === 'addOnly' &&
|
syncLevel === 'addOnly' &&
|
||||||
<Label kind={kinds.WARNING}>
|
<Label kind={kinds.WARNING}>
|
||||||
Add and Remove Only
|
{translate('AddRemoveOnly')}
|
||||||
</Label>
|
</Label>
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
syncLevel === 'fullSync' &&
|
syncLevel === 'fullSync' &&
|
||||||
<Label kind={kinds.SUCCESS}>
|
<Label kind={kinds.SUCCESS}>
|
||||||
Full Sync
|
{translate('FullSync')}
|
||||||
</Label>
|
</Label>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ class Application extends Component {
|
|||||||
kind={kinds.DISABLED}
|
kind={kinds.DISABLED}
|
||||||
outline={true}
|
outline={true}
|
||||||
>
|
>
|
||||||
Disabled
|
{translate('Disabled')}
|
||||||
</Label>
|
</Label>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import Modal from 'Components/Modal/Modal';
|
||||||
|
import { sizes } from 'Helpers/Props';
|
||||||
|
import AddCategoryModalContentConnector from './AddCategoryModalContentConnector';
|
||||||
|
|
||||||
|
function AddCategoryModal({ isOpen, onModalClose, ...otherProps }) {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
size={sizes.MEDIUM}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
>
|
||||||
|
<AddCategoryModalContentConnector
|
||||||
|
{...otherProps}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
AddCategoryModal.propTypes = {
|
||||||
|
isOpen: PropTypes.bool.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddCategoryModal;
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||||
|
import AddCategoryModal from './AddCategoryModal';
|
||||||
|
|
||||||
|
function createMapDispatchToProps(dispatch, props) {
|
||||||
|
const section = 'settings.downloadClientCategories';
|
||||||
|
|
||||||
|
return {
|
||||||
|
dispatchClearPendingChanges() {
|
||||||
|
dispatch(clearPendingChanges({ section }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class AddCategoryModalConnector extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onModalClose = () => {
|
||||||
|
this.props.dispatchClearPendingChanges();
|
||||||
|
this.props.onModalClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
dispatchClearPendingChanges,
|
||||||
|
...otherProps
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AddCategoryModal
|
||||||
|
{...otherProps}
|
||||||
|
onModalClose={this.onModalClose}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AddCategoryModalConnector.propTypes = {
|
||||||
|
onModalClose: PropTypes.func.isRequired,
|
||||||
|
dispatchClearPendingChanges: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(null, createMapDispatchToProps)(AddCategoryModalConnector);
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
.deleteButton {
|
||||||
|
composes: button from '~Components/Link/Button.css';
|
||||||
|
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import Form from 'Components/Form/Form';
|
||||||
|
import FormGroup from 'Components/Form/FormGroup';
|
||||||
|
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||||
|
import FormLabel from 'Components/Form/FormLabel';
|
||||||
|
import Button from 'Components/Link/Button';
|
||||||
|
import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton';
|
||||||
|
import ModalBody from 'Components/Modal/ModalBody';
|
||||||
|
import ModalContent from 'Components/Modal/ModalContent';
|
||||||
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
|
import { inputTypes, kinds } from 'Helpers/Props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import styles from './AddCategoryModalContent.css';
|
||||||
|
|
||||||
|
function AddCategoryModalContent(props) {
|
||||||
|
const {
|
||||||
|
advancedSettings,
|
||||||
|
item,
|
||||||
|
onInputChange,
|
||||||
|
onFieldChange,
|
||||||
|
onCancelPress,
|
||||||
|
onSavePress,
|
||||||
|
onDeleteSpecificationPress,
|
||||||
|
...otherProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
clientCategory,
|
||||||
|
categories
|
||||||
|
} = item;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalContent onModalClose={onCancelPress}>
|
||||||
|
<ModalHeader>
|
||||||
|
{`${id ? 'Edit' : 'Add'} Category`}
|
||||||
|
</ModalHeader>
|
||||||
|
|
||||||
|
<ModalBody>
|
||||||
|
<Form
|
||||||
|
{...otherProps}
|
||||||
|
>
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>
|
||||||
|
{translate('DownloadClientCategory')}
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.TEXT}
|
||||||
|
name="clientCategory"
|
||||||
|
{...clientCategory}
|
||||||
|
onChange={onInputChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>
|
||||||
|
{translate('MappedCategories')}
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.CATEGORY_SELECT}
|
||||||
|
name="categories"
|
||||||
|
{...categories}
|
||||||
|
onChange={onInputChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</Form>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
{
|
||||||
|
id &&
|
||||||
|
<Button
|
||||||
|
className={styles.deleteButton}
|
||||||
|
kind={kinds.DANGER}
|
||||||
|
onPress={onDeleteSpecificationPress}
|
||||||
|
>
|
||||||
|
{translate('Delete')}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onPress={onCancelPress}
|
||||||
|
>
|
||||||
|
{translate('Cancel')}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<SpinnerErrorButton
|
||||||
|
isSpinning={false}
|
||||||
|
onPress={onSavePress}
|
||||||
|
>
|
||||||
|
{translate('Save')}
|
||||||
|
</SpinnerErrorButton>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
AddCategoryModalContent.propTypes = {
|
||||||
|
advancedSettings: PropTypes.bool.isRequired,
|
||||||
|
item: PropTypes.object.isRequired,
|
||||||
|
onInputChange: PropTypes.func.isRequired,
|
||||||
|
onFieldChange: PropTypes.func.isRequired,
|
||||||
|
onCancelPress: PropTypes.func.isRequired,
|
||||||
|
onSavePress: PropTypes.func.isRequired,
|
||||||
|
onDeleteSpecificationPress: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddCategoryModalContent;
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import { clearDownloadClientCategoryPending, saveDownloadClientCategory, setDownloadClientCategoryFieldValue, setDownloadClientCategoryValue } from 'Store/Actions/settingsActions';
|
||||||
|
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
||||||
|
import AddCategoryModalContent from './AddCategoryModalContent';
|
||||||
|
|
||||||
|
function createMapStateToProps() {
|
||||||
|
return createSelector(
|
||||||
|
(state) => state.settings.advancedSettings,
|
||||||
|
createProviderSettingsSelector('downloadClientCategories'),
|
||||||
|
(advancedSettings, specification) => {
|
||||||
|
return {
|
||||||
|
advancedSettings,
|
||||||
|
...specification
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
setDownloadClientCategoryValue,
|
||||||
|
setDownloadClientCategoryFieldValue,
|
||||||
|
saveDownloadClientCategory,
|
||||||
|
clearDownloadClientCategoryPending
|
||||||
|
};
|
||||||
|
|
||||||
|
class AddCategoryModalContentConnector extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onInputChange = ({ name, value }) => {
|
||||||
|
this.props.setDownloadClientCategoryValue({ name, value });
|
||||||
|
};
|
||||||
|
|
||||||
|
onFieldChange = ({ name, value }) => {
|
||||||
|
this.props.setDownloadClientCategoryFieldValue({ name, value });
|
||||||
|
};
|
||||||
|
|
||||||
|
onCancelPress = () => {
|
||||||
|
this.props.clearDownloadClientCategoryPending();
|
||||||
|
this.props.onModalClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
onSavePress = () => {
|
||||||
|
this.props.saveDownloadClientCategory({ id: this.props.id });
|
||||||
|
this.props.onModalClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<AddCategoryModalContent
|
||||||
|
{...this.props}
|
||||||
|
onCancelPress={this.onCancelPress}
|
||||||
|
onSavePress={this.onSavePress}
|
||||||
|
onInputChange={this.onInputChange}
|
||||||
|
onFieldChange={this.onFieldChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AddCategoryModalContentConnector.propTypes = {
|
||||||
|
id: PropTypes.number,
|
||||||
|
item: PropTypes.object.isRequired,
|
||||||
|
setDownloadClientCategoryValue: PropTypes.func.isRequired,
|
||||||
|
setDownloadClientCategoryFieldValue: PropTypes.func.isRequired,
|
||||||
|
clearDownloadClientCategoryPending: PropTypes.func.isRequired,
|
||||||
|
saveDownloadClientCategory: PropTypes.func.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(createMapStateToProps, mapDispatchToProps)(AddCategoryModalContentConnector);
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
.customFormat {
|
||||||
|
composes: card from '~Components/Card.css';
|
||||||
|
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nameContainer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
@add-mixin truncate;
|
||||||
|
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.labels {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-top: 5px;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltipLabel {
|
||||||
|
composes: label from '~Components/Label.css';
|
||||||
|
|
||||||
|
margin: 0;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import Card from 'Components/Card';
|
||||||
|
import Label from 'Components/Label';
|
||||||
|
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||||
|
import { kinds } from 'Helpers/Props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import AddCategoryModalConnector from './AddCategoryModalConnector';
|
||||||
|
import styles from './Category.css';
|
||||||
|
|
||||||
|
class Category extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
isEditSpecificationModalOpen: false,
|
||||||
|
isDeleteSpecificationModalOpen: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onEditSpecificationPress = () => {
|
||||||
|
this.setState({ isEditSpecificationModalOpen: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
onEditSpecificationModalClose = () => {
|
||||||
|
this.setState({ isEditSpecificationModalOpen: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
onDeleteSpecificationPress = () => {
|
||||||
|
this.setState({
|
||||||
|
isEditSpecificationModalOpen: false,
|
||||||
|
isDeleteSpecificationModalOpen: true
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onDeleteSpecificationModalClose = () => {
|
||||||
|
this.setState({ isDeleteSpecificationModalOpen: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
onConfirmDeleteSpecification = () => {
|
||||||
|
this.props.onConfirmDeleteSpecification(this.props.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
clientCategory,
|
||||||
|
categories
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
className={styles.customFormat}
|
||||||
|
overlayContent={true}
|
||||||
|
onPress={this.onEditSpecificationPress}
|
||||||
|
>
|
||||||
|
<div className={styles.nameContainer}>
|
||||||
|
<div className={styles.name}>
|
||||||
|
{clientCategory}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Label kind={kinds.PRIMARY}>
|
||||||
|
{`${categories.length} ${categories.length > 1 ? translate('Categories') : translate('Category')}`}
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<AddCategoryModalConnector
|
||||||
|
id={id}
|
||||||
|
isOpen={this.state.isEditSpecificationModalOpen}
|
||||||
|
onModalClose={this.onEditSpecificationModalClose}
|
||||||
|
onDeleteSpecificationPress={this.onDeleteSpecificationPress}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ConfirmModal
|
||||||
|
isOpen={this.state.isDeleteSpecificationModalOpen}
|
||||||
|
kind={kinds.DANGER}
|
||||||
|
title={translate('DeleteClientCategory')}
|
||||||
|
message={
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
{translate('AreYouSureYouWantToDeleteCategory', [name])}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
confirmLabel={translate('Delete')}
|
||||||
|
onConfirm={this.onConfirmDeleteSpecification}
|
||||||
|
onCancel={this.onDeleteSpecificationModalClose}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Category.propTypes = {
|
||||||
|
id: PropTypes.number.isRequired,
|
||||||
|
categories: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||||
|
clientCategory: PropTypes.string.isRequired,
|
||||||
|
onConfirmDeleteSpecification: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Category;
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import Alert from 'Components/Alert';
|
import Alert from 'Components/Alert';
|
||||||
|
import Card from 'Components/Card';
|
||||||
|
import FieldSet from 'Components/FieldSet';
|
||||||
import Form from 'Components/Form/Form';
|
import Form from 'Components/Form/Form';
|
||||||
import FormGroup from 'Components/Form/FormGroup';
|
import FormGroup from 'Components/Form/FormGroup';
|
||||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||||
import FormLabel from 'Components/Form/FormLabel';
|
import FormLabel from 'Components/Form/FormLabel';
|
||||||
import ProviderFieldFormGroup from 'Components/Form/ProviderFieldFormGroup';
|
import ProviderFieldFormGroup from 'Components/Form/ProviderFieldFormGroup';
|
||||||
|
import Icon from 'Components/Icon';
|
||||||
import Button from 'Components/Link/Button';
|
import Button from 'Components/Link/Button';
|
||||||
import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton';
|
import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
@@ -13,12 +16,33 @@ import ModalBody from 'Components/Modal/ModalBody';
|
|||||||
import ModalContent from 'Components/Modal/ModalContent';
|
import ModalContent from 'Components/Modal/ModalContent';
|
||||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
import { inputTypes, kinds } from 'Helpers/Props';
|
import { icons, inputTypes, kinds } from 'Helpers/Props';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
|
import AddCategoryModalConnector from './Categories/AddCategoryModalConnector';
|
||||||
|
import Category from './Categories/Category';
|
||||||
import styles from './EditDownloadClientModalContent.css';
|
import styles from './EditDownloadClientModalContent.css';
|
||||||
|
|
||||||
class EditDownloadClientModalContent extends Component {
|
class EditDownloadClientModalContent extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
isAddCategoryModalOpen: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onAddCategoryPress = () => {
|
||||||
|
this.setState({ isAddCategoryModalOpen: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
onAddCategoryModalClose = () => {
|
||||||
|
this.setState({ isAddCategoryModalOpen: false });
|
||||||
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
@@ -27,6 +51,7 @@ class EditDownloadClientModalContent extends Component {
|
|||||||
advancedSettings,
|
advancedSettings,
|
||||||
isFetching,
|
isFetching,
|
||||||
error,
|
error,
|
||||||
|
categories,
|
||||||
isSaving,
|
isSaving,
|
||||||
isTesting,
|
isTesting,
|
||||||
saveError,
|
saveError,
|
||||||
@@ -37,15 +62,21 @@ class EditDownloadClientModalContent extends Component {
|
|||||||
onSavePress,
|
onSavePress,
|
||||||
onTestPress,
|
onTestPress,
|
||||||
onDeleteDownloadClientPress,
|
onDeleteDownloadClientPress,
|
||||||
|
onConfirmDeleteCategory,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
isAddCategoryModalOpen
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
implementationName,
|
implementationName,
|
||||||
name,
|
name,
|
||||||
enable,
|
enable,
|
||||||
priority,
|
priority,
|
||||||
|
supportsCategories,
|
||||||
fields,
|
fields,
|
||||||
message
|
message
|
||||||
} = item;
|
} = item;
|
||||||
@@ -136,6 +167,43 @@ class EditDownloadClientModalContent extends Component {
|
|||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
|
{
|
||||||
|
supportsCategories.value ?
|
||||||
|
<FieldSet legend={translate('MappedCategories')}>
|
||||||
|
<div className={styles.customFormats}>
|
||||||
|
{
|
||||||
|
categories.map((tag) => {
|
||||||
|
return (
|
||||||
|
<Category
|
||||||
|
key={tag.id}
|
||||||
|
{...tag}
|
||||||
|
onConfirmDeleteSpecification={onConfirmDeleteCategory}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
<Card
|
||||||
|
className={styles.addCategory}
|
||||||
|
onPress={this.onAddCategoryPress}
|
||||||
|
>
|
||||||
|
<div className={styles.center}>
|
||||||
|
<Icon
|
||||||
|
name={icons.ADD}
|
||||||
|
size={25}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</FieldSet> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
<AddCategoryModalConnector
|
||||||
|
isOpen={isAddCategoryModalOpen}
|
||||||
|
onModalClose={this.onAddCategoryModalClose}
|
||||||
|
/>
|
||||||
|
|
||||||
</Form>
|
</Form>
|
||||||
}
|
}
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
@@ -185,13 +253,15 @@ EditDownloadClientModalContent.propTypes = {
|
|||||||
isSaving: PropTypes.bool.isRequired,
|
isSaving: PropTypes.bool.isRequired,
|
||||||
saveError: PropTypes.object,
|
saveError: PropTypes.object,
|
||||||
isTesting: PropTypes.bool.isRequired,
|
isTesting: PropTypes.bool.isRequired,
|
||||||
|
categories: PropTypes.arrayOf(PropTypes.object),
|
||||||
item: PropTypes.object.isRequired,
|
item: PropTypes.object.isRequired,
|
||||||
onInputChange: PropTypes.func.isRequired,
|
onInputChange: PropTypes.func.isRequired,
|
||||||
onFieldChange: PropTypes.func.isRequired,
|
onFieldChange: PropTypes.func.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired,
|
onModalClose: PropTypes.func.isRequired,
|
||||||
onSavePress: PropTypes.func.isRequired,
|
onSavePress: PropTypes.func.isRequired,
|
||||||
onTestPress: PropTypes.func.isRequired,
|
onTestPress: PropTypes.func.isRequired,
|
||||||
onDeleteDownloadClientPress: PropTypes.func
|
onDeleteDownloadClientPress: PropTypes.func,
|
||||||
|
onConfirmDeleteCategory: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default EditDownloadClientModalContent;
|
export default EditDownloadClientModalContent;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { saveDownloadClient, setDownloadClientFieldValue, setDownloadClientValue, testDownloadClient } from 'Store/Actions/settingsActions';
|
import { deleteDownloadClientCategory, fetchDownloadClientCategories, saveDownloadClient, setDownloadClientFieldValue, setDownloadClientValue, testDownloadClient } from 'Store/Actions/settingsActions';
|
||||||
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
||||||
import EditDownloadClientModalContent from './EditDownloadClientModalContent';
|
import EditDownloadClientModalContent from './EditDownloadClientModalContent';
|
||||||
|
|
||||||
@@ -10,10 +10,12 @@ function createMapStateToProps() {
|
|||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.settings.advancedSettings,
|
(state) => state.settings.advancedSettings,
|
||||||
createProviderSettingsSelector('downloadClients'),
|
createProviderSettingsSelector('downloadClients'),
|
||||||
(advancedSettings, downloadClient) => {
|
(state) => state.settings.downloadClientCategories,
|
||||||
|
(advancedSettings, downloadClient, categories) => {
|
||||||
return {
|
return {
|
||||||
advancedSettings,
|
advancedSettings,
|
||||||
...downloadClient
|
...downloadClient,
|
||||||
|
categories: categories.items
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -23,7 +25,9 @@ const mapDispatchToProps = {
|
|||||||
setDownloadClientValue,
|
setDownloadClientValue,
|
||||||
setDownloadClientFieldValue,
|
setDownloadClientFieldValue,
|
||||||
saveDownloadClient,
|
saveDownloadClient,
|
||||||
testDownloadClient
|
testDownloadClient,
|
||||||
|
fetchDownloadClientCategories,
|
||||||
|
deleteDownloadClientCategory
|
||||||
};
|
};
|
||||||
|
|
||||||
class EditDownloadClientModalContentConnector extends Component {
|
class EditDownloadClientModalContentConnector extends Component {
|
||||||
@@ -31,6 +35,14 @@ class EditDownloadClientModalContentConnector extends Component {
|
|||||||
//
|
//
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
tagsFromId
|
||||||
|
} = this.props;
|
||||||
|
this.props.fetchDownloadClientCategories({ id: tagsFromId || id });
|
||||||
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
if (prevProps.isSaving && !this.props.isSaving && !this.props.saveError) {
|
if (prevProps.isSaving && !this.props.isSaving && !this.props.saveError) {
|
||||||
this.props.onModalClose();
|
this.props.onModalClose();
|
||||||
@@ -56,6 +68,10 @@ class EditDownloadClientModalContentConnector extends Component {
|
|||||||
this.props.testDownloadClient({ id: this.props.id });
|
this.props.testDownloadClient({ id: this.props.id });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onConfirmDeleteCategory = (id) => {
|
||||||
|
this.props.deleteDownloadClientCategory({ id });
|
||||||
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
@@ -67,6 +83,7 @@ class EditDownloadClientModalContentConnector extends Component {
|
|||||||
onTestPress={this.onTestPress}
|
onTestPress={this.onTestPress}
|
||||||
onInputChange={this.onInputChange}
|
onInputChange={this.onInputChange}
|
||||||
onFieldChange={this.onFieldChange}
|
onFieldChange={this.onFieldChange}
|
||||||
|
onConfirmDeleteCategory={this.onConfirmDeleteCategory}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -74,10 +91,13 @@ class EditDownloadClientModalContentConnector extends Component {
|
|||||||
|
|
||||||
EditDownloadClientModalContentConnector.propTypes = {
|
EditDownloadClientModalContentConnector.propTypes = {
|
||||||
id: PropTypes.number,
|
id: PropTypes.number,
|
||||||
|
tagsFromId: PropTypes.number,
|
||||||
isFetching: PropTypes.bool.isRequired,
|
isFetching: PropTypes.bool.isRequired,
|
||||||
isSaving: PropTypes.bool.isRequired,
|
isSaving: PropTypes.bool.isRequired,
|
||||||
saveError: PropTypes.object,
|
saveError: PropTypes.object,
|
||||||
item: PropTypes.object.isRequired,
|
item: PropTypes.object.isRequired,
|
||||||
|
fetchDownloadClientCategories: PropTypes.func.isRequired,
|
||||||
|
deleteDownloadClientCategory: PropTypes.func.isRequired,
|
||||||
setDownloadClientValue: PropTypes.func.isRequired,
|
setDownloadClientValue: PropTypes.func.isRequired,
|
||||||
setDownloadClientFieldValue: PropTypes.func.isRequired,
|
setDownloadClientFieldValue: PropTypes.func.isRequired,
|
||||||
saveDownloadClient: PropTypes.func.isRequired,
|
saveDownloadClient: PropTypes.func.isRequired,
|
||||||
|
|||||||
@@ -11,12 +11,20 @@ import ConfirmModal from 'Components/Modal/ConfirmModal';
|
|||||||
import { icons, inputTypes, kinds } from 'Helpers/Props';
|
import { icons, inputTypes, kinds } from 'Helpers/Props';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
|
|
||||||
const authenticationMethodOptions = [
|
export const authenticationRequiredWarning = translate('AuthenticationRequiredWarning');
|
||||||
{ key: 'none', value: 'None' },
|
|
||||||
|
export const authenticationMethodOptions = [
|
||||||
|
{ key: 'none', value: 'None', isDisabled: true },
|
||||||
|
{ key: 'external', value: 'External', isHidden: true },
|
||||||
{ key: 'basic', value: 'Basic (Browser Popup)' },
|
{ key: 'basic', value: 'Basic (Browser Popup)' },
|
||||||
{ key: 'forms', value: 'Forms (Login Page)' }
|
{ key: 'forms', value: 'Forms (Login Page)' }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const authenticationRequiredOptions = [
|
||||||
|
{ key: 'enabled', value: 'Enabled' },
|
||||||
|
{ key: 'disabledForLocalAddresses', value: 'Disabled for Local Addresses' }
|
||||||
|
];
|
||||||
|
|
||||||
const certificateValidationOptions = [
|
const certificateValidationOptions = [
|
||||||
{ key: 'enabled', value: 'Enabled' },
|
{ key: 'enabled', value: 'Enabled' },
|
||||||
{ key: 'disabledForLocalAddresses', value: 'Disabled for Local Addresses' },
|
{ key: 'disabledForLocalAddresses', value: 'Disabled for Local Addresses' },
|
||||||
@@ -68,6 +76,7 @@ class SecuritySettings extends Component {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
authenticationMethod,
|
authenticationMethod,
|
||||||
|
authenticationRequired,
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
apiKey,
|
apiKey,
|
||||||
@@ -86,13 +95,31 @@ class SecuritySettings extends Component {
|
|||||||
name="authenticationMethod"
|
name="authenticationMethod"
|
||||||
values={authenticationMethodOptions}
|
values={authenticationMethodOptions}
|
||||||
helpText={translate('AuthenticationMethodHelpText')}
|
helpText={translate('AuthenticationMethodHelpText')}
|
||||||
|
helpTextWarning={authenticationRequiredWarning}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...authenticationMethod}
|
{...authenticationMethod}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
{
|
{
|
||||||
authenticationEnabled &&
|
authenticationEnabled ?
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('AuthenticationRequired')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.SELECT}
|
||||||
|
name="authenticationRequired"
|
||||||
|
values={authenticationRequiredOptions}
|
||||||
|
helpText={translate('AuthenticationRequiredHelpText')}
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...authenticationRequired}
|
||||||
|
/>
|
||||||
|
</FormGroup> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
authenticationEnabled ?
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>{translate('Username')}</FormLabel>
|
<FormLabel>{translate('Username')}</FormLabel>
|
||||||
|
|
||||||
@@ -102,11 +129,12 @@ class SecuritySettings extends Component {
|
|||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...username}
|
{...username}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
authenticationEnabled &&
|
authenticationEnabled ?
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>{translate('Password')}</FormLabel>
|
<FormLabel>{translate('Password')}</FormLabel>
|
||||||
|
|
||||||
@@ -116,7 +144,8 @@ class SecuritySettings extends Component {
|
|||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...password}
|
{...password}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ class IndexerProxy extends Component {
|
|||||||
kind={kinds.DISABLED}
|
kind={kinds.DISABLED}
|
||||||
outline={true}
|
outline={true}
|
||||||
>
|
>
|
||||||
Disabled
|
{translate('Disabled')}
|
||||||
</Label> :
|
</Label> :
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,17 +56,9 @@ class Notification extends Component {
|
|||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
onGrab,
|
onGrab,
|
||||||
onDownload,
|
|
||||||
onUpgrade,
|
|
||||||
onRename,
|
|
||||||
onDelete,
|
|
||||||
onHealthIssue,
|
onHealthIssue,
|
||||||
onApplicationUpdate,
|
onApplicationUpdate,
|
||||||
supportsOnGrab,
|
supportsOnGrab,
|
||||||
supportsOnDownload,
|
|
||||||
supportsOnUpgrade,
|
|
||||||
supportsOnRename,
|
|
||||||
supportsOnDelete,
|
|
||||||
supportsOnHealthIssue,
|
supportsOnHealthIssue,
|
||||||
supportsOnApplicationUpdate
|
supportsOnApplicationUpdate
|
||||||
} = this.props;
|
} = this.props;
|
||||||
@@ -88,34 +80,6 @@ class Notification extends Component {
|
|||||||
</Label>
|
</Label>
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
supportsOnDelete && onDelete &&
|
|
||||||
<Label kind={kinds.SUCCESS}>
|
|
||||||
{translate('OnDelete')}
|
|
||||||
</Label>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
supportsOnDownload && onDownload &&
|
|
||||||
<Label kind={kinds.SUCCESS}>
|
|
||||||
{translate('OnImport')}
|
|
||||||
</Label>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
supportsOnUpgrade && onDownload && onUpgrade &&
|
|
||||||
<Label kind={kinds.SUCCESS}>
|
|
||||||
{translate('OnUpgrade')}
|
|
||||||
</Label>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
supportsOnRename && onRename &&
|
|
||||||
<Label kind={kinds.SUCCESS}>
|
|
||||||
{translate('OnRename')}
|
|
||||||
</Label>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
supportsOnHealthIssue && onHealthIssue &&
|
supportsOnHealthIssue && onHealthIssue &&
|
||||||
<Label kind={kinds.SUCCESS}>
|
<Label kind={kinds.SUCCESS}>
|
||||||
@@ -132,7 +96,7 @@ class Notification extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!onGrab && !onDownload && !onRename && !onHealthIssue && !onDelete && !onApplicationUpdate ?
|
!onGrab && !onHealthIssue && !onApplicationUpdate ?
|
||||||
<Label
|
<Label
|
||||||
kind={kinds.DISABLED}
|
kind={kinds.DISABLED}
|
||||||
outline={true}
|
outline={true}
|
||||||
@@ -167,17 +131,9 @@ Notification.propTypes = {
|
|||||||
id: PropTypes.number.isRequired,
|
id: PropTypes.number.isRequired,
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
onGrab: PropTypes.bool.isRequired,
|
onGrab: PropTypes.bool.isRequired,
|
||||||
onDownload: PropTypes.bool.isRequired,
|
|
||||||
onUpgrade: PropTypes.bool.isRequired,
|
|
||||||
onRename: PropTypes.bool.isRequired,
|
|
||||||
onDelete: PropTypes.bool.isRequired,
|
|
||||||
onHealthIssue: PropTypes.bool.isRequired,
|
onHealthIssue: PropTypes.bool.isRequired,
|
||||||
onApplicationUpdate: PropTypes.bool.isRequired,
|
onApplicationUpdate: PropTypes.bool.isRequired,
|
||||||
supportsOnGrab: PropTypes.bool.isRequired,
|
supportsOnGrab: PropTypes.bool.isRequired,
|
||||||
supportsOnDownload: PropTypes.bool.isRequired,
|
|
||||||
supportsOnDelete: PropTypes.bool.isRequired,
|
|
||||||
supportsOnUpgrade: PropTypes.bool.isRequired,
|
|
||||||
supportsOnRename: PropTypes.bool.isRequired,
|
|
||||||
supportsOnHealthIssue: PropTypes.bool.isRequired,
|
supportsOnHealthIssue: PropTypes.bool.isRequired,
|
||||||
supportsOnApplicationUpdate: PropTypes.bool.isRequired,
|
supportsOnApplicationUpdate: PropTypes.bool.isRequired,
|
||||||
onConfirmDeleteNotification: PropTypes.func.isRequired
|
onConfirmDeleteNotification: PropTypes.func.isRequired
|
||||||
|
|||||||
@@ -15,8 +15,11 @@ function NotificationEventItems(props) {
|
|||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
onGrab,
|
||||||
onHealthIssue,
|
onHealthIssue,
|
||||||
onApplicationUpdate,
|
onApplicationUpdate,
|
||||||
|
supportsOnGrab,
|
||||||
|
includeManualGrabs,
|
||||||
supportsOnHealthIssue,
|
supportsOnHealthIssue,
|
||||||
includeHealthWarnings,
|
includeHealthWarnings,
|
||||||
supportsOnApplicationUpdate
|
supportsOnApplicationUpdate
|
||||||
@@ -31,6 +34,31 @@ function NotificationEventItems(props) {
|
|||||||
link="https://wiki.servarr.com/prowlarr/settings#connections"
|
link="https://wiki.servarr.com/prowlarr/settings#connections"
|
||||||
/>
|
/>
|
||||||
<div className={styles.events}>
|
<div className={styles.events}>
|
||||||
|
<div>
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.CHECK}
|
||||||
|
name="onGrab"
|
||||||
|
helpText={translate('OnGrabHelpText')}
|
||||||
|
isDisabled={!supportsOnGrab.value}
|
||||||
|
{...onGrab}
|
||||||
|
onChange={onInputChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
onGrab.value &&
|
||||||
|
<div>
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.CHECK}
|
||||||
|
name="includeManualGrabs"
|
||||||
|
helpText={translate('IncludeManualGrabsHelpText')}
|
||||||
|
isDisabled={!supportsOnGrab.value}
|
||||||
|
{...includeManualGrabs}
|
||||||
|
onChange={onInputChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ function PendingChangesModal(props) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
bindShortcut('enter', onConfirm);
|
bindShortcut('enter', onConfirm);
|
||||||
}, [onConfirm]);
|
}, [bindShortcut, onConfirm]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
|||||||
@@ -62,8 +62,6 @@ class UISettings extends Component {
|
|||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const uiLanguages = languages.filter((item) => item.value !== 'Original');
|
|
||||||
|
|
||||||
const themeOptions = Object.keys(themes)
|
const themeOptions = Object.keys(themes)
|
||||||
.map((theme) => ({ key: theme, value: titleCase(theme) }));
|
.map((theme) => ({ key: theme, value: titleCase(theme) }));
|
||||||
|
|
||||||
@@ -172,7 +170,7 @@ class UISettings extends Component {
|
|||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.SELECT}
|
type={inputTypes.SELECT}
|
||||||
name="uiLanguage"
|
name="uiLanguage"
|
||||||
values={uiLanguages}
|
values={languages}
|
||||||
helpText={translate('UILanguageHelpText')}
|
helpText={translate('UILanguageHelpText')}
|
||||||
helpTextWarning={translate('UILanguageHelpTextWarning')}
|
helpTextWarning={translate('UILanguageHelpTextWarning')}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import React, { Component } from 'react';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||||
|
import { fetchLocalizationOptions } from 'Store/Actions/localizationActions';
|
||||||
import { fetchUISettings, saveUISettings, setUISettingsValue } from 'Store/Actions/settingsActions';
|
import { fetchUISettings, saveUISettings, setUISettingsValue } from 'Store/Actions/settingsActions';
|
||||||
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
|
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
|
||||||
import UISettings from './UISettings';
|
import UISettings from './UISettings';
|
||||||
@@ -11,18 +12,17 @@ const SECTION = 'ui';
|
|||||||
|
|
||||||
function createLanguagesSelector() {
|
function createLanguagesSelector() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.settings.languages,
|
(state) => state.localization,
|
||||||
(languages) => {
|
(localization) => {
|
||||||
const items = languages.items;
|
const items = localization.items;
|
||||||
const filterItems = ['Any', 'Unknown'];
|
|
||||||
|
|
||||||
if (!items) {
|
if (!items) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const newItems = items.filter((lang) => !filterItems.includes(lang.name)).map((item) => {
|
const newItems = items.filter((lang) => !items.includes(lang.name)).map((item) => {
|
||||||
return {
|
return {
|
||||||
key: item.id,
|
key: item.value,
|
||||||
value: item.name
|
value: item.name
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -51,6 +51,7 @@ const mapDispatchToProps = {
|
|||||||
setUISettingsValue,
|
setUISettingsValue,
|
||||||
saveUISettings,
|
saveUISettings,
|
||||||
fetchUISettings,
|
fetchUISettings,
|
||||||
|
fetchLocalizationOptions,
|
||||||
clearPendingChanges
|
clearPendingChanges
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -61,6 +62,7 @@ class UISettingsConnector extends Component {
|
|||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.fetchUISettings();
|
this.props.fetchUISettings();
|
||||||
|
this.props.fetchLocalizationOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
@@ -96,6 +98,7 @@ UISettingsConnector.propTypes = {
|
|||||||
setUISettingsValue: PropTypes.func.isRequired,
|
setUISettingsValue: PropTypes.func.isRequired,
|
||||||
saveUISettings: PropTypes.func.isRequired,
|
saveUISettings: PropTypes.func.isRequired,
|
||||||
fetchUISettings: PropTypes.func.isRequired,
|
fetchUISettings: PropTypes.func.isRequired,
|
||||||
|
fetchLocalizationOptions: PropTypes.func.isRequired,
|
||||||
clearPendingChanges: PropTypes.func.isRequired
|
clearPendingChanges: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
169
frontend/src/Store/Actions/Settings/downloadClientCategories.js
Normal file
169
frontend/src/Store/Actions/Settings/downloadClientCategories.js
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
import { createAction } from 'redux-actions';
|
||||||
|
import { batchActions } from 'redux-batched-actions';
|
||||||
|
import createClearReducer from 'Store/Actions/Creators/Reducers/createClearReducer';
|
||||||
|
import createSetProviderFieldValueReducer from 'Store/Actions/Creators/Reducers/createSetProviderFieldValueReducer';
|
||||||
|
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
|
||||||
|
import { createThunk } from 'Store/thunks';
|
||||||
|
import getNextId from 'Utilities/State/getNextId';
|
||||||
|
import getProviderState from 'Utilities/State/getProviderState';
|
||||||
|
import getSectionState from 'Utilities/State/getSectionState';
|
||||||
|
import selectProviderSchema from 'Utilities/State/selectProviderSchema';
|
||||||
|
import { removeItem, set, update, updateItem } from '../baseActions';
|
||||||
|
|
||||||
|
//
|
||||||
|
// Variables
|
||||||
|
|
||||||
|
const section = 'settings.downloadClientCategories';
|
||||||
|
|
||||||
|
//
|
||||||
|
// Actions Types
|
||||||
|
|
||||||
|
export const FETCH_DOWNLOAD_CLIENT_CATEGORIES = 'settings/downloadClientCategories/fetchDownloadClientCategories';
|
||||||
|
export const FETCH_DOWNLOAD_CLIENT_CATEGORY_SCHEMA = 'settings/downloadClientCategories/fetchDownloadClientCategorySchema';
|
||||||
|
export const SELECT_DOWNLOAD_CLIENT_CATEGORY_SCHEMA = 'settings/downloadClientCategories/selectDownloadClientCategorySchema';
|
||||||
|
export const SET_DOWNLOAD_CLIENT_CATEGORY_VALUE = 'settings/downloadClientCategories/setDownloadClientCategoryValue';
|
||||||
|
export const SET_DOWNLOAD_CLIENT_CATEGORY_FIELD_VALUE = 'settings/downloadClientCategories/setDownloadClientCategoryFieldValue';
|
||||||
|
export const SAVE_DOWNLOAD_CLIENT_CATEGORY = 'settings/downloadClientCategories/saveDownloadClientCategory';
|
||||||
|
export const DELETE_DOWNLOAD_CLIENT_CATEGORY = 'settings/downloadClientCategories/deleteDownloadClientCategory';
|
||||||
|
export const DELETE_ALL_DOWNLOAD_CLIENT_CATEGORY = 'settings/downloadClientCategories/deleteAllDownloadClientCategory';
|
||||||
|
export const CLEAR_DOWNLOAD_CLIENT_CATEGORIES = 'settings/downloadClientCategories/clearDownloadClientCategories';
|
||||||
|
export const CLEAR_DOWNLOAD_CLIENT_CATEGORY_PENDING = 'settings/downloadClientCategories/clearDownloadClientCategoryPending';
|
||||||
|
//
|
||||||
|
// Action Creators
|
||||||
|
|
||||||
|
export const fetchDownloadClientCategories = createThunk(FETCH_DOWNLOAD_CLIENT_CATEGORIES);
|
||||||
|
export const fetchDownloadClientCategorySchema = createThunk(FETCH_DOWNLOAD_CLIENT_CATEGORY_SCHEMA);
|
||||||
|
export const selectDownloadClientCategorySchema = createAction(SELECT_DOWNLOAD_CLIENT_CATEGORY_SCHEMA);
|
||||||
|
|
||||||
|
export const saveDownloadClientCategory = createThunk(SAVE_DOWNLOAD_CLIENT_CATEGORY);
|
||||||
|
export const deleteDownloadClientCategory = createThunk(DELETE_DOWNLOAD_CLIENT_CATEGORY);
|
||||||
|
export const deleteAllDownloadClientCategory = createThunk(DELETE_ALL_DOWNLOAD_CLIENT_CATEGORY);
|
||||||
|
|
||||||
|
export const setDownloadClientCategoryValue = createAction(SET_DOWNLOAD_CLIENT_CATEGORY_VALUE, (payload) => {
|
||||||
|
return {
|
||||||
|
section,
|
||||||
|
...payload
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const setDownloadClientCategoryFieldValue = createAction(SET_DOWNLOAD_CLIENT_CATEGORY_FIELD_VALUE, (payload) => {
|
||||||
|
return {
|
||||||
|
section,
|
||||||
|
...payload
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const clearDownloadClientCategory = createAction(CLEAR_DOWNLOAD_CLIENT_CATEGORIES);
|
||||||
|
|
||||||
|
export const clearDownloadClientCategoryPending = createThunk(CLEAR_DOWNLOAD_CLIENT_CATEGORY_PENDING);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Details
|
||||||
|
|
||||||
|
export default {
|
||||||
|
|
||||||
|
//
|
||||||
|
// State
|
||||||
|
|
||||||
|
defaultState: {
|
||||||
|
isPopulated: false,
|
||||||
|
error: null,
|
||||||
|
isSchemaFetching: false,
|
||||||
|
isSchemaPopulated: false,
|
||||||
|
schemaError: null,
|
||||||
|
schema: [],
|
||||||
|
selectedSchema: {},
|
||||||
|
isSaving: false,
|
||||||
|
saveError: null,
|
||||||
|
items: [],
|
||||||
|
pendingChanges: {}
|
||||||
|
},
|
||||||
|
|
||||||
|
//
|
||||||
|
// Action Handlers
|
||||||
|
|
||||||
|
actionHandlers: {
|
||||||
|
[FETCH_DOWNLOAD_CLIENT_CATEGORIES]: (getState, payload, dispatch) => {
|
||||||
|
let tags = [];
|
||||||
|
if (payload.id) {
|
||||||
|
const cfState = getSectionState(getState(), 'settings.downloadClients', true);
|
||||||
|
const cf = cfState.items[cfState.itemMap[payload.id]];
|
||||||
|
tags = cf.categories.map((tag, i) => {
|
||||||
|
return {
|
||||||
|
id: i + 1,
|
||||||
|
...tag
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(batchActions([
|
||||||
|
update({ section, data: tags }),
|
||||||
|
set({
|
||||||
|
section,
|
||||||
|
isPopulated: true
|
||||||
|
})
|
||||||
|
]));
|
||||||
|
},
|
||||||
|
|
||||||
|
[SAVE_DOWNLOAD_CLIENT_CATEGORY]: (getState, payload, dispatch) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
...otherPayload
|
||||||
|
} = payload;
|
||||||
|
|
||||||
|
const saveData = getProviderState({ id, ...otherPayload }, getState, section, false);
|
||||||
|
|
||||||
|
// we have to set id since not actually posting to server yet
|
||||||
|
if (!saveData.id) {
|
||||||
|
saveData.id = getNextId(getState().settings.downloadClientCategories.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(batchActions([
|
||||||
|
updateItem({ section, ...saveData }),
|
||||||
|
set({
|
||||||
|
section,
|
||||||
|
pendingChanges: {}
|
||||||
|
})
|
||||||
|
]));
|
||||||
|
},
|
||||||
|
|
||||||
|
[DELETE_DOWNLOAD_CLIENT_CATEGORY]: (getState, payload, dispatch) => {
|
||||||
|
const id = payload.id;
|
||||||
|
return dispatch(removeItem({ section, id }));
|
||||||
|
},
|
||||||
|
|
||||||
|
[DELETE_ALL_DOWNLOAD_CLIENT_CATEGORY]: (getState, payload, dispatch) => {
|
||||||
|
return dispatch(set({
|
||||||
|
section,
|
||||||
|
items: []
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
[CLEAR_DOWNLOAD_CLIENT_CATEGORY_PENDING]: (getState, payload, dispatch) => {
|
||||||
|
return dispatch(set({
|
||||||
|
section,
|
||||||
|
pendingChanges: {}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
//
|
||||||
|
// Reducers
|
||||||
|
|
||||||
|
reducers: {
|
||||||
|
[SET_DOWNLOAD_CLIENT_CATEGORY_VALUE]: createSetSettingValueReducer(section),
|
||||||
|
[SET_DOWNLOAD_CLIENT_CATEGORY_FIELD_VALUE]: createSetProviderFieldValueReducer(section),
|
||||||
|
|
||||||
|
[SELECT_DOWNLOAD_CLIENT_CATEGORY_SCHEMA]: (state, { payload }) => {
|
||||||
|
return selectProviderSchema(state, section, payload, (selectedSchema) => {
|
||||||
|
return selectedSchema;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
[CLEAR_DOWNLOAD_CLIENT_CATEGORIES]: createClearReducer(section, {
|
||||||
|
isPopulated: false,
|
||||||
|
error: null,
|
||||||
|
items: []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -9,6 +9,7 @@ import createSetProviderFieldValueReducer from 'Store/Actions/Creators/Reducers/
|
|||||||
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
|
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
|
||||||
import { createThunk } from 'Store/thunks';
|
import { createThunk } from 'Store/thunks';
|
||||||
import selectProviderSchema from 'Utilities/State/selectProviderSchema';
|
import selectProviderSchema from 'Utilities/State/selectProviderSchema';
|
||||||
|
import { set } from '../baseActions';
|
||||||
|
|
||||||
//
|
//
|
||||||
// Variables
|
// Variables
|
||||||
@@ -90,10 +91,34 @@ export default {
|
|||||||
[FETCH_DOWNLOAD_CLIENTS]: createFetchHandler(section, '/downloadclient'),
|
[FETCH_DOWNLOAD_CLIENTS]: createFetchHandler(section, '/downloadclient'),
|
||||||
[FETCH_DOWNLOAD_CLIENT_SCHEMA]: createFetchSchemaHandler(section, '/downloadclient/schema'),
|
[FETCH_DOWNLOAD_CLIENT_SCHEMA]: createFetchSchemaHandler(section, '/downloadclient/schema'),
|
||||||
|
|
||||||
[SAVE_DOWNLOAD_CLIENT]: createSaveProviderHandler(section, '/downloadclient'),
|
[SAVE_DOWNLOAD_CLIENT]: (getState, payload, dispatch) => {
|
||||||
|
// move the format tags in as a pending change
|
||||||
|
const state = getState();
|
||||||
|
const pendingChanges = state.settings.downloadClients.pendingChanges;
|
||||||
|
pendingChanges.categories = state.settings.downloadClientCategories.items;
|
||||||
|
dispatch(set({
|
||||||
|
section,
|
||||||
|
pendingChanges
|
||||||
|
}));
|
||||||
|
|
||||||
|
createSaveProviderHandler(section, '/downloadclient')(getState, payload, dispatch);
|
||||||
|
},
|
||||||
|
|
||||||
[CANCEL_SAVE_DOWNLOAD_CLIENT]: createCancelSaveProviderHandler(section),
|
[CANCEL_SAVE_DOWNLOAD_CLIENT]: createCancelSaveProviderHandler(section),
|
||||||
[DELETE_DOWNLOAD_CLIENT]: createRemoveItemHandler(section, '/downloadclient'),
|
[DELETE_DOWNLOAD_CLIENT]: createRemoveItemHandler(section, '/downloadclient'),
|
||||||
[TEST_DOWNLOAD_CLIENT]: createTestProviderHandler(section, '/downloadclient'),
|
|
||||||
|
[TEST_DOWNLOAD_CLIENT]: (getState, payload, dispatch) => {
|
||||||
|
const state = getState();
|
||||||
|
const pendingChanges = state.settings.downloadClients.pendingChanges;
|
||||||
|
pendingChanges.categories = state.settings.downloadClientCategories.items;
|
||||||
|
dispatch(set({
|
||||||
|
section,
|
||||||
|
pendingChanges
|
||||||
|
}));
|
||||||
|
|
||||||
|
createTestProviderHandler(section, '/downloadclient')(getState, payload, dispatch);
|
||||||
|
},
|
||||||
|
|
||||||
[CANCEL_TEST_DOWNLOAD_CLIENT]: createCancelTestProviderHandler(section),
|
[CANCEL_TEST_DOWNLOAD_CLIENT]: createCancelTestProviderHandler(section),
|
||||||
[TEST_ALL_DOWNLOAD_CLIENTS]: createTestAllProvidersHandler(section, '/downloadclient')
|
[TEST_ALL_DOWNLOAD_CLIENTS]: createTestAllProvidersHandler(section, '/downloadclient')
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
|
|
||||||
import { createThunk } from 'Store/thunks';
|
|
||||||
|
|
||||||
//
|
|
||||||
// Variables
|
|
||||||
|
|
||||||
const section = 'settings.languages';
|
|
||||||
|
|
||||||
//
|
|
||||||
// Actions Types
|
|
||||||
|
|
||||||
export const FETCH_LANGUAGES = 'settings/languages/fetchLanguages';
|
|
||||||
|
|
||||||
//
|
|
||||||
// Action Creators
|
|
||||||
|
|
||||||
export const fetchLanguages = createThunk(FETCH_LANGUAGES);
|
|
||||||
|
|
||||||
//
|
|
||||||
// Details
|
|
||||||
|
|
||||||
export default {
|
|
||||||
|
|
||||||
//
|
|
||||||
// State
|
|
||||||
|
|
||||||
defaultState: {
|
|
||||||
isFetching: false,
|
|
||||||
isPopulated: false,
|
|
||||||
error: null,
|
|
||||||
items: []
|
|
||||||
},
|
|
||||||
|
|
||||||
//
|
|
||||||
// Action Handlers
|
|
||||||
|
|
||||||
actionHandlers: {
|
|
||||||
[FETCH_LANGUAGES]: createFetchHandler(section, '/language')
|
|
||||||
},
|
|
||||||
|
|
||||||
//
|
|
||||||
// Reducers
|
|
||||||
|
|
||||||
reducers: {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
@@ -103,9 +103,6 @@ export default {
|
|||||||
[SELECT_NOTIFICATION_SCHEMA]: (state, { payload }) => {
|
[SELECT_NOTIFICATION_SCHEMA]: (state, { payload }) => {
|
||||||
return selectProviderSchema(state, section, payload, (selectedSchema) => {
|
return selectProviderSchema(state, section, payload, (selectedSchema) => {
|
||||||
selectedSchema.onGrab = selectedSchema.supportsOnGrab;
|
selectedSchema.onGrab = selectedSchema.supportsOnGrab;
|
||||||
selectedSchema.onDownload = selectedSchema.supportsOnDownload;
|
|
||||||
selectedSchema.onUpgrade = selectedSchema.supportsOnUpgrade;
|
|
||||||
selectedSchema.onRename = selectedSchema.supportsOnRename;
|
|
||||||
selectedSchema.onApplicationUpdate = selectedSchema.supportsOnApplicationUpdate;
|
selectedSchema.onApplicationUpdate = selectedSchema.supportsOnApplicationUpdate;
|
||||||
|
|
||||||
return selectedSchema;
|
return selectedSchema;
|
||||||
|
|||||||
@@ -36,31 +36,31 @@ export const defaultState = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'indexer',
|
name: 'indexer',
|
||||||
label: 'Indexer',
|
label: translate('Indexer'),
|
||||||
isSortable: false,
|
isSortable: false,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'query',
|
name: 'query',
|
||||||
label: 'Query',
|
label: translate('Query'),
|
||||||
isSortable: false,
|
isSortable: false,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'parameters',
|
name: 'parameters',
|
||||||
label: 'Parameters',
|
label: translate('Parameters'),
|
||||||
isSortable: false,
|
isSortable: false,
|
||||||
isVisible: false
|
isVisible: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'grabTitle',
|
name: 'grabTitle',
|
||||||
label: 'Grab Title',
|
label: translate('GrabTitle'),
|
||||||
isSortable: false,
|
isSortable: false,
|
||||||
isVisible: false
|
isVisible: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'categories',
|
name: 'categories',
|
||||||
label: 'Categories',
|
label: translate('Categories'),
|
||||||
isSortable: false,
|
isSortable: false,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
@@ -72,13 +72,13 @@ export const defaultState = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'source',
|
name: 'source',
|
||||||
label: 'Source',
|
label: translate('Source'),
|
||||||
isSortable: false,
|
isSortable: false,
|
||||||
isVisible: false
|
isVisible: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'elapsedTime',
|
name: 'elapsedTime',
|
||||||
label: 'Elapsed Time',
|
label: translate('ElapsedTime'),
|
||||||
isSortable: false,
|
isSortable: false,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import * as indexers from './indexerActions';
|
|||||||
import * as indexerIndex from './indexerIndexActions';
|
import * as indexerIndex from './indexerIndexActions';
|
||||||
import * as indexerStats from './indexerStatsActions';
|
import * as indexerStats from './indexerStatsActions';
|
||||||
import * as indexerStatus from './indexerStatusActions';
|
import * as indexerStatus from './indexerStatusActions';
|
||||||
|
import * as localization from './localizationActions';
|
||||||
import * as oAuth from './oAuthActions';
|
import * as oAuth from './oAuthActions';
|
||||||
import * as paths from './pathActions';
|
import * as paths from './pathActions';
|
||||||
import * as providerOptions from './providerOptionActions';
|
import * as providerOptions from './providerOptionActions';
|
||||||
@@ -25,6 +26,7 @@ export default [
|
|||||||
paths,
|
paths,
|
||||||
providerOptions,
|
providerOptions,
|
||||||
releases,
|
releases,
|
||||||
|
localization,
|
||||||
indexers,
|
indexers,
|
||||||
indexerIndex,
|
indexerIndex,
|
||||||
indexerStats,
|
indexerStats,
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export const defaultState = {
|
|||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
name: 'select',
|
name: 'select',
|
||||||
columnLabel: 'Select',
|
columnLabel: translate('Select'),
|
||||||
isSortable: false,
|
isSortable: false,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
isModifiable: false,
|
isModifiable: false,
|
||||||
@@ -51,7 +51,7 @@ export const defaultState = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'sortName',
|
name: 'sortName',
|
||||||
label: 'Indexer Name',
|
label: translate('IndexerName'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
isModifiable: false
|
isModifiable: false
|
||||||
@@ -88,7 +88,7 @@ export const defaultState = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'capabilities',
|
name: 'capabilities',
|
||||||
label: 'Categories',
|
label: translate('Categories'),
|
||||||
isSortable: false,
|
isSortable: false,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
|
|||||||
39
frontend/src/Store/Actions/localizationActions.js
Normal file
39
frontend/src/Store/Actions/localizationActions.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
|
||||||
|
import { createThunk, handleThunks } from 'Store/thunks';
|
||||||
|
import createHandleActions from './Creators/createHandleActions';
|
||||||
|
|
||||||
|
//
|
||||||
|
// Variables
|
||||||
|
|
||||||
|
export const section = 'localization';
|
||||||
|
|
||||||
|
//
|
||||||
|
// State
|
||||||
|
|
||||||
|
export const defaultState = {
|
||||||
|
isFetching: false,
|
||||||
|
isPopulated: false,
|
||||||
|
error: null,
|
||||||
|
items: []
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Actions Types
|
||||||
|
|
||||||
|
export const FETCH_LOCALIZATION_OPTIONS = 'localization/fetchLocalizationOptions';
|
||||||
|
|
||||||
|
//
|
||||||
|
// Action Creators
|
||||||
|
|
||||||
|
export const fetchLocalizationOptions = createThunk(FETCH_LOCALIZATION_OPTIONS);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Action Handlers
|
||||||
|
export const actionHandlers = handleThunks({
|
||||||
|
|
||||||
|
[FETCH_LOCALIZATION_OPTIONS]: createFetchHandler(section, '/localization/options')
|
||||||
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
// Reducers
|
||||||
|
export const reducers = createHandleActions({}, defaultState, section);
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import $ from 'jquery';
|
||||||
import { createAction } from 'redux-actions';
|
import { createAction } from 'redux-actions';
|
||||||
import { batchActions } from 'redux-batched-actions';
|
import { batchActions } from 'redux-batched-actions';
|
||||||
import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props';
|
import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props';
|
||||||
@@ -229,6 +230,7 @@ export const CANCEL_FETCH_RELEASES = 'releases/cancelFetchReleases';
|
|||||||
export const SET_RELEASES_SORT = 'releases/setReleasesSort';
|
export const SET_RELEASES_SORT = 'releases/setReleasesSort';
|
||||||
export const CLEAR_RELEASES = 'releases/clearReleases';
|
export const CLEAR_RELEASES = 'releases/clearReleases';
|
||||||
export const GRAB_RELEASE = 'releases/grabRelease';
|
export const GRAB_RELEASE = 'releases/grabRelease';
|
||||||
|
export const SAVE_RELEASE = 'releases/saveRelease';
|
||||||
export const BULK_GRAB_RELEASES = 'release/bulkGrabReleases';
|
export const BULK_GRAB_RELEASES = 'release/bulkGrabReleases';
|
||||||
export const UPDATE_RELEASE = 'releases/updateRelease';
|
export const UPDATE_RELEASE = 'releases/updateRelease';
|
||||||
export const SET_RELEASES_FILTER = 'releases/setReleasesFilter';
|
export const SET_RELEASES_FILTER = 'releases/setReleasesFilter';
|
||||||
@@ -243,6 +245,7 @@ export const cancelFetchReleases = createThunk(CANCEL_FETCH_RELEASES);
|
|||||||
export const setReleasesSort = createAction(SET_RELEASES_SORT);
|
export const setReleasesSort = createAction(SET_RELEASES_SORT);
|
||||||
export const clearReleases = createAction(CLEAR_RELEASES);
|
export const clearReleases = createAction(CLEAR_RELEASES);
|
||||||
export const grabRelease = createThunk(GRAB_RELEASE);
|
export const grabRelease = createThunk(GRAB_RELEASE);
|
||||||
|
export const saveRelease = createThunk(SAVE_RELEASE);
|
||||||
export const bulkGrabReleases = createThunk(BULK_GRAB_RELEASES);
|
export const bulkGrabReleases = createThunk(BULK_GRAB_RELEASES);
|
||||||
export const updateRelease = createAction(UPDATE_RELEASE);
|
export const updateRelease = createAction(UPDATE_RELEASE);
|
||||||
export const setReleasesFilter = createAction(SET_RELEASES_FILTER);
|
export const setReleasesFilter = createAction(SET_RELEASES_FILTER);
|
||||||
@@ -304,14 +307,38 @@ export const actionHandlers = handleThunks({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
[SAVE_RELEASE]: function(getState, payload, dispatch) {
|
||||||
|
const link = payload.downloadUrl;
|
||||||
|
const file = payload.fileName;
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: link,
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'X-Prowlarr-Client': true
|
||||||
|
},
|
||||||
|
xhrFields: {
|
||||||
|
responseType: 'blob'
|
||||||
|
},
|
||||||
|
success: function(data) {
|
||||||
|
const a = document.createElement('a');
|
||||||
|
const url = window.URL.createObjectURL(data);
|
||||||
|
a.href = url;
|
||||||
|
a.download = file;
|
||||||
|
document.body.append(a);
|
||||||
|
a.click();
|
||||||
|
a.remove();
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
[BULK_GRAB_RELEASES]: function(getState, payload, dispatch) {
|
[BULK_GRAB_RELEASES]: function(getState, payload, dispatch) {
|
||||||
dispatch(set({
|
dispatch(set({
|
||||||
section,
|
section,
|
||||||
isGrabbing: true
|
isGrabbing: true
|
||||||
}));
|
}));
|
||||||
|
|
||||||
console.log(payload);
|
|
||||||
|
|
||||||
const promise = createAjaxRequest({
|
const promise = createAjaxRequest({
|
||||||
url: '/search/bulk',
|
url: '/search/bulk',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
@@ -4,19 +4,19 @@ import createHandleActions from './Creators/createHandleActions';
|
|||||||
import applications from './Settings/applications';
|
import applications from './Settings/applications';
|
||||||
import appProfiles from './Settings/appProfiles';
|
import appProfiles from './Settings/appProfiles';
|
||||||
import development from './Settings/development';
|
import development from './Settings/development';
|
||||||
|
import downloadClientCategories from './Settings/downloadClientCategories';
|
||||||
import downloadClients from './Settings/downloadClients';
|
import downloadClients from './Settings/downloadClients';
|
||||||
import general from './Settings/general';
|
import general from './Settings/general';
|
||||||
import indexerCategories from './Settings/indexerCategories';
|
import indexerCategories from './Settings/indexerCategories';
|
||||||
import indexerProxies from './Settings/indexerProxies';
|
import indexerProxies from './Settings/indexerProxies';
|
||||||
import languages from './Settings/languages';
|
|
||||||
import notifications from './Settings/notifications';
|
import notifications from './Settings/notifications';
|
||||||
import ui from './Settings/ui';
|
import ui from './Settings/ui';
|
||||||
|
|
||||||
|
export * from './Settings/downloadClientCategories';
|
||||||
export * from './Settings/downloadClients';
|
export * from './Settings/downloadClients';
|
||||||
export * from './Settings/general';
|
export * from './Settings/general';
|
||||||
export * from './Settings/indexerCategories';
|
export * from './Settings/indexerCategories';
|
||||||
export * from './Settings/indexerProxies';
|
export * from './Settings/indexerProxies';
|
||||||
export * from './Settings/languages';
|
|
||||||
export * from './Settings/notifications';
|
export * from './Settings/notifications';
|
||||||
export * from './Settings/applications';
|
export * from './Settings/applications';
|
||||||
export * from './Settings/appProfiles';
|
export * from './Settings/appProfiles';
|
||||||
@@ -34,11 +34,11 @@ export const section = 'settings';
|
|||||||
export const defaultState = {
|
export const defaultState = {
|
||||||
advancedSettings: false,
|
advancedSettings: false,
|
||||||
|
|
||||||
|
downloadClientCategories: downloadClientCategories.defaultState,
|
||||||
downloadClients: downloadClients.defaultState,
|
downloadClients: downloadClients.defaultState,
|
||||||
general: general.defaultState,
|
general: general.defaultState,
|
||||||
indexerCategories: indexerCategories.defaultState,
|
indexerCategories: indexerCategories.defaultState,
|
||||||
indexerProxies: indexerProxies.defaultState,
|
indexerProxies: indexerProxies.defaultState,
|
||||||
languages: languages.defaultState,
|
|
||||||
notifications: notifications.defaultState,
|
notifications: notifications.defaultState,
|
||||||
applications: applications.defaultState,
|
applications: applications.defaultState,
|
||||||
appProfiles: appProfiles.defaultState,
|
appProfiles: appProfiles.defaultState,
|
||||||
@@ -64,11 +64,11 @@ export const toggleAdvancedSettings = createAction(TOGGLE_ADVANCED_SETTINGS);
|
|||||||
// Action Handlers
|
// Action Handlers
|
||||||
|
|
||||||
export const actionHandlers = handleThunks({
|
export const actionHandlers = handleThunks({
|
||||||
|
...downloadClientCategories.actionHandlers,
|
||||||
...downloadClients.actionHandlers,
|
...downloadClients.actionHandlers,
|
||||||
...general.actionHandlers,
|
...general.actionHandlers,
|
||||||
...indexerCategories.actionHandlers,
|
...indexerCategories.actionHandlers,
|
||||||
...indexerProxies.actionHandlers,
|
...indexerProxies.actionHandlers,
|
||||||
...languages.actionHandlers,
|
|
||||||
...notifications.actionHandlers,
|
...notifications.actionHandlers,
|
||||||
...applications.actionHandlers,
|
...applications.actionHandlers,
|
||||||
...appProfiles.actionHandlers,
|
...appProfiles.actionHandlers,
|
||||||
@@ -85,11 +85,11 @@ export const reducers = createHandleActions({
|
|||||||
return Object.assign({}, state, { advancedSettings: !state.advancedSettings });
|
return Object.assign({}, state, { advancedSettings: !state.advancedSettings });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
...downloadClientCategories.reducers,
|
||||||
...downloadClients.reducers,
|
...downloadClients.reducers,
|
||||||
...general.reducers,
|
...general.reducers,
|
||||||
...indexerCategories.reducers,
|
...indexerCategories.reducers,
|
||||||
...indexerProxies.reducers,
|
...indexerProxies.reducers,
|
||||||
...languages.reducers,
|
|
||||||
...notifications.reducers,
|
...notifications.reducers,
|
||||||
...applications.reducers,
|
...applications.reducers,
|
||||||
...appProfiles.reducers,
|
...appProfiles.reducers,
|
||||||
|
|||||||
@@ -168,10 +168,11 @@ module.exports = {
|
|||||||
//
|
//
|
||||||
// Popover
|
// Popover
|
||||||
|
|
||||||
popoverTitleBackgroundColor: '#f7f7f7',
|
popoverTitleBackgroundColor: '#424242',
|
||||||
popoverTitleBorderColor: '#ebebeb',
|
popoverTitleBorderColor: '#2a2a2a',
|
||||||
|
popoverBodyBackgroundColor: '#2a2a2a',
|
||||||
popoverShadowColor: 'rgba(0, 0, 0, 0.2)',
|
popoverShadowColor: 'rgba(0, 0, 0, 0.2)',
|
||||||
popoverArrowBorderColor: '#fff',
|
popoverArrowBorderColor: '#2a2a2a',
|
||||||
|
|
||||||
popoverTitleBackgroundInverseColor: '#595959',
|
popoverTitleBackgroundInverseColor: '#595959',
|
||||||
popoverTitleBorderInverseColor: '#707070',
|
popoverTitleBorderInverseColor: '#707070',
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import * as dark from './dark';
|
import * as dark from './dark';
|
||||||
import * as light from './light';
|
import * as light from './light';
|
||||||
|
|
||||||
|
const defaultDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
|
const auto = defaultDark ? { ...dark } : { ...light };
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
auto,
|
||||||
light,
|
light,
|
||||||
dark
|
dark
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -170,6 +170,7 @@ module.exports = {
|
|||||||
|
|
||||||
popoverTitleBackgroundColor: '#f7f7f7',
|
popoverTitleBackgroundColor: '#f7f7f7',
|
||||||
popoverTitleBorderColor: '#ebebeb',
|
popoverTitleBorderColor: '#ebebeb',
|
||||||
|
popoverBodyBackgroundColor: '#e9e9e9',
|
||||||
popoverShadowColor: 'rgba(0, 0, 0, 0.2)',
|
popoverShadowColor: 'rgba(0, 0, 0, 0.2)',
|
||||||
popoverArrowBorderColor: '#fff',
|
popoverArrowBorderColor: '#fff',
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const columns = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'size',
|
name: 'size',
|
||||||
label: 'Size',
|
label: translate('Size'),
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class Donations extends Component {
|
|||||||
return (
|
return (
|
||||||
<FieldSet legend={translate('Donations')}>
|
<FieldSet legend={translate('Donations')}>
|
||||||
<div className={styles.logoContainer} title="Radarr">
|
<div className={styles.logoContainer} title="Radarr">
|
||||||
<Link to="https://opencollective.com/radarr">
|
<Link to="https://radarr.video/donate">
|
||||||
<img
|
<img
|
||||||
className={styles.logo}
|
className={styles.logo}
|
||||||
src={`${window.Prowlarr.urlBase}/Content/Images/Icons/logo-radarr.png`}
|
src={`${window.Prowlarr.urlBase}/Content/Images/Icons/logo-radarr.png`}
|
||||||
@@ -21,7 +21,7 @@ class Donations extends Component {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.logoContainer} title="Lidarr">
|
<div className={styles.logoContainer} title="Lidarr">
|
||||||
<Link to="https://opencollective.com/lidarr">
|
<Link to="https://lidarr.audio/donate">
|
||||||
<img
|
<img
|
||||||
className={styles.logo}
|
className={styles.logo}
|
||||||
src={`${window.Prowlarr.urlBase}/Content/Images/Icons/logo-lidarr.png`}
|
src={`${window.Prowlarr.urlBase}/Content/Images/Icons/logo-lidarr.png`}
|
||||||
@@ -29,7 +29,7 @@ class Donations extends Component {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.logoContainer} title="Readarr">
|
<div className={styles.logoContainer} title="Readarr">
|
||||||
<Link to="https://opencollective.com/readarr">
|
<Link to="https://readarr.com/donate">
|
||||||
<img
|
<img
|
||||||
className={styles.logo}
|
className={styles.logo}
|
||||||
src={`${window.Prowlarr.urlBase}/Content/Images/Icons/logo-readarr.png`}
|
src={`${window.Prowlarr.urlBase}/Content/Images/Icons/logo-readarr.png`}
|
||||||
@@ -37,7 +37,7 @@ class Donations extends Component {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.logoContainer} title="Prowlarr">
|
<div className={styles.logoContainer} title="Prowlarr">
|
||||||
<Link to="https://opencollective.com/prowlarr">
|
<Link to="https://prowlarr.com/donate">
|
||||||
<img
|
<img
|
||||||
className={styles.logo}
|
className={styles.logo}
|
||||||
src={`${window.Prowlarr.urlBase}/Content/Images/Icons/logo-prowlarr.png`}
|
src={`${window.Prowlarr.urlBase}/Content/Images/Icons/logo-prowlarr.png`}
|
||||||
|
|||||||
@@ -15,27 +15,27 @@ const columns = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'commandName',
|
name: 'commandName',
|
||||||
label: 'Name',
|
label: translate('Name'),
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'queued',
|
name: 'queued',
|
||||||
label: 'Queued',
|
label: translate('Queued'),
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'started',
|
name: 'started',
|
||||||
label: 'Started',
|
label: translate('Started'),
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'ended',
|
name: 'ended',
|
||||||
label: 'Ended',
|
label: translate('Ended'),
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'duration',
|
name: 'duration',
|
||||||
label: 'Duration',
|
label: translate('Duration'),
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,27 +10,27 @@ import ScheduledTaskRowConnector from './ScheduledTaskRowConnector';
|
|||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
name: 'name',
|
name: 'name',
|
||||||
label: 'Name',
|
label: translate('Name'),
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'interval',
|
name: 'interval',
|
||||||
label: 'Interval',
|
label: translate('Interval'),
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'lastExecution',
|
name: 'lastExecution',
|
||||||
label: 'Last Execution',
|
label: translate('LastExecution'),
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'lastDuration',
|
name: 'lastDuration',
|
||||||
label: 'Last Duration',
|
label: translate('LastDuration'),
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'nextExecution',
|
name: 'nextExecution',
|
||||||
label: 'Next Execution',
|
label: translate('NextExecution'),
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ class Updates extends Component {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className={styles.message}>
|
<div className={styles.message}>
|
||||||
The latest version of Prowlarr is already installed
|
{translate('TheLatestVersionIsAlreadyInstalled', ['Prowlarr'])}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import filesize from 'filesize';
|
import { filesize } from 'filesize';
|
||||||
|
|
||||||
function formatBytes(input) {
|
function formatBytes(input) {
|
||||||
const size = Number(input);
|
const size = Number(input);
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ function addApiKey(ajaxOptions) {
|
|||||||
ajaxOptions.headers['X-Api-Key'] = window.Prowlarr.apiKey;
|
ajaxOptions.headers['X-Api-Key'] = window.Prowlarr.apiKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addUIHeader(ajaxOptions) {
|
||||||
|
ajaxOptions.headers = ajaxOptions.headers || {};
|
||||||
|
ajaxOptions.headers['X-Prowlarr-Client'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
function addContentType(ajaxOptions) {
|
function addContentType(ajaxOptions) {
|
||||||
if (
|
if (
|
||||||
ajaxOptions.contentType == null &&
|
ajaxOptions.contentType == null &&
|
||||||
@@ -42,6 +47,7 @@ export default function createAjaxRequest(originalAjaxOptions) {
|
|||||||
if (isRelative(ajaxOptions)) {
|
if (isRelative(ajaxOptions)) {
|
||||||
addRootUrl(ajaxOptions);
|
addRootUrl(ajaxOptions);
|
||||||
addApiKey(ajaxOptions);
|
addApiKey(ajaxOptions);
|
||||||
|
addUIHeader(ajaxOptions);
|
||||||
addContentType(ajaxOptions);
|
addContentType(ajaxOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<!-- Windows Phone -->
|
<!-- Windows Phone -->
|
||||||
<meta name="msapplication-navbutton-color" content="#3a3f51" />
|
<meta name="msapplication-navbutton-color" content="#3a3f51" />
|
||||||
|
|
||||||
<meta name="description" content="Prowlarr (Preview)" />
|
<meta name="description" content="Prowlarr" />
|
||||||
|
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-icon"
|
rel="apple-touch-icon"
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
<link rel="stylesheet" type="text/css" href="/Content/Fonts/fonts.css">
|
<link rel="stylesheet" type="text/css" href="/Content/Fonts/fonts.css">
|
||||||
<!-- webpack bundles head -->
|
<!-- webpack bundles head -->
|
||||||
|
|
||||||
<title>Prowlarr (Preview)</title>
|
<title>Prowlarr</title>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
The super basic styling for .root will live here,
|
The super basic styling for .root will live here,
|
||||||
|
|||||||
116
package.json
116
package.json
@@ -11,7 +11,8 @@
|
|||||||
"lint": "esprint check",
|
"lint": "esprint check",
|
||||||
"lint-fix": "esprint check --fix",
|
"lint-fix": "esprint check --fix",
|
||||||
"stylelint-linux": "stylelint $(find frontend -name '*.css') --config frontend/.stylelintrc",
|
"stylelint-linux": "stylelint $(find frontend -name '*.css') --config frontend/.stylelintrc",
|
||||||
"stylelint-windows": "stylelint frontend/**/*.css --config frontend/.stylelintrc"
|
"stylelint-windows": "stylelint frontend/**/*.css --config frontend/.stylelintrc",
|
||||||
|
"check-modules": "are-you-es5 check . -r"
|
||||||
},
|
},
|
||||||
"repository": "https://github.com/Prowlarr/Prowlarr",
|
"repository": "https://github.com/Prowlarr/Prowlarr",
|
||||||
"author": "Team Prowlarr",
|
"author": "Team Prowlarr",
|
||||||
@@ -25,107 +26,110 @@
|
|||||||
"not chrome < 60"
|
"not chrome < 60"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "6.1.1",
|
"@fortawesome/fontawesome-free": "6.2.1",
|
||||||
"@fortawesome/fontawesome-svg-core": "6.1.1",
|
"@fortawesome/fontawesome-svg-core": "6.2.1",
|
||||||
"@fortawesome/free-regular-svg-icons": "6.1.1",
|
"@fortawesome/free-regular-svg-icons": "6.2.1",
|
||||||
"@fortawesome/free-solid-svg-icons": "6.1.1",
|
"@fortawesome/free-solid-svg-icons": "6.2.1",
|
||||||
"@fortawesome/react-fontawesome": "0.1.18",
|
"@fortawesome/react-fontawesome": "0.2.0",
|
||||||
"@microsoft/signalr": "6.0.3",
|
"@microsoft/signalr": "6.0.13",
|
||||||
"@sentry/browser": "6.19.2",
|
"@sentry/browser": "7.28.0",
|
||||||
"@sentry/integrations": "6.19.2",
|
"@sentry/integrations": "7.28.0",
|
||||||
"chart.js": "3.7.1",
|
"chart.js": "4.1.1",
|
||||||
"classnames": "2.3.1",
|
"classnames": "2.3.2",
|
||||||
"clipboard": "2.0.10",
|
"clipboard": "2.0.11",
|
||||||
"connected-react-router": "6.9.1",
|
"connected-react-router": "6.9.3",
|
||||||
"element-class": "0.2.2",
|
"element-class": "0.2.2",
|
||||||
"filesize": "6.3.0",
|
"filesize": "10.0.6",
|
||||||
"history": "4.10.1",
|
"history": "4.10.1",
|
||||||
"https-browserify": "1.0.0",
|
"https-browserify": "1.0.0",
|
||||||
"jdu": "1.0.0",
|
"jdu": "1.0.0",
|
||||||
"jquery": "3.6.0",
|
"jquery": "3.6.2",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"mobile-detect": "1.4.5",
|
"mobile-detect": "1.4.5",
|
||||||
"moment": "2.29.2",
|
"moment": "2.29.4",
|
||||||
"mousetrap": "1.6.5",
|
"mousetrap": "1.6.5",
|
||||||
"normalize.css": "8.0.1",
|
"normalize.css": "8.0.1",
|
||||||
"prop-types": "15.8.1",
|
"prop-types": "15.8.1",
|
||||||
"qs": "6.10.3",
|
"qs": "6.11.0",
|
||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
"react-addons-shallow-compare": "15.6.3",
|
"react-addons-shallow-compare": "15.6.3",
|
||||||
"react-async-script": "1.2.0",
|
"react-async-script": "1.2.0",
|
||||||
"react-autosuggest": "10.1.0",
|
"react-autosuggest": "10.1.0",
|
||||||
"react-custom-scrollbars-2": "4.4.0",
|
"react-custom-scrollbars-2": "4.5.0",
|
||||||
"react-dnd": "14.0.4",
|
"react-dnd": "14.0.4",
|
||||||
"react-dnd-html5-backend": "14.0.2",
|
"react-dnd-html5-backend": "14.0.2",
|
||||||
"react-dnd-multi-backend": "6.0.2",
|
"react-dnd-multi-backend": "6.0.2",
|
||||||
"react-dnd-touch-backend": "14.1.1",
|
"react-dnd-touch-backend": "14.1.1",
|
||||||
"react-document-title": "2.0.3",
|
"react-document-title": "2.0.3",
|
||||||
"react-dom": "17.0.2",
|
"react-dom": "17.0.2",
|
||||||
"react-focus-lock": "2.5.0",
|
"react-focus-lock": "2.9.2",
|
||||||
"react-google-recaptcha": "2.1.0",
|
"react-google-recaptcha": "2.1.0",
|
||||||
"react-lazyload": "3.2.0",
|
"react-lazyload": "3.2.0",
|
||||||
"react-measure": "1.4.7",
|
"react-measure": "1.4.7",
|
||||||
"react-popper": "1.3.7",
|
"react-popper": "1.3.7",
|
||||||
"react-redux": "7.2.4",
|
"react-redux": "8.0.5",
|
||||||
"react-router": "5.2.0",
|
"react-router": "5.2.0",
|
||||||
"react-router-dom": "5.2.0",
|
"react-router-dom": "5.2.0",
|
||||||
|
"react-text-truncate": "0.19.0",
|
||||||
"react-virtualized": "9.21.1",
|
"react-virtualized": "9.21.1",
|
||||||
"redux": "4.1.0",
|
"redux": "4.2.0",
|
||||||
"redux-actions": "2.6.5",
|
"redux-actions": "2.6.5",
|
||||||
"redux-batched-actions": "0.5.0",
|
"redux-batched-actions": "0.5.0",
|
||||||
"redux-localstorage": "0.4.1",
|
"redux-localstorage": "0.4.1",
|
||||||
"redux-thunk": "2.3.0",
|
"redux-thunk": "2.4.2",
|
||||||
"reselect": "4.0.0"
|
"reselect": "4.1.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.17.8",
|
"@babel/core": "7.20.5",
|
||||||
"@babel/eslint-parser": "7.17.0",
|
"@babel/eslint-parser": "7.19.1",
|
||||||
"@babel/plugin-proposal-class-properties": "7.16.7",
|
"@babel/plugin-proposal-class-properties": "7.18.6",
|
||||||
"@babel/plugin-proposal-decorators": "7.17.8",
|
"@babel/plugin-proposal-decorators": "7.20.5",
|
||||||
"@babel/plugin-proposal-export-default-from": "7.16.7",
|
"@babel/plugin-proposal-export-default-from": "7.18.10",
|
||||||
"@babel/plugin-proposal-export-namespace-from": "7.16.7",
|
"@babel/plugin-proposal-export-namespace-from": "7.18.9",
|
||||||
"@babel/plugin-proposal-function-sent": "7.16.7",
|
"@babel/plugin-proposal-function-sent": "7.18.6",
|
||||||
"@babel/plugin-proposal-nullish-coalescing-operator": "7.16.7",
|
"@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6",
|
||||||
"@babel/plugin-proposal-numeric-separator": "7.16.7",
|
"@babel/plugin-proposal-numeric-separator": "7.18.6",
|
||||||
"@babel/plugin-proposal-optional-chaining": "7.16.7",
|
"@babel/plugin-proposal-optional-chaining": "7.18.9",
|
||||||
"@babel/plugin-proposal-throw-expressions": "7.16.7",
|
"@babel/plugin-proposal-throw-expressions": "7.18.6",
|
||||||
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
||||||
"@babel/preset-env": "7.16.11",
|
"@babel/preset-env": "7.20.2",
|
||||||
"@babel/preset-react": "7.16.7",
|
"@babel/preset-react": "7.18.6",
|
||||||
"autoprefixer": "10.4.4",
|
"are-you-es5": "2.1.2",
|
||||||
"babel-loader": "8.2.4",
|
"autoprefixer": "10.4.13",
|
||||||
|
"babel-loader": "9.1.0",
|
||||||
"babel-plugin-inline-classnames": "2.0.1",
|
"babel-plugin-inline-classnames": "2.0.1",
|
||||||
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
|
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
|
||||||
"core-js": "3.21.1",
|
"core-js": "3.26.1",
|
||||||
"css-loader": "6.7.1",
|
"css-loader": "6.7.3",
|
||||||
"eslint": "8.11.0",
|
"eslint": "8.30.0",
|
||||||
"eslint-plugin-filenames": "1.3.2",
|
"eslint-plugin-filenames": "1.3.2",
|
||||||
"eslint-plugin-import": "2.25.4",
|
"eslint-plugin-import": "2.26.0",
|
||||||
"eslint-plugin-react": "7.29.4",
|
"eslint-plugin-react": "7.31.11",
|
||||||
"eslint-plugin-simple-import-sort": "7.0.0",
|
"eslint-plugin-react-hooks": "4.6.0",
|
||||||
"esprint": "3.3.0",
|
"eslint-plugin-simple-import-sort": "8.0.0",
|
||||||
|
"esprint": "3.6.0",
|
||||||
"file-loader": "6.2.0",
|
"file-loader": "6.2.0",
|
||||||
"filemanager-webpack-plugin": "6.1.7",
|
"filemanager-webpack-plugin": "8.0.0",
|
||||||
"html-webpack-plugin": "5.5.0",
|
"html-webpack-plugin": "5.5.0",
|
||||||
"loader-utils": "^3.0.0",
|
"loader-utils": "^3.2.1",
|
||||||
"mini-css-extract-plugin": "2.6.0",
|
"mini-css-extract-plugin": "2.7.2",
|
||||||
"postcss": "8.4.12",
|
"postcss": "8.4.20",
|
||||||
"postcss-color-function": "4.1.0",
|
"postcss-color-function": "4.1.0",
|
||||||
"postcss-loader": "6.2.1",
|
"postcss-loader": "7.0.2",
|
||||||
"postcss-mixins": "9.0.2",
|
"postcss-mixins": "9.0.4",
|
||||||
"postcss-nested": "5.0.6",
|
"postcss-nested": "6.0.0",
|
||||||
"postcss-simple-vars": "6.0.3",
|
"postcss-simple-vars": "7.0.1",
|
||||||
"postcss-url": "10.1.3",
|
"postcss-url": "10.1.3",
|
||||||
"require-nocache": "1.0.0",
|
"require-nocache": "1.0.0",
|
||||||
"rimraf": "3.0.2",
|
"rimraf": "3.0.2",
|
||||||
"run-sequence": "2.2.1",
|
"run-sequence": "2.2.1",
|
||||||
"streamqueue": "1.1.2",
|
"streamqueue": "1.1.2",
|
||||||
"style-loader": "3.3.1",
|
"style-loader": "3.3.1",
|
||||||
"stylelint": "14.6.0",
|
"stylelint": "14.16.0",
|
||||||
"stylelint-order": "5.0.0",
|
"stylelint-order": "5.0.0",
|
||||||
"url-loader": "4.1.1",
|
"url-loader": "4.1.1",
|
||||||
"webpack": "5.70.0",
|
"webpack": "5.75.0",
|
||||||
"webpack-cli": "4.9.2",
|
"webpack-cli": "5.0.1",
|
||||||
"webpack-livereload-plugin": "3.0.2"
|
"webpack-livereload-plugin": "3.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3
src/.globalconfig
Normal file
3
src/.globalconfig
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
is_global = true
|
||||||
|
|
||||||
|
dotnet_diagnostic.CA1014.severity = none
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<!-- Common to all Prowlarr Projects -->
|
<!-- Common to all Prowlarr Projects -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<AnalysisLevel>6.0-all</AnalysisLevel>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
|
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
@@ -94,7 +95,7 @@
|
|||||||
|
|
||||||
<!-- Standard testing packages -->
|
<!-- Standard testing packages -->
|
||||||
<ItemGroup Condition="'$(TestProject)'=='true'">
|
<ItemGroup Condition="'$(TestProject)'=='true'">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||||
<PackageReference Include="NunitXml.TestLogger" Version="3.0.117" />
|
<PackageReference Include="NunitXml.TestLogger" Version="3.0.117" />
|
||||||
|
|||||||
@@ -44,9 +44,9 @@ namespace NzbDrone.Automation.Test
|
|||||||
|
|
||||||
driver.Manage().Window.Size = new System.Drawing.Size(1920, 1080);
|
driver.Manage().Window.Size = new System.Drawing.Size(1920, 1080);
|
||||||
|
|
||||||
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger());
|
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger(), null);
|
||||||
_runner.KillAll();
|
_runner.KillAll();
|
||||||
_runner.Start();
|
_runner.Start(true);
|
||||||
|
|
||||||
driver.Url = "http://localhost:9696";
|
driver.Url = "http://localhost:9696";
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,16 @@ namespace NzbDrone.Common.Test.DiskTests
|
|||||||
public abstract class DiskProviderFixtureBase<TSubject> : TestBase<TSubject>
|
public abstract class DiskProviderFixtureBase<TSubject> : TestBase<TSubject>
|
||||||
where TSubject : class, IDiskProvider
|
where TSubject : class, IDiskProvider
|
||||||
{
|
{
|
||||||
|
[Test]
|
||||||
|
public void writealltext_should_truncate_existing()
|
||||||
|
{
|
||||||
|
var file = GetTempFilePath();
|
||||||
|
|
||||||
|
Subject.WriteAllText(file, "A pretty long string");
|
||||||
|
Subject.WriteAllText(file, "A short string");
|
||||||
|
Subject.ReadAllText(file).Should().Be("A short string");
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
[Retry(5)]
|
[Retry(5)]
|
||||||
public void directory_exist_should_be_able_to_find_existing_folder()
|
public void directory_exist_should_be_able_to_find_existing_folder()
|
||||||
|
|||||||
@@ -402,6 +402,40 @@ namespace NzbDrone.Common.Test.DiskTests
|
|||||||
VerifyCopyFolder(source.FullName, destination.FullName);
|
VerifyCopyFolder(source.FullName, destination.FullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CopyFolder_should_detect_caseinsensitive_parents()
|
||||||
|
{
|
||||||
|
WindowsOnly();
|
||||||
|
|
||||||
|
WithRealDiskProvider();
|
||||||
|
|
||||||
|
var original = GetFilledTempFolder();
|
||||||
|
var root = new DirectoryInfo(GetTempFilePath());
|
||||||
|
var source = new DirectoryInfo(root.FullName + "A/series");
|
||||||
|
var destination = new DirectoryInfo(root.FullName + "a/series");
|
||||||
|
|
||||||
|
Subject.TransferFolder(original.FullName, source.FullName, TransferMode.Copy);
|
||||||
|
|
||||||
|
Assert.Throws<IOException>(() => Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Copy));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CopyFolder_should_detect_caseinsensitive_folder()
|
||||||
|
{
|
||||||
|
WindowsOnly();
|
||||||
|
|
||||||
|
WithRealDiskProvider();
|
||||||
|
|
||||||
|
var original = GetFilledTempFolder();
|
||||||
|
var root = new DirectoryInfo(GetTempFilePath());
|
||||||
|
var source = new DirectoryInfo(root.FullName + "A/series");
|
||||||
|
var destination = new DirectoryInfo(root.FullName + "A/Series");
|
||||||
|
|
||||||
|
Subject.TransferFolder(original.FullName, source.FullName, TransferMode.Copy);
|
||||||
|
|
||||||
|
Assert.Throws<IOException>(() => Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Copy));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void CopyFolder_should_ignore_nfs_temp_file()
|
public void CopyFolder_should_ignore_nfs_temp_file()
|
||||||
{
|
{
|
||||||
@@ -451,6 +485,42 @@ namespace NzbDrone.Common.Test.DiskTests
|
|||||||
VerifyMoveFolder(original.FullName, source.FullName, destination.FullName);
|
VerifyMoveFolder(original.FullName, source.FullName, destination.FullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void MoveFolder_should_detect_caseinsensitive_parents()
|
||||||
|
{
|
||||||
|
WindowsOnly();
|
||||||
|
|
||||||
|
WithRealDiskProvider();
|
||||||
|
|
||||||
|
var original = GetFilledTempFolder();
|
||||||
|
var root = new DirectoryInfo(GetTempFilePath());
|
||||||
|
var source = new DirectoryInfo(root.FullName + "A/series");
|
||||||
|
var destination = new DirectoryInfo(root.FullName + "a/series");
|
||||||
|
|
||||||
|
Subject.TransferFolder(original.FullName, source.FullName, TransferMode.Copy);
|
||||||
|
|
||||||
|
Assert.Throws<IOException>(() => Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Move));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void MoveFolder_should_rename_caseinsensitive_folder()
|
||||||
|
{
|
||||||
|
WindowsOnly();
|
||||||
|
|
||||||
|
WithRealDiskProvider();
|
||||||
|
|
||||||
|
var original = GetFilledTempFolder();
|
||||||
|
var root = new DirectoryInfo(GetTempFilePath());
|
||||||
|
var source = new DirectoryInfo(root.FullName + "A/series");
|
||||||
|
var destination = new DirectoryInfo(root.FullName + "A/Series");
|
||||||
|
|
||||||
|
Subject.TransferFolder(original.FullName, source.FullName, TransferMode.Copy);
|
||||||
|
|
||||||
|
Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Move);
|
||||||
|
|
||||||
|
source.FullName.GetActualCasing().Should().Be(destination.FullName);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_throw_if_destination_is_readonly()
|
public void should_throw_if_destination_is_readonly()
|
||||||
{
|
{
|
||||||
@@ -553,6 +623,23 @@ namespace NzbDrone.Common.Test.DiskTests
|
|||||||
VerifyCopyFolder(original.FullName, destination.FullName);
|
VerifyCopyFolder(original.FullName, destination.FullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void MirrorFolder_should_handle_trailing_slash()
|
||||||
|
{
|
||||||
|
WithRealDiskProvider();
|
||||||
|
|
||||||
|
var original = GetFilledTempFolder();
|
||||||
|
var source = new DirectoryInfo(GetTempFilePath());
|
||||||
|
var destination = new DirectoryInfo(GetTempFilePath());
|
||||||
|
|
||||||
|
Subject.TransferFolder(original.FullName, source.FullName, TransferMode.Copy);
|
||||||
|
|
||||||
|
var count = Subject.MirrorFolder(source.FullName + Path.DirectorySeparatorChar, destination.FullName);
|
||||||
|
|
||||||
|
count.Should().Equals(3);
|
||||||
|
VerifyCopyFolder(original.FullName, destination.FullName);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TransferFolder_should_use_movefolder_if_on_same_mount()
|
public void TransferFolder_should_use_movefolder_if_on_same_mount()
|
||||||
{
|
{
|
||||||
@@ -752,6 +839,10 @@ namespace NzbDrone.Common.Test.DiskTests
|
|||||||
.Setup(v => v.CreateFolder(It.IsAny<string>()))
|
.Setup(v => v.CreateFolder(It.IsAny<string>()))
|
||||||
.Callback<string>(v => Directory.CreateDirectory(v));
|
.Callback<string>(v => Directory.CreateDirectory(v));
|
||||||
|
|
||||||
|
Mocker.GetMock<IDiskProvider>()
|
||||||
|
.Setup(v => v.MoveFolder(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>()))
|
||||||
|
.Callback<string, string, bool>((v, r, b) => Directory.Move(v, r));
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
.Setup(v => v.DeleteFolder(It.IsAny<string>(), It.IsAny<bool>()))
|
.Setup(v => v.DeleteFolder(It.IsAny<string>(), It.IsAny<bool>()))
|
||||||
.Callback<string, bool>((v, r) => Directory.Delete(v, r));
|
.Callback<string, bool>((v, r) => Directory.Delete(v, r));
|
||||||
|
|||||||
@@ -28,14 +28,6 @@ namespace NzbDrone.Common.Test.DiskTests
|
|||||||
Subject.GetAvailableSpace(Path.Combine(path, "invalidFolder")).Should().NotBe(0);
|
Subject.GetAvailableSpace(Path.Combine(path, "invalidFolder")).Should().NotBe(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Ignore("Docker")]
|
|
||||||
[Test]
|
|
||||||
public void should_be_able_to_check_space_on_ramdrive()
|
|
||||||
{
|
|
||||||
PosixOnly();
|
|
||||||
Subject.GetAvailableSpace("/run/").Should().NotBe(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Ignore("Docker")]
|
[Ignore("Docker")]
|
||||||
[Test]
|
[Test]
|
||||||
public void should_return_free_disk_space()
|
public void should_return_free_disk_space()
|
||||||
@@ -44,35 +36,6 @@ namespace NzbDrone.Common.Test.DiskTests
|
|||||||
result.Should().BeGreaterThan(0);
|
result.Should().BeGreaterThan(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_be_able_to_get_space_on_unc()
|
|
||||||
{
|
|
||||||
WindowsOnly();
|
|
||||||
|
|
||||||
var result = Subject.GetAvailableSpace(@"\\localhost\c$\Windows");
|
|
||||||
result.Should().BeGreaterThan(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_throw_if_drive_doesnt_exist()
|
|
||||||
{
|
|
||||||
WindowsOnly();
|
|
||||||
|
|
||||||
// Find a drive that doesn't exist.
|
|
||||||
for (char driveletter = 'Z'; driveletter > 'D'; driveletter--)
|
|
||||||
{
|
|
||||||
if (new DriveInfo(driveletter.ToString()).IsReady)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.Throws<DirectoryNotFoundException>(() => Subject.GetAvailableSpace(driveletter + @":\NOT_A_REAL_PATH\DOES_NOT_EXIST".AsOsAgnostic()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.Inconclusive("No drive available for testing.");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Ignore("Docker")]
|
[Ignore("Docker")]
|
||||||
[Test]
|
[Test]
|
||||||
public void should_be_able_to_get_space_on_folder_that_doesnt_exist()
|
public void should_be_able_to_get_space_on_folder_that_doesnt_exist()
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using FluentAssertions;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
|
||||||
|
namespace NzbDrone.Common.Test.ExtensionTests.StringExtensionTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class IsValidIPAddressFixture
|
||||||
|
{
|
||||||
|
[TestCase("192.168.0.1")]
|
||||||
|
[TestCase("::1")]
|
||||||
|
[TestCase("2001:db8:4006:812::200e")]
|
||||||
|
public void should_validate_ip_address(string input)
|
||||||
|
{
|
||||||
|
input.IsValidIpAddress().Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase("sonarr.tv")]
|
||||||
|
public void should_not_parse_non_ip_address(string input)
|
||||||
|
{
|
||||||
|
input.IsValidIpAddress().Should().BeFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user