Compare commits
774 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
1019fecc9e | ||
![]() |
1855f93c1c | ||
![]() |
52e6a44aa4 | ||
![]() |
0b77808af6 | ||
![]() |
9233ed5c53 | ||
![]() |
ee68c0f622 | ||
![]() |
366823cee9 | ||
![]() |
40e1eb9a49 | ||
![]() |
1af419a860 | ||
![]() |
397f18c435 | ||
![]() |
2e5dd05a6c | ||
![]() |
a9fb8ddfb8 | ||
![]() |
562c726787 | ||
![]() |
5f82c1dc17 | ||
![]() |
222800bdb6 | ||
![]() |
5dd3636571 | ||
![]() |
2296a9fbb3 | ||
![]() |
63b5a7c036 | ||
![]() |
b74ca2670e | ||
![]() |
393f4e0e58 | ||
![]() |
3a9ca29e99 | ||
![]() |
32995fef24 | ||
![]() |
a73c99fc64 | ||
![]() |
a5834470ba | ||
![]() |
da3bc127dc | ||
![]() |
0dddc4d58f | ||
![]() |
a4d5d9157b | ||
![]() |
c70d5d4398 | ||
![]() |
7c08b07ef5 | ||
![]() |
e426b5dd35 | ||
![]() |
2fdf619582 | ||
![]() |
d9eed14b7a | ||
![]() |
8230ffb8a4 | ||
![]() |
7098930b19 | ||
![]() |
56244245a4 | ||
![]() |
dd2f12fa8e | ||
![]() |
9598247a0d | ||
![]() |
230ee90b1c | ||
![]() |
e705bedc91 | ||
![]() |
b5ebe7590c | ||
![]() |
6d0831ceaa | ||
![]() |
19e00ee2f2 | ||
![]() |
80723d224e | ||
![]() |
0c82bb023a | ||
![]() |
0a86f24095 | ||
![]() |
b41249cfa8 | ||
![]() |
6659802689 | ||
![]() |
964c503223 | ||
![]() |
15568bf20a | ||
![]() |
d10cd324bb | ||
![]() |
2a22ab8c33 | ||
![]() |
ca736cdae2 | ||
![]() |
d589c57dd2 | ||
![]() |
9b0caf2a47 | ||
![]() |
f8b00bbd67 | ||
![]() |
91a8c0e7a0 | ||
![]() |
2089172384 | ||
![]() |
1ab87e5334 | ||
![]() |
b5e6861032 | ||
![]() |
189930918a | ||
![]() |
ff1bd0a4b8 | ||
![]() |
e544d0dd07 | ||
![]() |
3e0b240154 | ||
![]() |
199119cafb | ||
![]() |
89ab665923 | ||
![]() |
dfb60de6d2 | ||
![]() |
da8d41868d | ||
![]() |
e9db43ebf6 | ||
![]() |
c0453eae47 | ||
![]() |
a8863a5aeb | ||
![]() |
a8adad7dbb | ||
![]() |
4cfa5ac10b | ||
![]() |
55090ddeaa | ||
![]() |
14346b0e69 | ||
![]() |
ac24acf9ce | ||
![]() |
4cde62fde9 | ||
![]() |
7489bc8d98 | ||
![]() |
cde9287d85 | ||
![]() |
558023e18e | ||
![]() |
8157ee7811 | ||
![]() |
d746d2913f | ||
![]() |
0136fc6436 | ||
![]() |
7ce280cb92 | ||
![]() |
0209fa87aa | ||
![]() |
62cc2f769f | ||
![]() |
a49d44c880 | ||
![]() |
dab288380a | ||
![]() |
2ac5c35065 | ||
![]() |
ec9e2fe0f0 | ||
![]() |
ecbe79b5b9 | ||
![]() |
c4ac03738b | ||
![]() |
352dbd9bc8 | ||
![]() |
393b395df0 | ||
![]() |
1a96da04a1 | ||
![]() |
615b98955a | ||
![]() |
11b2b67f9d | ||
![]() |
0e44255e6a | ||
![]() |
a649d2ec12 | ||
![]() |
8a953e789c | ||
![]() |
317d32eb0c | ||
![]() |
b2ddedc0ae | ||
![]() |
620d2cf730 | ||
![]() |
2578592cc7 | ||
![]() |
44c643d7da | ||
![]() |
39d6edd581 | ||
![]() |
5a14b5bc35 | ||
![]() |
82d9719eee | ||
![]() |
401b75a76b | ||
![]() |
ab75628cf7 | ||
![]() |
57d08e231c | ||
![]() |
f6b800c372 | ||
![]() |
26773ac67f | ||
![]() |
5b63cb38ae | ||
![]() |
30b655a32a | ||
![]() |
f3fa9601c0 | ||
![]() |
034ad05383 | ||
![]() |
7b936fd664 | ||
![]() |
a120f52e0d | ||
![]() |
58f2d22ef4 | ||
![]() |
962777284a | ||
![]() |
d4c8066209 | ||
![]() |
9a9db88efd | ||
![]() |
f5ad9cfe14 | ||
![]() |
d268a7aa23 | ||
![]() |
1896239bd3 | ||
![]() |
07c71750d5 | ||
![]() |
d5171109f5 | ||
![]() |
36aa795c52 | ||
![]() |
7914f56ec3 | ||
![]() |
a39c6c1047 | ||
![]() |
270e07341a | ||
![]() |
da7c66f414 | ||
![]() |
d97b87d9cc | ||
![]() |
bee3361ace | ||
![]() |
276ea4dd98 | ||
![]() |
dd45b47032 | ||
![]() |
1fd4ec3ca3 | ||
![]() |
1e807af2d4 | ||
![]() |
88a5db05b7 | ||
![]() |
e6c8bd0c13 | ||
![]() |
3be9c84f2b | ||
![]() |
881f37f731 | ||
![]() |
4d37f2bab2 | ||
![]() |
3217c2da0b | ||
![]() |
280ae04b3d | ||
![]() |
d5705a52e9 | ||
![]() |
43ab2f22a8 | ||
![]() |
8bb40036bc | ||
![]() |
8ee934404f | ||
![]() |
aa9dbafa28 | ||
![]() |
2b8fea8bf8 | ||
![]() |
985f4293b3 | ||
![]() |
22b162b3c4 | ||
![]() |
7f3d8cfb8d | ||
![]() |
9a7627e35e | ||
![]() |
f141c67ceb | ||
![]() |
e4372644e1 | ||
![]() |
8552b00be4 | ||
![]() |
978fea5dde | ||
![]() |
b3eeaeeda5 | ||
![]() |
0f2ac5104e | ||
![]() |
f7766fff14 | ||
![]() |
b7c2e42190 | ||
![]() |
56472f8dd5 | ||
![]() |
969934b8c0 | ||
![]() |
62f153acd2 | ||
![]() |
b53f16645c | ||
![]() |
6c2786dd78 | ||
![]() |
64a9b0e622 | ||
![]() |
3d05a74ef4 | ||
![]() |
3a1c92944f | ||
![]() |
6b34b82f52 | ||
![]() |
322c090d8a | ||
![]() |
4bb49f9836 | ||
![]() |
20566168a1 | ||
![]() |
03bf4a9ef8 | ||
![]() |
842a76aae1 | ||
![]() |
e3214946a3 | ||
![]() |
36f877c7ff | ||
![]() |
6e41b7ef3d | ||
![]() |
1fc9a9bcea | ||
![]() |
3a439cb81c | ||
![]() |
e8b0de0320 | ||
![]() |
4d033bb379 | ||
![]() |
fffd1ffda3 | ||
![]() |
151f23fd92 | ||
![]() |
25572d6a5b | ||
![]() |
e27efb3946 | ||
![]() |
ca69293d8b | ||
![]() |
f7fa773ec7 | ||
![]() |
f84c4ca73c | ||
![]() |
f9d828ea67 | ||
![]() |
739c977cd7 | ||
![]() |
14b98a32e0 | ||
![]() |
34c9ede9c9 | ||
![]() |
be9f06795d | ||
![]() |
ea9904bd56 | ||
![]() |
501f08dd5e | ||
![]() |
a985cec9c2 | ||
![]() |
c8b0ff22f6 | ||
![]() |
3cc8c1f8c5 | ||
![]() |
5e8b946571 | ||
![]() |
dd4c0d24b7 | ||
![]() |
fc39f1521d | ||
![]() |
60cadb1e11 | ||
![]() |
28c745c19c | ||
![]() |
900b524672 | ||
![]() |
627129dd95 | ||
![]() |
8d18e98ca7 | ||
![]() |
0ba755e463 | ||
![]() |
72215a9f44 | ||
![]() |
2803a6095b | ||
![]() |
d1172f4975 | ||
![]() |
478c4540b1 | ||
![]() |
cc1076e122 | ||
![]() |
15e928ecf2 | ||
![]() |
2c360b6472 | ||
![]() |
f5c99f712a | ||
![]() |
f151bb1451 | ||
![]() |
1061c334ae | ||
![]() |
c5ea50d480 | ||
![]() |
84207effab | ||
![]() |
b568af0a90 | ||
![]() |
11f2f8ff81 | ||
![]() |
454235dd9a | ||
![]() |
ad8dee3c47 | ||
![]() |
5dc0d5536d | ||
![]() |
86699ece8e | ||
![]() |
e3eca5af46 | ||
![]() |
e9f464e34d | ||
![]() |
97775e2a3b | ||
![]() |
54433c43e6 | ||
![]() |
3376908710 | ||
![]() |
284f77b9ae | ||
![]() |
2fe49f316f | ||
![]() |
1428a2485f | ||
![]() |
3a1b4e34aa | ||
![]() |
26fb9a6803 | ||
![]() |
31e6f4282d | ||
![]() |
6bc7de7a6d | ||
![]() |
d9ece291b7 | ||
![]() |
0203a1d4dc | ||
![]() |
221d6e136a | ||
![]() |
ad6e314343 | ||
![]() |
9fc4dbc6d6 | ||
![]() |
5dade92221 | ||
![]() |
4e29960238 | ||
![]() |
3973c57020 | ||
![]() |
dd1dc00430 | ||
![]() |
9a7d6ea7d7 | ||
![]() |
02d4a3b9fe | ||
![]() |
0a60d5f2b2 | ||
![]() |
13ff8f3a84 | ||
![]() |
3efee000ce | ||
![]() |
5915937975 | ||
![]() |
c7621a9e36 | ||
![]() |
28e2463c4f | ||
![]() |
7016d3feea | ||
![]() |
44b4c10bf9 | ||
![]() |
be82c8f6d9 | ||
![]() |
acebf96d2f | ||
![]() |
27c5061d17 | ||
![]() |
fcd034da00 | ||
![]() |
4ee9dbab41 | ||
![]() |
112811190e | ||
![]() |
746295aa16 | ||
![]() |
693c0ba658 | ||
![]() |
e9f37d578e | ||
![]() |
8f4da14611 | ||
![]() |
063b7ce7cc | ||
![]() |
b7243271f3 | ||
![]() |
395ab97191 | ||
![]() |
76da200794 | ||
![]() |
47695debdd | ||
![]() |
a5a2ba9d85 | ||
![]() |
3fa601db3e | ||
![]() |
b60dcb2a23 | ||
![]() |
1e173c6eeb | ||
![]() |
adb11db317 | ||
![]() |
068cb51635 | ||
![]() |
b1eab8bb0d | ||
![]() |
2a4b48d0fa | ||
![]() |
a8e0502b41 | ||
![]() |
ccf0e0dae7 | ||
![]() |
bfa4d3dfec | ||
![]() |
93997c11dc | ||
![]() |
7ce92d5f17 | ||
![]() |
9740010368 | ||
![]() |
e4e0b765b6 | ||
![]() |
51d1dccb42 | ||
![]() |
6f362ee2ad | ||
![]() |
f77bbda5ac | ||
![]() |
dceeaa77c5 | ||
![]() |
d7c96d46e0 | ||
![]() |
b9f5251188 | ||
![]() |
75cdc2c5e8 | ||
![]() |
7eedb14834 | ||
![]() |
ca06154805 | ||
![]() |
35cdef1340 | ||
![]() |
d609c0daeb | ||
![]() |
db0b157d43 | ||
![]() |
906aedd2f1 | ||
![]() |
07a9bdbde3 | ||
![]() |
588b1b1bc3 | ||
![]() |
eb63f89b1f | ||
![]() |
fb81d1b6f3 | ||
![]() |
b897212050 | ||
![]() |
3f6612fe9a | ||
![]() |
bf1a59c5c0 | ||
![]() |
6fb3a3a3c8 | ||
![]() |
ed454b2a4a | ||
![]() |
27f828e619 | ||
![]() |
bde0ce20d8 | ||
![]() |
28c6163a31 | ||
![]() |
14bb377794 | ||
![]() |
61c692ad4e | ||
![]() |
42856e5ac8 | ||
![]() |
e82ad09a8d | ||
![]() |
40fbc55ab3 | ||
![]() |
a27a5b023b | ||
![]() |
a7eb563c2e | ||
![]() |
43fefcf748 | ||
![]() |
621fb95227 | ||
![]() |
d3704fcee6 | ||
![]() |
55100dfb7a | ||
![]() |
010fefcbbc | ||
![]() |
7627f025ed | ||
![]() |
e256d2080d | ||
![]() |
58292067f0 | ||
![]() |
d5e91801d6 | ||
![]() |
91d545f480 | ||
![]() |
9c2599acbe | ||
![]() |
06341ee632 | ||
![]() |
bcc693e4c7 | ||
![]() |
8b8afacaea | ||
![]() |
deb49d7ff9 | ||
![]() |
6334ffa197 | ||
![]() |
b872cce2a4 | ||
![]() |
de2e2ee962 | ||
![]() |
5468676811 | ||
![]() |
c102020698 | ||
![]() |
0ff363b6ee | ||
![]() |
c324cf69ed | ||
![]() |
721cf5c930 | ||
![]() |
f07bcca96a | ||
![]() |
056d0d81ac | ||
![]() |
38ccd37867 | ||
![]() |
21799116c5 | ||
![]() |
60bdf1d1ce | ||
![]() |
02658759ea | ||
![]() |
68946ceede | ||
![]() |
9184ae4608 | ||
![]() |
de64b5ddfa | ||
![]() |
b3ffbbf3ea | ||
![]() |
aa80fdf738 | ||
![]() |
9ad95f51d4 | ||
![]() |
0902a61341 | ||
![]() |
55ffd54e5b | ||
![]() |
e014bfa63e | ||
![]() |
687672e9c1 | ||
![]() |
137889dc9c | ||
![]() |
f24f4a4250 | ||
![]() |
95fc108d57 | ||
![]() |
95f48ba9f6 | ||
![]() |
d80cf232c8 | ||
![]() |
ab3ec875a3 | ||
![]() |
668c9e6045 | ||
![]() |
67b452a461 | ||
![]() |
81ee44b60f | ||
![]() |
9b3bfd14db | ||
![]() |
e00c8fb186 | ||
![]() |
a0919e246d | ||
![]() |
003f684f8a | ||
![]() |
69d55c60c3 | ||
![]() |
560094dcf6 | ||
![]() |
4edd6ce911 | ||
![]() |
f76bd2af8e | ||
![]() |
7747503fee | ||
![]() |
1e1a8ddfb0 | ||
![]() |
9bcd18f1b6 | ||
![]() |
50b6f9a8f2 | ||
![]() |
b4ba88b3e5 | ||
![]() |
ba9acd6e23 | ||
![]() |
dd9513313b | ||
![]() |
288a1c86ab | ||
![]() |
8e28cb10fa | ||
![]() |
3d35a525d3 | ||
![]() |
f7153d0f3b | ||
![]() |
4285b55c15 | ||
![]() |
b54576f08f | ||
![]() |
6b4db681ff | ||
![]() |
f582f781f3 | ||
![]() |
9baecb0a41 | ||
![]() |
91a18e1a92 | ||
![]() |
acfbb0e96d | ||
![]() |
c52292962d | ||
![]() |
6e53743716 | ||
![]() |
873194b402 | ||
![]() |
21dec5feb3 | ||
![]() |
bee4106af0 | ||
![]() |
bbb6e46515 | ||
![]() |
570ebb4f73 | ||
![]() |
d93204af4e | ||
![]() |
702f116db9 | ||
![]() |
0c8607b3ec | ||
![]() |
3a2cc6efc7 | ||
![]() |
1b37ff1655 | ||
![]() |
769934c8a5 | ||
![]() |
7f1a4ec34a | ||
![]() |
27438f7915 | ||
![]() |
8651bef9c1 | ||
![]() |
36324d10dc | ||
![]() |
0272c35047 | ||
![]() |
70c0f912e2 | ||
![]() |
59a6acc088 | ||
![]() |
10b0726727 | ||
![]() |
8f1360d7c2 | ||
![]() |
e0e5ac9ecc | ||
![]() |
c814f219a2 | ||
![]() |
9095fc0c7a | ||
![]() |
a675202537 | ||
![]() |
b52ab4885b | ||
![]() |
43e26c9b56 | ||
![]() |
703a7feed2 | ||
![]() |
7b69ed4cec | ||
![]() |
fcca7f969e | ||
![]() |
ec34ea2116 | ||
![]() |
3dc36c3b92 | ||
![]() |
f0d4fd5523 | ||
![]() |
7fe6c72fe2 | ||
![]() |
d216d0f27f | ||
![]() |
43a7758acd | ||
![]() |
3043956dec | ||
![]() |
06665fdd06 | ||
![]() |
beff5caaac | ||
![]() |
3859412b2c | ||
![]() |
f7ec476fc0 | ||
![]() |
b97d32671d | ||
![]() |
01c56ef280 | ||
![]() |
b9422312f3 | ||
![]() |
9a0f83c3e7 | ||
![]() |
fbfedb2e62 | ||
![]() |
4f8a462041 | ||
![]() |
141d043a6a | ||
![]() |
c1266fed12 | ||
![]() |
4a4be9798d | ||
![]() |
172692ccca | ||
![]() |
50e7c0469f | ||
![]() |
44f74e3590 | ||
![]() |
63656b73c2 | ||
![]() |
40ecf56904 | ||
![]() |
b4a10adec2 | ||
![]() |
1698622d63 | ||
![]() |
fa27271647 | ||
![]() |
d837811c68 | ||
![]() |
ad195f0969 | ||
![]() |
4a8748e322 | ||
![]() |
0f016c83ea | ||
![]() |
061ae44da4 | ||
![]() |
a8b90bf100 | ||
![]() |
eb3cd49bc4 | ||
![]() |
416d869288 | ||
![]() |
a116c26c25 | ||
![]() |
cc4ec53dac | ||
![]() |
63164c7ff5 | ||
![]() |
9815c014e8 | ||
![]() |
69675151bf | ||
![]() |
99e395ddfa | ||
![]() |
7fe1e542df | ||
![]() |
938134081b | ||
![]() |
3fd2234a92 | ||
![]() |
41843dc573 | ||
![]() |
cc6bd528a5 | ||
![]() |
2625ef5fb9 | ||
![]() |
dbd2d28877 | ||
![]() |
f70f814c70 | ||
![]() |
6710e42134 | ||
![]() |
78c5b45e43 | ||
![]() |
e562ec96fa | ||
![]() |
9b5e01c319 | ||
![]() |
0097532f4a | ||
![]() |
91935c9018 | ||
![]() |
83df807f7e | ||
![]() |
eb3db20340 | ||
![]() |
6dab6194ea | ||
![]() |
356f64cac0 | ||
![]() |
f77f289125 | ||
![]() |
280257477a | ||
![]() |
660141cb16 | ||
![]() |
cd8a899521 | ||
![]() |
cb577c51b8 | ||
![]() |
1c395ab10c | ||
![]() |
07d7170e49 | ||
![]() |
88e23627fd | ||
![]() |
48f846da40 | ||
![]() |
ff887d9948 | ||
![]() |
617b0d6fd9 | ||
![]() |
805d45bd33 | ||
![]() |
fef428202f | ||
![]() |
40fd82febd | ||
![]() |
45f0001da5 | ||
![]() |
c7a3e1e3bf | ||
![]() |
9dd8cc9e49 | ||
![]() |
d252d4cd2d | ||
![]() |
bc1328040c | ||
![]() |
82919d3c1d | ||
![]() |
7c801c2f5e | ||
![]() |
9a932aea12 | ||
![]() |
5696e75abe | ||
![]() |
efb3f748c2 | ||
![]() |
450b3865a8 | ||
![]() |
970667adca | ||
![]() |
89307dad01 | ||
![]() |
451feda86b | ||
![]() |
4d241fac48 | ||
![]() |
4390f5cbc8 | ||
![]() |
7f9d46eac3 | ||
![]() |
d0f28883aa | ||
![]() |
48203e64a9 | ||
![]() |
42b17ca495 | ||
![]() |
d8080fe506 | ||
![]() |
be910e24f7 | ||
![]() |
ce6d70f6fd | ||
![]() |
827e05e4d7 | ||
![]() |
43e40e99f1 | ||
![]() |
d95afa990d | ||
![]() |
e14457da58 | ||
![]() |
9613934ae5 | ||
![]() |
07a48c04d7 | ||
![]() |
fbcf59abf0 | ||
![]() |
2ef40a6a1c | ||
![]() |
5b5c4d1a8b | ||
![]() |
5f2a74893a | ||
![]() |
0741b4021c | ||
![]() |
f2323b0dff | ||
![]() |
0462121f69 | ||
![]() |
fe4ddaeb52 | ||
![]() |
bdbfafabbd | ||
![]() |
42c6340c06 | ||
![]() |
39e1caec0f | ||
![]() |
ef72832e5a | ||
![]() |
39eb657012 | ||
![]() |
b8f8d45807 | ||
![]() |
b01fefc235 | ||
![]() |
09f6eb8e19 | ||
![]() |
e5d4969917 | ||
![]() |
53aa740305 | ||
![]() |
9a00350ffc | ||
![]() |
98ffa3735b | ||
![]() |
9073568c0f | ||
![]() |
17a01d65aa | ||
![]() |
5089575aac | ||
![]() |
ae88489e55 | ||
![]() |
b57065d6ee | ||
![]() |
cbcad30a6c | ||
![]() |
e2c2f66e97 | ||
![]() |
eeff665680 | ||
![]() |
6d23ef9105 | ||
![]() |
6c8b425fb3 | ||
![]() |
bc017fb010 | ||
![]() |
8a8d47f8e7 | ||
![]() |
b01fac9641 | ||
![]() |
25c850e243 | ||
![]() |
8c7476a670 | ||
![]() |
12effd643f | ||
![]() |
209008e50d | ||
![]() |
b336f07ff9 | ||
![]() |
73f6012507 | ||
![]() |
b73564d2e0 | ||
![]() |
00adb45086 | ||
![]() |
d604d40e91 | ||
![]() |
ba3f6935db | ||
![]() |
980c4f7618 | ||
![]() |
a869859491 | ||
![]() |
15a638b86e | ||
![]() |
e999000102 | ||
![]() |
95bdc000ca | ||
![]() |
1d46efe037 | ||
![]() |
a5653e365e | ||
![]() |
e698bcb375 | ||
![]() |
33d5aca6d4 | ||
![]() |
058bd32329 | ||
![]() |
52d38883dc | ||
![]() |
c1d98ab901 | ||
![]() |
e555b7e456 | ||
![]() |
031bef8c02 | ||
![]() |
3bf138e2ad | ||
![]() |
4e0563bbf9 | ||
![]() |
a8783ac351 | ||
![]() |
fb51894fad | ||
![]() |
25d65e8d65 | ||
![]() |
3e10e0e511 | ||
![]() |
c3245c1f03 | ||
![]() |
5b022599b4 | ||
![]() |
d5d219d46f | ||
![]() |
e546689e01 | ||
![]() |
cac9e0b164 | ||
![]() |
4bb5920c04 | ||
![]() |
3ea257f8f3 | ||
![]() |
347db6b770 | ||
![]() |
fafe28a6d6 | ||
![]() |
eb6cb60ee3 | ||
![]() |
8226a14b00 | ||
![]() |
c6bd1b06f2 | ||
![]() |
be38028244 | ||
![]() |
b8ea04f5a4 | ||
![]() |
cd5ed1d748 | ||
![]() |
00c9fc79f9 | ||
![]() |
d5373c3992 | ||
![]() |
3001ff8c53 | ||
![]() |
0571a091f7 | ||
![]() |
1bca410bcb | ||
![]() |
463ed2f46a | ||
![]() |
f8f0717913 | ||
![]() |
53cd759422 | ||
![]() |
7047ac8007 | ||
![]() |
2efd81dc6a | ||
![]() |
773ee8664c | ||
![]() |
d779e72bcd | ||
![]() |
2e101dcf7d | ||
![]() |
e6befab6bb | ||
![]() |
9ee2c1f7a6 | ||
![]() |
5b82a86fa8 | ||
![]() |
922bb2760c | ||
![]() |
315be9f3eb | ||
![]() |
7bb9c6c915 | ||
![]() |
4b5f880ccb | ||
![]() |
5db309d142 | ||
![]() |
5d8a7d80eb | ||
![]() |
1394339df6 | ||
![]() |
801510c61e | ||
![]() |
6c8d6ed2ca | ||
![]() |
d8f223327e | ||
![]() |
0343d47a9d | ||
![]() |
e527a88a2e | ||
![]() |
d6b619934a | ||
![]() |
93f070f0ac | ||
![]() |
3ca4351aeb | ||
![]() |
0ed4b69b8f | ||
![]() |
94f929743c | ||
![]() |
9e9ad72dc2 | ||
![]() |
422a89c26c | ||
![]() |
798c17706c | ||
![]() |
0886d133a8 | ||
![]() |
435230711e | ||
![]() |
d75744bb4a | ||
![]() |
86d737dcf6 | ||
![]() |
9e0153e962 | ||
![]() |
fb395fc2e9 | ||
![]() |
573ff3f2a6 | ||
![]() |
b9f614c66f | ||
![]() |
e26182c96e | ||
![]() |
f4eff8a8c5 | ||
![]() |
b3f8341e0c | ||
![]() |
47db4e0559 | ||
![]() |
b8179678c6 | ||
![]() |
c1a7b3753c | ||
![]() |
8b312c8d2d | ||
![]() |
ab36041fef | ||
![]() |
3f87996bfc | ||
![]() |
4edd2001b3 | ||
![]() |
155b98bb0c | ||
![]() |
f72d93216c | ||
![]() |
c6cf293b12 | ||
![]() |
0f13329ddd | ||
![]() |
1dfbef89ff | ||
![]() |
c55c00a19e | ||
![]() |
2fa62f71e1 | ||
![]() |
846a8cac98 | ||
![]() |
9ee7918e59 | ||
![]() |
faf5cb0f8d | ||
![]() |
bde6309277 | ||
![]() |
cc05552685 | ||
![]() |
465f50666f | ||
![]() |
e6d0212604 | ||
![]() |
2eebacc3a6 | ||
![]() |
f362880eb6 | ||
![]() |
68a06d1bbc | ||
![]() |
82c09570c4 | ||
![]() |
58eb426eea | ||
![]() |
1c932057b8 | ||
![]() |
4564623884 | ||
![]() |
843a400b2d | ||
![]() |
5b067bd17d | ||
![]() |
ed07bd374c | ||
![]() |
078685a2a3 | ||
![]() |
2ce5194156 | ||
![]() |
fa97d3f88d | ||
![]() |
08c8ee0774 | ||
![]() |
9725c82187 | ||
![]() |
24277f1e3c | ||
![]() |
b58fb1da33 | ||
![]() |
bed1cd8fb5 | ||
![]() |
c2d17c285a | ||
![]() |
42262b0bb6 | ||
![]() |
510dddf724 | ||
![]() |
702b2fe167 | ||
![]() |
f24c2a8b77 | ||
![]() |
a675c2c4f2 | ||
![]() |
2984629b39 | ||
![]() |
1c56d9c513 | ||
![]() |
e06210f21c | ||
![]() |
ad112e0a44 | ||
![]() |
2b0e7daf7c | ||
![]() |
060dff0162 | ||
![]() |
4ae09774f7 | ||
![]() |
033a364699 | ||
![]() |
56a66976e6 | ||
![]() |
0f02fab259 | ||
![]() |
2917b609c3 | ||
![]() |
b9a80d06e4 | ||
![]() |
af46a02146 | ||
![]() |
19d8c1be5a | ||
![]() |
f63c1c2f7f | ||
![]() |
5045e406a1 | ||
![]() |
8d5bc88fd9 | ||
![]() |
b39ac866f2 | ||
![]() |
4c211342a2 | ||
![]() |
6b7cd38d71 | ||
![]() |
485609fbb9 | ||
![]() |
a44709a43d | ||
![]() |
65e9e2b680 | ||
![]() |
d84dc23b46 | ||
![]() |
e333940826 | ||
![]() |
70f7fd2de9 | ||
![]() |
411d88d798 | ||
![]() |
dce8248eb8 | ||
![]() |
3b8234ce67 | ||
![]() |
ac63d3c3ce | ||
![]() |
197c3a327b | ||
![]() |
0bb97fee31 | ||
![]() |
1bdf6bbb66 | ||
![]() |
077dfe7164 | ||
![]() |
169f83ac4a | ||
![]() |
121dad588e | ||
![]() |
bb3a11ad00 | ||
![]() |
64d3bd9c4f | ||
![]() |
e6be03a770 | ||
![]() |
5f722570d2 | ||
![]() |
dcbeca5f7f | ||
![]() |
16742d4705 | ||
![]() |
d21a03905d | ||
![]() |
0608b2a1df | ||
![]() |
5f237c7c71 | ||
![]() |
4c98b0a43d | ||
![]() |
05afa0859c | ||
![]() |
597cc9fe29 | ||
![]() |
ab6196589b | ||
![]() |
221be380ee | ||
![]() |
a68e5f6519 | ||
![]() |
bc81f19715 | ||
![]() |
ceeeea94ba | ||
![]() |
31ab5daa91 | ||
![]() |
8f6639028f | ||
![]() |
a2b686f6df | ||
![]() |
2dcc74d82d | ||
![]() |
d460263b97 | ||
![]() |
b8cfa343ae | ||
![]() |
8d391f125c | ||
![]() |
1532bb731a | ||
![]() |
357ba9ec59 | ||
![]() |
183c810c76 | ||
![]() |
f2d7beec90 | ||
![]() |
84ce4758d1 | ||
![]() |
4d6279a626 | ||
![]() |
f28e741ad7 | ||
![]() |
23c4e5b09d | ||
![]() |
cd6057e1ca | ||
![]() |
1771674b53 | ||
![]() |
2a9d0ea7d2 | ||
![]() |
e19938b05e | ||
![]() |
244a3e5be3 | ||
![]() |
e5a3d534b2 | ||
![]() |
c279057f91 |
@@ -3,6 +3,9 @@
|
||||
.gitignore
|
||||
contrib
|
||||
init-scripts
|
||||
package
|
||||
pylintrc
|
||||
snap
|
||||
*.md
|
||||
!CHANGELOG*.md
|
||||
start.bat
|
||||
|
20
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
## Description
|
||||
|
||||
Please include a summary of the change and which issue is fixed.
|
||||
|
||||
Fixes Tautulli/Tautulli-Issues#(issue)
|
||||
|
||||
## Type of change
|
||||
|
||||
Please delete options that are not relevant.
|
||||
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
|
||||
## Checklist:
|
||||
|
||||
- [ ] My code follows the style guidelines of this project
|
||||
- [ ] I have performed a self-review of my own code
|
||||
- [ ] I have commented my code, particularly in hard-to-understand areas
|
||||
- [ ] I have added or updated the docstring for new or existing methods
|
114
.github/workflows/publish-docker.yml
vendored
@@ -1,12 +1,19 @@
|
||||
name: Publish Docker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master, beta, nightly]
|
||||
tags: [v*]
|
||||
pull_request: ~
|
||||
|
||||
jobs:
|
||||
build:
|
||||
build-docker:
|
||||
name: Build Docker Image
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Prepare
|
||||
id: prepare
|
||||
run: |
|
||||
@@ -17,67 +24,92 @@ jobs:
|
||||
else
|
||||
echo ::set-output name=tag::${GITHUB_REF#refs/heads/}
|
||||
fi
|
||||
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||
if [[ $GITHUB_REF == refs/tags/*-beta ]]; then
|
||||
echo ::set-output name=branch::beta
|
||||
elif [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||
echo ::set-output name=branch::master
|
||||
else
|
||||
echo ::set-output name=branch::${GITHUB_REF#refs/heads/}
|
||||
fi
|
||||
echo ::set-output name=commit::${GITHUB_SHA}
|
||||
echo ::set-output name=build_date::$(date -u +'%Y-%m-%dT%H:%M:%SZ')
|
||||
echo ::set-output name=docker_platforms::linux/amd64,linux/arm64,linux/arm
|
||||
echo ::set-output name=docker_image::tautulli/tautulli
|
||||
echo ::set-output name=docker_platforms::linux/amd64,linux/arm64/v8,linux/arm/v7,linux/arm/v6
|
||||
echo ::set-output name=docker_image::${{ secrets.DOCKER_REPO }}/tautulli
|
||||
|
||||
- name: Set Up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
id: buildx
|
||||
uses: crazy-max/ghaction-docker-buildx@v1
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Cache Docker Layers
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-
|
||||
|
||||
- name: Docker Buildx (no push)
|
||||
run: |
|
||||
docker buildx build \
|
||||
--platform ${{ steps.prepare.outputs.docker_platforms }} \
|
||||
--output "type=image,push=false" \
|
||||
--build-arg "TAG=${{ steps.prepare.outputs.tag }}" \
|
||||
--build-arg "BRANCH=${{ steps.prepare.outputs.branch }}" \
|
||||
--build-arg "COMMIT=${{ steps.prepare.outputs.commit }}" \
|
||||
--build-arg "BUILD_DATE=${{ steps.prepare.outputs.build_date }}" \
|
||||
--tag "${{ steps.prepare.outputs.docker_image }}:${{ steps.prepare.outputs.tag }}" \
|
||||
--file Dockerfile .
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
if: success() && github.event_name != 'pull_request'
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Docker Login
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1
|
||||
if: success() && github.event_name != 'pull_request'
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.GHCR_TOKEN }}
|
||||
|
||||
- name: Docker Build and Push
|
||||
uses: docker/build-push-action@v2
|
||||
if: success()
|
||||
env:
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
run: |
|
||||
echo "${DOCKER_PASSWORD}" | docker login --username "${{ secrets.DOCKER_USERNAME }}" --password-stdin
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
platforms: ${{ steps.prepare.outputs.docker_platforms }}
|
||||
build-args: |
|
||||
TAG=${{ steps.prepare.outputs.tag }}
|
||||
BRANCH=${{ steps.prepare.outputs.branch }}
|
||||
COMMIT=${{ steps.prepare.outputs.commit }}
|
||||
BUILD_DATE=${{ steps.prepare.outputs.build_date }}
|
||||
tags: |
|
||||
${{ steps.prepare.outputs.docker_image }}:${{ steps.prepare.outputs.tag }}
|
||||
ghcr.io/${{ steps.prepare.outputs.docker_image }}:${{ steps.prepare.outputs.tag }}
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache
|
||||
|
||||
- name: Docker Buildx (push)
|
||||
if: success()
|
||||
run: |
|
||||
docker buildx build \
|
||||
--platform ${{ steps.prepare.outputs.docker_platforms }} \
|
||||
--output "type=image,push=true" \
|
||||
--build-arg "TAG=${{ steps.prepare.outputs.tag }}" \
|
||||
--build-arg "BRANCH=${{ steps.prepare.outputs.branch }}" \
|
||||
--build-arg "COMMIT=${{ steps.prepare.outputs.commit }}" \
|
||||
--build-arg "BUILD_DATE=${{ steps.prepare.outputs.build_date }}" \
|
||||
--tag "${{ steps.prepare.outputs.docker_image }}:${{ steps.prepare.outputs.tag }}" \
|
||||
--file Dockerfile .
|
||||
discord:
|
||||
name: Discord Notification
|
||||
needs: build-docker
|
||||
if: always() && github.event_name != 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get Build Job Status
|
||||
uses: technote-space/workflow-conclusion-action@v1
|
||||
|
||||
- name: Clear
|
||||
if: always()
|
||||
- name: Combine Job Status
|
||||
id: status
|
||||
run: |
|
||||
rm -f ${HOME}/.docker/config.json
|
||||
failures=(neutral, skipped, timed_out, action_required)
|
||||
if [[ ${array[@]} =~ $WORKFLOW_CONCLUSION ]]; then
|
||||
echo ::set-output name=status::failure
|
||||
else
|
||||
echo ::set-output name=status::$WORKFLOW_CONCLUSION
|
||||
fi
|
||||
|
||||
- name: Post Status to Discord
|
||||
uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
status: ${{ job.status }}
|
||||
job: ${{ github.workflow }}
|
||||
status: ${{ steps.status.outputs.status }}
|
||||
title: ${{ github.workflow }}
|
||||
nofail: true
|
||||
|
183
.github/workflows/publish-installers.yml
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
name: Publish Installers
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master, beta, nightly]
|
||||
tags: [v*]
|
||||
pull_request: ~
|
||||
|
||||
jobs:
|
||||
build-installer:
|
||||
name: Build ${{ matrix.os_upper }} Installer
|
||||
runs-on: ${{ matrix.os }}-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: 'windows'
|
||||
os_upper: 'Windows'
|
||||
ext: 'exe'
|
||||
- os: 'macos'
|
||||
os_upper: 'MacOS'
|
||||
ext: 'pkg'
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set Release Version
|
||||
id: get_version
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||
echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
||||
VERSION_NSIS=${GITHUB_REF#refs/tags/v}.1
|
||||
echo ::set-output name=VERSION_NSIS::${VERSION_NSIS/%-beta.1/.0}
|
||||
echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/v}
|
||||
echo ::set-output name=RELEASE_VERSION::${GITHUB_REF#refs/tags/}
|
||||
else
|
||||
echo "VERSION=0.0.0" >> $GITHUB_ENV
|
||||
echo ::set-output name=VERSION_NSIS::0.0.0.0
|
||||
echo ::set-output name=VERSION::0.0.0
|
||||
echo ::set-output name=RELEASE_VERSION::${GITHUB_SHA::7}
|
||||
fi
|
||||
echo $GITHUB_SHA > version.txt
|
||||
|
||||
- name: Set Up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
|
||||
- name: Cache Dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~\AppData\Local\pip\Cache
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles(format('package/requirements-{0}.txt', matrix.os)) }}
|
||||
restore-keys: ${{ runner.os }}-pip-
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r package/requirements-${{ matrix.os }}.txt
|
||||
|
||||
- name: Build Package
|
||||
run: |
|
||||
pyinstaller -y ./package/Tautulli-${{ matrix.os }}.spec
|
||||
|
||||
- name: Create Windows Installer
|
||||
uses: joncloud/makensis-action@v1.2
|
||||
if: matrix.os == 'windows'
|
||||
with:
|
||||
script-file: ./package/Tautulli.nsi
|
||||
arguments: >
|
||||
/DVERSION=${{ steps.get_version.outputs.VERSION_NSIS }}
|
||||
/DINSTALLER_NAME=..\Tautulli-windows-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.exe
|
||||
include-more-plugins: true
|
||||
include-custom-plugins-path: package/nsis-plugins
|
||||
|
||||
- name: Create MacOS Installer
|
||||
if: matrix.os == 'macos'
|
||||
run: |
|
||||
sudo pkgbuild \
|
||||
--install-location /Applications \
|
||||
--version ${{ steps.get_version.outputs.VERSION }} \
|
||||
--component ./dist/Tautulli.app \
|
||||
--scripts ./package/macos-scripts \
|
||||
Tautulli-macos-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.pkg
|
||||
|
||||
- name: Upload Installer
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Tautulli-${{ matrix.os }}-installer
|
||||
path: Tautulli-${{ matrix.os }}-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.${{ matrix.ext }}
|
||||
|
||||
release:
|
||||
name: Release Installers
|
||||
needs: build-installer
|
||||
if: always() && startsWith(github.ref, 'refs/tags/') && github.event_name != 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get Build Job Status
|
||||
uses: technote-space/workflow-conclusion-action@v1
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set Release Version
|
||||
id: get_version
|
||||
run: |
|
||||
echo ::set-output name=RELEASE_VERSION::${GITHUB_REF#refs/tags/}
|
||||
|
||||
- name: Download Installers
|
||||
if: env.WORKFLOW_CONCLUSION == 'success'
|
||||
uses: actions/download-artifact@v2
|
||||
|
||||
- name: Get Changelog
|
||||
id: get_changelog
|
||||
run: |
|
||||
echo ::set-output name=CHANGELOG::"$( sed -n '/^## /{p; :loop n; p; /^## /q; b loop}' CHANGELOG.md \
|
||||
| sed '$d' | sed '$d' | sed '$d' | sed ':a;N;$!ba;s/\n/%0A/g' )"
|
||||
|
||||
- name: Create Release
|
||||
uses: actions/create-release@v1
|
||||
id: create_release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ steps.get_version.outputs.RELEASE_VERSION }}
|
||||
release_name: Tautulli ${{ steps.get_version.outputs.RELEASE_VERSION }}
|
||||
body: |
|
||||
## Changelog
|
||||
|
||||
##${{ steps.get_changelog.outputs.CHANGELOG }}
|
||||
draft: false
|
||||
prerelease: ${{ endsWith(steps.get_version.outputs.RELEASE_VERSION, '-beta') }}
|
||||
|
||||
- name: Upload Windows Installer
|
||||
uses: actions/upload-release-asset@v1
|
||||
if: env.WORKFLOW_CONCLUSION == 'success'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: Tautulli-windows-installer/Tautulli-windows-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.exe
|
||||
asset_name: Tautulli-windows-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.exe
|
||||
asset_content_type: application/vnd.microsoft.portable-executable
|
||||
|
||||
- name: Upload MacOS Installer
|
||||
uses: actions/upload-release-asset@v1
|
||||
if: env.WORKFLOW_CONCLUSION == 'success'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: Tautulli-macos-installer/Tautulli-macos-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.pkg
|
||||
asset_name: Tautulli-macos-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.pkg
|
||||
asset_content_type: application/vnd.apple.installer+xml
|
||||
|
||||
discord:
|
||||
name: Discord Notification
|
||||
needs: [build-installer, release]
|
||||
if: always() && github.event_name != 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get Build Job Status
|
||||
uses: technote-space/workflow-conclusion-action@v1
|
||||
|
||||
- name: Combine Job Status
|
||||
id: status
|
||||
run: |
|
||||
failures=(neutral, skipped, timed_out, action_required)
|
||||
if [[ ${array[@]} =~ $WORKFLOW_CONCLUSION ]]; then
|
||||
echo ::set-output name=status::failure
|
||||
else
|
||||
echo ::set-output name=status::$WORKFLOW_CONCLUSION
|
||||
fi
|
||||
|
||||
- name: Post Status to Discord
|
||||
uses: sarisia/actions-status-discord@v1
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
status: ${{ steps.status.outputs.status }}
|
||||
title: ${{ github.workflow }}
|
||||
nofail: true
|
28
.github/workflows/publish-release.yml
vendored
@@ -1,28 +0,0 @@
|
||||
name: Publish Release
|
||||
on:
|
||||
push:
|
||||
tags: [v*]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@master
|
||||
- name: Get Release Version
|
||||
run: echo ::set-env name=RELEASE_VERSION::${GITHUB_REF#refs/tags/}
|
||||
- name: Get Changelog
|
||||
run: echo ::set-env name=CHANGELOG::"$( sed -n '/^## /{p; :loop n; p; /^## /q; b loop}' CHANGELOG.md | sed '$d' | sed '$d' | sed '$d' | sed ':a;N;$!ba;s/\n/%0A/g' )"
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ env.RELEASE_VERSION }}
|
||||
release_name: Tautulli ${{ env.RELEASE_VERSION }}
|
||||
body: |
|
||||
## Changelog
|
||||
|
||||
##${{ env.CHANGELOG }}
|
||||
draft: false
|
||||
prerelease: ${{ endsWith(env.RELEASE_VERSION, '-beta') }}
|
94
.github/workflows/publish-snap.yml
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
name: Publish Snap
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master, beta, nightly]
|
||||
tags: [v*]
|
||||
pull_request: ~
|
||||
|
||||
jobs:
|
||||
build-snap:
|
||||
name: Build Snap Package (${{ matrix.architecture }})
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
architecture:
|
||||
- i386
|
||||
- amd64
|
||||
- arm64
|
||||
- armhf
|
||||
- ppc64el
|
||||
#- s390x # broken at the moment
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Prepare
|
||||
id: prepare
|
||||
run: |
|
||||
git fetch --prune --unshallow --tags
|
||||
if [[ $GITHUB_REF == refs/tags/*-beta || $GITHUB_REF == refs/heads/beta ]]; then
|
||||
echo ::set-output name=RELEASE::beta
|
||||
elif [[ $GITHUB_REF == refs/tags/* || $GITHUB_REF == refs/heads/master ]]; then
|
||||
echo ::set-output name=RELEASE::stable
|
||||
else
|
||||
echo ::set-output name=RELEASE::edge
|
||||
fi
|
||||
|
||||
- name: Set Up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
|
||||
- name: Build Snap Package
|
||||
uses: diddlesnaps/snapcraft-multiarch-action@v1
|
||||
id: build
|
||||
with:
|
||||
architecture: ${{ matrix.architecture }}
|
||||
|
||||
- name: Upload Snap Package
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Tautulli-snap-package-${{ matrix.architecture }}
|
||||
path: ${{ steps.build.outputs.snap }}
|
||||
|
||||
- name: Review Snap Package
|
||||
uses: diddlesnaps/snapcraft-review-tools-action@v1
|
||||
with:
|
||||
snap: ${{ steps.build.outputs.snap }}
|
||||
|
||||
- name: Publish Snap Package
|
||||
uses: snapcore/action-publish@v1
|
||||
if: >
|
||||
github.event_name != 'pull_request' &&
|
||||
(startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/nightly')
|
||||
with:
|
||||
store_login: ${{ secrets.SNAP_LOGIN }}
|
||||
snap: ${{ steps.build.outputs.snap }}
|
||||
release: ${{ steps.prepare.outputs.RELEASE }}
|
||||
|
||||
discord:
|
||||
name: Discord Notification
|
||||
needs: build-snap
|
||||
if: always() && github.event_name != 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get Build Job Status
|
||||
uses: technote-space/workflow-conclusion-action@v1
|
||||
|
||||
- name: Combine Job Status
|
||||
id: status
|
||||
run: |
|
||||
failures=(neutral, skipped, timed_out, action_required)
|
||||
if [[ ${array[@]} =~ $WORKFLOW_CONCLUSION ]]; then
|
||||
echo ::set-output name=status::failure
|
||||
else
|
||||
echo ::set-output name=status::$WORKFLOW_CONCLUSION
|
||||
fi
|
||||
|
||||
- name: Post Status to Discord
|
||||
uses: sarisia/actions-status-discord@v1
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
status: ${{ steps.status.outputs.status }}
|
||||
title: ${{ github.workflow }}
|
||||
nofail: true
|
20
.gitignore
vendored
@@ -17,8 +17,11 @@ version.lock
|
||||
logs/*
|
||||
backups/*
|
||||
cache/*
|
||||
exports/*
|
||||
newsletters/*
|
||||
*.mmdb
|
||||
version.txt
|
||||
branch.txt
|
||||
|
||||
# HTTPS Cert/Key #
|
||||
##################
|
||||
@@ -74,3 +77,20 @@ _ReSharper*/
|
||||
/logs
|
||||
.project
|
||||
.pydevproject
|
||||
|
||||
#Ignore files generated by pyinstaller
|
||||
/build
|
||||
/dist
|
||||
|
||||
#snapcraft specifics
|
||||
/parts/
|
||||
/stage/
|
||||
/prime/
|
||||
|
||||
*.snap
|
||||
|
||||
.snapcraft
|
||||
__pycache__
|
||||
*.pyc
|
||||
*_source.tar.bz2
|
||||
snap/.snapcraft
|
184
CHANGELOG.md
@@ -1,5 +1,189 @@
|
||||
# Changelog
|
||||
|
||||
## v2.6.4 (2020-12-20)
|
||||
|
||||
* Other:
|
||||
* Fix: Restore Snap data folder from previous installs.
|
||||
|
||||
|
||||
## v2.6.3 (2020-12-19)
|
||||
|
||||
* Announcements:
|
||||
* This is the last Tautulli version to support Python 2. Python 3 will be required to continue receiving updates. You can check your Python version on the settings page.
|
||||
* Exporter:
|
||||
* Fix: Accessible and exists attributes were blank for media info export level 9.
|
||||
* UI:
|
||||
* Fix: Guest usernames were not masked on mouse hover.
|
||||
* Other:
|
||||
* Fix: macOS menu bar icon for light and dark mode.
|
||||
* New: Tautulli can officially be installed on Linux using a Snap package. See the installation wiki for details.
|
||||
|
||||
|
||||
## v2.6.2 (2020-12-05)
|
||||
|
||||
* Notifications:
|
||||
* Change: Send a notification of a user new device for the first time only. This can be toggled off in the settings.
|
||||
* Exporter:
|
||||
* Fix: Allow exporting child fields only without requiring the parent fields as well.
|
||||
* Fix: Exporting individual collection would fail.
|
||||
* Change: Remove accessible and exists fields from the default media info export levels. This prevents the Plex server from reading the media files unnecessarily.
|
||||
* Other:
|
||||
* Fix: Enable high resolution for the macOS system tray icon and menu.
|
||||
* New: Added rate limiting for failed login attempts.
|
||||
* Change: Use a white logo for the macOS system tray icon.
|
||||
* API:
|
||||
* New: Added machine_id to the get_history API response.
|
||||
|
||||
|
||||
## v2.6.1 (2020-11-03)
|
||||
|
||||
* Other:
|
||||
* Fix: High CPU/memory usage in some instances.
|
||||
* Fix: Logger error preventing Tautulli from starting.
|
||||
* Fix: Database issue with non-unique image hashes.
|
||||
|
||||
|
||||
## v2.6.0 (2020-10-31)
|
||||
|
||||
* Exporter:
|
||||
* New: New exporter feature that allows you to export the metadata and images for any library, collection, playlist, or media item to csv, json, xml, or m3u8. Refer to the Exporter Guide in the wiki for more details.
|
||||
* UI:
|
||||
* Fix: Margin on the homepage activity and statistic/library cards. (Thanks @dotsam)
|
||||
* Fix: Movie ratings not showing on the info page for the new Plex Movie agent.
|
||||
* New: Added ability to browse collections and playlists from the library and user pages.
|
||||
* Change: Updated platform brand logos and colours.
|
||||
* API:
|
||||
* New: Added export_metadata, download_export, and delete_export API commands.
|
||||
* New: Added get_collections_table, and get_playlists_table API commands.
|
||||
* New: Added min_version parameter to the register_device API command.
|
||||
* New: Added include_activity parameter to the get_history API command.
|
||||
* New: Added sync_id parameter to the get_metadata API command.
|
||||
* New: Added delete_synced_item API command.
|
||||
* New: Added a stat_id and stats_start parameters to the get_home_stats API command.
|
||||
* New: Allow deleting a mobile device using the registration device_id for the delete_mobile_device API command.
|
||||
* Change: Return Plex server info and Tautulli info from the register_device command.
|
||||
* Other:
|
||||
* New: The Docker container is now also built for the arm32v6 architecture.
|
||||
* New: The Docker container is also published to the GitHub Container Registry at ghcr.io/tautulli/tautulli.
|
||||
* Change: Tautulli is now using a forked version of plexapi 3.6.0. This is to support the exporter feature while still maintaining Python 2 compatibility.
|
||||
* Change: Updated systemd script to remove process forking. (Thanks @MichaIng)
|
||||
* Change: Cache GitHub update check on startup.
|
||||
|
||||
|
||||
## v2.5.6 (2020-10-02)
|
||||
|
||||
* Activity:
|
||||
* Change: Renamed container "Transcode" to "Converting" on activity cards.
|
||||
* Notifications:
|
||||
* New: Added a silent notification option for Telegram. (Thanks @JohnnyKing94)
|
||||
* New: Added container_decision notification parameter.
|
||||
* New: Added notification trigger for Playback Error.
|
||||
* New: Added remote access down notification threshold setting.
|
||||
* Newsletter:
|
||||
* Change: Stop flooring newsletter start date.
|
||||
* UI:
|
||||
* Fix: Unable to purge history from the library edit modal.
|
||||
* Fix: QR code not showing up for localhost address when trying to register a device.
|
||||
* New: Added library name to the fix metadata modal.
|
||||
* API:
|
||||
* New: Added default thumb and art to the Live TV library.
|
||||
* Other:
|
||||
* Fix: Synced items not loading for guest access.
|
||||
* New: Schedule some more automatic database optimizations.
|
||||
* Change: Added automatic uninstall before installing to the Windows installer.
|
||||
|
||||
|
||||
## v2.5.5 (2020-09-06)
|
||||
|
||||
* Activity:
|
||||
* Fix: Filter out TV show background theme music sessions.
|
||||
* Notifications:
|
||||
* New: Check Plex external guids for notification metadata provider links.
|
||||
* UI:
|
||||
* Fix: Incorrect sorting for user/library recently played items.
|
||||
* API:
|
||||
* Fix: get_synced_items API command returning error with empty result.
|
||||
* Fix: Download API commands not returning the file.
|
||||
* Fix: get_logs API command encoding error.
|
||||
* Fix: get_user_player_stats API command returning error instead of empty result.
|
||||
* New: Added get_server_info API command.
|
||||
* New: Added external guids to get_metadata API command.
|
||||
* New: Added support for multi-column sorting for datatable API commands.
|
||||
* Change: get_activity API command return thumbnail override for clips.
|
||||
* Change: get_libraries_table API command return custom library artwork.
|
||||
* Other:
|
||||
* Fix: Tautulli failed to run with a stale pid file.
|
||||
* New: Added scheduled task to optimize the Tautulli database.
|
||||
* Change: Update plexapi to 3.6.0.
|
||||
* Change: Update some libraries for Python 3 compatibility.
|
||||
|
||||
|
||||
## v2.5.4 (2020-07-31)
|
||||
|
||||
* Monitoring:
|
||||
* Change: Montitoring remote access changed to use websockets. Refer to Tautulli/Tautulli-Issues#251 for details.
|
||||
* Notifications:
|
||||
* Fix: Uploading images to Cloudinary failed for titles with non-ASCII characters on Python 2.
|
||||
* New: Added plex_id notification parameter.
|
||||
* Remove: Running .exe files directly using script notifications is no longer supported.
|
||||
* Remove: php, perl, and ruby prefix overrides for script notifications is no longer supported.
|
||||
* Change: Stricter checking of file extensions for script notifications.
|
||||
* Change: Fallback to The Movie Database lookup using title and year.
|
||||
* Change: Fallback to TVmaze lookup using title.
|
||||
* UI:
|
||||
* New: Added ability to import a previous Tautullli configuration file in the settings.
|
||||
* New: Added a browse button for settings which require a folder or file input.
|
||||
* New: Added first streamed column to user IP addresses table. (Thanks @dotsam)
|
||||
* New: Added The Movie Database rating image to media page.
|
||||
* Change: Different icon to represent direct stream in the history tables.
|
||||
* API:
|
||||
* New: Updated API docs for importing a database and configuration file.
|
||||
|
||||
|
||||
## v2.5.3 (2020-07-10)
|
||||
|
||||
* History:
|
||||
* Fix: Unable to delete more than 1000 history entries at the same time.
|
||||
* Notifications:
|
||||
* Change: Python script notifications to run using the same Python interpreter as Tautulli.
|
||||
* Newsletters:
|
||||
* Fix: Unable to view newsletters with special characters.
|
||||
* Other:
|
||||
* Fix: Tautulli failing to start after enabling HTTPS when installed using the Windows / macOS installers.
|
||||
* Fix: Startup script not working on macOS.
|
||||
* Fix: Unable to hide dock icon on macOS with the pkg install. Refer to the FAQ regarding the Python rocket dock icon.
|
||||
* Change: Added path to Python interpreter in system startup (daemon) scripts.
|
||||
* Change: Added Python version to Google analytics.
|
||||
|
||||
|
||||
## v2.5.2 (2020-07-01)
|
||||
|
||||
* Announcements:
|
||||
* Tautulli now supports Python 3!
|
||||
* Python 2 is still supported for the time being, but it is recommended to upgrade to Python 3.
|
||||
* Notifications:
|
||||
* Fix: Error uploading images to Cloudinary on Python 2.
|
||||
* Fix: Testing browser notifications alert not disappearing.
|
||||
* Change: Default recently added notification delay set to 300 seconds.
|
||||
* UI:
|
||||
* Fix: MacOS menu bar icon causing Tautulli to fail to start.
|
||||
* Fix: Unable to login to Tautulli on Python 2.
|
||||
* New: Windows and MacOS setting to enable Tautulli to start automatically when you login.
|
||||
* New: Added menu bar icon for MacOS.
|
||||
* New: Ability to import a Tautulli database in the settings.
|
||||
* New: Added Tautulli news area on the settings page.
|
||||
* New: Added platform icon for LG devices.
|
||||
* Remove: Ability to login to Tautulli using a Plex username and password has been removed. Login using a Plex.tv account is only supported via OAuth.
|
||||
* Mobile App:
|
||||
* Fix: Improved API security and validation when registering the Android app.
|
||||
* Docker:
|
||||
* Fix: Docker container not respecting the PUID and PGID environment variables.
|
||||
* Other:
|
||||
* Fix: Error creating self-signed certificates on Python 3.
|
||||
* Fix: Tautulli login session cookie not set on the HTTP root path.
|
||||
* New: Windows and MacOS app installers to install Tautulli without needing Python installed.
|
||||
|
||||
|
||||
## v2.2.4 (2020-05-16)
|
||||
|
||||
* Monitoring:
|
||||
|
@@ -9,7 +9,7 @@ All pull requests should be based on the `nightly` branch, to minimize cross mer
|
||||
### Python Code
|
||||
|
||||
#### Compatibility
|
||||
The code should work with Python 2.7. Note that Tautulli runs on many different platforms.
|
||||
The code should work with Python 2.7.17 or Python 3.6+. Note that Tautulli runs on many different platforms.
|
||||
|
||||
Re-use existing code. Do not hesitate to add logging in your code. You can the logger module `plexpy.logger.*` for this. Web requests are invoked via `plexpy.request.*` and derived ones. Use these methods to automatically add proper and meaningful error handling.
|
||||
|
||||
@@ -38,4 +38,4 @@ HTML5 compatible browsers are targeted.
|
||||
* 4 space indentation
|
||||
* `methodName`
|
||||
* `variableName`
|
||||
* `ClassName`
|
||||
* `ClassName`
|
||||
|
@@ -1,4 +1,4 @@
|
||||
FROM tautulli/tautulli-baseimage:latest
|
||||
FROM tautulli/tautulli-baseimage:python3
|
||||
|
||||
LABEL maintainer="Tautulli"
|
||||
|
||||
@@ -11,13 +11,16 @@ ENV TZ=UTC
|
||||
WORKDIR /app
|
||||
|
||||
RUN \
|
||||
groupadd -g 1000 tautulli && \
|
||||
useradd -u 1000 -g 1000 tautulli && \
|
||||
echo ${BRANCH} > /app/branch.txt && \
|
||||
echo ${COMMIT} > /app/version.txt
|
||||
|
||||
COPY . /app
|
||||
|
||||
CMD [ "python", "Tautulli.py", "--datadir", "/config" ]
|
||||
ENTRYPOINT [ "./start.sh" ]
|
||||
|
||||
VOLUME /config
|
||||
EXPOSE 8181
|
||||
HEALTHCHECK --start-period=90s CMD curl -ILfSs http://localhost:8181/status > /dev/null || curl -ILfkSs https://localhost:8181/status > /dev/null || exit 1
|
||||
HEALTHCHECK --start-period=90s CMD curl -ILfSs http://localhost:8181/status > /dev/null || curl -ILfkSs https://localhost:8181/status > /dev/null || exit 1
|
||||
|
@@ -1,8 +1,4 @@
|
||||
#!/bin/sh
|
||||
''''which python >/dev/null 2>&1 && exec python "$0" "$@" # '''
|
||||
''''which python2 >/dev/null 2>&1 && exec python2 "$0" "$@" # '''
|
||||
''''which python2.7 >/dev/null 2>&1 && exec python2.7 "$0" "$@" # '''
|
||||
''''exec echo "Error: Python not found!" # '''
|
||||
#!/usr/bin/env python
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
@@ -29,14 +29,17 @@ This project is based on code from [Headphones](https://github.com/rembo10/headp
|
||||
|
||||
## Installation & Support
|
||||
|
||||
[](https://python.org/downloads/release/python-2717/)
|
||||
[](https://python.org/downloads)
|
||||
[](https://hub.docker.com/r/tautulli/tautulli)
|
||||
[](https://hub.docker.com/r/tautulli/tautulli)
|
||||
[](https://github.com/Tautulli/Tautulli/releases/latest)
|
||||
|
||||
| Status | Branch: `master` | Branch: `beta` | Branch: `nightly` |
|
||||
| --- | --- | --- | --- |
|
||||
| Release | [](https://github.com/Tautulli/Tautulli/releases/latest) <br> [](https://github.com/Tautulli/Tautulli/releases/latest) | [](https://github.com/Tautulli/Tautulli/releases) <br> [](https://github.com/Tautulli/Tautulli/commits/beta) | [](https://github.com/Tautulli/Tautulli/commits/nightly) <br> [](https://github.com/Tautulli/Tautulli/commits/nightly) |
|
||||
| Docker | [](https://hub.docker.com/r/tautulli/tautulli) <br> [](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Docker"+branch%3Amaster) | [](https://hub.docker.com/r/tautulli/tautulli) <br> [](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Docker"+branch%3Abeta) | [](https://hub.docker.com/r/tautulli/tautulli) <br> [](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Docker"+branch%3Anightly) |
|
||||
| Release | [](https://github.com/Tautulli/Tautulli/releases/latest) <br> [](https://github.com/Tautulli/Tautulli/releases/latest) | [](https://github.com/Tautulli/Tautulli/releases) <br> [](https://github.com/Tautulli/Tautulli/commits/beta) | [](https://github.com/Tautulli/Tautulli/commits/nightly) <br> [](https://github.com/Tautulli/Tautulli/commits/nightly) |
|
||||
| Docker | [](https://hub.docker.com/r/tautulli/tautulli) <br> [](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Docker"+branch%3Amaster) | [](https://hub.docker.com/r/tautulli/tautulli) <br> [](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Docker"+branch%3Abeta) | [](https://hub.docker.com/r/tautulli/tautulli) <br> [](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Docker"+branch%3Anightly) |
|
||||
| Snap | [](https://snapcraft.io/tautulli) <br> [](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Snap"+branch%3Amaster) | [](https://snapcraft.io/tautulli) <br> [](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Snap"+branch%3Abeta) | [](https://snapcraft.io/tautulli) <br> [](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Snap"+branch%3Anightly) |
|
||||
| Installer | [](https://github.com/Tautulli/Tautulli/releases/latest) <br> [](https://github.com/Tautulli/Tautulli/releases/latest) <br> [](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Installers"+branch%3Amaster) | [](https://github.com/Tautulli/Tautulli/releases) <br> [](https://github.com/Tautulli/Tautulli/releases) <br> [](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Installers"+branch%3Abeta) | [](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Installers"+branch%3Anightly) |
|
||||
|
||||
[](https://github.com/Tautulli/Tautulli-Wiki/wiki)
|
||||
[](https://tautulli.com/discord)
|
||||
|
85
Tautulli.py
@@ -1,8 +1,4 @@
|
||||
#!/bin/sh
|
||||
''''which python >/dev/null 2>&1 && exec python "$0" "$@" # '''
|
||||
''''which python2 >/dev/null 2>&1 && exec python2 "$0" "$@" # '''
|
||||
''''which python2.7 >/dev/null 2>&1 && exec python2.7 "$0" "$@" # '''
|
||||
''''exec echo "Error: Python not found!" # '''
|
||||
#!/usr/bin/env python
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
@@ -27,17 +23,24 @@ import sys
|
||||
# Ensure lib added to path, before any other imports
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'lib'))
|
||||
|
||||
from future.builtins import str
|
||||
|
||||
import appdirs
|
||||
import argparse
|
||||
import datetime
|
||||
import locale
|
||||
import pytz
|
||||
import signal
|
||||
import time
|
||||
import threading
|
||||
import tzlocal
|
||||
|
||||
import plexpy
|
||||
from plexpy import config, database, helpers, logger, webstart
|
||||
|
||||
from plexpy import common, config, database, helpers, logger, webstart
|
||||
if common.PLATFORM == 'Windows':
|
||||
from plexpy import windows
|
||||
elif common.PLATFORM == 'Darwin':
|
||||
from plexpy import macos
|
||||
|
||||
# Register signals, such as CTRL + C
|
||||
signal.signal(signal.SIGINT, plexpy.sig_handler)
|
||||
@@ -51,12 +54,14 @@ def main():
|
||||
"""
|
||||
|
||||
# Fixed paths to Tautulli
|
||||
if hasattr(sys, 'frozen'):
|
||||
if hasattr(sys, 'frozen') and hasattr(sys, '_MEIPASS'):
|
||||
plexpy.FROZEN = True
|
||||
plexpy.FULL_PATH = os.path.abspath(sys.executable)
|
||||
plexpy.PROG_DIR = sys._MEIPASS
|
||||
else:
|
||||
plexpy.FULL_PATH = os.path.abspath(__file__)
|
||||
plexpy.PROG_DIR = os.path.dirname(plexpy.FULL_PATH)
|
||||
|
||||
plexpy.PROG_DIR = os.path.dirname(plexpy.FULL_PATH)
|
||||
plexpy.ARGS = sys.argv[1:]
|
||||
|
||||
# From sickbeard
|
||||
@@ -119,15 +124,16 @@ def main():
|
||||
|
||||
if helpers.bool_true(os.getenv('TAUTULLI_DOCKER', False)):
|
||||
plexpy.DOCKER = True
|
||||
if helpers.bool_true(os.getenv('TAUTULLI_SNAP', False)):
|
||||
plexpy.SNAP = True
|
||||
|
||||
if args.dev:
|
||||
plexpy.DEV = True
|
||||
logger.debug(u"Tautulli is running in the dev environment.")
|
||||
logger.debug("Tautulli is running in the dev environment.")
|
||||
|
||||
if args.daemon:
|
||||
if sys.platform == 'win32':
|
||||
sys.stderr.write(
|
||||
"Daemonizing not supported under Windows, starting normally\n")
|
||||
logger.warn("Daemonizing not supported under Windows, starting normally")
|
||||
else:
|
||||
plexpy.DAEMON = True
|
||||
plexpy.QUIET = True
|
||||
@@ -145,11 +151,13 @@ def main():
|
||||
try:
|
||||
with open(plexpy.PIDFILE, 'r') as fp:
|
||||
pid = int(fp.read())
|
||||
os.kill(pid, 0)
|
||||
except IOError as e:
|
||||
raise SystemExit("Unable to read PID file: %s", e)
|
||||
|
||||
try:
|
||||
os.kill(pid, 0)
|
||||
except OSError:
|
||||
logger.warn("PID file '%s' already exists, but PID %d is " \
|
||||
logger.warn("PID file '%s' already exists, but PID %d is "
|
||||
"not running. Ignoring PID file." %
|
||||
(plexpy.PIDFILE, pid))
|
||||
else:
|
||||
@@ -175,6 +183,8 @@ def main():
|
||||
# Determine which data directory and config file to use
|
||||
if args.datadir:
|
||||
plexpy.DATA_DIR = args.datadir
|
||||
elif plexpy.FROZEN:
|
||||
plexpy.DATA_DIR = appdirs.user_data_dir("Tautulli", False)
|
||||
else:
|
||||
plexpy.DATA_DIR = plexpy.PROG_DIR
|
||||
|
||||
@@ -229,25 +239,51 @@ def main():
|
||||
try:
|
||||
import OpenSSL
|
||||
except ImportError:
|
||||
logger.warn("The pyOpenSSL module is missing. Install this " \
|
||||
logger.warn("The pyOpenSSL module is missing. Install this "
|
||||
"module to enable HTTPS. HTTPS will be disabled.")
|
||||
plexpy.CONFIG.ENABLE_HTTPS = False
|
||||
|
||||
# Try to start the server. Will exit here is address is already in use.
|
||||
webstart.start()
|
||||
|
||||
# Windows system tray icon
|
||||
if os.name == 'nt' and plexpy.CONFIG.WIN_SYS_TRAY:
|
||||
plexpy.win_system_tray()
|
||||
|
||||
logger.info("Tautulli is ready!")
|
||||
if common.PLATFORM == 'Windows':
|
||||
if plexpy.CONFIG.SYS_TRAY_ICON:
|
||||
plexpy.WIN_SYS_TRAY_ICON = windows.WindowsSystemTray()
|
||||
plexpy.WIN_SYS_TRAY_ICON.start()
|
||||
windows.set_startup()
|
||||
elif common.PLATFORM == 'Darwin':
|
||||
macos.set_startup()
|
||||
|
||||
# Open webbrowser
|
||||
if plexpy.CONFIG.LAUNCH_BROWSER and not args.nolaunch and not plexpy.DEV:
|
||||
plexpy.launch_browser(plexpy.CONFIG.HTTP_HOST, plexpy.HTTP_PORT,
|
||||
plexpy.HTTP_ROOT)
|
||||
|
||||
# Wait endlessy for a signal to happen
|
||||
if common.PLATFORM == 'Darwin' and plexpy.CONFIG.SYS_TRAY_ICON:
|
||||
if not macos.HAS_PYOBJC:
|
||||
logger.warn("The pyobjc module is missing. Install this "
|
||||
"module to enable the MacOS menu bar icon.")
|
||||
plexpy.CONFIG.SYS_TRAY_ICON = False
|
||||
|
||||
if plexpy.CONFIG.SYS_TRAY_ICON:
|
||||
# MacOS menu bar icon must be run on the main thread and is blocking
|
||||
# Start the rest of Tautulli on a new thread
|
||||
thread = threading.Thread(target=wait)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
plexpy.MAC_SYS_TRAY_ICON = macos.MacOSSystemTray()
|
||||
plexpy.MAC_SYS_TRAY_ICON.start()
|
||||
else:
|
||||
wait()
|
||||
else:
|
||||
wait()
|
||||
|
||||
|
||||
def wait():
|
||||
logger.info("Tautulli is ready!")
|
||||
|
||||
# Wait endlessly for a signal to happen
|
||||
while True:
|
||||
if not plexpy.SIGNAL:
|
||||
try:
|
||||
@@ -265,11 +301,14 @@ def main():
|
||||
plexpy.shutdown(restart=True, checkout=True)
|
||||
elif plexpy.SIGNAL == 'reset':
|
||||
plexpy.shutdown(restart=True, reset=True)
|
||||
else:
|
||||
elif plexpy.SIGNAL == 'update':
|
||||
plexpy.shutdown(restart=True, update=True)
|
||||
else:
|
||||
logger.error('Unknown signal. Shutting down...')
|
||||
plexpy.shutdown()
|
||||
|
||||
plexpy.SIGNAL = None
|
||||
|
||||
# Call main()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Display information
|
||||
echo "This script will remove *.pyc files. These files are generated by Python, but they can cause conflicts after an upgrade. It's safe to remove them, because they will be regenerated."
|
||||
|
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Parameter check
|
||||
if [ -z "$1" ]; then
|
||||
|
@@ -5,46 +5,105 @@
|
||||
<h4 class="modal-title">Import ${app} Database</h4>
|
||||
</div>
|
||||
<div class="modal-body" id="modal-text">
|
||||
<p class="help-block">
|
||||
<%
|
||||
v = ''
|
||||
if app == 'PlexWatch':
|
||||
v = '0.3.2'
|
||||
elif app == 'Plexivity':
|
||||
v = '0.9.8'
|
||||
%>
|
||||
<strong>Please ensure your ${app} database is at version ${v} or higher.</strong>
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<label for="db_location">Database Location</label>
|
||||
<div class="row">
|
||||
<div class="col-xs-8">
|
||||
<input type="text" class="form-control" id="db_location" name="db_location" value="" required>
|
||||
<form id="import_database_form" enctype="multipart/form-data" method="post" name="import_database_form">
|
||||
<input type="hidden" id="import_app" name="import_app" value="${app.lower()}" />
|
||||
% if app in ('PlexWatch', 'Plexivity'):
|
||||
<p class="help-block">
|
||||
<%
|
||||
v = ''
|
||||
if app == 'PlexWatch':
|
||||
v = '0.3.2'
|
||||
elif app == 'Plexivity':
|
||||
v = '0.9.8'
|
||||
%>
|
||||
<strong>Please ensure your ${app} database is at version ${v} or higher.</strong>
|
||||
</p>
|
||||
% endif
|
||||
<div class="form-group">
|
||||
<label for="import_database_file">Option 1: Upload a Database File</label>
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="input-group">
|
||||
<label for="import_database_file" class="input-group-btn">
|
||||
<span class="btn btn-form">Upload</span>
|
||||
<input type="file" style="display: none;" id="import_database_file" name="import_database_file" required>
|
||||
</label>
|
||||
<input id="import_database_file_name" type="text" class="form-control" placeholder="tautulli.db" disabled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">Upload the ${app} database file you wish to import.</p>
|
||||
</div>
|
||||
<p class="help-block">Enter the path and file name for the ${app} database you wish to import.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="table_name">Table Name</label>
|
||||
<div class="row">
|
||||
<div class="col-xs-4">
|
||||
<select id="table_name" class="form-control" name="table_name">
|
||||
<option value="processed">processed</option>
|
||||
<option value="grouped">grouped</option>
|
||||
</select>
|
||||
<div class="form-group">
|
||||
<label for="import_database_path">Option 2: Browse for a Database File</label>
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="input-group">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-form" type="button" id="import_database_path_browse" data-toggle="browse" data-description="Database File" data-filter=".db" data-target="#import_database_path">Browse</button>
|
||||
</span>
|
||||
<input type="text" class="form-control" id="import_database_path" name="import_database_path" value="" placeholder="tautulli.db" required disabled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">Browse for the ${app} database file you wish to import.</p>
|
||||
</div>
|
||||
<p class="help-block">The table name from which you wish to import. Only import one of these, importing both will result in duplicated data.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="import_ignore_interval">Ignore Interval</label>
|
||||
<div class="row">
|
||||
<div class="col-xs-2">
|
||||
<input type="text" class="form-control" id="import_ignore_interval" name="import_ignore_interval" value="120" required>
|
||||
% if app == 'Tautulli':
|
||||
<div class="form-group">
|
||||
<label for="table_name">Import Method</label>
|
||||
<div class="row">
|
||||
<div class="col-xs-4">
|
||||
<select class="form-control" id="import_method" name="import_method">
|
||||
<option value="merge">Merge</option>
|
||||
<option value="overwrite">Overwrite</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">Select how you would like to import the Tautulli history.</p>
|
||||
<ul class="help-block" style="padding-inline-start: 15px;">
|
||||
<li><strong>Merge</strong> will add all history and remove any duplicates from the imported database into the current database.</li>
|
||||
<li><strong>Overwrite</strong> will replace all history in the current database with the imported database.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p class="help-block">Enter the minimum duration (in seconds) an item must have been active for. Set to 0 to import all.</p>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="import_backup_db" id="import_backup_db" value="1" checked> Backup Current Database
|
||||
</label>
|
||||
<p class="help-block">Automatically create a backup of the current database before importing.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Import Notes</label>
|
||||
<p class="help-block">The following data will also be imported:</p>
|
||||
<ul class="help-block" style="padding-inline-start: 15px;">
|
||||
<li>Libraries and Users</li>
|
||||
<li>Notification / Newsletter Agents</li>
|
||||
<li>Registered Mobile Devices</li>
|
||||
</ul>
|
||||
</div>
|
||||
% else:
|
||||
<div class="form-group">
|
||||
<label for="import_table_name">Table Name</label>
|
||||
<div class="row">
|
||||
<div class="col-xs-4">
|
||||
<select class="form-control" id="import_table_name" name="import_table_name">
|
||||
<option value="processed">Processed</option>
|
||||
<option value="grouped">Grouped</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">Select the table name from which you wish to import. Only import one of these, importing both will result in duplicated data.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="import_ignore_interval">Ignore Interval</label>
|
||||
<div class="row">
|
||||
<div class="col-xs-2">
|
||||
<input type="text" class="form-control" id="import_ignore_interval" name="import_ignore_interval" value="120" required>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">Enter the minimum duration (in seconds) an item must have been active for. Set to 0 to import all.</p>
|
||||
</div>
|
||||
% endif
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div>
|
||||
@@ -55,24 +114,87 @@
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
// Send database path to import script
|
||||
$("#import_database_file").change(function() {
|
||||
if ($(this)[0].files[0]) {
|
||||
$("#import_database_file_name").val($(this)[0].files[0].name);
|
||||
}
|
||||
});
|
||||
|
||||
$("#import_db").click(function() {
|
||||
var database_path = $("#db_location").val();
|
||||
var table_name = $("#table_name").val();
|
||||
var import_ignore_interval = $("#import_ignore_interval").val();
|
||||
$(this).prop('disabled', true);
|
||||
|
||||
var app = $("#import_app").val();
|
||||
var database_file = $("#import_database_file")[0].files[0];
|
||||
var database_path = $("#import_database_path").val();
|
||||
var method = $("#import_method").val();
|
||||
var backup = $("#import_backup_db").is(':checked');
|
||||
var table_name = $("#import_table_name").val();
|
||||
var ignore_interval = $("#import_ignore_interval").val();
|
||||
|
||||
var content_type;
|
||||
var process_data;
|
||||
var data;
|
||||
|
||||
if (database_file) {
|
||||
content_type = false;
|
||||
process_data = false;
|
||||
data = new FormData();
|
||||
data.append('app', app);
|
||||
data.append('database_file', database_file);
|
||||
data.append('method', method);
|
||||
data.append('backup', backup);
|
||||
data.append('table_name', table_name);
|
||||
data.append('ignore_interval', ignore_interval);
|
||||
} else {
|
||||
content_type = 'application/x-www-form-urlencoded; charset=UTF-8';
|
||||
process_data = true;
|
||||
data = {
|
||||
app: app,
|
||||
database_path: database_path,
|
||||
method: method,
|
||||
backup: backup,
|
||||
table_name: table_name,
|
||||
ignore_interval: ignore_interval
|
||||
}
|
||||
}
|
||||
|
||||
if (database_file) {
|
||||
$("#status-message").html('<i class="fa fa-fw fa-spin fa-refresh"></i> Uploading database file...');
|
||||
} else {
|
||||
$("#status-message").html('<i class="fa fa-fw fa-spin fa-refresh"></i>');
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: 'import_database',
|
||||
data: {
|
||||
app: "${app}",
|
||||
database_path: database_path,
|
||||
table_name: table_name,
|
||||
import_ignore_interval: import_ignore_interval
|
||||
},
|
||||
type: 'POST',
|
||||
data: data,
|
||||
cache: false,
|
||||
async: true,
|
||||
contentType: content_type,
|
||||
processData: process_data,
|
||||
success: function(data) {
|
||||
$("#status-message").html(data);
|
||||
$("#db_location").val('')
|
||||
var msg;
|
||||
if (data.result === 'success') {
|
||||
msg = "<i class='fa fa-check'></i> " + data.message;
|
||||
} else {
|
||||
msg = "<i class='fa fa-exclamation-triangle'></i> " + data.message;
|
||||
}
|
||||
$("#status-message").html(msg);
|
||||
$("#import_database_file").val(null);
|
||||
$("#import_database_file_name").val('');
|
||||
$("#import_database_path").val('');
|
||||
},
|
||||
error: function (xhr) {
|
||||
var msg = "<i class='fa fa-exclamation-triangle'></i> Error (" + xhr.status + "): ";
|
||||
if (xhr.status === 413) {
|
||||
msg += "file is too large to upload"
|
||||
} else {
|
||||
msg += 'try again'
|
||||
}
|
||||
$("#status-message").html(msg);
|
||||
},
|
||||
complete: function(xhr) {
|
||||
$("#import_db").prop('disabled', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@@ -13,8 +13,10 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<link href="${http_root}css/bootstrap3/bootstrap.css" rel="stylesheet">
|
||||
<link href="${http_root}css/bootstrap3/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="${http_root}css/pnotify.custom.min.css" rel="stylesheet" />
|
||||
<link href="${http_root}css/selectize.bootstrap3.css" rel="stylesheet" />
|
||||
<link href="${http_root}css/selectize.min.css" rel="stylesheet" />
|
||||
<link href="${http_root}css/tautulli.css${cache_param}" rel="stylesheet">
|
||||
<link href="${http_root}css/opensans.min.css" rel="stylesheet">
|
||||
<link href="${http_root}css/font-awesome.all.min.css" rel="stylesheet">
|
||||
@@ -22,21 +24,21 @@
|
||||
${next.headIncludes()}
|
||||
|
||||
<!-- Favicons -->
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="${http_root}images/favicon/favicon-32x32.png?v=2.0.5">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="${http_root}images/favicon/favicon-16x16.png?v=2.0.5">
|
||||
<link rel="shortcut icon" href="${http_root}images/favicon/favicon.ico?v=2.0.5">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="${http_root}images/favicon/favicon-32x32.png?v=2.6.0">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="${http_root}images/favicon/favicon-16x16.png?v=2.6.0">
|
||||
<link rel="shortcut icon" href="${http_root}images/favicon/favicon.ico?v=2.6.0">
|
||||
|
||||
<!-- ICONS -->
|
||||
<!-- Android -->
|
||||
<link rel="manifest" href="${http_root}images/favicon/manifest.json?v=2.0.5" crossorigin="use-credentials">
|
||||
<link rel="manifest" href="${http_root}images/favicon/manifest.json?v=2.6.0" crossorigin="use-credentials">
|
||||
<meta name="theme-color" content="#282a2d">
|
||||
<!-- Apple -->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="${http_root}images/favicon/apple-touch-icon.png?v=2.0.5">
|
||||
<link rel="mask-icon" href="${http_root}images/favicon/safari-pinned-tab.svg?v=2.0.5" color="#282a2d">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="${http_root}images/favicon/apple-touch-icon.png?v=2.6.0">
|
||||
<link rel="mask-icon" href="${http_root}images/favicon/safari-pinned-tab.svg?v=2.6.0" color="#282a2d">
|
||||
<meta name="apple-mobile-web-app-title" content="Tautulli">
|
||||
<!-- Microsoft -->
|
||||
<meta name="application-name" content="Tautulli">
|
||||
<meta name="msapplication-config" content="${http_root}images/favicon/browserconfig.xml?v=2.0.5">
|
||||
<meta name="msapplication-config" content="${http_root}images/favicon/browserconfig.xml?v=2.6.0">
|
||||
</head>
|
||||
|
||||
<body class="content">
|
||||
@@ -48,15 +50,19 @@
|
||||
% if plexpy.UPDATE_AVAILABLE is None:
|
||||
You are running an unknown version of Tautulli.<br />
|
||||
% elif plexpy.UPDATE_AVAILABLE == 'release':
|
||||
A <a href="${anon_url('https://github.com/%s/%s/releases/tag/%s' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO, plexpy.LATEST_RELEASE))}" target="_blank">
|
||||
A <a href="${anon_url('https://github.com/%s/%s/releases/tag/%s' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO, plexpy.LATEST_RELEASE))}" target="_blank" rel="noreferrer">
|
||||
new release (${plexpy.LATEST_RELEASE})</a> of Tautulli is available!<br />
|
||||
% elif plexpy.UPDATE_AVAILABLE == 'commit':
|
||||
A <a href="${anon_url('https://github.com/%s/%s/compare/%s...%s' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO, plexpy.CURRENT_VERSION, plexpy.LATEST_VERSION))}" target="_blank">
|
||||
A <a href="${anon_url('https://github.com/%s/%s/compare/%s...%s' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO, plexpy.CURRENT_VERSION, plexpy.LATEST_VERSION))}" target="_blank" rel="noreferrer">
|
||||
newer version</a> of Tautulli is available!<br />
|
||||
You are ${plexpy.COMMITS_BEHIND} commit${'s' if plexpy.COMMITS_BEHIND > 1 else ''} behind.<br />
|
||||
% endif
|
||||
% if plexpy.DOCKER:
|
||||
% if plexpy.INSTALL_TYPE == 'docker':
|
||||
Update your Docker container or <a href="#" id="updateDismiss">Dismiss</a>
|
||||
% elif plexpy.INSTALL_TYPE == 'snap':
|
||||
Update your Snap package or <a href="#" id="updateDismiss">Dismiss</a>
|
||||
% elif plexpy.INSTALL_TYPE in ('windows', 'macos'):
|
||||
<a href="${anon_url('https://github.com/%s/%s/releases/tag/%s' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO, plexpy.LATEST_RELEASE))}" target="_blank" rel="noreferrer">Download</a> and install the latest version or <a href="#" id="updateDismiss">Dismiss</a>
|
||||
% else:
|
||||
<a href="update">Update</a> or <a href="#" id="updateDismiss">Dismiss</a>
|
||||
% endif
|
||||
@@ -134,7 +140,7 @@
|
||||
<li><a href="settings"><i class="fa fa-fw fa-cogs"></i> Settings</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li><a href="logs"><i class="fa fa-fw fa-list-alt"></i> View Logs</a></li>
|
||||
<li><a href="${anon_url('https://github.com/%s/%s-Wiki/wiki/Frequently-Asked-Questions' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}" target="_blank"><i class="fa fa-fw fa-question-circle"></i> FAQ</a></li>
|
||||
<li><a href="${anon_url('https://github.com/%s/%s-Wiki/wiki/Frequently-Asked-Questions' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}" target="_blank" rel="noreferrer"><i class="fa fa-fw fa-question-circle"></i> FAQ</a></li>
|
||||
<li><a href="support"><i class="fa fa-fw fa-comment"></i> Support</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li><a href="#" data-target="#donate-modal" data-toggle="modal"><i class="fa fa-fw fa-heart"></i> Donate</a></li>
|
||||
@@ -200,7 +206,7 @@ ${next.modalIncludes()}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<span id="incorrect-login" style="padding-right: 25px; display: none;">Incorrect username or password.</span>
|
||||
<span id="sign-in-alert" style="padding-right: 25px; display: none;"></span>
|
||||
<button id="sign-in" type="submit" class="btn btn-bright login-button"><i class="fa fa-sign-in"></i> Sign In</button>
|
||||
</div>
|
||||
<input type="hidden" id="admin_login" name="admin_login" value="1" />
|
||||
@@ -228,32 +234,32 @@ ${next.modalIncludes()}
|
||||
</div>
|
||||
</div>
|
||||
<ul id="donation_type" class="nav nav-pills" role="tablist" style="display: flex; justify-content: center; margin: 10px 0;">
|
||||
<li class="active"><a href="#patreon-donation" role="tab" data-toggle="tab">Patreon</a></li>
|
||||
<li><a href="#github-donation" role="tab" data-toggle="tab">GitHub</a></li>
|
||||
<li class="active"><a href="#github-donation" role="tab" data-toggle="tab">GitHub</a></li>
|
||||
<li><a href="#patreon-donation" role="tab" data-toggle="tab">Patreon</a></li>
|
||||
<li><a href="#paypal-donation" role="tab" data-toggle="tab">PayPal</a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="patreon-donation" style="text-align: center">
|
||||
<p>
|
||||
Click the button below to continue to Patreon.
|
||||
</p>
|
||||
<a href="${anon_url('https://www.patreon.com/join/tautulli')}" target="_blank">
|
||||
<img src="images/become_a_patron_button.png" alt="Become a Patron" height="40">
|
||||
</a>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="github-donation" style="text-align: center">
|
||||
<div role="tabpanel" class="tab-pane active" id="github-donation" style="text-align: center">
|
||||
<p>
|
||||
Click the button below to continue to GitHub.
|
||||
</p>
|
||||
<a href="${anon_url('https://github.com/sponsors/JonnyWong16')}" target="_blank" class="btn btn-sm btn-default" style="font-weight: 600;">
|
||||
<a href="${anon_url('https://github.com/sponsors/JonnyWong16')}" target="_blank" rel="noreferrer" class="btn btn-sm btn-default" style="font-weight: 600;">
|
||||
<i class="fa fa-heart fa-sm" style="color: #ea4aaa;"></i> Sponsor
|
||||
</a>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="patreon-donation" style="text-align: center">
|
||||
<p>
|
||||
Click the button below to continue to Patreon.
|
||||
</p>
|
||||
<a href="${anon_url('https://www.patreon.com/join/tautulli')}" target="_blank" rel="noreferrer">
|
||||
<img src="images/become_a_patron_button.png" alt="Become a Patron" width="170" height="40">
|
||||
</a>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="paypal-donation" style="text-align: center">
|
||||
<p>
|
||||
Click the button below to continue to PayPal.
|
||||
</p>
|
||||
<a href="${anon_url('https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=6XPPKTDSX9QFL&lc=US&item_name=Tautulli¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_LG%2egif%3aNonHosted')}" target="_blank">
|
||||
<a href="${anon_url('https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=6XPPKTDSX9QFL&lc=US&item_name=Tautulli¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_LG%2egif%3aNonHosted')}" target="_blank" rel="noreferrer">
|
||||
<img src="images/gold-rect-paypal-34px.png" alt="PayPal">
|
||||
</a>
|
||||
</div>
|
||||
@@ -286,17 +292,18 @@ ${next.modalIncludes()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="${http_root}js/jquery-2.1.4.min.js"></script>
|
||||
<script src="${http_root}js/jquery-3.5.1.min.js"></script>
|
||||
<script src="${http_root}js/bootstrap.min.js"></script>
|
||||
<script src="${http_root}js/bootstrap-hover-dropdown.min.js"></script>
|
||||
<script src="${http_root}js/moment-with-locales.min.js"></script>
|
||||
<script src="${http_root}js/moment-duration-format.min.js"></script>
|
||||
<script src="${http_root}js/pnotify.custom.min.js"></script>
|
||||
<script src="${http_root}js/platform.min.js"></script>
|
||||
<script src="${http_root}js/ipaddr.min.js"></script>
|
||||
<script src="${http_root}js/script.js${cache_param}"></script>
|
||||
<script src="${http_root}js/selectize.min.js"></script>
|
||||
<script src="${http_root}js/jquery.tripleclick.min.js"></script>
|
||||
% if _session['user_group'] == 'admin' and BROWSER_NOTIFIERS:
|
||||
<script src="${http_root}js/script.js${cache_param}"></script>
|
||||
<script src="${http_root}js/ajaxNotifications.js"></script>
|
||||
% endif
|
||||
<script>
|
||||
% if _session['user_group'] == 'admin':
|
||||
$('body').on('click', '#updateDismiss', function() {
|
||||
@@ -325,13 +332,17 @@ ${next.modalIncludes()}
|
||||
if (result.update === null) {
|
||||
msg = 'You are running an unknown version of Tautulli.<br />';
|
||||
} else if (result.update === true && result.release === true) {
|
||||
msg = 'A <a href="' + result.release_url + '" target="_blank">new release (' + result.latest_release + ')</a> of Tautulli is available!<br />';
|
||||
msg = 'A <a href="' + result.release_url + '" target="_blank" rel="noreferrer">new release (' + result.latest_release + ')</a> of Tautulli is available!<br />';
|
||||
} else if (result.update === true && result.release === false) {
|
||||
msg = 'A <a href="' + result.compare_url + '" target="_blank">newer version</a> of Tautulli is available!<br />' +
|
||||
msg = 'A <a href="' + result.compare_url + '" target="_blank" rel="noreferrer">newer version</a> of Tautulli is available!<br />' +
|
||||
'You are '+ result.commits_behind + ' commit' + (result.commits_behind > 1 ? 's' : '') + ' behind.<br />';
|
||||
}
|
||||
if (result.docker) {
|
||||
if (result.install_type === 'docker') {
|
||||
msg += 'Update your Docker container or <a href="#" id="updateDismiss">Dismiss</a>';
|
||||
} else if (result.install_type === 'snap') {
|
||||
msg += 'Update your Snap package or <a href="#" id="updateDismiss">Dismiss</a>';
|
||||
} else if (result.install_type === 'windows' || result.install_type === 'macos') {
|
||||
msg += '<a href="' + result.release_url + '" target="_blank" rel="noreferrer">Download</a> and install the latest version or <a href="#" id="updateDismiss">Dismiss</a>'
|
||||
} else {
|
||||
msg += '<a href="update">Update</a> or <a href="#" id="updateDismiss">Dismiss</a>';
|
||||
}
|
||||
@@ -419,6 +430,10 @@ ${next.modalIncludes()}
|
||||
$(document).on('hidden.bs.modal', '.modal', function () {
|
||||
$('.modal:visible').length && $(document.body).addClass('modal-open');
|
||||
});
|
||||
|
||||
% if _session['user_group'] == 'admin' and BROWSER_NOTIFIERS:
|
||||
check_notifications();
|
||||
% endif
|
||||
});
|
||||
|
||||
% if _session['user_group'] != 'admin':
|
||||
@@ -435,12 +450,16 @@ ${next.modalIncludes()}
|
||||
data: $(this).serialize(),
|
||||
dataType: 'json',
|
||||
statusCode: {
|
||||
200: function() {
|
||||
200: function(xhr, status) {
|
||||
window.location = "${http_root}";
|
||||
},
|
||||
401: function() {
|
||||
$('#incorrect-login').show();
|
||||
$('#username').focus();
|
||||
401: function(xhr, status) {
|
||||
$('#sign-in-alert').text('Incorrect username or password.').show();
|
||||
$('#username').focus();
|
||||
},
|
||||
429: function(xhr, status) {
|
||||
var retry = Math.ceil(xhr.getResponseHeader('Retry-After') / 60)
|
||||
$('#sign-in-alert').text('Too many login attempts. Try again in ' + retry + ' minute(s).').show();
|
||||
}
|
||||
},
|
||||
complete: function() {
|
||||
|
138
data/interfaces/default/config_import.html
Normal file
@@ -0,0 +1,138 @@
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
|
||||
<h4 class="modal-title">${title}</h4>
|
||||
</div>
|
||||
<div class="modal-body" id="modal-text">
|
||||
<form id="import_config_form" enctype="multipart/form-data" method="post" name="import_config_form">
|
||||
<div class="form-group">
|
||||
<label for="import_config_file">Option 1: Upload a Configuration File</label>
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="input-group">
|
||||
<label for="import_config_file" class="input-group-btn">
|
||||
<span class="btn btn-form">Upload</span>
|
||||
<input type="file" style="display: none;" id="import_config_file" name="import_config_file" required>
|
||||
</label>
|
||||
<input id="import_config_file_name" type="text" class="form-control" placeholder="config.ini" disabled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">Upload the Tautulli configuration file you wish to import.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="import_config_path">Option 2: Browse for a Configuration File</label>
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="input-group">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-form" type="button" id="import_config_path_browse" data-toggle="browse" data-description="Configuration File" data-filter=".ini" data-target="#import_config_path">Browse</button>
|
||||
</span>
|
||||
<input type="text" class="form-control" id="import_config_path" name="import_config_path" value="" placeholder="config.ini" required disabled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">Browse for the Tautulli configuration file you wish to import.</p>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="import_backup_config" id="import_backup_config" value="1" checked> Backup Current Configuration
|
||||
</label>
|
||||
<p class="help-block">Automatically create a backup of the current configuration before importing.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Import Notes</label>
|
||||
<p class="help-block">The following settings will <em>not</em> be imported:</p>
|
||||
<ul class="help-block" style="padding-inline-start: 15px;">
|
||||
<li>Git Path, Log / Backup / Cache Directory, Plex Logs Folder</li>
|
||||
<li>Custom Newsletter Templates Folder, Newsletter Output Directory</li>
|
||||
<li>HTTP Host / Port / Root / Username / Password</li>
|
||||
<li>Enable HTTPS, HTTPS Certificate / Certificate Chain / Key</li>
|
||||
</ul>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div>
|
||||
<span id="status-message" style="padding-right: 25px;"></span>
|
||||
<input type="button" id="import_config" class="btn btn-bright" value="Import">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$("#import_config_file").change(function() {
|
||||
if ($(this)[0].files[0]) {
|
||||
$("#import_config_file_name").val($(this)[0].files[0].name);
|
||||
}
|
||||
});
|
||||
|
||||
$("#import_config").click(function() {
|
||||
$(this).prop('disabled', true);
|
||||
|
||||
var config_file = $("#import_config_file")[0].files[0];
|
||||
var config_path = $("#import_config_path").val();
|
||||
var backup = $("#import_backup_config").is(':checked');
|
||||
|
||||
var content_type;
|
||||
var process_data;
|
||||
var data;
|
||||
|
||||
if (config_file) {
|
||||
content_type = false;
|
||||
process_data = false;
|
||||
data = new FormData();
|
||||
data.append('config_file', config_file);
|
||||
data.append('backup', backup);
|
||||
} else {
|
||||
content_type = 'application/x-www-form-urlencoded; charset=UTF-8';
|
||||
process_data = true;
|
||||
data = {
|
||||
config_path: config_path,
|
||||
backup: backup
|
||||
}
|
||||
}
|
||||
|
||||
if (config_file) {
|
||||
$("#status-message").html('<i class="fa fa-fw fa-spin fa-refresh"></i> Uploading config file...');
|
||||
} else {
|
||||
$("#status-message").html('<i class="fa fa-fw fa-spin fa-refresh"></i>');
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: 'import_config',
|
||||
type: 'POST',
|
||||
data: data,
|
||||
cache: false,
|
||||
async: true,
|
||||
contentType: content_type,
|
||||
processData: process_data,
|
||||
success: function(data) {
|
||||
var msg;
|
||||
if (data.result === 'success') {
|
||||
msg = "<i class='fa fa-check'></i> " + data.message;
|
||||
window.location.href = 'restart_import_config';
|
||||
} else {
|
||||
msg = "<i class='fa fa-exclamation-triangle'></i> " + data.message;
|
||||
}
|
||||
$("#status-message").html(msg);
|
||||
$("#import_config_file").val(null);
|
||||
$("#import_config_file_name").val('');
|
||||
$("#import_config_path").val('');
|
||||
},
|
||||
error: function (xhr) {
|
||||
var msg = "<i class='fa fa-exclamation-triangle'></i> Error (" + xhr.status + "): ";
|
||||
if (xhr.status === 413) {
|
||||
msg += "file is too large to upload"
|
||||
} else {
|
||||
msg += 'try again'
|
||||
}
|
||||
$("#status-message").html(msg);
|
||||
},
|
||||
complete: function(xhr) {
|
||||
$("#import_config").prop('disabled', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
@@ -22,11 +22,11 @@ DOCUMENTATION :: END
|
||||
% if plexpy.CURRENT_VERSION:
|
||||
<tr>
|
||||
<td>Git Branch:</td>
|
||||
<td><a class="no-highlight" href="${anon_url('https://github.com/%s/%s/tree/%s' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO, plexpy.CONFIG.GIT_BRANCH))}" target="_blank">${plexpy.CONFIG.GIT_BRANCH}</a></td>
|
||||
<td><a class="no-highlight" href="${anon_url('https://github.com/%s/%s/tree/%s' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO, plexpy.CONFIG.GIT_BRANCH))}" target="_blank" rel="noreferrer">${plexpy.CONFIG.GIT_BRANCH}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Git Commit Hash:</td>
|
||||
<td><a class="no-highlight" href="${anon_url('https://github.com/%s/%s/commit/%s' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO, plexpy.CURRENT_VERSION))}" target="_blank">${plexpy.CURRENT_VERSION}</a></td>
|
||||
<td><a class="no-highlight" href="${anon_url('https://github.com/%s/%s/commit/%s' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO, plexpy.CURRENT_VERSION))}" target="_blank" rel="noreferrer">${plexpy.CURRENT_VERSION}</a></td>
|
||||
</tr>
|
||||
% endif
|
||||
<tr>
|
||||
@@ -49,6 +49,10 @@ DOCUMENTATION :: END
|
||||
<td>Cache Directory:</td>
|
||||
<td>${plexpy.CONFIG.CACHE_DIR}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Export Directory:</td>
|
||||
<td>${plexpy.CONFIG.EXPORT_DIR}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Newsletter Directory:</td>
|
||||
<td>${plexpy.CONFIG.NEWSLETTER_DIR}</td>
|
||||
@@ -74,19 +78,19 @@ DOCUMENTATION :: END
|
||||
<tr>
|
||||
<td class="top-line">Resources:</td>
|
||||
<td class="top-line">
|
||||
<a class="no-highlight" href="${anon_url('https://tautulli.com')}" target="_blank">Tautulli Website</a> |
|
||||
<a class="no-highlight" href="${anon_url('https://github.com/%s/%s' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}" target="_blank">GitHub Source</a> |
|
||||
<a class="no-highlight guidelines-modal-link" href="${anon_url('https://github.com/%s/%s-Issues' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}" data-id="issue">GitHub Issues</a> |
|
||||
<a class="no-highlight" href="${anon_url('https://github.com/%s/%s-Wiki' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}" target="_blank">GitHub Wiki</a> |
|
||||
<a class="no-highlight guidelines-modal-link" href="${anon_url('http://feathub.com/%s/%s' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}" data-id="feature request">FeatHub Feature Requests</a>
|
||||
<a class="no-highlight" href="${anon_url('https://tautulli.com')}" target="_blank" rel="noreferrer">Tautulli Website</a> |
|
||||
<a class="no-highlight" href="${anon_url('https://github.com/%s/%s' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}" target="_blank" rel="noreferrer">GitHub Source</a> |
|
||||
<a class="no-highlight guidelines-modal-link" href="${anon_url('https://github.com/%s/%s-Issues' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}" rel="noreferrer" data-id="issue">GitHub Issues</a> |
|
||||
<a class="no-highlight" href="${anon_url('https://github.com/%s/%s-Wiki' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}" target="_blank" rel="noreferrer">GitHub Wiki</a> |
|
||||
<a class="no-highlight guidelines-modal-link" href="${anon_url('http://feathub.com/%s/%s' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}" rel="noreferrer" data-id="feature request">FeatHub Feature Requests</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Support:</td>
|
||||
<td>
|
||||
<a class="no-highlight support-modal-link" href="${anon_url('https://tautulli.com/discord')}" target="_blank">Tautulli Discord Server</a> |
|
||||
<a class="no-highlight support-modal-link" href="${anon_url('https://www.reddit.com/r/Tautulli')}" target="_blank">Tautulli Subreddit</a> |
|
||||
<a class="no-highlight support-modal-link" href="${anon_url('https://forums.plex.tv/t/tautulli-monitor-your-plex-media-server/225242')}" target="_blank">Plex Forums</a>
|
||||
<a class="no-highlight support-modal-link" href="${anon_url('https://tautulli.com/discord')}" target="_blank" rel="noreferrer">Tautulli Discord Server</a> |
|
||||
<a class="no-highlight support-modal-link" href="${anon_url('https://www.reddit.com/r/Tautulli')}" target="_blank" rel="noreferrer">Tautulli Subreddit</a> |
|
||||
<a class="no-highlight support-modal-link" href="${anon_url('https://forums.plex.tv/t/tautulli-monitor-your-plex-media-server/225242')}" target="_blank" rel="noreferrer">Plex Forums</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
@@ -71,7 +71,7 @@ ul.ColVis_collection {
|
||||
list-style: none;
|
||||
width: 150px;
|
||||
padding: 8px 8px 4px 8px;
|
||||
margin: 10px 0px 0px 0px;
|
||||
margin: 10px 0px 10px 0px;
|
||||
background-color: #444;
|
||||
overflow: hidden;
|
||||
z-index: 2002;
|
||||
|
@@ -118,9 +118,7 @@ DOCUMENTATION :: END
|
||||
<div id="poster-${sk}" class="dashboard-activity-cover" style="background-image: url(${page('pms_image_proxy', data['parent_thumb'], data['parent_rating_key'], 300, 300, fallback='cover', refresh=True)});"></div>
|
||||
</a>
|
||||
% elif data['media_type'] in ('photo', 'clip'):
|
||||
% if data['extra_type']:
|
||||
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(${page('pms_image_proxy', data['art'].replace('/art', '/thumb') or data['thumb'], data['rating_key'], 300, 450, fallback='poster', refresh=True)});"></div>
|
||||
% elif data['parent_thumb']:
|
||||
% if data['parent_thumb']:
|
||||
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(${page('pms_image_proxy', data['parent_thumb'], data['parent_rating_key'], 300, 450, fallback='poster', refresh=True)});"></div>
|
||||
% else:
|
||||
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(${page('pms_image_proxy', data['thumb'], data['rating_key'], 300, 450, fallback='poster', refresh=True)});"></div>
|
||||
@@ -220,7 +218,7 @@ DOCUMENTATION :: END
|
||||
<div class="sub-heading">Container</div>
|
||||
<div class="sub-value" id="transcode_container-${sk}">
|
||||
% if data['stream_container_decision'] == 'transcode':
|
||||
Transcode (${data['container'].upper()} <i class="fa fa-long-arrow-right"></i> ${data['stream_container'].upper()})
|
||||
Converting (${data['container'].upper()} <i class="fa fa-long-arrow-right"></i> ${data['stream_container'].upper()})
|
||||
% else:
|
||||
Direct Play (${data['stream_container'].upper()})
|
||||
% endif
|
||||
@@ -399,7 +397,7 @@ DOCUMENTATION :: END
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard-activity-metadata-wrapper">
|
||||
<a href="${user_href}" title="${data['friendly_name']}">
|
||||
<a href="${user_href}" title="${data['username']}">
|
||||
<div class="dashboard-activity-metadata-user-thumb" style="background-image: url(${data['user_thumb']});"></div>
|
||||
</a>
|
||||
<div class="dashboard-activity-metadata-title-container">
|
||||
@@ -410,6 +408,10 @@ DOCUMENTATION :: END
|
||||
<i class="fa fa-fw fa-pause"></i>
|
||||
% elif data['state'] == 'buffering':
|
||||
<i class="fa fa-fw fa-spinner"></i>
|
||||
% elif data['state'] == 'error':
|
||||
<i class="fa fa-fw fa-exclamation-triangle"></i>
|
||||
% else:
|
||||
<i class="fa fa-fw fa-question-circle"></i>
|
||||
% endif
|
||||
</div>
|
||||
<div class="dashboard-activity-metadata-title">
|
||||
@@ -521,7 +523,7 @@ DOCUMENTATION :: END
|
||||
% endif
|
||||
</div>
|
||||
<div class="dashboard-activity-metadata-user">
|
||||
<a href="${user_href}" title="${data['friendly_name']}">${data['friendly_name']}</a>
|
||||
<a href="${user_href}" title="${data['username']}">${data['friendly_name']}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -115,21 +115,13 @@ DOCUMENTATION :: END
|
||||
var msg = 'Are you REALLY sure you want to purge all history for the <strong>${data["section_name"]}</strong> library?<br>' +
|
||||
'This is permanent and cannot be undone!';
|
||||
var url = 'delete_all_library_history';
|
||||
confirmAjaxCall(url, msg, { section_id: '${data["section_id"]}' }, null, function () { location.reload(); });
|
||||
confirmAjaxCall(url, msg, { server_id: '${server_id}', section_id: '${data["section_id"]}' }, null, function () { location.reload(); });
|
||||
});
|
||||
|
||||
$('#undelete-library').click(function () {
|
||||
var msg = 'Are you sure you want to undelete this user?';
|
||||
var msg = 'Are you sure you want to undelete this library?';
|
||||
var url = 'undelete_library';
|
||||
confirmAjaxCall(url, msg, { section_id: '${data["section_id"]}' }, null, function () { location.reload(); });
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
// Move #confirm-modal to parent container
|
||||
if (!($('#edit-library-modal').next().is('#confirm-modal-purge'))) {
|
||||
$('#confirm-modal-purge').appendTo($('#edit-library-modal').parent());
|
||||
}
|
||||
$('#edit-library-modal > #confirm-modal-purge').remove();
|
||||
});
|
||||
</script>
|
||||
% endif
|
@@ -134,13 +134,5 @@ DOCUMENTATION :: END
|
||||
var url = 'undelete_user';
|
||||
confirmAjaxCall(url, msg, { user_id: '${data["user_id"]}' }, null, function () { location.reload(); });
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
// Move #confirm-modal-purge to parent container
|
||||
if (!($('#edit-user-modal').next().is('#confirm-modal-purge'))) {
|
||||
$('#confirm-modal-purge').appendTo($('#edit-user-modal').parent());
|
||||
}
|
||||
$('#edit-user-modal > #confirm-modal-purge').remove();
|
||||
});
|
||||
</script>
|
||||
% endif
|
289
data/interfaces/default/export_modal.html
Normal file
@@ -0,0 +1,289 @@
|
||||
<%doc>
|
||||
USAGE DOCUMENTATION :: PLEASE LEAVE THIS AT THE TOP OF THIS FILE
|
||||
|
||||
For Mako templating syntax documentation please visit: http://docs.makotemplates.org/en/latest/
|
||||
|
||||
Filename: export_modal.html
|
||||
Version: 0.1
|
||||
Variable names: data [list]
|
||||
|
||||
data :: Usable parameters
|
||||
|
||||
== Global keys ==
|
||||
|
||||
DOCUMENTATION :: END
|
||||
</%doc>
|
||||
<%
|
||||
import plexpy
|
||||
from plexpy import exporter
|
||||
from plexpy.helpers import anon_url
|
||||
export = exporter.Export()
|
||||
thumb_media_types = ', '.join([export.PLURAL_MEDIA_TYPES[k] for k, v in export.MEDIA_TYPES.items() if v[0]])
|
||||
art_media_types = ', '.join([export.PLURAL_MEDIA_TYPES[k] for k, v in export.MEDIA_TYPES.items() if v[1]])
|
||||
%>
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="info-modal-title">
|
||||
${title}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="post" class="form" id="export_metadata_form">
|
||||
<input type="hidden" id="export_section_id" name="export_section_id" value="${section_id or ''}" />
|
||||
<input type="hidden" id="export_user_id" name="export_user_id" value="${user_id or ''}" />
|
||||
<input type="hidden" id="export_rating_key" name="export_rating_key" value="${rating_key or ''}" />
|
||||
<input type="hidden" id="export_media_type" name="export_media_type" value="${media_type or ''}" />
|
||||
<input type="hidden" id="export_sub_media_type" name="export_sub_media_type" value="${sub_media_type or ''}" />
|
||||
<input type="hidden" id="export_export_type" name="export_export_type" value="${export_type or ''}" />
|
||||
<div class="form-group">
|
||||
<label>Instructions</label>
|
||||
<p class="help-block">
|
||||
Please see the <a href="${anon_url('https://github.com/%s/%s-Wiki/wiki/Exporter-Guide' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}" target="_blank" rel="noreferrer">Exporter Guide</a> for more details about each option.
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="export_file_format">Data File Format</label>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<select class="form-control" id="export_file_format" name="export_file_format">
|
||||
% for format in file_formats:
|
||||
<option value="${format}">${format.upper()}</option>
|
||||
% endfor
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">Select the export data file format.</p>
|
||||
</div>
|
||||
% if not rating_key:
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" id="export_individual_files" name="export_individual_files" value="1"> Export Individual Files
|
||||
</label>
|
||||
<p class="help-block">Enable to export one file for each ${media_type} instead of a single file containing all ${media_type}s.</p>
|
||||
</div>
|
||||
% endif
|
||||
<div class="form-group">
|
||||
<label for="export_metadata_level">Metadata Export Level</label>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<select class="form-control" id="export_metadata_level" name="export_metadata_level">
|
||||
<option value="0">Level 0 - None / Custom</option>
|
||||
<option value="1" selected>Level 1 - Basic Metadata</option>
|
||||
<option value="2">Level 2 - Extended Metadata</option>
|
||||
<option value="3">Level 3 - Advanced Metadata</option>
|
||||
<option value="9">Level 9 - All Metadata</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">Select the metadata export level. Higher levels include all fields from the lower levels.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="export_custom_metadata_fields">Custom Metadata Fields</label>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<input type="text" class="form-control" id="export_custom_metadata_fields" name="export_custom_metadata_fields" data-field_type="Metadata">
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">Add additional fields to the selected metadata export level.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="export_media_info_level">Media Info Export Level</label>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<select class="form-control" id="export_media_info_level" name="export_media_info_level">
|
||||
<option value="0">Level 0 - None / Custom</option>
|
||||
<option value="1" selected>Level 1 - Basic Media Info</option>
|
||||
<option value="2">Level 2 - Extended Media Info</option>
|
||||
<option value="3">Level 3 - Advanced Media Info</option>
|
||||
<option value="9">Level 9 - All Media Info</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">Select the media info export level. Higher levels include all fields from the lower levels.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="export_custom_media_info_fields">Custom Media Info Fields</label>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<input type="text" class="form-control" id="export_custom_media_info_fields" name="export_custom_media_info_fields" data-field_type="Media Info">
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">Add additional fields to the selected media info export level.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="export_thumb_level">Poster and Cover Image Export Level</label>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<select class="form-control" id="export_thumb_level" name="export_thumb_level">
|
||||
<option value="0" selected>Level 0 - None / Custom</option>
|
||||
<option value="1">Level 1 - Uploaded and Selected Posters and Covers Only</option>
|
||||
<option value="2">Level 2 - Selected and Locked Posters and Covers Only</option>
|
||||
<option value="9">Level 9 - All Selected Posters and Covers</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">
|
||||
Select the level to export poster and cover image files.<br>Note: Only applies to ${thumb_media_types}.
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="export_art_level">Background Artwork Image Export Level</label>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<select class="form-control" id="export_art_level" name="export_art_level">
|
||||
<option value="0" selected>Level 0 - None / Custom</option>
|
||||
<option value="1">Level 1 - Uploaded and Selected Artwork Only</option>
|
||||
<option value="2">Level 2 - Selected and Locked Artwork Only</option>
|
||||
<option value="9">Level 9 - All Selected Artwork</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">
|
||||
Select the level to export background artwork image files.<br>Note: Only applies to ${art_media_types}.
|
||||
</p>
|
||||
</div>
|
||||
<p class="help-block">
|
||||
Warning: Exporting images may take a long time! Images will be saved to a folder alongside the data file.
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div>
|
||||
<input type="button" class="btn btn-bright btn-ok" data-dismiss="modal" id="export_metadata" value="Export">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="${http_root}js/selectize.plugin.disable-options.js"></script>
|
||||
<script>
|
||||
$('#export_metadata_form').submit(function(e) {
|
||||
e.preventDefault();
|
||||
})
|
||||
|
||||
var optgroups = (function () {
|
||||
var optgroups = [];
|
||||
for (var i = 0; i <= 9; i++) {
|
||||
optgroups.push({$order: i+1, value: i});
|
||||
}
|
||||
return optgroups
|
||||
})()
|
||||
|
||||
var $export_custom_fields = $('#export_custom_metadata_fields, #export_custom_media_info_fields').selectize({
|
||||
plugins: {
|
||||
'remove_button': {},
|
||||
'disable_options': {
|
||||
disableField: 'level'
|
||||
}
|
||||
},
|
||||
maxItems: null,
|
||||
valueField: 'field',
|
||||
labelField: 'field',
|
||||
sortField: 'field',
|
||||
searchField: ['field'],
|
||||
optgroupField: 'level',
|
||||
optgroups: optgroups,
|
||||
lockOptgroupOrder: true,
|
||||
render: {
|
||||
optgroup_header: function(data, escape) {
|
||||
return '<div class="optgroup-header">' + escape(this.$input.data('field_type') + ' Level: ' + data.value) + '</div>';
|
||||
},
|
||||
option: function (item, escape) {
|
||||
return '<div data-field="' + escape(item.field) + '" data-level="' + escape(item.level) + '">' + escape(item.field) +'</div>';
|
||||
}
|
||||
}
|
||||
});
|
||||
var export_custom_metadata_fields = $export_custom_fields[0].selectize;
|
||||
var export_custom_media_info_fields = $export_custom_fields[1].selectize;
|
||||
|
||||
function setDisabledFields() {
|
||||
var metadata_export_level = $('#export_metadata_level option:selected').val();
|
||||
var media_info_export_level = $('#export_media_info_level option:selected').val();
|
||||
export_custom_metadata_fields.setDisabledOptions([...Array(parseInt(metadata_export_level) + 1).keys()]);
|
||||
export_custom_media_info_fields.setDisabledOptions([...Array(parseInt(media_info_export_level) + 1).keys()]);
|
||||
}
|
||||
|
||||
$('#export_metadata_level, #export_media_info_level').on('change', setDisabledFields);
|
||||
|
||||
function getExportFields() {
|
||||
$.ajax({
|
||||
url: 'get_export_fields',
|
||||
async: true,
|
||||
data: {
|
||||
media_type: $('#export_media_type').val(),
|
||||
sub_media_type: $('#export_sub_media_type').val()
|
||||
},
|
||||
success: function (result) {
|
||||
if (result) {
|
||||
export_custom_metadata_fields.addOption(result.metadata_fields);
|
||||
export_custom_media_info_fields.addOption(result.media_info_fields);
|
||||
setDisabledFields();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
getExportFields();
|
||||
|
||||
$('#export_file_format').on('change', function() {
|
||||
if ($(this).val() === 'm3u8') {
|
||||
$('#export_metadata_level').prop('disabled', true);
|
||||
$('#export_media_info_level').prop('disabled', true);
|
||||
$("#export_thumb_level").prop('disabled', true);
|
||||
$("#export_art_level").prop('disabled', true);
|
||||
export_custom_metadata_fields.disable();
|
||||
export_custom_media_info_fields.disable();
|
||||
} else {
|
||||
$('#export_metadata_level').prop('disabled', false);
|
||||
$('#export_media_info_level').prop('disabled', false);
|
||||
$("#export_thumb_level").prop('disabled', false);
|
||||
$("#export_art_level").prop('disabled', false);
|
||||
export_custom_metadata_fields.enable();
|
||||
export_custom_media_info_fields.enable();
|
||||
}
|
||||
})
|
||||
|
||||
$("#export_metadata").click(function() {
|
||||
var section_id = $('#export_section_id').val();
|
||||
var user_id = $('#export_user_id').val();
|
||||
var rating_key = $('#export_rating_key').val();
|
||||
var metadata_export_level = $('#export_metadata_level option:selected').val();
|
||||
var media_info_export_level = $('#export_media_info_level option:selected').val();
|
||||
var file_format = $('#export_file_format option:selected').val();
|
||||
var thumb_level = $("#export_thumb_level option:selected").val();
|
||||
var art_level = $("#export_art_level option:selected").val();
|
||||
var custom_fields = [
|
||||
$('#export_custom_metadata_fields').val(),
|
||||
$('#export_custom_media_info_fields').val()
|
||||
].filter(Boolean).join(',');
|
||||
var export_type = $('#export_export_type').val()
|
||||
var individual_files = $('#export_individual_files').is(':checked')
|
||||
|
||||
$.ajax({
|
||||
url: 'export_metadata',
|
||||
data: {
|
||||
section_id: section_id,
|
||||
user_id: user_id,
|
||||
rating_key: rating_key,
|
||||
metadata_level: metadata_export_level,
|
||||
media_info_level: media_info_export_level,
|
||||
file_format: file_format,
|
||||
thumb_level: thumb_level,
|
||||
art_level: art_level,
|
||||
custom_fields: custom_fields,
|
||||
export_type: export_type,
|
||||
individual_files: individual_files
|
||||
},
|
||||
async: true,
|
||||
success: function (data) {
|
||||
if (data.result === 'success') {
|
||||
$("#nav-tabs-export").click();
|
||||
redrawExportTable();
|
||||
showMsg('<i class="fa fa-check"></i> ' + data.message, false, true, 5000);
|
||||
} else {
|
||||
showMsg('<i class="fa fa-exclamation-circle"></i> ' + data.message, false, true, 5000, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
@@ -40,14 +40,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='table-card-back'>
|
||||
<ul class="nav nav-pills" role="tablist" id="graph-tabs">
|
||||
<li role="presentation"><a href="#tabs-1" aria-controls="tabs-1" data-toggle="tab" role="tab">Plays by Period</a></li>
|
||||
<li role="presentation"><a href="#tabs-2" aria-controls="tabs-2" data-toggle="tab" role="tab">Stream Info</a></li>
|
||||
<li role="presentation"><a href="#tabs-3" aria-controls="tabs-3" data-toggle="tab" role="tab">Play Totals</a></li>
|
||||
<div class="table-card-back">
|
||||
<ul class="nav nav-list nav-pills" role="tablist" id="graph-tabs">
|
||||
<li role="presentation"><a id="nav-tabs-plays" href="#tabs-plays" aria-controls="tabs-plays" data-toggle="tab" role="tab">Plays by Period</a></li>
|
||||
<li role="presentation"><a id="nav-tabs-stream" href="#tabs-stream" aria-controls="tabs-stream" data-toggle="tab" role="tab">Stream Info</a></li>
|
||||
<li role="presentation"><a id="nav-tabs-total" href="#tabs-total" aria-controls="tabs-total" data-toggle="tab" role="tab">Play Totals</a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-1">
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-plays">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h4><i class="fa fa-history"></i> Daily <span class="yaxis-text">Play count</span> <small>Last <span class="days">30</span> days</small></h4>
|
||||
@@ -123,7 +123,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-2">
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-stream">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h4><i class="fa fa-video-camera"></i> Daily Stream type breakdown <small>Last <span class="days">30</span> days</small></h4>
|
||||
@@ -195,7 +195,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-3">
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-total">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h4><i class="fa fa-calendar"></i> Plays by month <small>Last <span class="months">12</span> months</small></h4>
|
||||
@@ -225,8 +225,6 @@
|
||||
</%def>
|
||||
|
||||
<%def name="javascriptIncludes()">
|
||||
<script src="${http_root}js/moment-with-locale.js"></script>
|
||||
<script src="${http_root}js/moment-duration-format.js"></script>
|
||||
<script src="${http_root}js/highcharts/js/highcharts.js"></script>
|
||||
<script src="${http_root}js/jquery.dataTables.min.js"></script>
|
||||
<script src="${http_root}js/dataTables.bootstrap.min.js"></script>
|
||||
@@ -341,14 +339,29 @@
|
||||
var yaxis = getLocalStorage('graph_type', 'plays');
|
||||
var current_day_range = getLocalStorage('graph_days', 30);
|
||||
var current_month_range = getLocalStorage('graph_months', 12);
|
||||
var current_tab = '#' + getLocalStorage('graph_tab', 'tabs-1');
|
||||
var current_tab = '#' + getLocalStorage('graph_tab', 'tabs-plays');
|
||||
|
||||
// Update tab values from upgrading
|
||||
switch (current_tab) {
|
||||
case '#tabs-1':
|
||||
current_tab = '#tabs-plays'
|
||||
break
|
||||
case '#tabs-2':
|
||||
current_tab = '#tabs-stream'
|
||||
break
|
||||
case '#tabs-3':
|
||||
current_tab = '#tabs-total'
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
$('#yaxis-' + yaxis).prop('checked', true);
|
||||
$('#yaxis-' + yaxis).closest('label').addClass('active');
|
||||
$('#graph-days').val(current_day_range);
|
||||
$('#graph-months').val(current_month_range);
|
||||
$('#graph-tabs a[href="' + current_tab + '"]').closest('li').addClass('active');
|
||||
$(current_tab).addClass('active');
|
||||
$('#nav-' + current_tab.replace('#', '')).tab('show').trigger('show.bs.tab');
|
||||
//$(current_tab).addClass('active');
|
||||
|
||||
|
||||
$('.days').html(current_day_range);
|
||||
@@ -469,7 +482,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
$('#graph-tabs a[href="#tabs-1"]').tab('show')
|
||||
$('#nav-tabs-plays').tab('show');
|
||||
}
|
||||
|
||||
function loadGraphsTab2(time_range, yaxis) {
|
||||
@@ -562,7 +575,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
$('#graph-tabs a[href="#tabs-2"]').tab('show')
|
||||
$('#nav-tabs-2').tab('show');
|
||||
}
|
||||
|
||||
function loadGraphsTab3(time_range, yaxis) {
|
||||
@@ -586,16 +599,16 @@
|
||||
}
|
||||
});
|
||||
|
||||
$('#graph-tabs a[href="#tabs-3"]').tab('show')
|
||||
$('#nav-tabs-total').tab('show');
|
||||
}
|
||||
|
||||
// Set initial state
|
||||
if (current_tab === '#tabs-1') { loadGraphsTab1(current_day_range, yaxis); }
|
||||
if (current_tab === '#tabs-2') { loadGraphsTab2(current_day_range, yaxis); }
|
||||
if (current_tab === '#tabs-3') { loadGraphsTab3(current_month_range, yaxis); }
|
||||
if (current_tab === '#tabs-plays') { loadGraphsTab1(current_day_range, yaxis); }
|
||||
if (current_tab === '#tabs-stream') { loadGraphsTab2(current_day_range, yaxis); }
|
||||
if (current_tab === '#tabs-total') { loadGraphsTab3(current_month_range, yaxis); }
|
||||
|
||||
// Tab1 opened
|
||||
$('#graph-tabs a[href="#tabs-1"]').on('shown.bs.tab', function (e) {
|
||||
$('#nav-tabs-plays').on('shown.bs.tab', function (e) {
|
||||
e.preventDefault();
|
||||
current_tab = $(this).attr('href');
|
||||
setLocalStorage('graph_tab', current_tab.replace('#',''));
|
||||
@@ -603,7 +616,7 @@
|
||||
});
|
||||
|
||||
// Tab2 opened
|
||||
$('#graph-tabs a[href="#tabs-2"]').on('shown.bs.tab', function (e) {
|
||||
$('#nav-tabs-stream').on('shown.bs.tab', function (e) {
|
||||
e.preventDefault();
|
||||
current_tab = $(this).attr('href');
|
||||
setLocalStorage('graph_tab', current_tab.replace('#',''));
|
||||
@@ -611,7 +624,7 @@
|
||||
});
|
||||
|
||||
// Tab3 opened
|
||||
$('#graph-tabs a[href="#tabs-3"]').on('shown.bs.tab', function (e) {
|
||||
$('#nav-tabs-total').on('shown.bs.tab', function (e) {
|
||||
e.preventDefault();
|
||||
current_tab = $(this).attr('href');
|
||||
setLocalStorage('graph_tab', current_tab.replace('#',''));
|
||||
@@ -624,8 +637,8 @@
|
||||
forceMinMax($(this));
|
||||
current_day_range = $(this).val();
|
||||
setLocalStorage('graph_days', current_day_range);
|
||||
if (current_tab === '#tabs-1') { loadGraphsTab1(current_day_range, yaxis); }
|
||||
if (current_tab === '#tabs-2') { loadGraphsTab2(current_day_range, yaxis); }
|
||||
if (current_tab === '#tabs-plays') { loadGraphsTab1(current_day_range, yaxis); }
|
||||
if (current_tab === '#tabs-stream') { loadGraphsTab2(current_day_range, yaxis); }
|
||||
$('.days').html(current_day_range);
|
||||
});
|
||||
|
||||
@@ -635,25 +648,25 @@
|
||||
forceMinMax($(this));
|
||||
current_month_range = $(this).val();
|
||||
setLocalStorage('graph_months', current_month_range);
|
||||
if (current_tab === '#tabs-3') { loadGraphsTab3(current_month_range, yaxis); }
|
||||
if (current_tab === '#tabs-total') { loadGraphsTab3(current_month_range, yaxis); }
|
||||
$('.months').html(current_month_range);
|
||||
});
|
||||
|
||||
// User changed
|
||||
$('#graph-user').on('change', function() {
|
||||
selected_user_id = $(this).val() || null;
|
||||
if (current_tab === '#tabs-1') { loadGraphsTab1(current_day_range, yaxis); }
|
||||
if (current_tab === '#tabs-2') { loadGraphsTab2(current_day_range, yaxis); }
|
||||
if (current_tab === '#tabs-3') { loadGraphsTab3(current_month_range, yaxis); }
|
||||
if (current_tab === '#tabs-plays') { loadGraphsTab1(current_day_range, yaxis); }
|
||||
if (current_tab === '#tabs-stream') { loadGraphsTab2(current_day_range, yaxis); }
|
||||
if (current_tab === '#tabs-total') { loadGraphsTab3(current_month_range, yaxis); }
|
||||
});
|
||||
|
||||
// Y-axis changed
|
||||
$('#yaxis-selection').on('change', function() {
|
||||
yaxis = $('input[name=yaxis-options]:checked', '#yaxis-selection').val();
|
||||
setLocalStorage('graph_type', yaxis);
|
||||
if (current_tab === '#tabs-1') { loadGraphsTab1(current_day_range, yaxis); }
|
||||
if (current_tab === '#tabs-2') { loadGraphsTab2(current_day_range, yaxis); }
|
||||
if (current_tab === '#tabs-3') { loadGraphsTab3(current_month_range, yaxis); }
|
||||
if (current_tab === '#tabs-plays') { loadGraphsTab1(current_day_range, yaxis); }
|
||||
if (current_tab === '#tabs-stream') { loadGraphsTab2(current_day_range, yaxis); }
|
||||
if (current_tab === '#tabs-total') { loadGraphsTab3(current_month_range, yaxis); }
|
||||
});
|
||||
|
||||
function setGraphFormat(type) {
|
||||
|
@@ -8,6 +8,13 @@
|
||||
|
||||
<%def name="body()">
|
||||
<div class='container-fluid'>
|
||||
% if config['database_is_importing']:
|
||||
<div style="text-align: center; margin-top: 20px;">
|
||||
<i class="fa fa-refresh fa-spin"></i> Tautulli is importing history from another database. This could take a few minutes depending on the size of your database.
|
||||
<br />
|
||||
You may leave this page and check back later.
|
||||
</div>
|
||||
% endif
|
||||
<div class='table-card-header'>
|
||||
<div class="header-bar">
|
||||
<span><i class="fa fa-history"></i> History</span>
|
||||
@@ -110,7 +117,6 @@
|
||||
<script src="${http_root}js/dataTables.colVis.js"></script>
|
||||
<script src="${http_root}js/dataTables.bootstrap.min.js"></script>
|
||||
<script src="${http_root}js/dataTables.bootstrap.pagination.js"></script>
|
||||
<script src="${http_root}js/moment-with-locale.js"></script>
|
||||
<script src="${http_root}js/tables/history_table.js${cache_param}"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
|
@@ -104,7 +104,7 @@ DOCUMENTATION :: END
|
||||
</div>
|
||||
% elif stat_id == 'top_users':
|
||||
<% user_href = page('user', row0['user_id']) if row0['user_id'] else '#' %>
|
||||
<a id="stats-thumb-url-${stat_id}" href="${user_href}" title="${row0['friendly_name']}" class="hidden-xs">
|
||||
<a id="stats-thumb-url-${stat_id}" href="${user_href}" title="${row0['user']}" class="hidden-xs">
|
||||
<div id="stats-thumb-${stat_id}" class="dashboard-stats-circle" style="background-image: url(${row0['user_thumb'] or 'images/gravatar-default.png'})"></div>
|
||||
</a>
|
||||
% elif stat_id == 'top_platforms':
|
||||
@@ -122,7 +122,7 @@ DOCUMENTATION :: END
|
||||
% elif stat_id.startswith('popular'):
|
||||
<span class="dashboard-stats-stats-units">users</span>
|
||||
% elif stat_id == 'last_watched':
|
||||
<span class="dashboard-stats-stats-units" id="last-watched-header-info">${row0['friendly_name']}</span>
|
||||
<span class="dashboard-stats-stats-units" id="last-watched-header-info" title="${row0['user']}">${row0['friendly_name']}</span>
|
||||
% elif stat_id == 'most_concurrent':
|
||||
<span class="dashboard-stats-stats-units" id="most-concurrent-header-info">streams</span>
|
||||
% endif
|
||||
@@ -134,7 +134,7 @@ DOCUMENTATION :: END
|
||||
<li class="dashboard-stats-info-item ${'expanded' if loop.index == 0 else ''}" data-stat_id="${stat_id}"
|
||||
data-rating_key="${row.get('rating_key')}" data-guid="${row.get('guid')}" data-title="${row.get('title')}"
|
||||
data-art="${row.get('art')}" data-thumb="${row.get('thumb')}" data-platform="${row.get('platform_name')}"
|
||||
data-user_id="${row.get('user_id')}" data-friendly_name="${row.get('friendly_name')}" data-user_thumb="${row.get('user_thumb')}"
|
||||
data-user_id="${row.get('user_id')}" data-user="${row.get('user')}" data-friendly_name="${row.get('friendly_name')}" data-user_thumb="${row.get('user_thumb')}"
|
||||
data-last_watch="${row.get('last_watch')}" data-started="${row.get('started')}" data-live="${row.get('live')}">
|
||||
<div class="sub-list">${loop.index + 1}</div>
|
||||
<div class="sub-value">
|
||||
@@ -152,7 +152,7 @@ DOCUMENTATION :: END
|
||||
</a>
|
||||
% elif stat_id == 'top_users':
|
||||
<% user_href = page('user', row['user_id']) if row['user_id'] else '#' %>
|
||||
<a href="${user_href}" title="${row['friendly_name']}">
|
||||
<a href="${user_href}" title="${row['user']}">
|
||||
${row['friendly_name']}
|
||||
</a>
|
||||
% elif stat_id == 'top_platforms':
|
||||
|
BIN
data/interfaces/default/images/check-solid.ico
Normal file
After Width: | Height: | Size: 99 KiB |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 4.5 KiB |
@@ -2,7 +2,7 @@
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="mstile-150x150.png?v=2.0.5"/>
|
||||
<square150x150logo src="mstile-150x150.png?v=2.6.0"/>
|
||||
<TileColor>#282a2d</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
|
Before Width: | Height: | Size: 553 B After Width: | Height: | Size: 997 B |
Before Width: | Height: | Size: 971 B After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -6,12 +6,12 @@
|
||||
"scope": "../../",
|
||||
"icons": [
|
||||
{
|
||||
"src": "android-chrome-192x192.png?v=2.0.5",
|
||||
"src": "android-chrome-192x192.png?v=2.6.0",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "android-chrome-256x256.png?v=2.0.5",
|
||||
"src": "android-chrome-256x256.png?v=2.6.0",
|
||||
"sizes": "256x256",
|
||||
"type": "image/png"
|
||||
}
|
||||
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 10 KiB |
@@ -1 +1,32 @@
|
||||
<svg version="1" xmlns="http://www.w3.org/2000/svg" width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000" preserveAspectRatio="xMidYMid meet"><g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)" fill="#000000" stroke="none"><path d="M5695 6555 c-135 -34 -244 -94 -342 -189 -40 -39 -73 -76 -73 -83 0 -7 -4 -13 -10 -13 -14 0 -87 -156 -106 -225 -22 -83 -26 -234 -8 -320 17 -79 86 -230 133 -288 l30 -39 -48 -71 c-39 -57 -159 -228 -251 -357 -69 -97 -398 -564 -416 -590 -13 -19 -60 -87 -105 -150 -45 -63 -107 -151 -138 -195 -30 -44 -59 -84 -63 -90 -7 -9 -251 -354 -346 -490 -92 -131 -173 -245 -175 -245 -1 0 -34 9 -72 21 -130 38 -325 31 -454 -18 -168 -63 -313 -196 -385 -354 -39 -87 -65 -183 -68 -256 0 -24 -3 -43 -4 -43 -2 0 -43 46 -91 102 -49 57 -100 117 -115 133 -14 17 -128 149 -253 295 -125 146 -251 292 -279 324 -56 65 -77 89 -108 126 -58 68 -152 178 -172 200 -12 14 -50 57 -83 96 l-61 71 27 44 c58 93 91 217 92 342 2 161 -38 294 -125 412 -133 181 -316 279 -542 292 -470 27 -833 -434 -699 -887 74 -251 275 -437 530 -490 132 -28 334 -6 421 45 l42 24 173 -197 c96 -108 186 -210 200 -227 15 -16 163 -187 330 -380 458 -529 491 -567 526 -605 18 -19 31 -35 30 -36 -6 -5 -265 -161 -277 -167 -8 -4 -34 -20 -58 -35 -194 -124 -634 -382 -651 -382 -12 0 -46 20 -75 44 -60 49 -180 112 -242 127 -21 5 -48 12 -59 15 -11 4 -65 9 -121 11 -81 4 -117 1 -182 -15 -261 -66 -462 -270 -528 -537 -10 -40 -11 -217 -2 -258 5 -23 11 -51 14 -61 29 -145 147 -312 284 -403 123 -82 224 -114 370 -118 83 -3 124 2 240 29 36 9 133 57 187 94 60 41 111 91 153 152 14 19 28 37 32 40 19 15 71 140 89 217 17 73 20 107 16 198 -4 61 -7 121 -9 134 -3 28 -46 0 482 321 179 108 379 228 444 265 104 59 120 65 133 52 13 -13 12 -22 -10 -78 -49 -123 -58 -165 -62 -262 -7 -149 25 -286 89 -383 47 -72 91 -128 125 -158 19 -17 39 -36 45 -42 27 -25 136 -94 150 -94 8 0 17 -4 20 -9 3 -5 16 -11 28 -14 13 -3 50 -12 83 -21 74 -19 278 -15 345 7 198 65 358 196 435 358 16 34 20 36 49 28 17 -4 49 -10 71 -14 22 -3 99 -16 170 -30 72 -13 144 -26 160 -29 28 -5 101 -18 170 -31 17 -3 80 -14 140 -25 61 -11 124 -22 140 -25 17 -4 49 -9 72 -12 40 -5 42 -7 48 -47 14 -98 29 -147 73 -235 36 -75 61 -110 121 -171 154 -154 280 -210 480 -213 134 -2 180 5 273 40 212 83 371 262 427 481 24 93 25 255 2 342 -64 241 -245 428 -481 501 -62 18 -97 23 -200 22 -107 0 -136 -4 -205 -26 -44 -15 -109 -43 -145 -64 -83 -48 -208 -171 -250 -245 -17 -32 -35 -60 -38 -61 -4 -2 -46 4 -93 13 -48 10 -104 20 -125 23 -22 3 -46 8 -54 11 -8 3 -33 7 -55 10 -38 5 -58 9 -122 21 -16 3 -53 10 -83 15 -30 6 -66 12 -79 15 -13 2 -103 19 -200 36 -169 30 -207 42 -196 60 10 16 -28 155 -62 224 -19 39 -54 96 -78 127 l-45 58 40 52 c96 125 143 266 143 433 1 164 -27 263 -108 391 -19 30 -35 57 -35 61 0 3 31 49 69 102 57 81 450 638 625 889 28 40 62 88 76 107 14 18 194 274 400 568 291 414 379 534 393 531 10 -2 27 -6 37 -9 78 -25 240 -29 338 -9 433 87 677 573 489 974 -93 200 -255 332 -478 389 -87 22 -227 25 -304 6z"/></g></svg>
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="350.000000pt" height="350.000000pt" viewBox="0 0 350.000000 350.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.11, written by Peter Selinger 2001-2013
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,350.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M1566 3489 c-433 -46 -867 -274 -1141 -601 -404 -481 -526 -1100
|
||||
-334 -1688 91 -278 283 -569 498 -756 676 -589 1646 -589 2322 0 215 187 407
|
||||
478 498 756 142 436 113 895 -84 1305 -320 666 -1027 1061 -1759 984z m1147
|
||||
-604 c87 -36 146 -118 154 -214 10 -111 -39 -203 -137 -254 -49 -26 -63 -28
|
||||
-131 -25 l-76 3 -109 -154 c-60 -85 -190 -269 -290 -409 l-181 -255 26 -46
|
||||
c22 -38 26 -59 26 -121 0 -63 -5 -84 -29 -132 -27 -54 -28 -59 -13 -76 22 -24
|
||||
47 -86 47 -117 0 -14 6 -28 13 -30 6 -3 91 -16 187 -30 157 -23 175 -24 183
|
||||
-10 38 68 115 118 199 130 103 15 220 -51 268 -151 26 -52 29 -154 6 -207 -19
|
||||
-48 -82 -114 -129 -138 -151 -77 -346 22 -373 189 -7 46 15 39 -222 74 -142
|
||||
20 -155 21 -163 6 -65 -116 -225 -163 -347 -102 -116 58 -167 187 -126 323 8
|
||||
29 13 55 11 57 -3 3 -65 -33 -138 -79 -74 -46 -162 -100 -196 -120 l-62 -38 6
|
||||
-47 c11 -100 -46 -207 -136 -254 -43 -23 -66 -28 -121 -28 -77 0 -124 16 -175
|
||||
62 -48 41 -76 99 -82 167 -7 72 9 129 50 183 85 112 256 132 372 44 l31 -24
|
||||
174 109 c96 60 180 111 185 113 6 2 -2 16 -16 32 -35 39 -412 468 -414 471 0
|
||||
1 -21 -5 -45 -13 -57 -20 -142 -14 -196 14 -162 84 -197 288 -71 419 102 108
|
||||
291 101 386 -14 62 -75 78 -185 40 -273 l-21 -49 23 -28 c13 -16 102 -118 198
|
||||
-227 l175 -198 20 61 c26 78 64 125 124 155 63 31 117 39 177 26 49 -11 51
|
||||
-11 72 17 21 26 533 749 548 773 4 6 -4 28 -17 48 -88 133 -44 307 94 376 61
|
||||
31 163 36 221 11z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 1.8 KiB |
BIN
data/interfaces/default/images/logo-circle.icns
Normal file
BIN
data/interfaces/default/images/logo-circle.ico
Normal file
After Width: | Height: | Size: 118 KiB |
BIN
data/interfaces/default/images/logo-flat-white.ico
Normal file
After Width: | Height: | Size: 200 KiB |
BIN
data/interfaces/default/images/logo-flat-white.png
Normal file
After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 112 KiB |
Before Width: | Height: | Size: 107 KiB |
@@ -1,8 +1,5 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
|
||||
<title>android</title>
|
||||
<path fill="#fff" d="M31.944 21.318c5.556 0 11.113 0 16.67 0 0.042 0 0.084-0 0.126 0.001 0.548 0.012 0.554 0.012 0.554 0.555 0.002 2.526 0.001 5.052 0.001 7.577 0 5.789 0.003 11.577-0.002 17.365-0.001 1.197-0.344 2.274-1.205 3.155-0.759 0.777-1.671 1.191-2.753 1.22-0.757 0.019-1.515 0.011-2.273 0.016-0.772 0.005-0.774 0.006-0.774 0.751-0.001 2.505-0.032 5.010 0.013 7.514 0.024 1.305-0.386 2.363-1.302 3.29-1.214 1.23-3.457 1.485-4.769 0.396-1.051-0.873-1.725-1.978-1.715-3.423 0.019-2.547 0.010-5.093 0.003-7.64-0.003-1.010 0.144-0.869-0.858-0.876-1.158-0.008-2.315-0.005-3.473-0.001-0.829 0.003-0.76-0.103-0.76 0.794-0.002 2.505-0.027 5.010 0.010 7.514 0.019 1.278-0.377 2.325-1.281 3.235-1.199 1.208-3.371 1.494-4.716 0.437-1.067-0.838-1.779-1.932-1.77-3.386 0.017-2.61 0.005-5.219 0.005-7.829 0-0.147-0.008-0.295 0-0.442 0.013-0.24-0.092-0.339-0.334-0.335-0.736 0.012-1.473 0.002-2.209 0.022-0.575 0.015-1.129-0.058-1.673-0.251-1.682-0.597-2.691-2.017-2.737-3.858-0.063-2.566-0.031-5.135-0.035-7.703-0.007-5.304-0.010-10.608-0.016-15.912-0.001-0.568-0.017-1.136-0.018-1.704-0-0.464 0.006-0.472 0.494-0.479 0.989-0.013 1.978-0.023 2.968-0.023 4.609-0.002 9.219-0.001 13.829-0.001-0.001 0.006-0.001 0.014-0.001 0.021z"></path>
|
||||
<path fill="#fff" d="M31.944 19.89c-5.535 0-11.071 0.002-16.606-0.002-0.717-0-0.772 0.153-0.687-0.747 0.189-2.003 0.58-3.948 1.437-5.784 1.041-2.228 2.47-4.152 4.433-5.648 0.864-0.658 1.646-1.43 2.624-1.932 0.216-0.111 0.25-0.23 0.129-0.443-0.363-0.64-0.715-1.286-1.059-1.937-0.441-0.835-0.877-1.674-1.302-2.518-0.247-0.491-0.206-0.765 0.103-0.941 0.342-0.194 0.625-0.077 0.892 0.415 0.721 1.329 1.429 2.664 2.142 3.997 0.069 0.13 0.141 0.258 0.215 0.386 0.226 0.39 0.228 0.394 0.671 0.218 2.478-0.987 5.051-1.43 7.715-1.338 2.143 0.074 4.214 0.501 6.214 1.273 0.118 0.045 0.241 0.081 0.35 0.142 0.186 0.102 0.303 0.067 0.405-0.126 0.534-1.023 1.075-2.043 1.617-3.062 0.297-0.557 0.592-1.115 0.908-1.66 0.189-0.325 0.514-0.408 0.809-0.253 0.292 0.153 0.366 0.43 0.175 0.817-0.39 0.79-0.791 1.575-1.204 2.353-0.383 0.725-0.789 1.438-1.18 2.159-0.19 0.351-0.181 0.348 0.158 0.573 1.666 1.102 3.266 2.297 4.577 3.814 1.895 2.192 3.115 4.723 3.574 7.598 0.119 0.746 0.175 1.503 0.266 2.254 0.038 0.311-0.097 0.421-0.393 0.394-0.146-0.014-0.295-0.002-0.442-0.002-5.514 0-11.028 0-16.543 0zM25.561 12.038c-0.063-1.117-0.623-1.553-1.433-1.566-0.833-0.014-1.419 0.462-1.455 1.603-0.025 0.776 0.66 1.407 1.463 1.409 0.79 0.001 1.421-0.64 1.424-1.445zM39.872 13.483c0.788-0.007 1.497-0.676 1.439-1.441-0.076-0.997-0.486-1.549-1.506-1.576-0.841-0.022-1.403 0.67-1.386 1.605 0.016 0.816 0.635 1.418 1.453 1.411z"></path>
|
||||
<path fill="#fff" d="M50.587 32.655c0-2.715-0.003-5.429 0.001-8.143 0.003-1.77 0.853-2.959 2.453-3.698 0.717-0.331 1.433-0.52 2.172-0.287 0.794 0.251 1.537 0.649 2.123 1.273 0.519 0.552 0.839 1.207 0.944 1.957 0.052 0.374 0.082 0.754 0.083 1.131 0.005 5.282-0.005 10.564 0.010 15.846 0.004 1.249-0.402 2.288-1.278 3.179-1.245 1.267-3.35 1.546-4.76 0.479-1.076-0.815-1.719-1.943-1.745-3.342-0.019-1.010-0.013-2.020-0.014-3.030-0.002-1.789-0.001-3.578-0.001-5.366 0.004-0 0.008-0 0.012-0z"></path>
|
||||
<path fill="#fff" d="M13.369 32.464c0 2.335-0.001 4.669 0.001 7.004 0 0.63 0.047 1.263 0.002 1.889-0.072 1.003-0.541 1.811-1.23 2.554-0.931 1.004-2.059 1.18-3.323 1.058-1.55-0.15-3.156-2.028-3.181-3.665-0.004-0.231-0.015-0.462-0.014-0.694 0.003-5.406 0.007-10.812 0.011-16.218 0.001-1.655 0.863-2.749 2.268-3.501 0.683-0.366 1.397-0.602 2.158-0.402 1.622 0.427 3.305 1.697 3.292 3.834-0.016 2.713-0.004 5.427-0.004 8.141 0.007-0 0.013-0 0.020 0z"></path>
|
||||
<path fill="#fff" d="M46.73 40.88c-0.003 0-0.007 0-0.010 0-1.475 0-2.67-1.195-2.67-2.67s1.195-2.67 2.67-2.67c1.475 0 2.67 1.195 2.67 2.67v0c0 0 0 0 0 0 0 1.471-1.19 2.664-2.659 2.67h-0.001zM17.27 40.88c-1.475 0-2.67-1.195-2.67-2.67s1.195-2.67 2.67-2.67c1.475 0 2.67 1.195 2.67 2.67v0c0 0.003 0 0.007 0 0.010 0 1.469-1.191 2.66-2.66 2.66-0.003 0-0.007 0-0.011 0h0.001zM47.68 24.83l5.32-9.23c0.095-0.159 0.151-0.351 0.151-0.557 0-0.405-0.219-0.76-0.546-0.951l-0.005-0.003c-0.16-0.095-0.354-0.152-0.56-0.152-0.407 0-0.764 0.22-0.957 0.547l-0.003 0.005-5.38 9.34c-4.027-1.851-8.738-2.93-13.7-2.93s-9.673 1.079-13.909 3.016l0.209-0.086-5.39-9.34c-0.204-0.28-0.531-0.46-0.9-0.46-0.613 0-1.11 0.497-1.11 1.11 0 0.167 0.037 0.325 0.103 0.467l-0.003-0.007 5.33 9.23c-9.153 5.047-15.453 14.286-16.323 25.059l-0.007 0.111h64c-0.875-10.883-7.171-20.121-16.158-25.088l-0.162-0.082z"></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 1.0 KiB |
7
data/interfaces/default/images/platforms/lg.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
|
||||
<title>lg</title>
|
||||
<path fill="#fff" d="M30.203 31.797c0 8.176-6.654 14.832-14.835 14.82-7.927-0.011-14.818-6.28-14.812-14.838 0.005-8.282 6.541-14.82 14.841-14.803 8.618 0.017 14.807 6.969 14.806 14.822zM26.577 32.388c-0.087 4.433-3.485 9.518-9.37 10.487-6.122 1.008-11.584-2.989-12.814-8.656-0.632-2.912-0.221-5.696 1.362-8.228 2.347-3.754 5.815-5.502 10.222-5.453 0-0.387 0-0.761 0-1.134-4.114-0.281-9.226 1.824-11.763 6.923-2.454 4.932-1.296 10.953 2.811 14.672 4.153 3.762 10.224 4.309 14.953 1.326 2.328-1.468 3.999-3.496 4.997-6.067 0.628-1.617 0.882-3.296 0.813-5.032-2.967 0-5.909 0-8.864 0 0 0.39 0 0.768 0 1.162 2.558-0 5.097-0 7.652-0zM15.991 37.112c0-0.129 0-0.221 0-0.313 0-3.731 0-7.463 0-11.194 0-0.060-0.004-0.119 0-0.179 0.009-0.118-0.038-0.166-0.16-0.163-0.278 0.006-0.556 0.012-0.833-0.002-0.178-0.008-0.237 0.042-0.237 0.23 0.005 4.194 0.005 8.389-0 12.583-0 0.198 0.065 0.239 0.249 0.237 1.224-0.007 2.448-0.004 3.672-0.004 0.072 0 0.143 0 0.244 0 0-0.343-0.008-0.665 0.003-0.987 0.006-0.166-0.050-0.214-0.214-0.212-0.82 0.007-1.641 0.003-2.461 0.003-0.078 0-0.155 0-0.263 0zM12.434 27.068c0.003-0.987-0.799-1.798-1.785-1.805s-1.799 0.796-1.805 1.782c-0.006 0.985 0.799 1.8 1.783 1.805 0.985 0.004 1.804-0.803 1.807-1.783z"></path>
|
||||
<path fill="#fff" d="M63.467 30.606c0 2.864 0 5.707 0 8.571-1.242 0-2.479 0-3.742 0 0-0.468 0-0.933 0-1.433-0.203 0.226-0.366 0.432-0.553 0.612-0.683 0.656-1.518 1-2.441 1.136-1.187 0.174-2.348 0.075-3.462-0.4-1.234-0.526-2.145-1.407-2.8-2.565-0.599-1.058-0.906-2.207-1.035-3.409-0.148-1.367-0.103-2.723 0.28-4.051 0.797-2.764 2.635-4.391 5.453-4.899 1.534-0.277 3.058-0.208 4.54 0.311 1.243 0.436 2.298 1.139 3.011 2.276 0.431 0.688 0.584 1.467 0.687 2.258 0.013 0.097 0.028 0.195 0.046 0.318-0.064 0.003-0.126 0.010-0.188 0.010-1.389 0.001-2.779-0.002-4.169 0.003-0.151 0.001-0.215-0.034-0.245-0.197-0.229-1.234-1.281-1.773-2.308-1.679-1.182 0.108-1.823 0.859-2.22 1.888-0.211 0.547-0.315 1.12-0.352 1.703-0.066 1.061-0.039 2.117 0.31 3.138 0.211 0.618 0.523 1.173 1.050 1.579 1.371 1.055 3.326 0.436 3.877-1.228 0.090-0.274 0.157-0.557 0.246-0.875-0.112 0-0.182 0-0.251 0-0.794 0-1.588-0.005-2.382 0.004-0.168 0.002-0.213-0.053-0.212-0.214 0.006-0.887 0.005-1.774 0.001-2.66-0.001-0.139 0.019-0.215 0.192-0.215 2.17 0.006 4.341 0.004 6.511 0.004 0.045 0 0.090 0.008 0.157 0.013z"></path>
|
||||
<path fill="#fff" d="M48.501 35.522c0 1.233 0 2.44 0 3.661-3.613 0-7.216 0-10.834 0 0-4.923 0-9.841 0-14.77 1.44 0 2.872 0 4.331 0 0 0.092 0 0.175 0 0.259 0 3.526 0 7.053 0 10.579 0 0.271 0 0.271 0.267 0.271 1.985 0 3.97 0 5.954 0 0.086 0 0.171 0 0.281 0z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 2.7 KiB |
1
data/interfaces/default/images/rating/themoviedb.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 190.24 81.52"><defs><linearGradient id="a" y1="40.76" x2="190.24" y2="40.76" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#90cea1"/><stop offset=".56" stop-color="#3cbec9"/><stop offset="1" stop-color="#00b3e5"/></linearGradient></defs><g data-name="Layer 2"><path d="M105.67 36.06h66.9a17.67 17.67 0 0017.67-17.66A17.67 17.67 0 00172.57.73h-66.9A17.67 17.67 0 0088 18.4a17.67 17.67 0 0017.67 17.66zm-88 45h76.9a17.67 17.67 0 0017.67-17.66 17.67 17.67 0 00-17.67-17.67h-76.9A17.67 17.67 0 000 63.4a17.67 17.67 0 0017.67 17.66zm-7.26-45.64h7.8V6.92h10.1V0h-28v6.9h10.1zm28.1 0h7.8V8.25h.1l9 27.15h6l9.3-27.15h.1V35.4h7.8V0H66.76l-8.2 23.1h-.1L50.31 0h-11.8zm113.92 20.25a15.07 15.07 0 00-4.52-5.52 18.57 18.57 0 00-6.68-3.08 33.54 33.54 0 00-8.07-1h-11.7v35.4h12.75a24.58 24.58 0 007.55-1.15 19.34 19.34 0 006.35-3.32 16.27 16.27 0 004.37-5.5 16.91 16.91 0 001.63-7.58 18.5 18.5 0 00-1.68-8.25zM145 68.6a8.8 8.8 0 01-2.64 3.4 10.7 10.7 0 01-4 1.82 21.57 21.57 0 01-5 .55h-4.05v-21h4.6a17 17 0 014.67.63 11.66 11.66 0 013.88 1.87A9.14 9.14 0 01145 59a9.87 9.87 0 011 4.52 11.89 11.89 0 01-1 5.08zm44.63-.13a8 8 0 00-1.58-2.62 8.38 8.38 0 00-2.42-1.85 10.31 10.31 0 00-3.17-1v-.1a9.22 9.22 0 004.42-2.82 7.43 7.43 0 001.68-5 8.42 8.42 0 00-1.15-4.65 8.09 8.09 0 00-3-2.72 12.56 12.56 0 00-4.18-1.3 32.84 32.84 0 00-4.62-.33h-13.2v35.4h14.5a22.41 22.41 0 004.72-.5 13.53 13.53 0 004.28-1.65 9.42 9.42 0 003.1-3 8.52 8.52 0 001.2-4.68 9.39 9.39 0 00-.55-3.18zm-19.42-15.75h5.3a10 10 0 011.85.18 6.18 6.18 0 011.7.57 3.39 3.39 0 011.22 1.13 3.22 3.22 0 01.48 1.82 3.63 3.63 0 01-.43 1.8 3.4 3.4 0 01-1.12 1.2 4.92 4.92 0 01-1.58.65 7.51 7.51 0 01-1.77.2h-5.65zm11.72 20a3.9 3.9 0 01-1.22 1.3 4.64 4.64 0 01-1.68.7 8.18 8.18 0 01-1.82.2h-7v-8h5.9a15.35 15.35 0 012 .15 8.47 8.47 0 012.05.55 4 4 0 011.57 1.18 3.11 3.11 0 01.63 2 3.71 3.71 0 01-.43 1.92z" fill="url(#a)" data-name="Layer 1"/></g></svg>
|
After Width: | Height: | Size: 1.9 KiB |
@@ -24,17 +24,13 @@
|
||||
</div>
|
||||
<div id="currentActivity">
|
||||
% if PLEX_SERVER_UP:
|
||||
<div class="text-muted" id="dashboard-checking-activity"><i class="fa fa-refresh fa-spin"></i> Checking for activity...</div>
|
||||
<div class="text-muted" id="dashboard-checking-activity"><i class="fa fa-refresh fa-spin"></i> Checking for activity...</div>
|
||||
% elif config['pms_is_cloud']:
|
||||
<div id="dashboard-no-activity" class="text-muted">Plex Cloud server is sleeping.</div>
|
||||
% elif not config['first_run_complete']:
|
||||
<div id="dashboard-no-activity" class="text-muted">The Tautulli setup wizard has not been completed. Please click <a href="welcome">here</a> to go to the setup wizard.</div>
|
||||
% else:
|
||||
<div id="dashboard-no-activity" class="text-muted">There was an error communicating with your Plex Server.
|
||||
% if _session['user_group'] == 'admin':
|
||||
Check the <a href="logs">logs</a> and verify your server connection in the <a href="settings#tab_tabs-plex_media_server">settings</a>.
|
||||
% endif
|
||||
</div>
|
||||
<div class="text-muted" id="dashboard-checking-activity"><i class="fa fa-refresh fa-spin"></i> Tautulli is connecting to the Plex server...</div>
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
@@ -65,7 +61,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div id="home-stats" class="home-platforms">
|
||||
<div class="text-muted"><i class="fa fa-refresh fa-spin"></i> Loading stats...</div>
|
||||
<div class="text-muted"><i class="fa fa-refresh fa-spin"></i> Loading stats...</div>
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
@@ -84,7 +80,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div id="library-stats" class="library-platforms">
|
||||
<div class="text-muted"><i class="fa fa-refresh fa-spin"></i> Loading stats...</div>
|
||||
<div class="text-muted"><i class="fa fa-refresh fa-spin"></i> Loading stats...</div>
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
@@ -132,17 +128,12 @@
|
||||
<div class="col-md-12">
|
||||
<div id="recentlyAdded" style="margin-right: -15px;">
|
||||
% if PLEX_SERVER_UP:
|
||||
<div class="text-muted"><i class="fa fa-refresh fa-spin"></i> Looking for new items...</div>
|
||||
<div id="dashboard-checking-recently-added" class="text-muted"><i class="fa fa-refresh fa-spin"></i> Looking for new items...</div>
|
||||
% elif config['pms_is_cloud']:
|
||||
<div class="text-muted">Plex Cloud server is sleeping.</div>
|
||||
% else:
|
||||
<div class="text-muted">There was an error communicating with your Plex Server.
|
||||
% if _session['user_group'] == 'admin':
|
||||
Check the <a href="logs">logs</a> and verify your server connection in the <a href="settings#tab_tabs-plex_media_server">settings</a>.
|
||||
% endif
|
||||
</div>
|
||||
<div id="dashboard-no-recently-added" class="text-muted"><i class="fa fa-refresh fa-spin"></i> Tautulli is connecting to your Plex server...</div>
|
||||
% endif
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -179,10 +170,10 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
|
||||
<h4 class="modal-title">Terminate Session</h4>
|
||||
<h4 class="modal-title">Terminate Stream</h4>
|
||||
</div>
|
||||
<div class="modal-body" style="text-align: center;">
|
||||
<p>Are you sure you want to terminate this session?</p>
|
||||
<p>Are you sure you want to terminate this stream?</p>
|
||||
<p>
|
||||
<strong>
|
||||
<span id="terminate-user"></span><br />
|
||||
@@ -221,6 +212,28 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% from plexpy.helpers import anon_url %>
|
||||
<div id="python2-modal" class="modal fade wide" tabindex="-1" role="dialog" aria-labelledby="python2-modal">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
|
||||
<h4 class="modal-title">Unable to Update</h4>
|
||||
</div>
|
||||
<div class="modal-body" style="text-align: center;">
|
||||
<p>Tautulli is still running using Python 2 and cannot be updated past v2.6.3.</p>
|
||||
<p>Python 3 is required to continue receiving updates.</p>
|
||||
<p>
|
||||
<strong>Please see the <a href="${anon_url('https://github.com/Tautulli/Tautulli-Wiki/wiki/Upgrading-to-Python-3-%28Tautulli-v2.5%29')}" target="_blank" rel="noreferrer">wiki</a>
|
||||
for instructions on how to upgrade to Python 3.</strong>
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<input type="button" class="btn btn-bright" data-dismiss="modal" value="Close">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
|
||||
<div class="modal fade" id="ip-info-modal" tabindex="-1" role="dialog" aria-labelledby="ip-info-modal">
|
||||
@@ -229,8 +242,6 @@
|
||||
</%def>
|
||||
|
||||
<%def name="javascriptIncludes()">
|
||||
<% from plexpy import PLEX_SERVER_UP %>
|
||||
<script src="${http_root}js/moment-with-locale.js"></script>
|
||||
<script src="${http_root}js/jquery.scrollbar.min.js"></script>
|
||||
<script src="${http_root}js/jquery.mousewheel.min.js"></script>
|
||||
<script>
|
||||
@@ -260,8 +271,33 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
% if _session['user_group'] == 'admin':
|
||||
var msg_settings = ' Check the <a href="logs">logs</a> and verify your server connection in the <a href="settings#tab_tabs-plex_media_server">settings</a>.';
|
||||
% else:
|
||||
var msg_settings = '';
|
||||
% endif
|
||||
|
||||
var error_msg = 'There was an error communicating with your Plex Server.' + msg_settings;
|
||||
|
||||
var server_status;
|
||||
server_status = setInterval(function() {
|
||||
$.getJSON('server_status', function (data) {
|
||||
if (data.connected === true) {
|
||||
clearInterval(server_status);
|
||||
$('#currentActivity').html('<div id="dashboard-checking-activity" class="text-muted"><i class="fa fa-refresh fa-spin"></i> Checking for activity...</div>');
|
||||
$('#recentlyAdded').html('<div id="dashboard-checking-recently-added" class="text-muted"><i class="fa fa-refresh fa-spin"></i> Looking for new items...</div>');
|
||||
activityConnected();
|
||||
recentlyAddedConnected();
|
||||
} else if (data.connected === false) {
|
||||
clearInterval(server_status);
|
||||
$('#currentActivity').html('<div id="dashboard-no-activity" class="text-muted">' + error_msg + '</div>');
|
||||
$('#recentlyAdded').html('<div id="dashboard-no-recently-added" class="text-muted">' + error_msg + '</div>');
|
||||
}
|
||||
});
|
||||
}, 1000);
|
||||
</script>
|
||||
% if 'current_activity' in config['home_sections'] and PLEX_SERVER_UP:
|
||||
% if 'current_activity' in config['home_sections']:
|
||||
<script>
|
||||
var defaultHandler = {
|
||||
get: function(target, name) {
|
||||
@@ -272,7 +308,7 @@
|
||||
var create_instances = [];
|
||||
var activity_ready = true;
|
||||
|
||||
$('#currentActivityHeader-bandwidth-tooltip').tooltip({ container: 'body', placement: 'right', delay: 50 });
|
||||
$('#currentActivityHeader-bandwidth-tooltip').tooltip({ container: 'body', placement: 'right', delay: 50 });
|
||||
|
||||
function getCurrentActivity() {
|
||||
activity_ready = false;
|
||||
@@ -298,13 +334,8 @@
|
||||
}
|
||||
|
||||
if (!(current_activity)) {
|
||||
% if _session['user_group'] == 'admin':
|
||||
var msg_settings = ' Check the <a href="logs">logs</a> and verify your server connection in the <a href="settings#tab_tabs-plex_media_server">settings</a>.';
|
||||
% else:
|
||||
var msg_settings = '';
|
||||
% endif
|
||||
$('#currentActivityHeader').hide();
|
||||
$('#currentActivity').html('<div id="dashboard-no-activity" class="text-muted">There was an error communicating with your Plex Server.' + msg_settings + '</div>');
|
||||
$('#currentActivity').html('<div id="dashboard-no-activity" class="text-muted">' + error_msg + '</div>');
|
||||
return
|
||||
}
|
||||
|
||||
@@ -377,6 +408,9 @@
|
||||
case 'buffering':
|
||||
state_icon = '<i class="fa fa-fw fa-spinner"></i> ';
|
||||
break;
|
||||
case 'error':
|
||||
state_icon = '<i class="fa fa-fw fa-exclamation-triangle"></i> ';
|
||||
break;
|
||||
default:
|
||||
state_icon = '<i class="fa fa-fw fa-question-circle"></i> ';
|
||||
}
|
||||
@@ -431,7 +465,7 @@
|
||||
|
||||
var transcode_container = '';
|
||||
if (s.stream_container_decision === 'transcode') {
|
||||
transcode_container = 'Transcode (' + s.container.toUpperCase() + ' <i class="fa fa-long-arrow-right"></i> ' + s.stream_container.toUpperCase() + ')';
|
||||
transcode_container = 'Converting (' + s.container.toUpperCase() + ' <i class="fa fa-long-arrow-right"></i> ' + s.stream_container.toUpperCase() + ')';
|
||||
} else {
|
||||
transcode_container = 'Direct Play (' + s.stream_container.toUpperCase() + ')';
|
||||
}
|
||||
@@ -546,7 +580,7 @@
|
||||
}
|
||||
|
||||
// Update the progress bars, percent - 3 because of 3px padding-right
|
||||
$('#buffer-bar-' + key).width(parseInt(s.transcode_progress) - 3 + '%').html(s.transcode_progress + '%')
|
||||
$('#buffer-bar-' + key).css({width: parseInt(s.transcode_progress) - 3 + '%'}).html(s.transcode_progress + '%')
|
||||
.attr('data-original-title', 'Transcoder Progress ' + s.transcode_progress + '%');
|
||||
if (s.live !== 1) {
|
||||
var progress_bar = $('#progress-bar-' + key);
|
||||
@@ -623,34 +657,36 @@
|
||||
});
|
||||
}
|
||||
|
||||
getCurrentActivity();
|
||||
setInterval(function () {
|
||||
if (!(create_instances.length) && activity_ready) {
|
||||
getCurrentActivity();
|
||||
}
|
||||
}, ${config['home_refresh_interval'] * 1000});
|
||||
function activityConnected() {
|
||||
getCurrentActivity();
|
||||
setInterval(function () {
|
||||
if (!(create_instances.length) && activity_ready) {
|
||||
getCurrentActivity();
|
||||
}
|
||||
}, ${config['home_refresh_interval'] * 1000});
|
||||
|
||||
setInterval(function(){
|
||||
$('.progress_time_offset').each(function () {
|
||||
if ($(this).data('state') === 'playing' && $(this).data('view_offset') >= 0) {
|
||||
var view_offset = parseInt($(this).data('view_offset'));
|
||||
var stream_duration = parseInt($(this).data('stream_duration'));
|
||||
var timestamp = millisecondsToMinutes(Math.min(view_offset, stream_duration), false);
|
||||
$(this).html(timestamp).data('view_offset', Math.min(view_offset + 1000, stream_duration))
|
||||
}
|
||||
});
|
||||
$('.progress-bar').each(function () {
|
||||
if ($(this).data('state') === 'playing' && $(this).data('view_offset') >= 0) {
|
||||
var view_offset = parseInt($(this).data('view_offset'));
|
||||
var stream_duration = parseInt($(this).data('stream_duration'));
|
||||
var progress_percent = Math.floor(view_offset / stream_duration * 100);
|
||||
progress_percent = (progress_percent >= 0) ? Math.min(progress_percent, 100) : 100;
|
||||
$(this).width(progress_percent - 3 + '%').html(progress_percent + '%')
|
||||
.attr('data-original-title', 'Stream Progress ' + progress_percent + '%')
|
||||
.data('view_offset', Math.min(view_offset + 1000, stream_duration));
|
||||
}
|
||||
});
|
||||
}, 1000);
|
||||
setInterval(function(){
|
||||
$('.progress_time_offset').each(function () {
|
||||
if ($(this).data('state') === 'playing' && $(this).data('view_offset') >= 0) {
|
||||
var view_offset = parseInt($(this).data('view_offset'));
|
||||
var stream_duration = parseInt($(this).data('stream_duration'));
|
||||
var timestamp = millisecondsToMinutes(Math.min(view_offset, stream_duration), false);
|
||||
$(this).html(timestamp).data('view_offset', Math.min(view_offset + 1000, stream_duration))
|
||||
}
|
||||
});
|
||||
$('.progress-bar').each(function () {
|
||||
if ($(this).data('state') === 'playing' && $(this).data('view_offset') >= 0) {
|
||||
var view_offset = parseInt($(this).data('view_offset'));
|
||||
var stream_duration = parseInt($(this).data('stream_duration'));
|
||||
var progress_percent = Math.floor(view_offset / stream_duration * 100);
|
||||
progress_percent = (progress_percent >= 0) ? Math.min(progress_percent, 100) : 100;
|
||||
$(this).css({width: progress_percent - 3 + '%'}).html(progress_percent + '%')
|
||||
.attr('data-original-title', 'Stream Progress ' + progress_percent + '%')
|
||||
.data('view_offset', Math.min(view_offset + 1000, stream_duration));
|
||||
}
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
$('#currentActivity').on('click', '.external_ip-modal', function () {
|
||||
$.get('get_ip_address_details', {
|
||||
@@ -756,7 +792,7 @@
|
||||
if (user_id) {
|
||||
href = page('user', user_id);
|
||||
}
|
||||
$('#stats-thumb-url-' + stat_id).attr('href', href).prop('title', $(elem).data('friendly_name'));
|
||||
$('#stats-thumb-url-' + stat_id).attr('href', href).prop('title', $(elem).data('user'));
|
||||
} else if (stat_id === 'top_platforms') {
|
||||
$('#stats-thumb-' + stat_id).removeClass(function (index, className) {
|
||||
return (className.match (/(^|\s)platform-\S+/g) || []).join(' ');
|
||||
@@ -874,7 +910,7 @@
|
||||
getLibraryStats();
|
||||
</script>
|
||||
% endif
|
||||
% if 'recently_added' in config['home_sections'] and PLEX_SERVER_UP:
|
||||
% if 'recently_added' in config['home_sections']:
|
||||
<script>
|
||||
function recentlyAdded(recently_added_count, recently_added_type) {
|
||||
showMsg("Loading recently added items...", true, false, 0);
|
||||
@@ -902,7 +938,9 @@
|
||||
$('#recently-added-toggle-' + recently_added_type).closest('label').addClass('active');
|
||||
$('#recently-added-count').val(recently_added_count);
|
||||
|
||||
recentlyAdded(recently_added_count, recently_added_type);
|
||||
function recentlyAddedConnected() {
|
||||
recentlyAdded(recently_added_count, recently_added_type);
|
||||
}
|
||||
|
||||
function highlightAddedScrollerButton() {
|
||||
var scroller = $("#recently-added-row-scroller");
|
||||
@@ -994,4 +1032,16 @@
|
||||
});
|
||||
</script>
|
||||
% endif
|
||||
% if _session['user_group'] == 'admin':
|
||||
<script>
|
||||
const queryString = window.location.search;
|
||||
const urlParams = new URLSearchParams(queryString);
|
||||
if (urlParams.get('update') === 'python2') {
|
||||
$("#python2-modal").modal({
|
||||
backdrop: 'static',
|
||||
keyboard: false
|
||||
});
|
||||
}
|
||||
</script>
|
||||
% endif
|
||||
</%def>
|
@@ -41,18 +41,18 @@ DOCUMENTATION :: END
|
||||
|
||||
from plexpy import notifiers
|
||||
from plexpy.common import MEDIA_TYPE_HEADERS, MEDIA_FLAGS_AUDIO, MEDIA_FLAGS_VIDEO
|
||||
from plexpy.helpers import page, get_percent
|
||||
from plexpy.helpers import page, get_percent, cast_to_int
|
||||
|
||||
# Get audio codec file
|
||||
def af(codec):
|
||||
for pattern, file_type in MEDIA_FLAGS_AUDIO.iteritems():
|
||||
for pattern, file_type in MEDIA_FLAGS_AUDIO.items():
|
||||
if re.match(pattern, codec):
|
||||
return file_type
|
||||
return codec
|
||||
|
||||
# Get video codec file
|
||||
def vf(codec):
|
||||
for pattern, file_type in MEDIA_FLAGS_VIDEO.iteritems():
|
||||
for pattern, file_type in MEDIA_FLAGS_VIDEO.items():
|
||||
if re.match(pattern, codec):
|
||||
return file_type
|
||||
return codec
|
||||
@@ -84,8 +84,10 @@ DOCUMENTATION :: END
|
||||
%>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
% if data['media_type'] not in ('photo_album', 'photo', 'playlist'):
|
||||
<% fallback = 'art-live-full' if data['live'] else None %>
|
||||
<div class="art-face" style="background-image:url(${page('pms_image_proxy', data['art'], data['rating_key'], 1920, 1080, fallback=fallback)})"></div>
|
||||
% endif
|
||||
% if _session['user_group'] == 'admin':
|
||||
<span class="overlay-refresh-image info-art" title="Refresh background image"><i class="fa fa-refresh refresh_pms_image"></i></span>
|
||||
% endif
|
||||
@@ -150,6 +152,29 @@ DOCUMENTATION :: END
|
||||
<li><a href="${page('info', data['parent_rating_key'])}">${data['parent_title']}</a></li>
|
||||
<span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
|
||||
<li class="active metadata-xml">Track ${data['media_index']} - ${data['title']}</li>
|
||||
% elif data['media_type'] == 'photo_album':
|
||||
<li><a href="${page('library', data['section_id'])}">${data['library_name']}</a></li>
|
||||
<span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
|
||||
% if data['parent_title']:
|
||||
<li><a href="${page('info', data['parent_rating_key'])}">${data['parent_title']}</a></li>
|
||||
<span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
|
||||
% endif
|
||||
<li class="active metadata-xml">${data['title']}</li>
|
||||
% elif data['media_type'] in ('photo', 'clip'):
|
||||
<li class="hidden-xs hidden-sm"><a href="${page('library', data['section_id'])}">${data['library_name']}</a></li>
|
||||
<span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
|
||||
<li><a href="${page('info', data['parent_rating_key'])}">${data['parent_title']}</a></li>
|
||||
<span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
|
||||
<li class="active metadata-xml">${data['title']}</li>
|
||||
% elif data['media_type'] == 'playlist':
|
||||
% if user_info.get('user_id'):
|
||||
<li><a href="${page('user', user_info.get('user_id'))}">${user_info.get('friendly_name')}</a></li>
|
||||
<span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
|
||||
% elif data['section_id']:
|
||||
<li><a href="${page('library', data['section_id'])}">${data['library_name']}</a></li>
|
||||
<span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
|
||||
% endif
|
||||
<li class="active metadata-xml">${data['title']}</li>
|
||||
% endif
|
||||
</ul>
|
||||
</div>
|
||||
@@ -158,10 +183,13 @@ DOCUMENTATION :: END
|
||||
<div class="summary-content-title-wrapper">
|
||||
<div class="col-md-9">
|
||||
<div class="summary-content-poster hidden-xs hidden-sm">
|
||||
% if data['media_type'] == 'track':
|
||||
<a href="${config['pms_web_url']}#!/server/${config['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${data['parent_rating_key']}" target="_blank" title="View on Plex Web">
|
||||
<% legacy = '&legacy=1' if data['media_type'] in ('photo_album', 'photo', 'clip') else '' %>
|
||||
% if data['media_type'] in ('track', 'photo'):
|
||||
<a href="${config['pms_web_url']}#!/server/${config['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${data['parent_rating_key']}${legacy}" target="_blank" rel="noreferrer" title="View on Plex Web">
|
||||
% elif data['media_type'] == 'playlist':
|
||||
<a href="${config['pms_web_url']}#!/server/${config['pms_identifier']}/playlist?key=%2Fplaylists%2F${data['rating_key']}" target="_blank" rel="noreferrer" title="View on Plex Web">
|
||||
% elif not data['live']:
|
||||
<a href="${config['pms_web_url']}#!/server/${config['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${data['rating_key']}" target="_blank" title="View on Plex Web">
|
||||
<a href="${config['pms_web_url']}#!/server/${config['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${data['rating_key']}${legacy}" target="_blank" rel="noreferrer" title="View on Plex Web">
|
||||
% endif
|
||||
% if data['live']:
|
||||
<div class="summary-poster-face" style="background-image: url(${page('pms_image_proxy', data['grandparent_thumb'] or data['thumb'], data['rating_key'], 300, 450, fallback='poster-live')});">
|
||||
@@ -179,11 +207,14 @@ DOCUMENTATION :: END
|
||||
% if _session['user_group'] == 'admin':
|
||||
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
|
||||
% endif
|
||||
% elif data['media_type'] == 'artist' or data['media_type'] == 'album' or data['media_type'] == 'track':
|
||||
% elif data['media_type'] in ('artist', 'album', 'track', 'playlist', 'photo_album', 'photo', 'clip'):
|
||||
<div class="summary-poster-face-track" style="background-image: url(${page('pms_image_proxy', data['thumb'], data['rating_key'], 500, 500, fallback='cover')});">
|
||||
<div class="summary-poster-face-overlay">
|
||||
<span></span>
|
||||
</div>
|
||||
% if data['media_type'] == 'playlist' and data['smart']:
|
||||
<span class="smart-playlist-image" title="Smart Playlist"><i class="fa fa-cog"></i></span>
|
||||
% endif
|
||||
</div>
|
||||
% if _session['user_group'] == 'admin':
|
||||
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
|
||||
@@ -214,7 +245,7 @@ DOCUMENTATION :: END
|
||||
<h3 class="hidden-xs">S${data['parent_media_index']} · E${data['media_index']}</h3>
|
||||
% endif
|
||||
% endif
|
||||
% elif data['media_type'] in ('movie', 'show', 'artist', 'collection'):
|
||||
% elif data['media_type'] in ('movie', 'show', 'artist', 'collection', 'playlist', 'photo_album'):
|
||||
<h1> </h1><h1>${data['title']}</h1>
|
||||
% elif data['media_type'] == 'season':
|
||||
<h1> </h1><h1><a href="${page('info', data['parent_rating_key'])}">${data['parent_title']}</a></h1>
|
||||
@@ -230,26 +261,30 @@ DOCUMENTATION :: END
|
||||
<h1><a href="${page('info', data['grandparent_rating_key'])}">${data['original_title'] or data['grandparent_title']}</a></h1>
|
||||
<h2><a href="${page('info', data['parent_rating_key'])}">${data['parent_title']}</a> - ${data['title']}</h2>
|
||||
<h3 class="hidden-xs">T${data['media_index']}</h3>
|
||||
% elif data['media_type'] in ('photo', 'clip'):
|
||||
<h1><a href="${page('info', data['parent_rating_key'])}">${data['parent_title']}</a></h1>
|
||||
<h2>${data['title']}</h2>
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="summary-content-wrapper">
|
||||
<div class="col-md-9">
|
||||
% if data['media_type'] == 'movie' or data['live']:
|
||||
<div class="summary-content-padding hidden-xs hidden-sm" style="height: 305px;">
|
||||
% elif data['media_type'] in ('show', 'season', 'collection'):
|
||||
<div class="summary-content-padding hidden-xs hidden-sm" style="height: 270px;">
|
||||
% elif data['media_type'] == 'episode':
|
||||
<div class="summary-content-padding hidden-xs hidden-sm" style="height: 70px;">
|
||||
% elif data['media_type'] == 'artist' or data['media_type'] == 'album':
|
||||
<div class="summary-content-padding hidden-xs hidden-sm" style="height: 150px;">
|
||||
% elif data['media_type'] == 'track':
|
||||
<div class="summary-content-padding hidden-xs hidden-sm" style="height: 180px;">
|
||||
% else:
|
||||
<div class="summary-content-padding hidden-xs hidden-sm">
|
||||
% endif
|
||||
% if data['media_type'] in ('movie', 'episode', 'track'):
|
||||
<%
|
||||
padding_height = ''
|
||||
if data['media_type'] == 'movie' or data['live']:
|
||||
padding_height = 'height: 305px;'
|
||||
elif data['media_type'] in ('show', 'season', 'collection'):
|
||||
padding_height = 'height: 270px;'
|
||||
elif data['media_type'] == 'episode':
|
||||
padding_height = 'height: 70px;'
|
||||
elif data['media_type'] in ('artist', 'album', 'playlist', 'photo_album', 'photo'):
|
||||
padding_height = 'height: 150px;'
|
||||
elif data['media_type'] in ('track', 'clip'):
|
||||
padding_height = 'height: 180px;'
|
||||
%>
|
||||
<div class="summary-content-padding hidden-xs hidden-sm" style="${padding_height}">
|
||||
% if data['media_type'] in ('movie', 'episode', 'track', 'clip'):
|
||||
<div class="summary-content-media-info-wrapper">
|
||||
% if data['media_type'] != 'track' and media_info['video_codec']:
|
||||
<img class="summary-content-media-flag" title="${media_info['video_codec']}" src="${http_root}images/media_flags/video_codec/${media_info['video_codec'] | vf}.png" />
|
||||
@@ -268,11 +303,17 @@ DOCUMENTATION :: END
|
||||
</div>
|
||||
<div class="summary-content">
|
||||
<div class="summary-content-details-wrapper">
|
||||
% if data['rating']:
|
||||
% if data['rating_image']:
|
||||
% if data['rating_image'].startswith('imdb://'):
|
||||
<div class="critic-rating hidden-xs hidden-sm" title="${data['rating']}">
|
||||
<span class="rating-image rating-imdb"><strong>${data['rating']}</strong></span>
|
||||
<% rating = data['rating'] or data['audience_rating'] %>
|
||||
% if rating:
|
||||
% if data['audience_rating_image']:
|
||||
% if data['audience_rating_image'].startswith('imdb://'):
|
||||
<div class="critic-rating hidden-xs hidden-sm" title="${rating}">
|
||||
<span class="rating-image rating-imdb"><strong>${rating}</strong></span>
|
||||
</div>
|
||||
% endif
|
||||
% if data['audience_rating_image'].startswith('themoviedb://'):
|
||||
<div class="critic-rating hidden-xs hidden-sm" title="${rating}">
|
||||
<span class="rating-image rating-themoviedb"><strong>${get_percent(rating, 10)}%</strong></span>
|
||||
</div>
|
||||
% endif
|
||||
% if data['audience_rating_image'].startswith('rottentomatoes://'):
|
||||
@@ -286,11 +327,24 @@ DOCUMENTATION :: END
|
||||
</div>
|
||||
% endif
|
||||
% else:
|
||||
<div class="critic-rating hidden-xs hidden-sm" title="${data['rating']}">
|
||||
<i class="star-icon fa fa-star"></i> <strong>${get_percent(data['rating'], 10)}%</strong>
|
||||
<div class="critic-rating hidden-xs hidden-sm" title="${rating}">
|
||||
<i class="star-icon fa fa-star"></i> <strong>${get_percent(rating, 10)}%</strong>
|
||||
</div>
|
||||
% endif
|
||||
% endif
|
||||
<div class="summary-content-details-tag">
|
||||
% if data['media_type'] in ('collection', 'playlist') and data['children_count']:
|
||||
<%
|
||||
if data['media_type'] == 'collection':
|
||||
suffix = MEDIA_TYPE_HEADERS[data['sub_media_type']]
|
||||
elif data['media_type'] == 'playlist':
|
||||
suffix = MEDIA_TYPE_HEADERS[data['playlist_type']]
|
||||
if data['children_count'] == 1:
|
||||
suffix = suffix[:-1]
|
||||
%>
|
||||
Items <strong> ${data['children_count']} ${suffix} </strong>
|
||||
% endif
|
||||
</div>
|
||||
<div class="summary-content-details-tag">
|
||||
% if data['directors']:
|
||||
Directed by <strong> ${data['directors'][0]}</strong>
|
||||
@@ -310,6 +364,8 @@ DOCUMENTATION :: END
|
||||
Aired <strong> <span id="airdate">${data['originally_available_at']}</span></strong>
|
||||
% elif data['media_type'] == 'album' or data['media_type'] == 'track':
|
||||
Released <strong> ${data['year']}</strong>
|
||||
% elif data['media_type'] in ('photo', 'clip'):
|
||||
Taken <strong> <span id="airdate">${data['originally_available_at']}</span></strong>
|
||||
% elif data['media_type'] == 'collection':
|
||||
Year <strong> ${data['min_year']} - ${data['max_year']}</strong>
|
||||
% elif data['year']:
|
||||
@@ -318,7 +374,7 @@ DOCUMENTATION :: END
|
||||
</div>
|
||||
<div class="summary-content-details-tag">
|
||||
% if data['duration']:
|
||||
Runtime <strong> <span id="runtime">${data['duration']}</span> mins</strong>
|
||||
Runtime <strong> <span id="runtime">${data['duration']}</span></strong>
|
||||
% endif
|
||||
</div>
|
||||
<div class="summary-content-details-tag">
|
||||
@@ -434,6 +490,17 @@ DOCUMENTATION :: END
|
||||
<div id="children-list" class="children-list"><i class="fa fa-refresh fa-spin"></i> Loading track list...</div>
|
||||
</div>
|
||||
</div>
|
||||
% elif data['media_type'] == 'photo_album':
|
||||
<div class="col-md-12">
|
||||
<div class="table-card-header">
|
||||
<div class="header-bar">
|
||||
<span>Photo List for <strong>${data['title']}</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-card-back">
|
||||
<div id="children-list" class="children-list"><i class="fa fa-refresh fa-spin"></i> Loading photo list...</div>
|
||||
</div>
|
||||
</div>
|
||||
% elif data['media_type'] == 'collection':
|
||||
<div class="col-md-12">
|
||||
<div class="table-card-header">
|
||||
@@ -442,30 +509,36 @@ DOCUMENTATION :: END
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-card-back">
|
||||
<div id="children-list" class="children-list"><i class="fa fa-refresh fa-spin"></i> Loading movies list...</div>
|
||||
<div id="children-list" class="children-list"><i class="fa fa-refresh fa-spin"></i> Loading collection items...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="collection-related-list-container" style="display: none;">
|
||||
</div>
|
||||
% endif
|
||||
% if data['media_type'] != 'collection':
|
||||
% elif data['media_type'] == 'playlist':
|
||||
<div class="col-md-12">
|
||||
<div class="table-card-header">
|
||||
<div class="header-bar">
|
||||
% if data['media_type'] in ('artist', 'album', 'track'):
|
||||
<span>Play History for <strong>${data['title']}</strong></span>
|
||||
% else:
|
||||
<span>Watch History for <strong>${data['title']}</strong></span>
|
||||
% endif
|
||||
<span>${MEDIA_TYPE_HEADERS[data['playlist_type']]} List for <strong>${data['title']}</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-card-back">
|
||||
<div id="children-list" class="children-list"><i class="fa fa-refresh fa-spin"></i> Loading playlist items...</div>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
<%
|
||||
history_type = data['media_type'] in ('movie', 'show', 'season', 'episode', 'artist', 'album', 'track')
|
||||
history_active = 'active' if history_type else ''
|
||||
export_active = 'active' if not history_type else ''
|
||||
%>
|
||||
% if history_type and _session['user_group'] == 'admin':
|
||||
<div class="col-md-12">
|
||||
<div class="table-card-header">
|
||||
<ul class="nav nav-list nav-pills" role="tablist">
|
||||
<li class="${history_active}"><a id="nav-tabs-history" href="#tabs-history" role="tab" data-toggle="tab">History</a></li>
|
||||
<li class="${export_active}"><a id="nav-tabs-export" href="#tabs-export" role="tab" data-toggle="tab">Export</a></li>
|
||||
</ul>
|
||||
<div class="button-bar">
|
||||
% if _session['user_group'] == 'admin':
|
||||
<div class="alert alert-danger alert-edit" role="alert" id="row-edit-mode-alert"><i class="fa fa-exclamation-triangle"></i> Select rows to delete. Data is deleted upon exiting delete mode.</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-danger btn-edit" data-toggle="button" aria-pressed="false" autocomplete="off" id="row-edit-mode">
|
||||
<i class="fa fa-trash-o"></i> Delete mode
|
||||
</button>
|
||||
</div>
|
||||
% if source == 'history':
|
||||
<div class="btn-group">
|
||||
<a href="update_metadata?rating_key=${data['rating_key']}&update=True" class="btn btn-danger btn-edit" id="fix-metadata">
|
||||
@@ -505,37 +578,117 @@ DOCUMENTATION :: END
|
||||
</button>
|
||||
</div>
|
||||
% endif
|
||||
% endif
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-dark refresh-history-button" id="refresh-history-list"><i class="fa fa-refresh"></i> Refresh history</button>
|
||||
</div>
|
||||
<div class="btn-group colvis-button-bar"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-card-back">
|
||||
<table class="display history_table" id="history_table-RK-${data['rating_key']}" width="100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th align="left" id="delete">Delete</th>
|
||||
<th align="left" id="date">Date</th>
|
||||
<th align="left" id="friendly_name">User</th>
|
||||
<th align="left" id="ip_address">IP Address</th>
|
||||
<th align="left" id="platform">Platform</th>
|
||||
<th align="left" id="product">Product</th>
|
||||
<th align="left" id="player">Player</th>
|
||||
<th align="left" id="title">Title</th>
|
||||
<th align="left" id="started">Started</th>
|
||||
<th align="left" id="paused_counter">Paused</th>
|
||||
<th align="left" id="stopped">Stopped</th>
|
||||
<th align="left" id="duration">Duration</th>
|
||||
<th align="left" id="percent_complete"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
<div class="tab-content">
|
||||
% if history_type:
|
||||
<div role="tabpanel" class="tab-pane ${history_active}" id="tabs-history">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="table-card-header">
|
||||
<div class="header-bar">
|
||||
% if data['media_type'] in ('artist', 'album', 'track'):
|
||||
<span>Play History for <strong>${data['title']}</strong></span>
|
||||
% else:
|
||||
<span>Watch History for <strong>${data['title']}</strong></span>
|
||||
% endif
|
||||
</div>
|
||||
<div class="button-bar">
|
||||
% if _session['user_group'] == 'admin':
|
||||
<div class="alert alert-danger alert-edit" role="alert" id="row-edit-mode-alert"><i class="fa fa-exclamation-triangle"></i> Select rows to delete. Data is deleted upon exiting delete mode.</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-danger btn-edit" data-toggle="button" aria-pressed="false" autocomplete="off" id="row-edit-mode">
|
||||
<i class="fa fa-trash-o"></i> Delete mode
|
||||
</button>
|
||||
</div>
|
||||
% endif
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-dark refresh-history-button" id="refresh-history-list"><i class="fa fa-refresh"></i> Refresh history</button>
|
||||
</div>
|
||||
<div class="btn-group colvis-button-bar" id="button-bar-history"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-card-back">
|
||||
<table class="display history_table" id="history_table-RK-${data['rating_key']}" width="100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th align="left" id="delete">Delete</th>
|
||||
<th align="left" id="date">Date</th>
|
||||
<th align="left" id="friendly_name">User</th>
|
||||
<th align="left" id="ip_address">IP Address</th>
|
||||
<th align="left" id="platform">Platform</th>
|
||||
<th align="left" id="product">Product</th>
|
||||
<th align="left" id="player">Player</th>
|
||||
<th align="left" id="title">Title</th>
|
||||
<th align="left" id="started">Started</th>
|
||||
<th align="left" id="paused_counter">Paused</th>
|
||||
<th align="left" id="stopped">Stopped</th>
|
||||
<th align="left" id="duration">Duration</th>
|
||||
<th align="left" id="percent_complete"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
% if not data['live'] and _session['user_group'] == 'admin':
|
||||
<div role="tabpanel" class="tab-pane ${export_active}" id="tabs-export">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="table-card-header">
|
||||
<div class="header-bar">
|
||||
<span>Metadata Exports for <strong>${data['title']}</strong></span>
|
||||
</div>
|
||||
<div class="button-bar">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-dark export-button" id="toggle-export-modal" data-toggle="modal" data-target="#export-modal"
|
||||
data-section_id="${data['section_id']}" data-rating_key="${data['rating_key']}"
|
||||
data-media_type="${data['media_type']}" data-sub_media_type="${data['sub_media_type'] or data['playlist_type'] or ''}">
|
||||
<i class="fa fa-file-export"></i> Export metadata
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-dark refresh-export-table-button" id="refresh-export-table">
|
||||
<i class="fa fa-refresh"></i> Refresh exports
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group colvis-button-bar" id="button-bar-export"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-card-back">
|
||||
<table class="display export_table" id="export_table-RK-${data['rating_key']}" width="100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th align="left" id="timestamp">Exported At</th>
|
||||
<th align="left" id="media_type_title">Media Type</th>
|
||||
<th align="left" id="rating_key">Rating Key</th>
|
||||
<th align="left" id="filename">Filename</th>
|
||||
<th align="left" id="file_format">File Format</th>
|
||||
<th align="left" id="metadata_level">Metadata Level</th>
|
||||
<th align="left" id="media_info_level">Media Info Level</th>
|
||||
<th align="left" id="media_info_level">Custom Fields</th>
|
||||
<th align="left" id="file_size">File Size</th>
|
||||
<th align="left" id="complete">Download</th>
|
||||
<th align="left" id="delete">Delete</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -624,6 +777,8 @@ DOCUMENTATION :: END
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
<div id="export-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="export-modal">
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
<%def name="javascriptIncludes()">
|
||||
@@ -631,13 +786,14 @@ DOCUMENTATION :: END
|
||||
<script src="${http_root}js/dataTables.colVis.js"></script>
|
||||
<script src="${http_root}js/dataTables.bootstrap.min.js"></script>
|
||||
<script src="${http_root}js/dataTables.bootstrap.pagination.js"></script>
|
||||
<script src="${http_root}js/moment-with-locale.js"></script>
|
||||
|
||||
% if metadata:
|
||||
<%
|
||||
data = defaultdict(None, **metadata)
|
||||
history_user_id = '' if _session['user_group'] == 'admin' else _session['user_id']
|
||||
%>
|
||||
<script src="${http_root}js/tables/history_table.js${cache_param}"></script>
|
||||
<script src="${http_root}js/tables/export_table.js${cache_param}"></script>
|
||||
% if data['live']:
|
||||
<script>
|
||||
function get_history() {
|
||||
@@ -648,7 +804,7 @@ DOCUMENTATION :: END
|
||||
return {
|
||||
json_data: JSON.stringify( d ),
|
||||
guid: "${data['guid']}",
|
||||
user_id: "${_session['user_group']}" == "admin" ? null : "${_session['user_id']}"
|
||||
user_id: "${history_user_id}"
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -664,7 +820,7 @@ DOCUMENTATION :: END
|
||||
return {
|
||||
json_data: JSON.stringify( d ),
|
||||
grandparent_rating_key: "${data['rating_key']}",
|
||||
user_id: "${_session['user_group']}" == "admin" ? null : "${_session['user_id']}"
|
||||
user_id: "${history_user_id}"
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -680,7 +836,7 @@ DOCUMENTATION :: END
|
||||
return {
|
||||
json_data: JSON.stringify( d ),
|
||||
parent_rating_key: "${data['rating_key']}",
|
||||
user_id: "${_session['user_group']}" == "admin" ? null : "${_session['user_id']}"
|
||||
user_id: "${history_user_id}"
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -696,96 +852,43 @@ DOCUMENTATION :: END
|
||||
return {
|
||||
json_data: JSON.stringify( d ),
|
||||
rating_key: "${data['rating_key']}",
|
||||
user_id: "${_session['user_group']}" == "admin" ? null : "${_session['user_id']}"
|
||||
user_id: "${history_user_id}"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
% endif
|
||||
% if data['media_type'] != 'collection':
|
||||
% if data['media_type'] in ('movie', 'show', 'season', 'episode', 'artist', 'album', 'track'):
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
function loadHistoryTable() {
|
||||
get_history();
|
||||
history_table = $('#history_table-RK-${data["rating_key"]}').DataTable(history_table_options);
|
||||
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 12] });
|
||||
$(colvis.button()).appendTo('div.colvis-button-bar');
|
||||
$(colvis.button()).appendTo('#button-bar-history');
|
||||
|
||||
clearSearchButton('history_table-RK-${data["rating_key"]}', history_table);
|
||||
}
|
||||
|
||||
$('#row-edit-mode').on('click', function() {
|
||||
$('#row-edit-mode-alert').fadeIn(200);
|
||||
|
||||
if ($(this).hasClass('active')) {
|
||||
if (history_to_delete.length > 0) {
|
||||
$('#deleteCount').text(history_to_delete.length);
|
||||
$('#confirm-modal-delete').modal();
|
||||
$('#confirm-modal-delete').one('click', '#confirm-delete', function () {
|
||||
$.ajax({
|
||||
url: 'delete_history_rows',
|
||||
type: 'POST',
|
||||
data: { row_ids: history_to_delete.join(',') },
|
||||
async: true,
|
||||
success: function (data) {
|
||||
var msg = "History deleted";
|
||||
showMsg(msg, false, true, 2000);
|
||||
history_table.draw();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$('.delete-control').each(function () {
|
||||
$(this).addClass('hidden');
|
||||
$('#row-edit-mode-alert').fadeOut(200);
|
||||
});
|
||||
|
||||
} else {
|
||||
history_to_delete = [];
|
||||
$('.delete-control').each(function() {
|
||||
$(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger');
|
||||
$(this).removeClass('hidden');
|
||||
});
|
||||
}
|
||||
});
|
||||
$(document).ready(function () {
|
||||
loadHistoryTable();
|
||||
});
|
||||
|
||||
$("#refresh-history-list").click(function () {
|
||||
history_table.draw();
|
||||
});
|
||||
|
||||
// Send recently added notification
|
||||
$('#send-recently-added-notification').on('click', function () {
|
||||
var rating_key = $(this).data('id');
|
||||
|
||||
$('#send-recently-added-modal').modal();
|
||||
$('#send-recently-added-modal').one('click', '#confirm-send-notification', function () {
|
||||
$.ajax({
|
||||
url: 'send_manual_on_created',
|
||||
data: {
|
||||
rating_key: rating_key,
|
||||
notifier_id: $('#send-notification-notifier option:selected').val()
|
||||
},
|
||||
async: true,
|
||||
success: function (data) {
|
||||
if (data.result === 'success') {
|
||||
showMsg('<i class="fa fa-check"></i> ' + data.message, false, true, 5000);
|
||||
} else {
|
||||
showMsg('<i class="fa fa-exclamation-circle"></i> ' + data.message, false, true, 5000, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
% endif
|
||||
% if data['media_type'] in ('show', 'season', 'artist', 'album', 'collection'):
|
||||
% if data['media_type'] in ('show', 'season', 'artist', 'album', 'photo_album', 'collection', 'playlist'):
|
||||
<script>
|
||||
$.ajax({
|
||||
url: 'get_item_children',
|
||||
type: 'GET',
|
||||
async: true,
|
||||
data: { rating_key : "${data['rating_key']}" },
|
||||
data: {
|
||||
rating_key: "${data['rating_key']}",
|
||||
media_type: "${data['media_type']}"
|
||||
},
|
||||
complete: function(xhr, status) {
|
||||
$("#children-list").html(xhr.responseText);
|
||||
}
|
||||
@@ -799,7 +902,7 @@ DOCUMENTATION :: END
|
||||
type: 'GET',
|
||||
async: true,
|
||||
data: {
|
||||
rating_key : "${data['rating_key']}",
|
||||
rating_key: "${data['rating_key']}",
|
||||
title: "${data['title']}"
|
||||
},
|
||||
complete: function(xhr, status) {
|
||||
@@ -809,12 +912,29 @@ DOCUMENTATION :: END
|
||||
</script>
|
||||
% endif
|
||||
<script>
|
||||
$('.metadata-xml').on('tripleclick', function () {
|
||||
openPlexXML("/library/metadata/${data['rating_key']}");
|
||||
$(document).ready(function () {
|
||||
// Javascript to enable link to tab
|
||||
var hash = document.location.hash;
|
||||
var prefix = "tab_";
|
||||
if (hash) {
|
||||
$('.nav-list #nav-' + hash.replace('#' + prefix, "")).tab('show').trigger('show.bs.tab');
|
||||
}
|
||||
|
||||
// Change hash for page-reload
|
||||
$('.nav-list a').on('shown.bs.tab', function (e) {
|
||||
window.location.hash = e.target.hash.replace("#", "#" + prefix);
|
||||
});
|
||||
});
|
||||
|
||||
$("#airdate").html(moment($("#airdate").text()).format('MMM DD, YYYY'));
|
||||
$("#runtime").html(millisecondsToMinutes($("#runtime").text(), true));
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
$.fn.dataTable.tables({ visible: true, api: true }).columns.adjust();
|
||||
});
|
||||
|
||||
var airdate = $("#airdate")
|
||||
var runtime = $("#runtime")
|
||||
airdate.html(moment(airdate.text()).format('MMM DD, YYYY'));
|
||||
runtime.html(humanDuration(runtime.text()));
|
||||
|
||||
$('div.art-face').animate({ opacity: 0.2 }, { duration: 1000 });
|
||||
$('#channel-icon').popover({
|
||||
selector: '[data-toggle=popover]',
|
||||
@@ -828,6 +948,127 @@ DOCUMENTATION :: END
|
||||
}
|
||||
});
|
||||
</script>
|
||||
% if _session['user_group'] == 'admin':
|
||||
<script>
|
||||
$("#toggle-export-modal").click(function() {
|
||||
$.ajax({
|
||||
url: 'export_metadata_modal',
|
||||
data: {
|
||||
section_id: $(this).data('section_id'),
|
||||
rating_key: $(this).data('rating_key'),
|
||||
media_type: $(this).data('media_type'),
|
||||
sub_media_type: $(this).data('sub_media_type')
|
||||
},
|
||||
cache: false,
|
||||
async: true,
|
||||
complete: function(xhr, status) {
|
||||
$("#export-modal").html(xhr.responseText);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function loadExportTable() {
|
||||
// Build export table
|
||||
export_table_options.ajax = {
|
||||
url: 'get_export_list',
|
||||
type: 'POST',
|
||||
data: function ( d ) {
|
||||
return {
|
||||
json_data: JSON.stringify( d ),
|
||||
rating_key: "${data['rating_key']}"
|
||||
};
|
||||
}
|
||||
};
|
||||
export_table = $('#export_table-RK-${data["rating_key"]}').DataTable(export_table_options);
|
||||
export_table.columns([2, 7]).visible(false);
|
||||
|
||||
var colvis = new $.fn.dataTable.ColVis(export_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark' });
|
||||
$(colvis.button()).appendTo('#button-bar-export');
|
||||
|
||||
clearSearchButton('export_table-RK-${data["rating_key"]}', export_table);
|
||||
}
|
||||
|
||||
$('#nav-tabs-export').on('shown.bs.tab', function() {
|
||||
if (typeof(export_table) === 'undefined') {
|
||||
loadExportTable();
|
||||
}
|
||||
});
|
||||
|
||||
$(document).ready(function () {
|
||||
if (!($('#tabs-history').length)) {
|
||||
loadExportTable();
|
||||
}
|
||||
});
|
||||
|
||||
$("#refresh-export-table").click(function () {
|
||||
export_table.draw();
|
||||
});
|
||||
|
||||
$('#row-edit-mode').on('click', function() {
|
||||
$('#row-edit-mode-alert').fadeIn(200);
|
||||
|
||||
if ($(this).hasClass('active')) {
|
||||
if (history_to_delete.length > 0) {
|
||||
$('#deleteCount').text(history_to_delete.length);
|
||||
$('#confirm-modal-delete').modal();
|
||||
$('#confirm-modal-delete').one('click', '#confirm-delete', function () {
|
||||
$.ajax({
|
||||
url: 'delete_history_rows',
|
||||
type: 'POST',
|
||||
data: { row_ids: history_to_delete.join(',') },
|
||||
async: true,
|
||||
success: function (data) {
|
||||
var msg = "History deleted";
|
||||
showMsg(msg, false, true, 2000);
|
||||
history_table.draw();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$('.delete-control').each(function () {
|
||||
$(this).addClass('hidden');
|
||||
$('#row-edit-mode-alert').fadeOut(200);
|
||||
});
|
||||
|
||||
} else {
|
||||
history_to_delete = [];
|
||||
$('.delete-control').each(function() {
|
||||
$(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger');
|
||||
$(this).removeClass('hidden');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Send recently added notification
|
||||
$('#send-recently-added-notification').on('click', function () {
|
||||
var rating_key = $(this).data('id');
|
||||
|
||||
$('#send-recently-added-modal').modal();
|
||||
$('#send-recently-added-modal').one('click', '#confirm-send-notification', function () {
|
||||
$.ajax({
|
||||
url: 'send_manual_on_created',
|
||||
data: {
|
||||
rating_key: rating_key,
|
||||
notifier_id: $('#send-notification-notifier option:selected').val()
|
||||
},
|
||||
async: true,
|
||||
success: function (data) {
|
||||
if (data.result === 'success') {
|
||||
showMsg('<i class="fa fa-check"></i> ' + data.message, false, true, 5000);
|
||||
} else {
|
||||
showMsg('<i class="fa fa-exclamation-circle"></i> ' + data.message, false, true, 5000, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$('.metadata-xml').on('tripleclick', function () {
|
||||
openPlexXML("/library/metadata/${data['rating_key']}");
|
||||
});
|
||||
</script>
|
||||
% endif
|
||||
% if data.get('poster_url'):
|
||||
<script>
|
||||
$('#hosted-poster').popover({
|
||||
|
@@ -28,14 +28,15 @@ DOCUMENTATION :: END
|
||||
|
||||
% if data != None:
|
||||
<%
|
||||
from plexpy.helpers import page
|
||||
from plexpy.helpers import cast_to_int, page
|
||||
%>
|
||||
% if data['children_count'] > 0:
|
||||
<div class="item-children-wrapper">
|
||||
<ul class="item-children-instance list-unstyled">
|
||||
<% max_height ='max-height' if data['children_type'] in ('track', 'photo') or media_type == 'playlist' else '' %>
|
||||
<ul class="item-children-instance ${max_height} list-unstyled">
|
||||
% for child in data['children_list']:
|
||||
% if child['rating_key']:
|
||||
% if data['children_type'] == 'track':
|
||||
% if data['children_type'] in ('track', 'photo') or media_type == 'playlist':
|
||||
<li class="item-children-list-item">
|
||||
% else:
|
||||
<li>
|
||||
@@ -123,37 +124,144 @@ DOCUMENTATION :: END
|
||||
</h3>
|
||||
</div>
|
||||
% elif data['children_type'] == 'track':
|
||||
% if loop.index % 2 == 0:
|
||||
<div class="item-children-list-item-even">
|
||||
<span class="item-children-list-item-index"> ${child['media_index']}</span>
|
||||
<span class="item-children-list-item-title"><a href="${page('info', child['rating_key'])}" title="${child['title']}">${child['title']}</a>
|
||||
% if child['original_title']:
|
||||
<% e = 'even' if loop.index % 2 == 0 else 'odd' %>
|
||||
<div class="item-children-list-item-${e}">
|
||||
<span class="item-children-list-item-index">${child['media_index']}</span>
|
||||
<span class="item-children-list-item-title">
|
||||
<span class="media-type-tooltip" data-toggle="tooltip" title="Track"><i class="fa fa-music fa-fw"></i></span>
|
||||
<a href="${page('info', child['rating_key'])}" title="${child['title']}">
|
||||
<span class="thumb-tooltip" data-toggle="popover" data-img="${page('pms_image_proxy', child['parent_thumb'], child['parent_rating_key'], 300, 300, fallback='cover')}" data-height="80" data-width="80">
|
||||
${child['title']}
|
||||
</span>
|
||||
</a>
|
||||
% if child['original_title']:
|
||||
<span class="text-muted"> - ${child['original_title']}</span>
|
||||
% endif
|
||||
% endif
|
||||
</span>
|
||||
<span class="item-children-list-item-duration" id="item-children-list-item-duration-${loop.index + 1}">
|
||||
<script>$('#item-children-list-item-duration-${loop.index + 1}').text(moment.utc(${child['duration']}).format("m:ss"));</script>
|
||||
<% f = 'h:mm:ss' if cast_to_int(child['duration']) >= 3600000 else 'm:ss' %>
|
||||
<script>$('#item-children-list-item-duration-${loop.index + 1}').text(moment.utc(${child['duration']}).format("${f}"));</script>
|
||||
</span>
|
||||
</div>
|
||||
% else:
|
||||
<div class="item-children-list-item-odd">
|
||||
<span class="item-children-list-item-index"> ${child['media_index']}</span>
|
||||
<span class="item-children-list-item-title"><a href="${page('info', child['rating_key'])}" title="${child['title']}">${child['title']}</a>
|
||||
% if child['original_title']:
|
||||
<span class="text-muted"> - ${child['original_title']}</span>
|
||||
% endif
|
||||
% elif data['children_type'] == 'photo':
|
||||
<% e = 'even' if loop.index % 2 == 0 else 'odd' %>
|
||||
<div class="item-children-list-item-${e}">
|
||||
<span class="item-children-list-item-index">${loop.index + 1}</span>
|
||||
<span class="item-children-list-item-title">
|
||||
% if child['media_type'] == 'photo_album':
|
||||
<span class="media-type-tooltip" data-toggle="tooltip" title="Photo"><i class="fa fa-camera fa-fw"></i></span>
|
||||
% elif child['media_type'] == 'clip':
|
||||
<span class="media-type-tooltip" data-toggle="tooltip" title="Photo"><i class="fa fa-video-camera fa-fw"></i></span>
|
||||
% else:
|
||||
<span class="media-type-tooltip" data-toggle="tooltip" title="Photo"><i class="fa fa-picture-o fa-fw"></i></span>
|
||||
% endif
|
||||
<a href="${page('info', child['rating_key'])}" title="${child['title']}">
|
||||
<span class="thumb-tooltip" data-toggle="popover" data-img="${page('pms_image_proxy', child['thumb'], child['rating_key'], 300, 300, fallback='cover')}" data-height="80" data-width="80">
|
||||
${child['title']}
|
||||
</span>
|
||||
</a>
|
||||
</span>
|
||||
% if child['duration']:
|
||||
<span class="item-children-list-item-duration" id="item-children-list-item-duration-${loop.index + 1}">
|
||||
<script>$('#item-children-list-item-duration-${loop.index + 1}').text(moment.utc(${child['duration']}).format("m:ss"));</script>
|
||||
<% f = 'h:mm:ss' if cast_to_int(child['duration']) >= 3600000 else 'm:ss' %>
|
||||
<script>$('#item-children-list-item-duration-${loop.index + 1}').text(moment.utc(${child['duration']}).format("${f}"));</script>
|
||||
</span>
|
||||
% endif
|
||||
</div>
|
||||
% elif media_type == 'playlist':
|
||||
<% e = 'even' if loop.index % 2 == 0 else 'odd' %>
|
||||
<div class="item-children-list-item-${e}">
|
||||
<span class="item-children-list-item-index">${loop.index + 1}</span>
|
||||
<span class="item-children-list-item-title">
|
||||
% if child['media_type'] == 'movie':
|
||||
<span class="media-type-tooltip" data-toggle="tooltip" title="Movie"><i class="fa fa-film fa-fw"></i></span>
|
||||
<a href="${page('info', child['rating_key'])}" title="${child['title']}">
|
||||
<span class="thumb-tooltip" data-toggle="popover" data-img="${page('pms_image_proxy', child['thumb'], child['rating_key'], 300, 450, fallback='poster')}" data-height="120" data-width="80">
|
||||
${child['title']}
|
||||
</span>
|
||||
</a>
|
||||
<span class="text-muted"> (${child['year']})</span>
|
||||
% elif child['media_type'] == 'episode':
|
||||
<span class="media-type-tooltip" data-toggle="tooltip" title="Episode"><i class="fa fa-television fa-fw"></i></span>
|
||||
<a href="${page('info', child['grandparent_rating_key'])}" title="${child['grandparent_title']}">
|
||||
<span class="thumb-tooltip" data-toggle="popover" data-img="${page('pms_image_proxy', child['grandparent_thumb'], child['grandparent_rating_key'], 300, 450, fallback='poster')}" data-height="120" data-width="80">
|
||||
${child['grandparent_title']}
|
||||
</span>
|
||||
</a> -
|
||||
<a href="${page('info', child['rating_key'])}" title="${child['title']}">
|
||||
<span class="thumb-tooltip" data-toggle="popover" data-img="${page('pms_image_proxy', child['parent_thumb'], child['parent_rating_key'], 300, 450, fallback='poster')}" data-height="120" data-width="80">
|
||||
${child['title']}
|
||||
</span>
|
||||
</a>
|
||||
<span class="text-muted"> (<a class="no-highlight" href="${page('info', child['parent_rating_key'])}" title="${child['parent_title']}">S${child['parent_media_index']}</a> · <a class="no-highlight" href="${page('info', child['rating_key'])}" title="${child['title']}">E${child['media_index']}</a>)</span>
|
||||
% elif child['media_type'] == 'track':
|
||||
<span class="media-type-tooltip" data-toggle="tooltip" title="Track"><i class="fa fa-music fa-fw"></i></span>
|
||||
<a href="${page('info', child['rating_key'])}" title="${child['title']}">
|
||||
<span class="thumb-tooltip" data-toggle="popover" data-img="${page('pms_image_proxy', child['parent_thumb'], child['parent_rating_key'], 300, 300, fallback='cover')}" data-height="80" data-width="80">
|
||||
${child['title']}
|
||||
</span>
|
||||
</a> -
|
||||
<a href="${page('info', child['grandparent_rating_key'])}" title="${child['grandparent_title']}">
|
||||
<span class="thumb-tooltip" data-toggle="popover" data-img="${page('pms_image_proxy', child['grandparent_thumb'], child['grandparent_rating_key'], 300, 300, fallback='cover')}" data-height="80" data-width="80">
|
||||
${child['grandparent_title']}
|
||||
</span>
|
||||
</a>
|
||||
<span class="text-muted"> (<a class="no-highlight" href="${page('info', child['parent_rating_key'])}" title="${child['parent_title']}">${child['parent_title']}</a>)</span>
|
||||
% elif child['media_type'] == 'photo':
|
||||
<span class="media-type-tooltip" data-toggle="tooltip" title="Photo"><i class="fa fa-picture-o fa-fw"></i></span>
|
||||
<a href="${page('info', child['rating_key'])}" title="${child['title']}">
|
||||
<span class="thumb-tooltip" data-toggle="popover" data-img="${page('pms_image_proxy', child['thumb'], child['rating_key'], 300, 300, fallback='cover')}" data-height="80" data-width="80">
|
||||
${child['title']}
|
||||
</span>
|
||||
</a>
|
||||
% if child['grandparent_title']:
|
||||
- <a href="${page('info', child['grandparent_rating_key'])}" title="${child['grandparent_title']}">
|
||||
<span class="thumb-tooltip" data-toggle="popover" data-img="${page('pms_image_proxy', child['grandparent_thumb'], child['grandparent_rating_key'], 300, 300, fallback='cover')}" data-height="80" data-width="80">
|
||||
${child['grandparent_title']}
|
||||
</span>
|
||||
</a>
|
||||
% endif
|
||||
<span class="text-muted"> (<a class="no-highlight" href="${page('info', child['parent_rating_key'])}" title="${child['parent_title']}">${child['parent_title']}</a>)</span>
|
||||
% elif child['media_type'] == 'clip':
|
||||
<span class="media-type-tooltip" data-toggle="tooltip" title="Video"><i class="fa fa-video-camera fa-fw"></i></span>
|
||||
<a href="${page('info', child['rating_key'])}" title="${child['title']}">
|
||||
<span class="thumb-tooltip" data-toggle="popover" data-img="${page('pms_image_proxy', child['thumb'], child['rating_key'], 300, 300, fallback='cover')}" data-height="80" data-width="80">
|
||||
${child['title']}
|
||||
</span>
|
||||
</a>
|
||||
<span class="text-muted"> (<a class="no-highlight" href="${page('info', child['parent_rating_key'])}" title="${child['parent_title']}">${child['parent_title']}</a>)</span>
|
||||
% endif
|
||||
</span>
|
||||
% if child['duration']:
|
||||
<span class="item-children-list-item-duration" id="item-children-list-item-duration-${loop.index + 1}">
|
||||
<% f = 'h:mm:ss' if cast_to_int(child['duration']) >= 3600000 else 'm:ss' %>
|
||||
<script>$('#item-children-list-item-duration-${loop.index + 1}').text(moment.utc(${child['duration']}).format("${f}"));</script>
|
||||
</span>
|
||||
% endif
|
||||
</div>
|
||||
% endif
|
||||
% endif
|
||||
</li>
|
||||
% endif
|
||||
% endfor
|
||||
</ul>
|
||||
</div>
|
||||
<script>
|
||||
$('body').tooltip({
|
||||
selector: '[data-toggle="tooltip"]',
|
||||
container: 'body'
|
||||
});
|
||||
$('body').popover({
|
||||
selector: '[data-toggle="popover"]',
|
||||
html: true,
|
||||
container: 'body',
|
||||
trigger: 'hover',
|
||||
placement: 'right',
|
||||
template: '<div class="popover history-thumbnail-popover" role="tooltip"><div class="arrow" style="top: 50%;"></div><div class="popover-content"></div></div>',
|
||||
content: function () {
|
||||
return '<div class="history-thumbnail" style="background-image: url(' + $(this).data('img') + '); height: ' + $(this).data('height') + 'px; width: ' + $(this).data('width') + 'px;" />';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
% endif
|
||||
% endif
|
||||
|
||||
|
@@ -65,7 +65,7 @@ DOCUMENTATION :: END
|
||||
<ul class="item-children-instance list-unstyled">
|
||||
% for child in data['results_list']['collection']:
|
||||
<li>
|
||||
<a href="${page('info', child['rating_key'])}" id="${child['rating_key']}">
|
||||
<a href="${page('info', child['rating_key'])}" data-rating_key="${child['rating_key']}" data-library_name="${child['library_name']}">
|
||||
<div class="item-children-poster">
|
||||
<div class="item-children-poster-face poster-item" style="background-image: url(${page('pms_image_proxy', child['thumb'], child['rating_key'], 300, 450, fallback='poster')});"></div>
|
||||
% if _session['user_group'] == 'admin':
|
||||
@@ -90,7 +90,7 @@ DOCUMENTATION :: END
|
||||
<ul class="item-children-instance list-unstyled">
|
||||
% for child in data['results_list']['movie']:
|
||||
<li>
|
||||
<a href="${page('info', child['rating_key'])}" id="${child['rating_key']}">
|
||||
<a href="${page('info', child['rating_key'])}" data-rating_key="${child['rating_key']}" data-library_name="${child['library_name']}">
|
||||
<div class="item-children-poster">
|
||||
<div class="item-children-poster-face poster-item" style="background-image: url(${page('pms_image_proxy', child['thumb'], child['rating_key'], 300, 450, fallback='poster')});"></div>
|
||||
% if _session['user_group'] == 'admin':
|
||||
@@ -115,7 +115,7 @@ DOCUMENTATION :: END
|
||||
<ul class="item-children-instance list-unstyled">
|
||||
% for child in data['results_list']['show']:
|
||||
<li>
|
||||
<a href="${page('info', child['rating_key'])}" id="${child['rating_key']}">
|
||||
<a href="${page('info', child['rating_key'])}" data-rating_key="${child['rating_key']}" data-library_name="${child['library_name']}">
|
||||
<div class="item-children-poster">
|
||||
<div class="item-children-poster-face poster-item" style="background-image: url(${page('pms_image_proxy', child['thumb'], child['rating_key'], 300, 450, fallback='poster')});"></div>
|
||||
% if _session['user_group'] == 'admin':
|
||||
@@ -140,7 +140,7 @@ DOCUMENTATION :: END
|
||||
<ul class="item-children-instance list-unstyled">
|
||||
% for child in data['results_list']['season']:
|
||||
<li>
|
||||
<a href="${page('info', child['rating_key'])}" id="${child['rating_key']}">
|
||||
<a href="${page('info', child['rating_key'])}" data-rating_key="${child['rating_key']}" data-library_name="${child['library_name']}">
|
||||
<div class="item-children-poster">
|
||||
<div class="item-children-poster-face poster-item" style="background-image: url(${page('pms_image_proxy', child['thumb'], child['rating_key'], 300, 450, fallback='poster')});"></div>
|
||||
% if _session['user_group'] == 'admin':
|
||||
@@ -165,7 +165,7 @@ DOCUMENTATION :: END
|
||||
<ul class="item-children-instance list-unstyled">
|
||||
% for child in data['results_list']['episode']:
|
||||
<li>
|
||||
<a href="${page('info', child['rating_key'])}" id="${child['rating_key']}">
|
||||
<a href="${page('info', child['rating_key'])}" data-rating_key="${child['rating_key']}" data-library_name="${child['library_name']}">
|
||||
<div class="item-children-poster">
|
||||
<div class="item-children-poster-face episode-item" style="background-image: url(${page('pms_image_proxy', child['thumb'], child['rating_key'], 500, 280, fallback='art')});"></div>
|
||||
% if _session['user_group'] == 'admin':
|
||||
@@ -191,7 +191,7 @@ DOCUMENTATION :: END
|
||||
<ul class="item-children-instance list-unstyled">
|
||||
% for child in data['results_list']['artist']:
|
||||
<li>
|
||||
<a href="${page('info', child['rating_key'])}" id="${child['rating_key']}">
|
||||
<a href="${page('info', child['rating_key'])}" data-rating_key="${child['rating_key']}" data-library_name="${child['library_name']}">
|
||||
<div class="item-children-poster">
|
||||
<div class="item-children-poster-face cover-item" style="background-image: url(${page('pms_image_proxy', child['thumb'], child['rating_key'], 300, 300, fallback='cover')});"></div>
|
||||
% if _session['user_group'] == 'admin':
|
||||
@@ -215,7 +215,7 @@ DOCUMENTATION :: END
|
||||
<ul class="item-children-instance list-unstyled">
|
||||
% for child in data['results_list']['album']:
|
||||
<li>
|
||||
<a href="${page('info', child['rating_key'])}" id="${child['rating_key']}">
|
||||
<a href="${page('info', child['rating_key'])}" data-rating_key="${child['rating_key']}" data-library_name="${child['library_name']}">
|
||||
<div class="item-children-poster">
|
||||
<div class="item-children-poster-face cover-item" style="background-image: url(${page('pms_image_proxy', child['thumb'], child['rating_key'], 300, 300, fallback='cover')});"></div>
|
||||
% if _session['user_group'] == 'admin':
|
||||
@@ -240,7 +240,7 @@ DOCUMENTATION :: END
|
||||
<ul class="item-children-instance list-unstyled">
|
||||
% for child in data['results_list']['track']:
|
||||
<li>
|
||||
<a href="${page('info', child['rating_key'])}" id="${child['rating_key']}">
|
||||
<a href="${page('info', child['rating_key'])}" data-rating_key="${child['rating_key']}" data-library_name="${child['library_name']}">
|
||||
<div class="item-children-poster">
|
||||
<div class="item-children-poster-face cover-item" style="background-image: url(${page('pms_image_proxy', child['parent_thumb'], child['parent_rating_key'], 300, 300, fallback='cover')});">
|
||||
<div class="item-children-card-overlay">
|
||||
|
@@ -36,7 +36,3 @@ function check_notifications() {
|
||||
check_notifications();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
check_notifications();
|
||||
});
|
8
data/interfaces/default/js/bootstrap.min.js
vendored
@@ -790,6 +790,9 @@ ColVis.prototype = {
|
||||
oStyle.top = oPos.top+"px";
|
||||
oStyle.left = iDivX+"px";
|
||||
|
||||
var iDocWidth = $(document).width();
|
||||
var iDocHeight = $(document).height();
|
||||
|
||||
document.body.appendChild( nBackground );
|
||||
document.body.appendChild( nHidden );
|
||||
document.body.appendChild( this.dom.catcher );
|
||||
@@ -819,12 +822,17 @@ ColVis.prototype = {
|
||||
|
||||
var iDivWidth = $(nHidden).outerWidth();
|
||||
var iDivHeight = $(nHidden).outerHeight();
|
||||
var iDocWidth = $(document).width();
|
||||
var iDivMarginTop = parseInt($(nHidden).css("marginTop"), 10);
|
||||
var iDivMarginBottom = parseInt($(nHidden).css("marginBottom"), 10);
|
||||
|
||||
if ( iLeft + iDivWidth > iDocWidth )
|
||||
{
|
||||
nHidden.style.left = (iDocWidth-iDivWidth)+"px";
|
||||
}
|
||||
if ( iDivY + iDivHeight > iDocHeight )
|
||||
{
|
||||
nHidden.style.top = (oPos.top - iDivHeight - iDivMarginTop - iDivMarginBottom)+"px";
|
||||
}
|
||||
}
|
||||
|
||||
this.s.hidden = false;
|
||||
@@ -846,7 +854,8 @@ ColVis.prototype = {
|
||||
this.s.hidden = true;
|
||||
|
||||
$(this.dom.collection).animate({"opacity": 0}, that.s.iOverlayFade, function (e) {
|
||||
this.style.display = "none";
|
||||
// this.style.display = "none";
|
||||
document.body.removeChild( this );
|
||||
} );
|
||||
|
||||
$(this.dom.background).animate({"opacity": 0}, that.s.iOverlayFade, function (e) {
|
||||
|
2
data/interfaces/default/js/jquery-3.5.1.min.js
vendored
Normal file
1
data/interfaces/default/js/jquery.inputaffix.min.js
vendored
Normal file
@@ -1,482 +0,0 @@
|
||||
/*! Moment Duration Format v1.3.0
|
||||
* https://github.com/jsmreese/moment-duration-format
|
||||
* Date: 2014-07-15
|
||||
*
|
||||
* Duration format plugin function for the Moment.js library
|
||||
* http://momentjs.com/
|
||||
*
|
||||
* Copyright 2014 John Madhavan-Reese
|
||||
* Released under the MIT license
|
||||
*/
|
||||
|
||||
(function (root, undefined) {
|
||||
|
||||
// repeatZero(qty)
|
||||
// returns "0" repeated qty times
|
||||
function repeatZero(qty) {
|
||||
var result = "";
|
||||
|
||||
// exit early
|
||||
// if qty is 0 or a negative number
|
||||
// or doesn't coerce to an integer
|
||||
qty = parseInt(qty, 10);
|
||||
if (!qty || qty < 1) { return result; }
|
||||
|
||||
while (qty) {
|
||||
result += "0";
|
||||
qty -= 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// padZero(str, len [, isRight])
|
||||
// pads a string with zeros up to a specified length
|
||||
// will not pad a string if its length is aready
|
||||
// greater than or equal to the specified length
|
||||
// default output pads with zeros on the left
|
||||
// set isRight to `true` to pad with zeros on the right
|
||||
function padZero(str, len, isRight) {
|
||||
if (str == null) { str = ""; }
|
||||
str = "" + str;
|
||||
|
||||
return (isRight ? str : "") + repeatZero(len - str.length) + (isRight ? "" : str);
|
||||
}
|
||||
|
||||
// isArray
|
||||
function isArray(array) {
|
||||
return Object.prototype.toString.call(array) === "[object Array]";
|
||||
}
|
||||
|
||||
// isObject
|
||||
function isObject(obj) {
|
||||
return Object.prototype.toString.call(obj) === "[object Object]";
|
||||
}
|
||||
|
||||
// findLast
|
||||
function findLast(array, callback) {
|
||||
var index = array.length;
|
||||
|
||||
while (index -= 1) {
|
||||
if (callback(array[index])) { return array[index]; }
|
||||
}
|
||||
}
|
||||
|
||||
// find
|
||||
function find(array, callback) {
|
||||
var index = 0,
|
||||
max = array.length,
|
||||
match;
|
||||
|
||||
if (typeof callback !== "function") {
|
||||
match = callback;
|
||||
callback = function (item) {
|
||||
return item === match;
|
||||
};
|
||||
}
|
||||
|
||||
while (index < max) {
|
||||
if (callback(array[index])) { return array[index]; }
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// each
|
||||
function each(array, callback) {
|
||||
var index = 0,
|
||||
max = array.length;
|
||||
|
||||
if (!array || !max) { return; }
|
||||
|
||||
while (index < max) {
|
||||
if (callback(array[index], index) === false) { return; }
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// map
|
||||
function map(array, callback) {
|
||||
var index = 0,
|
||||
max = array.length,
|
||||
ret = [];
|
||||
|
||||
if (!array || !max) { return ret; }
|
||||
|
||||
while (index < max) {
|
||||
ret[index] = callback(array[index], index);
|
||||
index += 1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// pluck
|
||||
function pluck(array, prop) {
|
||||
return map(array, function (item) {
|
||||
return item[prop];
|
||||
});
|
||||
}
|
||||
|
||||
// compact
|
||||
function compact(array) {
|
||||
var ret = [];
|
||||
|
||||
each(array, function (item) {
|
||||
if (item) { ret.push(item); }
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// unique
|
||||
function unique(array) {
|
||||
var ret = [];
|
||||
|
||||
each(array, function (_a) {
|
||||
if (!find(ret, _a)) { ret.push(_a); }
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// intersection
|
||||
function intersection(a, b) {
|
||||
var ret = [];
|
||||
|
||||
each(a, function (_a) {
|
||||
each(b, function (_b) {
|
||||
if (_a === _b) { ret.push(_a); }
|
||||
});
|
||||
});
|
||||
|
||||
return unique(ret);
|
||||
}
|
||||
|
||||
// rest
|
||||
function rest(array, callback) {
|
||||
var ret = [];
|
||||
|
||||
each(array, function (item, index) {
|
||||
if (!callback(item)) {
|
||||
ret = array.slice(index);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// initial
|
||||
function initial(array, callback) {
|
||||
var reversed = array.slice().reverse();
|
||||
|
||||
return rest(reversed, callback).reverse();
|
||||
}
|
||||
|
||||
// extend
|
||||
function extend(a, b) {
|
||||
for (var key in b) {
|
||||
if (b.hasOwnProperty(key)) { a[key] = b[key]; }
|
||||
}
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
// define internal moment reference
|
||||
var moment;
|
||||
|
||||
if (typeof require === "function") {
|
||||
try { moment = require('moment'); }
|
||||
catch (e) {}
|
||||
}
|
||||
|
||||
if (!moment && root.moment) {
|
||||
moment = root.moment;
|
||||
}
|
||||
|
||||
if (!moment) {
|
||||
throw "Moment Duration Format cannot find Moment.js";
|
||||
}
|
||||
|
||||
// moment.duration.format([template] [, precision] [, settings])
|
||||
moment.duration.fn.format = function () {
|
||||
|
||||
var tokenizer, tokens, types, typeMap, momentTypes, foundFirst, trimIndex,
|
||||
args = [].slice.call(arguments),
|
||||
settings = extend({}, this.format.defaults),
|
||||
// keep a shadow copy of this moment for calculating remainders
|
||||
remainder = moment.duration(this);
|
||||
|
||||
// add a reference to this duration object to the settings for use
|
||||
// in a template function
|
||||
settings.duration = this;
|
||||
|
||||
// parse arguments
|
||||
each(args, function (arg) {
|
||||
if (typeof arg === "string" || typeof arg === "function") {
|
||||
settings.template = arg;
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof arg === "number") {
|
||||
settings.precision = arg;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isObject(arg)) {
|
||||
extend(settings, arg);
|
||||
}
|
||||
});
|
||||
|
||||
// types
|
||||
types = settings.types = (isArray(settings.types) ? settings.types : settings.types.split(" "));
|
||||
|
||||
// template
|
||||
if (typeof settings.template === "function") {
|
||||
settings.template = settings.template.apply(settings);
|
||||
}
|
||||
|
||||
// tokenizer regexp
|
||||
tokenizer = new RegExp(map(types, function (type) {
|
||||
return settings[type].source;
|
||||
}).join("|"), "g");
|
||||
|
||||
// token type map function
|
||||
typeMap = function (token) {
|
||||
return find(types, function (type) {
|
||||
return settings[type].test(token);
|
||||
});
|
||||
};
|
||||
|
||||
// tokens array
|
||||
tokens = map(settings.template.match(tokenizer), function (token, index) {
|
||||
var type = typeMap(token),
|
||||
length = token.length;
|
||||
|
||||
return {
|
||||
index: index,
|
||||
length: length,
|
||||
|
||||
// replace escaped tokens with the non-escaped token text
|
||||
token: (type === "escape" ? token.replace(settings.escape, "$1") : token),
|
||||
|
||||
// ignore type on non-moment tokens
|
||||
type: ((type === "escape" || type === "general") ? null : type)
|
||||
|
||||
// calculate base value for all moment tokens
|
||||
//baseValue: ((type === "escape" || type === "general") ? null : this.as(type))
|
||||
};
|
||||
}, this);
|
||||
|
||||
// unique moment token types in the template (in order of descending magnitude)
|
||||
momentTypes = intersection(types, unique(compact(pluck(tokens, "type"))));
|
||||
|
||||
// exit early if there are no momentTypes
|
||||
if (!momentTypes.length) {
|
||||
return pluck(tokens, "token").join("");
|
||||
}
|
||||
|
||||
// calculate values for each token type in the template
|
||||
each(momentTypes, function (momentType, index) {
|
||||
var value, wholeValue, decimalValue, isLeast, isMost;
|
||||
|
||||
// calculate integer and decimal value portions
|
||||
value = remainder.as(momentType);
|
||||
wholeValue = (value > 0 ? Math.floor(value) : Math.ceil(value));
|
||||
decimalValue = value - wholeValue;
|
||||
|
||||
// is this the least-significant moment token found?
|
||||
isLeast = ((index + 1) === momentTypes.length);
|
||||
|
||||
// is this the most-significant moment token found?
|
||||
isMost = (!index);
|
||||
|
||||
// update tokens array
|
||||
// using this algorithm to not assume anything about
|
||||
// the order or frequency of any tokens
|
||||
each(tokens, function (token) {
|
||||
if (token.type === momentType) {
|
||||
extend(token, {
|
||||
value: value,
|
||||
wholeValue: wholeValue,
|
||||
decimalValue: decimalValue,
|
||||
isLeast: isLeast,
|
||||
isMost: isMost
|
||||
});
|
||||
|
||||
if (isMost) {
|
||||
// note the length of the most-significant moment token:
|
||||
// if it is greater than one and forceLength is not set, default forceLength to `true`
|
||||
if (settings.forceLength == null && token.length > 1) {
|
||||
settings.forceLength = true;
|
||||
}
|
||||
|
||||
// rationale is this:
|
||||
// if the template is "h:mm:ss" and the moment value is 5 minutes, the user-friendly output is "5:00", not "05:00"
|
||||
// shouldn't pad the `minutes` token even though it has length of two
|
||||
// if the template is "hh:mm:ss", the user clearly wanted everything padded so we should output "05:00"
|
||||
// if the user wanted the full padded output, they can set `{ trim: false }` to get "00:05:00"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// update remainder
|
||||
remainder.subtract(wholeValue, momentType);
|
||||
});
|
||||
|
||||
// trim tokens array
|
||||
if (settings.trim) {
|
||||
tokens = (settings.trim === "left" ? rest : initial)(tokens, function (token) {
|
||||
// return `true` if:
|
||||
// the token is not the least moment token (don't trim the least moment token)
|
||||
// the token is a moment token that does not have a value (don't trim moment tokens that have a whole value)
|
||||
return !(token.isLeast || (token.type != null && token.wholeValue));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// build output
|
||||
|
||||
// the first moment token can have special handling
|
||||
foundFirst = false;
|
||||
|
||||
// run the map in reverse order if trimming from the right
|
||||
if (settings.trim === "right") {
|
||||
tokens.reverse();
|
||||
}
|
||||
|
||||
tokens = map(tokens, function (token) {
|
||||
var val,
|
||||
decVal;
|
||||
|
||||
if (!token.type) {
|
||||
// if it is not a moment token, use the token as its own value
|
||||
return token.token;
|
||||
}
|
||||
|
||||
// apply negative precision formatting to the least-significant moment token
|
||||
if (token.isLeast && (settings.precision < 0)) {
|
||||
val = (Math.floor(token.wholeValue * Math.pow(10, settings.precision)) * Math.pow(10, -settings.precision)).toString();
|
||||
} else {
|
||||
val = token.wholeValue.toString();
|
||||
}
|
||||
|
||||
// remove negative sign from the beginning
|
||||
val = val.replace(/^\-/, "");
|
||||
|
||||
// apply token length formatting
|
||||
// special handling for the first moment token that is not the most significant in a trimmed template
|
||||
if (token.length > 1 && (foundFirst || token.isMost || settings.forceLength)) {
|
||||
val = padZero(val, token.length);
|
||||
}
|
||||
|
||||
// add decimal value if precision > 0
|
||||
if (token.isLeast && (settings.precision > 0)) {
|
||||
decVal = token.decimalValue.toString().replace(/^\-/, "").split(/\.|e\-/);
|
||||
switch (decVal.length) {
|
||||
case 1:
|
||||
val += "." + padZero(decVal[0], settings.precision, true).slice(0, settings.precision);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
val += "." + padZero(decVal[1], settings.precision, true).slice(0, settings.precision);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
val += "." + padZero(repeatZero((+decVal[2]) - 1) + (decVal[0] || "0") + decVal[1], settings.precision, true).slice(0, settings.precision);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw "Moment Duration Format: unable to parse token decimal value.";
|
||||
}
|
||||
}
|
||||
|
||||
// add a negative sign if the value is negative and token is most significant
|
||||
if (token.isMost && token.value < 0) {
|
||||
val = "-" + val;
|
||||
}
|
||||
|
||||
foundFirst = true;
|
||||
|
||||
return val;
|
||||
});
|
||||
|
||||
// undo the reverse if trimming from the right
|
||||
if (settings.trim === "right") {
|
||||
tokens.reverse();
|
||||
}
|
||||
|
||||
return tokens.join("");
|
||||
};
|
||||
|
||||
moment.duration.fn.format.defaults = {
|
||||
// token definitions
|
||||
escape: /\[(.+?)\]/,
|
||||
years: /[Yy]+/,
|
||||
months: /M+/,
|
||||
weeks: /[Ww]+/,
|
||||
days: /[Dd]+/,
|
||||
hours: /[Hh]+/,
|
||||
minutes: /m+/,
|
||||
seconds: /s+/,
|
||||
milliseconds: /S+/,
|
||||
general: /.+?/,
|
||||
|
||||
// token type names
|
||||
// in order of descending magnitude
|
||||
// can be a space-separated token name list or an array of token names
|
||||
types: "escape years months weeks days hours minutes seconds milliseconds general",
|
||||
|
||||
// format options
|
||||
|
||||
// trim
|
||||
// "left" - template tokens are trimmed from the left until the first moment token that has a value >= 1
|
||||
// "right" - template tokens are trimmed from the right until the first moment token that has a value >= 1
|
||||
// (the final moment token is not trimmed, regardless of value)
|
||||
// `false` - template tokens are not trimmed
|
||||
trim: "left",
|
||||
|
||||
// precision
|
||||
// number of decimal digits to include after (to the right of) the decimal point (positive integer)
|
||||
// or the number of digits to truncate to 0 before (to the left of) the decimal point (negative integer)
|
||||
precision: 0,
|
||||
|
||||
// force first moment token with a value to render at full length even when template is trimmed and first moment token has length of 1
|
||||
forceLength: null,
|
||||
|
||||
// template used to format duration
|
||||
// may be a function or a string
|
||||
// template functions are executed with the `this` binding of the settings object
|
||||
// so that template strings may be dynamically generated based on the duration object
|
||||
// (accessible via `this.duration`)
|
||||
// or any of the other settings
|
||||
template: function () {
|
||||
var types = this.types,
|
||||
dur = this.duration,
|
||||
lastType = findLast(types, function (type) {
|
||||
return dur._data[type];
|
||||
});
|
||||
|
||||
// default template strings for each duration dimension type
|
||||
switch (lastType) {
|
||||
case "seconds":
|
||||
return "h:mm:ss";
|
||||
case "minutes":
|
||||
return "d[d] h:mm";
|
||||
case "hours":
|
||||
return "d[d] h[h]";
|
||||
case "days":
|
||||
return "M[m] d[d]";
|
||||
case "weeks":
|
||||
return "y[y] w[w]";
|
||||
case "months":
|
||||
return "y[y] M[m]";
|
||||
case "years":
|
||||
return "y[y]";
|
||||
default:
|
||||
return "y[y] M[m] d[d] h:mm:ss";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
})(this);
|
11
data/interfaces/default/js/moment-duration-format.min.js
vendored
Normal file
2
data/interfaces/default/js/moment-with-locales.min.js
vendored
Normal file
@@ -237,6 +237,27 @@ function doAjaxCall(url, elem, reload, form, showMsg, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
getBrowsePath = function (key, path, filter_ext) {
|
||||
var deferred = $.Deferred();
|
||||
|
||||
$.ajax({
|
||||
url: 'browse_path',
|
||||
type: 'GET',
|
||||
data: {
|
||||
key: key,
|
||||
path: path,
|
||||
filter_ext: filter_ext
|
||||
},
|
||||
success: function(data) {
|
||||
deferred.resolve(data);
|
||||
},
|
||||
error: function() {
|
||||
deferred.reject();
|
||||
}
|
||||
});
|
||||
return deferred;
|
||||
};
|
||||
|
||||
function doSimpleAjaxCall(url) {
|
||||
$.ajax(url);
|
||||
}
|
||||
@@ -309,31 +330,24 @@ function humanTime(seconds) {
|
||||
}
|
||||
}
|
||||
|
||||
function humanTimeClean(seconds) {
|
||||
var text;
|
||||
if (seconds >= 86400) {
|
||||
text = Math.floor(moment.duration(seconds, 'seconds').asDays()) + ' days ' + Math.floor(moment.duration((
|
||||
seconds % 86400), 'seconds').asHours()) + ' hrs ' + Math.floor(moment.duration(
|
||||
((seconds % 86400) % 3600), 'seconds').asMinutes()) + ' mins';
|
||||
return text;
|
||||
} else if (seconds >= 3600) {
|
||||
text = Math.floor(moment.duration((seconds % 86400), 'seconds').asHours()) + ' hrs ' + Math.floor(moment.duration(
|
||||
((seconds % 86400) % 3600), 'seconds').asMinutes()) + ' mins';
|
||||
return text;
|
||||
} else if (seconds >= 60) {
|
||||
text = Math.floor(moment.duration(((seconds % 86400) % 3600), 'seconds').asMinutes()) + ' mins';
|
||||
return text;
|
||||
} else {
|
||||
text = '0';
|
||||
return text;
|
||||
}
|
||||
}
|
||||
String.prototype.toProperCase = function () {
|
||||
return this.replace(/\w\S*/g, function (txt) {
|
||||
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
|
||||
});
|
||||
};
|
||||
|
||||
function getPercent(value1, value2) {
|
||||
value1 = parseFloat(value1) | 0
|
||||
value2 = parseFloat(value2) | 0
|
||||
|
||||
var percent = 0;
|
||||
if (value1 !== 0 && value2 !== 0) {
|
||||
percent = (value1 / value2) * 100
|
||||
}
|
||||
|
||||
return Math.round(percent)
|
||||
}
|
||||
|
||||
function millisecondsToMinutes(ms, roundToMinute) {
|
||||
if (ms > 0) {
|
||||
var minutes = Math.floor(ms / 60000);
|
||||
@@ -351,6 +365,61 @@ function millisecondsToMinutes(ms, roundToMinute) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function humanDuration(ms, sig='dhm', units='ms', return_seconds=300000) {
|
||||
var factors = {
|
||||
d: 86400000,
|
||||
h: 3600000,
|
||||
m: 60000,
|
||||
s: 1000,
|
||||
ms: 1
|
||||
}
|
||||
|
||||
ms = parseInt(ms);
|
||||
var d, h, m, s;
|
||||
|
||||
if (ms > 0) {
|
||||
if (return_seconds && ms < return_seconds) {
|
||||
sig = 'dhms'
|
||||
}
|
||||
|
||||
ms = ms * factors[units];
|
||||
|
||||
h = ms % factors['d'];
|
||||
d = Math.trunc(ms / factors['d']);
|
||||
|
||||
m = h % factors['h'];
|
||||
h = Math.trunc(h / factors['h']);
|
||||
|
||||
s = m % factors['m'];
|
||||
m = Math.trunc(m / factors['m']);
|
||||
|
||||
ms = s % factors['s'];
|
||||
s = Math.trunc(s / factors['s']);
|
||||
|
||||
var hd_list = [];
|
||||
if (sig >= 'd' && d > 0) {
|
||||
d = (sig === 'd' && h >= 12) ? d + 1 : d;
|
||||
hd_list.push(d.toString() + ' day' + ((d > 1) ? 's' : ''));
|
||||
}
|
||||
if (sig >= 'dh' && h > 0) {
|
||||
h = (sig === 'dh' && m >= 30) ? h + 1 : h;
|
||||
hd_list.push(h.toString() + ' hr' + ((h > 1) ? 's' : ''));
|
||||
}
|
||||
if (sig >= 'dhm' && m > 0) {
|
||||
m = (sig === 'dhm' && s >= 30) ? m + 1 : m;
|
||||
hd_list.push(m.toString() + ' min' + ((m > 1) ? 's' : ''));
|
||||
}
|
||||
if (sig >= 'dhms' && s > 0) {
|
||||
hd_list.push(s.toString() + ' sec' + ((s > 1) ? 's' : ''));
|
||||
}
|
||||
|
||||
return hd_list.join(' ')
|
||||
} else {
|
||||
return '0'
|
||||
}
|
||||
}
|
||||
|
||||
// Our countdown plugin takes a callback, a duration, and an optional message
|
||||
$.fn.countdown = function (callback, duration, message) {
|
||||
// If no message is provided, we use an empty string
|
||||
@@ -534,16 +603,18 @@ function PopupCenter(url, title, w, h) {
|
||||
|
||||
|
||||
function setLocalStorage(key, value, path) {
|
||||
var key_path = key;
|
||||
if (path !== false) {
|
||||
key = key + '_' + window.location.pathname;
|
||||
key_path = key_path + '_' + window.location.pathname;
|
||||
}
|
||||
localStorage.setItem(key, value);
|
||||
localStorage.setItem(key_path, value);
|
||||
}
|
||||
function getLocalStorage(key, default_value, path) {
|
||||
var key_path = key;
|
||||
if (path !== false) {
|
||||
key = key + '_' + window.location.pathname;
|
||||
key_path = key_path + '_' + window.location.pathname;
|
||||
}
|
||||
var value = localStorage.getItem(key);
|
||||
var value = localStorage.getItem(key_path);
|
||||
if (value !== null) {
|
||||
return value
|
||||
} else if (default_value !== undefined) {
|
||||
@@ -782,3 +853,16 @@ function user_page(user_id, user) {
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
MEDIA_TYPE_HEADERS = {
|
||||
'movie': 'Movies',
|
||||
'show': 'TV Shows',
|
||||
'season': 'Seasons',
|
||||
'episode': 'Episodes',
|
||||
'artist': 'Artists',
|
||||
'album': 'Albums',
|
||||
'track': 'Tracks',
|
||||
'video': 'Videos',
|
||||
'audio': 'Tracks',
|
||||
'photo': 'Photos'
|
||||
}
|
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Plugin: "disable_options" (selectize.js)
|
||||
* Copyright (c) 2013 Mondo Robot & contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
|
||||
* file except in compliance with the License. You may obtain a copy of the License at:
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
* ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*
|
||||
* @authors Jake Myers <jmyers0022@gmail.com>, Vaughn Draughon <vaughn@rocksolidwebdesign.com>
|
||||
*/
|
||||
|
||||
Selectize.define('disable_options', function(options) {
|
||||
var self = this;
|
||||
|
||||
options = $.extend({
|
||||
'disableField': '',
|
||||
'disableOptions': []
|
||||
}, options);
|
||||
|
||||
self.refreshOptions = (function() {
|
||||
var original = self.refreshOptions;
|
||||
|
||||
return function() {
|
||||
original.apply(this, arguments);
|
||||
|
||||
$.each(options.disableOptions, function(index, option) {
|
||||
self.$dropdown_content.find('[data-' + options.disableField + '="' + String(option) + '"]').addClass('option-disabled');
|
||||
});
|
||||
};
|
||||
})();
|
||||
|
||||
self.onOptionSelect = (function() {
|
||||
var original = self.onOptionSelect;
|
||||
|
||||
return function(e) {
|
||||
var value, $target, $option;
|
||||
|
||||
if (e.preventDefault) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
$target = $(e.currentTarget);
|
||||
|
||||
if ($target.hasClass('option-disabled')) {
|
||||
return;
|
||||
}
|
||||
return original.apply(this, arguments);
|
||||
};
|
||||
})();
|
||||
|
||||
self.disabledOptions = function() {
|
||||
return options.disableOptions;
|
||||
}
|
||||
|
||||
self.setDisabledOptions = function( values ) {
|
||||
options.disableOptions = values
|
||||
}
|
||||
|
||||
self.disableOptions = function( values ) {
|
||||
if ( ! ( values instanceof Array ) ) {
|
||||
values = [ values ]
|
||||
}
|
||||
values.forEach( function( val ) {
|
||||
if ( options.disableOptions.indexOf( val ) == -1 ) {
|
||||
options.disableOptions.push( val )
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
self.enableOptions = function( values ) {
|
||||
if ( ! ( values instanceof Array ) ) {
|
||||
values = [ values ]
|
||||
}
|
||||
values.forEach( function( val ) {
|
||||
var remove = options.disableOptions.indexOf( val );
|
||||
if ( remove + 1 ) {
|
||||
options.disableOptions.splice( remove, 1 );
|
||||
}
|
||||
} );
|
||||
}
|
||||
});
|
111
data/interfaces/default/js/tables/collections_table.js
Normal file
@@ -0,0 +1,111 @@
|
||||
collections_table_options = {
|
||||
"destroy": true,
|
||||
"language": {
|
||||
"search": "Search: ",
|
||||
"lengthMenu": "Show _MENU_ entries per page",
|
||||
"info": "Showing _START_ to _END_ of _TOTAL_ collections",
|
||||
"infoEmpty": "Showing 0 to 0 of 0 entries",
|
||||
"infoFiltered": "<span class='hidden-md hidden-sm hidden-xs'>(filtered from _MAX_ total entries)</span>",
|
||||
"emptyTable": "No data in table",
|
||||
"loadingRecords": '<i class="fa fa-refresh fa-spin"></i> Loading items...</div>'
|
||||
},
|
||||
"pagingType": "full_numbers",
|
||||
"stateSave": true,
|
||||
"stateDuration": 0,
|
||||
"processing": false,
|
||||
"serverSide": true,
|
||||
"pageLength": 25,
|
||||
"order": [0, 'asc'],
|
||||
"autoWidth": false,
|
||||
"scrollX": true,
|
||||
"columnDefs": [
|
||||
{
|
||||
"targets": [0],
|
||||
"data": "titleSort",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
var thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['ratingKey'], 300, 450, null, null, null, 'poster') + '" data-height="120" data-width="80">' + rowData['title'] + '</span>';
|
||||
$(td).html('<a href="' + page('info', rowData['ratingKey']) + '"><i class="fa fa-blank fa-fw"></i>' + thumb_popover + '</a>');
|
||||
}
|
||||
},
|
||||
"width": "50%",
|
||||
"className": "no-wrap"
|
||||
},
|
||||
{
|
||||
"targets": [1],
|
||||
"data": "collectionMode",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
var mode = '';
|
||||
if (cellData === -1) {
|
||||
mode = 'Library default';
|
||||
} else if (cellData === 0) {
|
||||
mode = 'Hide collection';
|
||||
} else if (cellData === 1) {
|
||||
mode = 'Hide items in this collection';
|
||||
} else if (cellData === 2) {
|
||||
mode = 'Show this collection and its items';
|
||||
}
|
||||
$(td).html(mode);
|
||||
}
|
||||
},
|
||||
"width": "20%",
|
||||
"className": "no-wrap"
|
||||
},
|
||||
{
|
||||
"targets": [2],
|
||||
"data": "collectionSort",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
var sort = '';
|
||||
if (cellData === 0) {
|
||||
sort = 'Release date';
|
||||
} else if (cellData === 1) {
|
||||
sort = 'Alphabetical';
|
||||
}
|
||||
$(td).html(sort);
|
||||
}
|
||||
},
|
||||
"width": "20%",
|
||||
"className": "no-wrap"
|
||||
},
|
||||
{
|
||||
"targets": [3],
|
||||
"data": "childCount",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
var type = MEDIA_TYPE_HEADERS[rowData['subtype']] || '';
|
||||
if (rowData['childCount'] == 1) {
|
||||
type = type.slice(0, -1);
|
||||
}
|
||||
$(td).html(cellData + ' ' + type);
|
||||
}
|
||||
},
|
||||
"width": "10%",
|
||||
"className": "no-wrap"
|
||||
}
|
||||
],
|
||||
"drawCallback": function (settings) {
|
||||
// Jump to top of page
|
||||
//$('html,body').scrollTop(0);
|
||||
$('#ajaxMsg').fadeOut();
|
||||
|
||||
$('body').popover({
|
||||
selector: '[data-toggle="popover"]',
|
||||
html: true,
|
||||
container: 'body',
|
||||
trigger: 'hover',
|
||||
placement: 'right',
|
||||
template: '<div class="popover history-thumbnail-popover" role="tooltip"><div class="arrow" style="top: 50%;"></div><div class="popover-content"></div></div>',
|
||||
content: function () {
|
||||
return '<div class="history-thumbnail" style="background-image: url(' + $(this).data('img') + '); height: ' + $(this).data('height') + 'px; width: ' + $(this).data('width') + 'px;" />';
|
||||
}
|
||||
});
|
||||
},
|
||||
"preDrawCallback": function(settings) {
|
||||
var msg = "<i class='fa fa-refresh fa-spin'></i> Fetching rows...";
|
||||
showMsg(msg, false, false, 0);
|
||||
},
|
||||
"rowCallback": function (row, rowData, rowIndex) {
|
||||
}
|
||||
};
|
251
data/interfaces/default/js/tables/export_table.js
Normal file
@@ -0,0 +1,251 @@
|
||||
var date_format = 'YYYY-MM-DD';
|
||||
var time_format = 'hh:mm a';
|
||||
|
||||
$.ajax({
|
||||
url: 'get_date_formats',
|
||||
type: 'GET',
|
||||
success: function (data) {
|
||||
date_format = data.date_format;
|
||||
time_format = data.time_format;
|
||||
}
|
||||
});
|
||||
|
||||
export_table_options = {
|
||||
"destroy": true,
|
||||
"language": {
|
||||
"search": "Search: ",
|
||||
"lengthMenu": "Show _MENU_ entries per page",
|
||||
"info": "Showing _START_ to _END_ of _TOTAL_ export items",
|
||||
"infoEmpty": "Showing 0 to 0 of 0 entries",
|
||||
"infoFiltered": "<span class='hidden-md hidden-sm hidden-xs'>(filtered from _MAX_ total entries)</span>",
|
||||
"emptyTable": "No data in table",
|
||||
"loadingRecords": '<i class="fa fa-refresh fa-spin"></i> Loading items...</div>'
|
||||
},
|
||||
"pagingType": "full_numbers",
|
||||
"stateSave": true,
|
||||
"stateDuration": 0,
|
||||
"processing": false,
|
||||
"serverSide": true,
|
||||
"pageLength": 25,
|
||||
"order": [0, 'desc'],
|
||||
"autoWidth": false,
|
||||
"scrollX": true,
|
||||
"columnDefs": [
|
||||
{
|
||||
"targets": [0],
|
||||
"data": "timestamp",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
$(td).html(moment(cellData, "X").format(date_format + ' ' + time_format));
|
||||
}
|
||||
},
|
||||
"width": "8%",
|
||||
"className": "no-wrap",
|
||||
"searchable": false
|
||||
},
|
||||
{
|
||||
"targets": [1],
|
||||
"data": "media_type_title",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
$(td).html(cellData);
|
||||
}
|
||||
},
|
||||
"width": "7%",
|
||||
"className": "no-wrap"
|
||||
},
|
||||
{
|
||||
"targets": [2],
|
||||
"data": "rating_key",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== null) {
|
||||
$(td).html('<a href="' + page('info', rowData['rating_key']) + '">' + cellData + '</a>');
|
||||
}
|
||||
},
|
||||
"width": "6%",
|
||||
"className": "no-wrap"
|
||||
},
|
||||
{
|
||||
"targets": [3],
|
||||
"data": "title",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
var tooltip;
|
||||
var filename;
|
||||
if (!rowData['individual_files']) {
|
||||
tooltip = '<span data-toggle="tooltip" title="Single File"><i class="fa fa-file-alt fa-fw"></i></span>';
|
||||
filename = cellData + '.' + rowData['file_format']
|
||||
} else {
|
||||
tooltip = '<span data-toggle="tooltip" title="Multiple Files"><i class="fa fa-folder fa-fw"></i></span>';
|
||||
filename = cellData
|
||||
}
|
||||
|
||||
if (rowData['complete'] === 1 && rowData['exists'] && !rowData['individual_files']) {
|
||||
$(td).html('<a href="view_export?export_id=' + rowData['export_id'] + '" target="_blank">' + tooltip + ' ' + filename + '</a>');
|
||||
} else {
|
||||
$(td).html(tooltip + ' ' + filename);
|
||||
}
|
||||
}
|
||||
},
|
||||
"width": "40%",
|
||||
"className": "no-wrap"
|
||||
},
|
||||
{
|
||||
"targets": [4],
|
||||
"data": "file_format",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
var images = '';
|
||||
if (rowData['thumb_level'] || rowData['art_level']) {
|
||||
images = ' + images';
|
||||
}
|
||||
$(td).html(cellData + images);
|
||||
}
|
||||
},
|
||||
"width": "7%",
|
||||
"className": "no-wrap"
|
||||
},
|
||||
{
|
||||
"targets": [5],
|
||||
"data": "metadata_level",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== null) {
|
||||
$(td).html(cellData);
|
||||
}
|
||||
},
|
||||
"width": "6%",
|
||||
"className": "no-wrap"
|
||||
},
|
||||
{
|
||||
"targets": [6],
|
||||
"data": "media_info_level",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== null) {
|
||||
$(td).html(cellData);
|
||||
}
|
||||
},
|
||||
"width": "6%",
|
||||
"className": "no-wrap"
|
||||
},
|
||||
{
|
||||
"targets": [7],
|
||||
"data": "custom_fields",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
$(td).html(cellData.replace(/,/g, ', '));
|
||||
}
|
||||
},
|
||||
"width": "6%",
|
||||
"className": "datatable-wrap"
|
||||
},
|
||||
{
|
||||
"targets": [8],
|
||||
"data": "file_size",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '' && cellData !== null) {
|
||||
$(td).html(humanFileSize(cellData));
|
||||
}
|
||||
},
|
||||
"width": "6%",
|
||||
"className": "no-wrap",
|
||||
"searchable": false
|
||||
},
|
||||
{
|
||||
"targets": [9],
|
||||
"data": "complete",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData === 1 && rowData['exists']) {
|
||||
var tooltip_title = '';
|
||||
var icon = '';
|
||||
if (rowData['thumb_level'] || rowData['art_level'] || rowData['individual_files']) {
|
||||
tooltip_title = 'Zip Archive';
|
||||
icon = 'fa-file-archive';
|
||||
} else {
|
||||
tooltip_title = rowData['file_format'].toUpperCase() + ' File';
|
||||
icon = 'fa-file-download';
|
||||
}
|
||||
var icon = (rowData['thumb_level'] || rowData['art_level'] || rowData['individual_files']) ? 'fa-file-archive' : 'fa-file-download';
|
||||
$(td).html('<button class="btn btn-xs btn-success pull-left" data-id="' + rowData['export_id'] + '"><span data-toggle="tooltip" data-placement="left" title="' + tooltip_title + '"><i class="fa ' + icon + ' fa-fw"></i> Download</span></button>');
|
||||
} else if (cellData === 0) {
|
||||
var percent = Math.min(getPercent(rowData['exported_items'], rowData['total_items']), 99)
|
||||
$(td).html('<span class="btn btn-xs btn-dark pull-left export-processing" data-id="' + rowData['export_id'] + '" disabled><i class="fa fa-spinner fa-spin fa-fw"></i> ' + percent + '%</span>');
|
||||
} else if (cellData === -1) {
|
||||
$(td).html('<span class="btn btn-xs btn-dark pull-left" data-id="' + rowData['export_id'] + '" disabled><i class="fa fa-exclamation-circle fa-fw"></i> Failed</span>');
|
||||
} else {
|
||||
$(td).html('<span class="btn btn-xs btn-dark pull-left" data-id="' + rowData['export_id'] + '" disabled><i class="fa fa-question-circle fa-fw"></i> Not Found</span>');
|
||||
}
|
||||
},
|
||||
"width": "7%",
|
||||
"className": "export_download",
|
||||
"searchable": false
|
||||
},
|
||||
{
|
||||
"targets": [10],
|
||||
"data": null,
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (rowData['complete'] !== 0) {
|
||||
$(td).html('<button class="btn btn-xs btn-danger pull-left" data-id="' + rowData['export_id'] + '"><i class="fa fa-trash-o fa-fw"></i> Delete</button>');
|
||||
} else {
|
||||
$(td).html('<span class="btn btn-xs btn-danger pull-left" data-id="' + rowData['export_id'] + '" disabled><i class="fa fa-trash-o fa-fw"></i> Delete</span>');
|
||||
}
|
||||
},
|
||||
"width": "7%",
|
||||
"className": "export_delete",
|
||||
"searchable": false
|
||||
}
|
||||
],
|
||||
"drawCallback": function (settings) {
|
||||
// Jump to top of page
|
||||
//$('html,body').scrollTop(0);
|
||||
$('#ajaxMsg').fadeOut();
|
||||
|
||||
// Create the tooltips.
|
||||
$('body').tooltip({
|
||||
selector: '[data-toggle="tooltip"]',
|
||||
container: 'body'
|
||||
});
|
||||
|
||||
if (export_processing_timer) {
|
||||
clearTimeout(export_processing_timer);
|
||||
}
|
||||
if ($('.export-processing').length) {
|
||||
export_processing_timer = setTimeout(redrawExportTable.bind(null, false), 2000);
|
||||
}
|
||||
},
|
||||
"preDrawCallback": function(settings) {
|
||||
if (!export_processing_timer) {
|
||||
var msg = "<i class='fa fa-refresh fa-spin'></i> Fetching rows...";
|
||||
showMsg(msg, false, false, 0)
|
||||
}
|
||||
},
|
||||
"rowCallback": function (row, rowData, rowIndex) {
|
||||
if (rowData['complete'] === 0) {
|
||||
$(row).addClass('current-activity-row');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$('.export_table').on('click', '> tbody > tr > td.export_download > button', function (e) {
|
||||
var tr = $(this).closest('tr');
|
||||
var row = export_table.row(tr);
|
||||
var rowData = row.data();
|
||||
|
||||
e.preventDefault();
|
||||
window.location.href = 'download_export?export_id=' + rowData['export_id'];
|
||||
});
|
||||
|
||||
$('.export_table').on('click', '> tbody > tr > td.export_delete > button', function (e) {
|
||||
var tr = $(this).closest('tr');
|
||||
var row = export_table.row(tr);
|
||||
var rowData = row.data();
|
||||
|
||||
var msg = 'Are you sure you want to delete the following export?<br /><br /><strong>' + rowData['title'] + '</strong>';
|
||||
var url = 'delete_export?export_id=' + rowData['export_id'];
|
||||
confirmAjaxCall(url, msg, null, null, redrawExportTable);
|
||||
});
|
||||
|
||||
function redrawExportTable(paging) {
|
||||
export_table.draw(paging);
|
||||
}
|
||||
|
||||
var export_processing_timer;
|
@@ -55,13 +55,17 @@ history_table_options = {
|
||||
if (rowData['state'] !== null) {
|
||||
var state = '';
|
||||
if (rowData['state'] === 'playing') {
|
||||
state = '<span class="current-activity-tooltip" data-toggle="tooltip" title="Currently Playing"><i class="fa fa-play fa-fw"></i></span>';
|
||||
state = '<span class="current-activity-tooltip" data-toggle="tooltip" title="Currently Playing"><i class="fa fa-fw fa-play"></i></span>';
|
||||
} else if (rowData['state'] === 'paused') {
|
||||
state = '<span class="current-activity-tooltip" data-toggle="tooltip" title="Currently Paused"><i class="fa fa-pause fa-fw"></i></span>';
|
||||
state = '<span class="current-activity-tooltip" data-toggle="tooltip" title="Currently Paused"><i class="fa fa-fw fa-pause"></i></span>';
|
||||
} else if (rowData['state'] === 'buffering') {
|
||||
state = '<span class="current-activity-tooltip" data-toggle="tooltip" title="Currently Buffering"><i class="fa fa-spinner fa-fw"></i></span>';
|
||||
state = '<span class="current-activity-tooltip" data-toggle="tooltip" title="Currently Buffering"><i class="fa fa-fw fa-spinner"></i></span>';
|
||||
} else if (rowData['state'] === 'error') {
|
||||
state = '<span class="current-activity-tooltip" data-toggle="tooltip" title="Playback Error"><i class="fa fa-fw fa-exclamation-triangle"></i></span>';
|
||||
} else if (rowData['state'] === 'stopped') {
|
||||
state = '<span class="current-activity-tooltip" data-toggle="tooltip" title="Currently Stopped"><i class="fa fa-stop fa-fw"></i></span>';
|
||||
state = '<span class="current-activity-tooltip" data-toggle="tooltip" title="Currently Stopped"><i class="fa fa-fw fa-stop"></i></span>';
|
||||
} else {
|
||||
state = '<span class="current-activity-tooltip" data-toggle="tooltip" title="Unknown"><i class="fa fa-fw fa-question-circle"></i></span>';
|
||||
}
|
||||
$(td).html('<div><div style="float: left;">' + state + ' ' + date + '</div></div>');
|
||||
} else if (rowData['group_count'] > 1) {
|
||||
@@ -81,9 +85,9 @@ history_table_options = {
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
if (rowData['user_id']) {
|
||||
$(td).html('<a href="' + page('user', rowData['user_id']) + '">' + cellData + '</a>');
|
||||
$(td).html('<a href="' + page('user', rowData['user_id']) + '" title="' + rowData['user'] + '">' + cellData + '</a>');
|
||||
} else {
|
||||
$(td).html('<a href="' + page('user', null, rowData['user']) + '">' + cellData + '</a>');
|
||||
$(td).html('<a href="' + page('user', null, rowData['user']) + '" title="' + rowData['user'] + '">' + cellData + '</a>');
|
||||
}
|
||||
} else {
|
||||
$(td).html(cellData);
|
||||
@@ -141,7 +145,7 @@ history_table_options = {
|
||||
if (rowData['transcode_decision'] === 'transcode') {
|
||||
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Transcode"><i class="fa fa-server fa-fw"></i></span>';
|
||||
} else if (rowData['transcode_decision'] === 'copy') {
|
||||
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Stream"><i class="fa fa-video-camera fa-fw"></i></span>';
|
||||
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Stream"><i class="fa fa-stream fa-fw"></i></span>';
|
||||
} else if (rowData['transcode_decision'] === 'direct play') {
|
||||
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Play"><i class="fa fa-play-circle fa-fw"></i></span>';
|
||||
}
|
||||
@@ -184,7 +188,9 @@ history_table_options = {
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 300, null, null, null, 'cover') + '" data-height="80" data-width="80">' + cellData + parent_info + '</span>';
|
||||
$(td).html('<div class="history-title"><a href="' + page('info', rowData['rating_key'], rowData['guid'], history, rowData['live']) + '"><div style="float: left;">' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
||||
} else if (rowData['media_type'] === 'clip') {
|
||||
$(td).html(cellData);
|
||||
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Clip"><i class="fa fa-video-camera fa-fw"></i></span>';
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 450, null, null, null, fallback) + '" data-height="120" data-width="80">' + cellData + parent_info + '</span>';
|
||||
$(td).html('<div class="history-title"><div style="float: left;">' + media_type + ' ' + thumb_popover + '</div></div>');
|
||||
} else {
|
||||
$(td).html('<a href="' + page('info', rowData['rating_key']) + '">' + cellData + '</a>');
|
||||
}
|
||||
|
@@ -83,7 +83,7 @@ history_table_modal_options = {
|
||||
if (rowData['transcode_decision'] === 'transcode') {
|
||||
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Transcode"><i class="fa fa-server fa-fw"></i></span>';
|
||||
} else if (rowData['transcode_decision'] === 'copy') {
|
||||
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Stream"><i class="fa fa-video-camera fa-fw"></i></span>';
|
||||
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Stream"><i class="fa fa-stream fa-fw"></i></span>';
|
||||
} else if (rowData['transcode_decision'] === 'direct play') {
|
||||
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Play"><i class="fa fa-play-circle fa-fw"></i></span>';
|
||||
}
|
||||
|
@@ -192,7 +192,7 @@ libraries_list_table_options = {
|
||||
"data": "duration",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== null && cellData !== '') {
|
||||
$(td).html(humanTimeClean(cellData));
|
||||
$(td).html(humanDuration(cellData, 'dhm', 's'));
|
||||
}
|
||||
},
|
||||
"searchable": false,
|
||||
|
@@ -107,15 +107,15 @@ media_info_table_options = {
|
||||
} else if (rowData['media_type'] === 'photo_album') {
|
||||
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Photo Album"><i class="fa fa-camera fa-fw"></i></span>';
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 450, null, null, null, 'poster') + '" data-height="120" data-width="80">' + rowData['title'] + '</span>';
|
||||
$(td).html('<div class="history-title"><div style="float: left; padding-left: 15px;">' + media_type + ' ' + thumb_popover + '</div></div>');
|
||||
$(td).html('<div class="history-title"><a href="' + page('info', rowData['rating_key']) + '"><div style="float: left;">' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
||||
} else if (rowData['media_type'] === 'photo') {
|
||||
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Photo"><i class="fa fa-picture-o fa-fw"></i></span>';
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 450, null, null, null, 'poster') + '" data-height="120" data-width="80">' + rowData['title'] + '</span>';
|
||||
$(td).html('<div class="history-title"><div style="float: left; padding-left: 15px;">' + media_type + ' ' + thumb_popover + '</div></div>');
|
||||
$(td).html('<div class="history-title"><a href="' + page('info', rowData['rating_key']) + '"><div style="float: left; padding-left: 15px;">' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
||||
} else if (rowData['media_type'] === 'clip') {
|
||||
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Video"><i class="fa fa-video-camera fa-fw"></i></span>';
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 500, 280, null, null, null, 'art') + '" data-height="80" data-width="140">' + rowData['title'] + '</span>';
|
||||
$(td).html('<div class="history-title"><div style="float: left; padding-left: 15px;">' + media_type + ' ' + thumb_popover + '</div></div>');
|
||||
$(td).html('<div class="history-title"><a href="' + page('info', rowData['rating_key']) + '"><div style="float: left; padding-left: 15px;">' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
||||
} else {
|
||||
$(td).html(cellData);
|
||||
}
|
||||
|
100
data/interfaces/default/js/tables/playlists_table.js
Normal file
@@ -0,0 +1,100 @@
|
||||
playlists_table_options = {
|
||||
"destroy": true,
|
||||
"language": {
|
||||
"search": "Search: ",
|
||||
"lengthMenu": "Show _MENU_ entries per page",
|
||||
"info": "Showing _START_ to _END_ of _TOTAL_ playlists",
|
||||
"infoEmpty": "Showing 0 to 0 of 0 entries",
|
||||
"infoFiltered": "<span class='hidden-md hidden-sm hidden-xs'>(filtered from _MAX_ total entries)</span>",
|
||||
"emptyTable": "No data in table",
|
||||
"loadingRecords": '<i class="fa fa-refresh fa-spin"></i> Loading items...</div>'
|
||||
},
|
||||
"pagingType": "full_numbers",
|
||||
"stateSave": true,
|
||||
"stateDuration": 0,
|
||||
"processing": false,
|
||||
"serverSide": true,
|
||||
"pageLength": 25,
|
||||
"order": [0, 'asc'],
|
||||
"autoWidth": false,
|
||||
"scrollX": true,
|
||||
"columnDefs": [
|
||||
{
|
||||
"targets": [0],
|
||||
"data": "title",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
var smart = '<i class="fa fa-blank fa-fw"></i>';
|
||||
if (rowData['smart']) {
|
||||
smart = '<span class="media-type-tooltip" data-toggle="tooltip" title="Smart Playlist"><i class="fa fa-cog fa-fw"></i></span> '
|
||||
}
|
||||
var breadcrumb = '';
|
||||
if (rowData['userID']) {
|
||||
breadcrumb = '&user_id=' + rowData['userID'];
|
||||
} else if (rowData['librarySectionID']) {
|
||||
breadcrumb = '§ion_id=' + rowData['librarySectionID'];
|
||||
}
|
||||
var thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['composite'], rowData['ratingKey'], 300, 300, null, null, null, 'cover') + '" data-height="80" data-width="80">' + smart + cellData + '</span>';
|
||||
$(td).html('<a href="' + page('info', rowData['ratingKey']) + breadcrumb +'">' + thumb_popover + '</a>');
|
||||
}
|
||||
},
|
||||
"width": "60%",
|
||||
"className": "no-wrap"
|
||||
},
|
||||
{
|
||||
"targets": [1],
|
||||
"data": "leafCount",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
var type = MEDIA_TYPE_HEADERS[rowData['playlistType']] || '';
|
||||
if (rowData['leafCount'] === 1) {
|
||||
type = type.slice(0, -1);
|
||||
}
|
||||
$(td).html(cellData + ' ' + type);
|
||||
}
|
||||
},
|
||||
"width": "20%",
|
||||
"className": "no-wrap"
|
||||
},
|
||||
{
|
||||
"targets": [2],
|
||||
"data": "duration",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
$(td).html(humanDuration(cellData, 'dhm'));
|
||||
}
|
||||
},
|
||||
"width": "20%",
|
||||
"className": "no-wrap"
|
||||
}
|
||||
],
|
||||
"drawCallback": function (settings) {
|
||||
// Jump to top of page
|
||||
//$('html,body').scrollTop(0);
|
||||
$('#ajaxMsg').fadeOut();
|
||||
|
||||
// Create the tooltips.
|
||||
$('body').tooltip({
|
||||
selector: '[data-toggle="tooltip"]',
|
||||
container: 'body'
|
||||
});
|
||||
$('body').popover({
|
||||
selector: '[data-toggle="popover"]',
|
||||
html: true,
|
||||
container: 'body',
|
||||
trigger: 'hover',
|
||||
placement: 'right',
|
||||
template: '<div class="popover history-thumbnail-popover" role="tooltip"><div class="arrow" style="top: 50%;"></div><div class="popover-content"></div></div>',
|
||||
content: function () {
|
||||
return '<div class="history-thumbnail" style="background-image: url(' + $(this).data('img') + '); height: ' + $(this).data('height') + 'px; width: ' + $(this).data('width') + 'px;" />';
|
||||
}
|
||||
});
|
||||
},
|
||||
"preDrawCallback": function(settings) {
|
||||
var msg = "<i class='fa fa-refresh fa-spin'></i> Fetching rows...";
|
||||
showMsg(msg, false, false, 0);
|
||||
$('[data-toggle="tooltip"]').tooltip('destroy');
|
||||
},
|
||||
"rowCallback": function (row, rowData, rowIndex) {
|
||||
}
|
||||
};
|
@@ -51,9 +51,9 @@ sync_table_options = {
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
if (rowData['user_id']) {
|
||||
$(td).html('<a href="' + page('user', rowData['user_id']) + '">' + cellData + '</a>');
|
||||
$(td).html('<a href="' + page('user', rowData['user_id']) + '" title="' + rowData['username'] + '">' + cellData + '</a>');
|
||||
} else {
|
||||
$(td).html('<a href="' + page('user', null, rowData['user']) + '">' + cellData + '</a>');
|
||||
$(td).html('<a href="' + page('user', null, rowData['user']) + '" title="' + rowData['username'] + '">' + cellData + '</a>');
|
||||
}
|
||||
} else {
|
||||
$(td).html(cellData);
|
||||
|
@@ -1,3 +1,25 @@
|
||||
var date_format = 'YYYY-MM-DD';
|
||||
var time_format = 'hh:mm a';
|
||||
|
||||
$.ajax({
|
||||
url: 'get_date_formats',
|
||||
type: 'GET',
|
||||
success: function(data) {
|
||||
date_format = data.date_format;
|
||||
time_format = data.time_format;
|
||||
}
|
||||
});
|
||||
|
||||
var seenRender = function (data, type, full) {
|
||||
return moment(data, "X").fromNow();
|
||||
};
|
||||
|
||||
var seenCreatedCell = function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== null) {
|
||||
$(td).attr('title', moment(cellData, "X").format(date_format + ' ' + time_format));
|
||||
}
|
||||
};
|
||||
|
||||
user_ip_table_options = {
|
||||
"destroy": true,
|
||||
"language": {
|
||||
@@ -21,16 +43,24 @@ user_ip_table_options = {
|
||||
"columnDefs": [
|
||||
{
|
||||
"targets": [0],
|
||||
"data":"last_seen",
|
||||
"render": function ( data, type, full ) {
|
||||
return moment(data, "X").fromNow();
|
||||
},
|
||||
"data": "last_seen",
|
||||
"render": seenRender,
|
||||
"createdCell": seenCreatedCell,
|
||||
"searchable": false,
|
||||
"width": "15%",
|
||||
"width": "12%",
|
||||
"className": "no-wrap"
|
||||
},
|
||||
{
|
||||
"targets": [1],
|
||||
"data": "first_seen",
|
||||
"render": seenRender,
|
||||
"createdCell": seenCreatedCell,
|
||||
"searchable": false,
|
||||
"width": "12%",
|
||||
"className": "no-wrap"
|
||||
},
|
||||
{
|
||||
"targets": [2],
|
||||
"data": "ip_address",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData) {
|
||||
@@ -44,22 +74,22 @@ user_ip_table_options = {
|
||||
$(td).html('n/a');
|
||||
}
|
||||
},
|
||||
"width": "15%",
|
||||
"width": "12%",
|
||||
"className": "no-wrap modal-control-ip"
|
||||
},
|
||||
{
|
||||
"targets": [2],
|
||||
"targets": [3],
|
||||
"data": "platform",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
$(td).html(cellData);
|
||||
}
|
||||
},
|
||||
"width": "15%",
|
||||
"width": "12%",
|
||||
"className": "no-wrap"
|
||||
},
|
||||
{
|
||||
"targets": [3],
|
||||
"targets": [4],
|
||||
"data": "player",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
@@ -67,18 +97,18 @@ user_ip_table_options = {
|
||||
if (rowData['transcode_decision'] === 'transcode') {
|
||||
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Transcode"><i class="fa fa-server fa-fw"></i></span>';
|
||||
} else if (rowData['transcode_decision'] === 'copy') {
|
||||
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Stream"><i class="fa fa-video-camera fa-fw"></i></span>';
|
||||
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Stream"><i class="fa fa-stream fa-fw"></i></span>';
|
||||
} else if (rowData['transcode_decision'] === 'direct play') {
|
||||
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Play"><i class="fa fa-play-circle fa-fw"></i></span>';
|
||||
}
|
||||
$(td).html('<div><a href="#" data-target="#info-modal" data-toggle="modal"><div style="float: left;">' + transcode_dec + ' ' + cellData + '</div></a></div>');
|
||||
}
|
||||
},
|
||||
"width": "15%",
|
||||
"width": "12%",
|
||||
"className": "no-wrap modal-control"
|
||||
},
|
||||
{
|
||||
"targets": [4],
|
||||
"targets": [5],
|
||||
"data": "last_played",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
@@ -119,7 +149,7 @@ user_ip_table_options = {
|
||||
"className": "datatable-wrap"
|
||||
},
|
||||
{
|
||||
"targets": [5],
|
||||
"targets": [6],
|
||||
"data": "play_count",
|
||||
"searchable": false,
|
||||
"width": "10%",
|
||||
|
@@ -62,9 +62,9 @@ users_list_table_options = {
|
||||
var inactive = '';
|
||||
if (!rowData['is_active']) { inactive = '<span class="inactive-user-tooltip" data-toggle="tooltip" title="User not on Plex server"><i class="fa fa-exclamation-triangle"></i></span>'; }
|
||||
if (cellData === '') {
|
||||
$(td).html('<a href="' + page('user', rowData['user_id']) + '"><div class="users-poster-face" style="background-image: url(../../images/gravatar-default-80x80.png);">' + inactive + '</div></a>');
|
||||
$(td).html('<a href="' + page('user', rowData['user_id']) + '"" title="' + rowData['username'] + '"><div class="users-poster-face" style="background-image: url(../../images/gravatar-default-80x80.png);">' + inactive + '</div></a>');
|
||||
} else {
|
||||
$(td).html('<a href="' + page('user', rowData['user_id']) + '"><div class="users-poster-face" style="background-image: url(' + rowData['user_thumb'] + ');">' + inactive + '</div></a>');
|
||||
$(td).html('<a href="' + page('user', rowData['user_id']) + '"" title="' + rowData['username'] + '"><div class="users-poster-face" style="background-image: url(' + rowData['user_thumb'] + ');">' + inactive + '</div></a>');
|
||||
}
|
||||
},
|
||||
"orderable": false,
|
||||
@@ -78,7 +78,7 @@ users_list_table_options = {
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== null && cellData !== '') {
|
||||
$(td).html('<div class="edit-user-name" data-id="' + rowData['row_id'] + '">' +
|
||||
'<a href="' + page('user', rowData['user_id']) + '">' + cellData + '</a>' +
|
||||
'<a href="' + page('user', rowData['user_id']) + '" title="' + rowData['username'] + '">' + cellData + '</a>' +
|
||||
'<input type="text" class="hidden" value="' + cellData + '">' +
|
||||
'</div>');
|
||||
} else {
|
||||
@@ -142,7 +142,7 @@ users_list_table_options = {
|
||||
if (rowData['transcode_decision'] === 'transcode') {
|
||||
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Transcode"><i class="fa fa-server fa-fw"></i></span>';
|
||||
} else if (rowData['transcode_decision'] === 'copy') {
|
||||
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Stream"><i class="fa fa-video-camera fa-fw"></i></span>';
|
||||
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Stream"><i class="fa fa-stream fa-fw"></i></span>';
|
||||
} else if (rowData['transcode_decision'] === 'direct play') {
|
||||
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Play"><i class="fa fa-play-circle fa-fw"></i></span>';
|
||||
}
|
||||
@@ -212,7 +212,7 @@ users_list_table_options = {
|
||||
"data": "duration",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== null && cellData !== '') {
|
||||
$(td).html(humanTimeClean(cellData));
|
||||
$(td).html(humanDuration(cellData, 'dhm', 's'));
|
||||
}
|
||||
},
|
||||
"searchable": false,
|
||||
|
@@ -38,7 +38,7 @@
|
||||
<th align="left" id="count">Total Movies / TV Shows / Artists</th>
|
||||
<th align="left" id="parent_count">Total Seasons / Albums</th>
|
||||
<th align="left" id="child_count">Total Episodes / Tracks</th>
|
||||
<th align="left" id="last_accessed">Last Accessed</th>
|
||||
<th align="left" id="last_accessed">Last Streamed</th>
|
||||
<th align="left" id="last_played">Last Played</th>
|
||||
<th align="left" id="total_plays">Total Plays</th>
|
||||
<th align="left" id="total_duration">Total Played Duration</th>
|
||||
@@ -79,7 +79,6 @@
|
||||
<script src="${http_root}js/dataTables.colVis.js"></script>
|
||||
<script src="${http_root}js/dataTables.bootstrap.min.js"></script>
|
||||
<script src="${http_root}js/dataTables.bootstrap.pagination.js"></script>
|
||||
<script src="${http_root}js/moment-with-locale.js"></script>
|
||||
<script src="${http_root}js/tables/libraries.js${cache_param}"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
|
@@ -87,12 +87,19 @@ DOCUMENTATION :: END
|
||||
% endif
|
||||
</div>
|
||||
<div class="user-info-nav">
|
||||
<ul class="user-info-nav" role="tablist">
|
||||
<li class="active"><a href="#tabs-profile" role="tab" data-toggle="tab">Profile</a></li>
|
||||
<li><a id="history-tab-btn" href="#tabs-history" role="tab" data-toggle="tab">History</a></li>
|
||||
% if _session['user_group'] == 'admin':
|
||||
<ul class="nav nav-list nav-pills" role="tablist">
|
||||
<li class="active"><a id="nav-tabs-profile" href="#tabs-profile" role="tab" data-toggle="tab">Profile</a></li>
|
||||
<li><a id="nav-tabs-history" href="#tabs-history" role="tab" data-toggle="tab">History</a></li>
|
||||
% if data['section_id'] != LIVE_TV_SECTION_ID:
|
||||
<li><a id="media-info-tab-btn" href="#tabs-mediainfo" role="tab" data-toggle="tab">Media Info</a></li>
|
||||
% if _session['user_group'] == 'admin':
|
||||
<li><a id="nav-tabs-mediainfo" href="#tabs-mediainfo" role="tab" data-toggle="tab">Media Info</a></li>
|
||||
% endif
|
||||
% if data['section_type'] != 'artist':
|
||||
<li><a id="nav-tabs-collections" href="#tabs-collections" role="tab" data-toggle="tab">Collections</a></li>
|
||||
% endif
|
||||
<li><a id="nav-tabs-playlists" href="#tabs-playlists" role="tab" data-toggle="tab">Playlists</a></li>
|
||||
% if _session['user_group'] == 'admin':
|
||||
<li><a id="nav-tabs-export" href="#tabs-export" role="tab" data-toggle="tab">Export</a></li>
|
||||
% endif
|
||||
% endif
|
||||
</ul>
|
||||
@@ -242,23 +249,22 @@ DOCUMENTATION :: END
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
% if _session['user_group'] == 'admin':
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-mediainfo">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
% if config['get_file_sizes'] and data['section_id'] in config['get_file_sizes_hold']['section_ids']:
|
||||
<div id="get_file_sizes_message" style="text-align: center; margin-top: 20px;">
|
||||
% else:
|
||||
<div id="get_file_sizes_message" style="text-align: center; margin-top: 20px; display: none;">
|
||||
% endif
|
||||
<i class="fa fa-refresh fa-spin"></i> Tautulli is calculating the file sizes for the library's media info. This could take a few minutes depending on the size of your library.
|
||||
<i class="fa fa-refresh fa-spin"></i> Tautulli is calculating the file sizes for the library's media info. This could take a few minutes depending on the size of your library.
|
||||
<br />
|
||||
You may leave this page and come back later.
|
||||
You may leave this page and check back later.
|
||||
</div>
|
||||
% endif
|
||||
<div class='table-card-header'>
|
||||
<div class="header-bar">
|
||||
<span>
|
||||
<i class="fa fa-history"></i> Media Info for <strong>
|
||||
<i class="fa fa-info-circle"></i> Media Info for <strong>
|
||||
<span class="set-username">${data['section_name']}</span>
|
||||
</strong>
|
||||
</span>
|
||||
@@ -305,6 +311,157 @@ DOCUMENTATION :: END
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
% if data['section_type'] != 'artist':
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-collections">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class='table-card-header'>
|
||||
<div class="header-bar">
|
||||
<span>
|
||||
<i class="fa fa-folder-open"></i> Collections for <strong>
|
||||
<span class="set-username">${data['section_name']}</span>
|
||||
</strong>
|
||||
</span>
|
||||
</div>
|
||||
<div class="button-bar">
|
||||
% if _session['user_group'] == 'admin':
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-dark export-button" id="toggle-export-modal" data-toggle="modal" data-target="#export-modal"
|
||||
data-section_id="${data['section_id']}" data-media_type="collection" data-sub_media_type="${data['section_type']}"
|
||||
data-export_type="collection">
|
||||
<i class="fa fa-file-export"></i> Export collections
|
||||
</button>
|
||||
</div>
|
||||
% endif
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-dark refresh-collections-table-button" id="refresh-collections-table">
|
||||
<i class="fa fa-refresh"></i> Refresh collections
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group colvis-button-bar" id="button-bar-collections"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-card-back">
|
||||
<table class="display collections_table" id="collections_table-SID-${data['section_id']}" width="100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th align="left" id="collectionTitle">Collection Title</th>
|
||||
<th align="left" id="collectionMode">Collection Mode</th>
|
||||
<th align="left" id="collectionSort">Collection Sort</th>
|
||||
<th align="left" id="collectionItems">Collection Items</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-playlists">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class='table-card-header'>
|
||||
<div class="header-bar">
|
||||
<span>
|
||||
<i class="fa fa-list-alt"></i> Playlists for <strong>
|
||||
<span class="set-username">${data['section_name']}</span>
|
||||
</strong>
|
||||
</span>
|
||||
</div>
|
||||
<div class="button-bar">
|
||||
% if _session['user_group'] == 'admin':
|
||||
<% playlist_sub_media_type = {'movie': 'video', 'show': 'video', 'artist': 'audio', 'photo': 'photo'} %>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-dark export-button" id="toggle-export-modal" data-toggle="modal" data-target="#export-modal"
|
||||
data-section_id="${data['section_id']}" data-media_type="playlist" data-sub_media_type="${playlist_sub_media_type.get(data['section_type'])}"
|
||||
data-export_type="playlist">
|
||||
<i class="fa fa-file-export"></i> Export playlists
|
||||
</button>
|
||||
</div>
|
||||
% endif
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-dark refresh-playlists-table-button" id="refresh-playlists-table">
|
||||
<i class="fa fa-refresh"></i> Refresh playlists
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group colvis-button-bar" id="button-bar-playlists"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-card-back">
|
||||
<table class="display playlists_table" id="playlists_table-SID-${data['section_id']}" width="100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th align="left" id="playlistTitle">Playlist Title</th>
|
||||
<th align="left" id="playlistLeafCount">Playlist Items</th>
|
||||
<th align="left" id="playlistDuration">Playlist Duration</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
% if _session['user_group'] == 'admin':
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-export">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class='table-card-header'>
|
||||
<div class="header-bar">
|
||||
<span>
|
||||
<i class="fa fa-file-export"></i> Metadata Exports for <strong>
|
||||
<span class="set-username">${data['section_name']}</span>
|
||||
</strong>
|
||||
</span>
|
||||
</div>
|
||||
<div class="button-bar">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-dark export-button" id="toggle-export-modal" data-toggle="modal" data-target="#export-modal"
|
||||
data-section_id="${data['section_id']}" data-media_type="${'photoalbum' if data['section_type'] == 'photo' else data['section_type']}"
|
||||
data-export_type="all">
|
||||
<i class="fa fa-file-export"></i> Export metadata
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-dark refresh-export-table-button" id="refresh-export-table">
|
||||
<i class="fa fa-refresh"></i> Refresh exports
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group colvis-button-bar" id="button-bar-export"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-card-back">
|
||||
<table class="display export_table" id="export_table-SID-${data['section_id']}" width="100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th align="left" id="timestamp">Exported At</th>
|
||||
<th align="left" id="media_type_title">Media Type</th>
|
||||
<th align="left" id="rating_key">Rating Key</th>
|
||||
<th align="left" id="filename">Filename</th>
|
||||
<th align="left" id="file_format">File Format</th>
|
||||
<th align="left" id="metadata_level">Metadata Level</th>
|
||||
<th align="left" id="media_info_level">Media Info Level</th>
|
||||
<th align="left" id="media_info_level">Custom Fields</th>
|
||||
<th align="left" id="file_size">File Size</th>
|
||||
<th align="left" id="complete">Download</th>
|
||||
<th align="left" id="delete">Delete</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -335,8 +492,7 @@ DOCUMENTATION :: END
|
||||
</%def>
|
||||
|
||||
<%def name="modalIncludes()">
|
||||
<div id="edit-library-modal" class="modal fade" tabindex="-1" role="dialog"
|
||||
aria-labelledby="edit-library-modal">
|
||||
<div id="edit-library-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="edit-library-modal">
|
||||
</div>
|
||||
<div class="modal fade" id="info-modal" tabindex="-1" role="dialog" aria-labelledby="info-modal">
|
||||
</div>
|
||||
@@ -360,6 +516,8 @@ DOCUMENTATION :: END
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="export-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="export-modal">
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
<%def name="javascriptIncludes()">
|
||||
@@ -369,6 +527,9 @@ DOCUMENTATION :: END
|
||||
<script src="${http_root}js/dataTables.bootstrap.pagination.js"></script>
|
||||
% if data:
|
||||
<% from plexpy.common import LIVE_TV_SECTION_ID %>
|
||||
<%
|
||||
history_user_id = '' if _session['user_group'] == 'admin' else _session['user_id']
|
||||
%>
|
||||
<script>
|
||||
% if str(data['section_id']).isdigit():
|
||||
var section_id = ${data['section_id']};
|
||||
@@ -384,14 +545,18 @@ DOCUMENTATION :: END
|
||||
var get_file_sizes = null;
|
||||
% endif
|
||||
</script>
|
||||
<script src="${http_root}js/moment-with-locale.js"></script>
|
||||
<script src="${http_root}js/tables/history_table.js${cache_param}"></script>
|
||||
<script src="${http_root}js/tables/media_info_table.js${cache_param}"></script>
|
||||
<script src="${http_root}js/tables/collections_table.js${cache_param}"></script>
|
||||
<script src="${http_root}js/tables/playlists_table.js${cache_param}"></script>
|
||||
<script src="${http_root}js/tables/export_table.js${cache_param}"></script>
|
||||
<script>
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
$.fn.dataTable.tables({ visible: true, api: true }).columns.adjust();
|
||||
});
|
||||
|
||||
$(".inactive-library-tooltip").tooltip();
|
||||
|
||||
function loadHistoryTable() {
|
||||
// Build watch history table
|
||||
history_table_options.ajax = {
|
||||
@@ -401,7 +566,7 @@ DOCUMENTATION :: END
|
||||
return {
|
||||
json_data: JSON.stringify( d ),
|
||||
section_id: section_id,
|
||||
user_id: "${_session['user_group']}" == "admin" ? null : "${_session['user_id']}"
|
||||
user_id: "${history_user_id}"
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -413,7 +578,7 @@ DOCUMENTATION :: END
|
||||
clearSearchButton('history_table-SID-${data["section_id"]}', history_table);
|
||||
}
|
||||
|
||||
$('a[href="#tabs-history"]').on('shown.bs.tab', function() {
|
||||
$('#nav-tabs-history').on('shown.bs.tab', function() {
|
||||
if (typeof(history_table) === 'undefined') {
|
||||
loadHistoryTable();
|
||||
}
|
||||
@@ -422,97 +587,70 @@ DOCUMENTATION :: END
|
||||
$("#refresh-history-list").click(function () {
|
||||
history_table.draw();
|
||||
});
|
||||
|
||||
$(".inactive-library-tooltip").tooltip();
|
||||
|
||||
% if _session['user_group'] == 'admin':
|
||||
function loadMediaInfoTable() {
|
||||
// Build media info table
|
||||
media_info_table_options.ajax = {
|
||||
url: 'get_library_media_info',
|
||||
</script>
|
||||
% if data['section_type'] != 'artist':
|
||||
<script>
|
||||
function loadCollectionsTable() {
|
||||
// Build collections table
|
||||
collections_table_options.ajax = {
|
||||
url: 'get_collections_list',
|
||||
type: 'POST',
|
||||
data: function ( d ) {
|
||||
return {
|
||||
json_data: JSON.stringify( d ),
|
||||
section_id: section_id,
|
||||
refresh: refresh_table
|
||||
section_id: section_id
|
||||
};
|
||||
}
|
||||
};
|
||||
media_info_table = $('#media_info_table-SID-${data["section_id"]}').DataTable(media_info_table_options);
|
||||
collections_table = $('#collections_table-SID-${data["section_id"]}').DataTable(collections_table_options);
|
||||
|
||||
var colvis = new $.fn.dataTable.ColVis(media_info_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark' });
|
||||
$(colvis.button()).appendTo('#button-bar-media-info');
|
||||
var colvis = new $.fn.dataTable.ColVis(collections_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark' });
|
||||
$(colvis.button()).appendTo('#button-bar-collections');
|
||||
|
||||
clearSearchButton('media_info_table-SID-${data["section_id"]}', media_info_table);
|
||||
clearSearchButton('collections_table-SID-${data["section_id"]}', collections_table);
|
||||
}
|
||||
|
||||
$('a[href="#tabs-mediainfo"]').on('shown.bs.tab', function() {
|
||||
if (typeof(media_info_table) === 'undefined') {
|
||||
loadMediaInfoTable();
|
||||
$('#nav-tabs-collections').on('shown.bs.tab', function() {
|
||||
if (typeof(collections_table) === 'undefined') {
|
||||
loadCollectionsTable();
|
||||
}
|
||||
});
|
||||
|
||||
$("#refresh-media-info-table").click(function () {
|
||||
media_info_child_table = {};
|
||||
refresh_table = true;
|
||||
refresh_child_tables = true;
|
||||
media_info_table.draw();
|
||||
refresh_table = false;
|
||||
$("#refresh-collections-table").click(function () {
|
||||
collections_table.draw();
|
||||
});
|
||||
|
||||
$("#edit-library-tooltip").tooltip();
|
||||
|
||||
// Load edit library modal
|
||||
$("#toggle-edit-library-modal").click(function() {
|
||||
$("#edit-library-tooltip").tooltip('hide');
|
||||
$.ajax({
|
||||
url: 'edit_library_dialog',
|
||||
data: { section_id: section_id },
|
||||
cache: false,
|
||||
async: true,
|
||||
complete: function(xhr, status) {
|
||||
$("#edit-library-modal").html(xhr.responseText);
|
||||
</script>
|
||||
% endif
|
||||
<script>
|
||||
function loadPlaylistsTable() {
|
||||
// Build playlists table
|
||||
playlists_table_options.ajax = {
|
||||
url: 'get_playlists_list',
|
||||
type: 'POST',
|
||||
data: function ( d ) {
|
||||
return {
|
||||
json_data: JSON.stringify( d ),
|
||||
section_id: section_id
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
playlists_table = $('#playlists_table-SID-${data["section_id"]}').DataTable(playlists_table_options);
|
||||
|
||||
$('#row-edit-mode').on('click', function() {
|
||||
$('#row-edit-mode-alert').fadeIn(200);
|
||||
var colvis = new $.fn.dataTable.ColVis(playlists_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark' });
|
||||
$(colvis.button()).appendTo('#button-bar-playlists');
|
||||
|
||||
if ($(this).hasClass('active')) {
|
||||
if (history_to_delete.length > 0) {
|
||||
$('#deleteCount').text(history_to_delete.length);
|
||||
$('#confirm-modal-delete').modal();
|
||||
$('#confirm-modal-delete').one('click', '#confirm-delete', function () {
|
||||
$.ajax({
|
||||
url: 'delete_history_rows',
|
||||
type: 'POST',
|
||||
data: { row_ids: history_to_delete.join(',') },
|
||||
async: true,
|
||||
success: function (data) {
|
||||
var msg = "History deleted";
|
||||
showMsg(msg, false, true, 2000);
|
||||
history_table.draw();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
clearSearchButton('playlists_table-SID-${data["section_id"]}', playlists_table);
|
||||
}
|
||||
|
||||
$('.delete-control').each(function () {
|
||||
$(this).addClass('hidden');
|
||||
$('#row-edit-mode-alert').fadeOut(200);
|
||||
});
|
||||
|
||||
} else {
|
||||
history_to_delete = [];
|
||||
$('.delete-control').each(function() {
|
||||
$(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger');
|
||||
$(this).removeClass('hidden');
|
||||
});
|
||||
$('#nav-tabs-playlists').on('shown.bs.tab', function() {
|
||||
if (typeof(playlists_table) === 'undefined') {
|
||||
loadPlaylistsTable();
|
||||
}
|
||||
});
|
||||
% endif
|
||||
|
||||
$("#refresh-playlists-table").click(function () {
|
||||
playlists_table.draw();
|
||||
});
|
||||
|
||||
function recentlyWatched() {
|
||||
// Populate recently watched
|
||||
@@ -634,11 +772,11 @@ DOCUMENTATION :: END
|
||||
var hash = document.location.hash;
|
||||
var prefix = "tab_";
|
||||
if (hash) {
|
||||
$('.user-info-nav a[href='+hash.replace(prefix,"")+']').tab('show').trigger('show.bs.tab');
|
||||
$('.nav-list #nav-' + hash.replace('#' + prefix, "")).tab('show').trigger('show.bs.tab');
|
||||
}
|
||||
|
||||
// Change hash for page-reload
|
||||
$('.user-info-nav a').on('shown.bs.tab', function (e) {
|
||||
$('.nav-list a').on('shown.bs.tab', function (e) {
|
||||
window.location.hash = e.target.hash.replace("#", "#" + prefix);
|
||||
});
|
||||
|
||||
@@ -664,5 +802,143 @@ DOCUMENTATION :: END
|
||||
|
||||
});
|
||||
</script>
|
||||
% if _session['user_group'] == 'admin':
|
||||
<script>
|
||||
function loadMediaInfoTable() {
|
||||
// Build media info table
|
||||
media_info_table_options.ajax = {
|
||||
url: 'get_library_media_info',
|
||||
type: 'POST',
|
||||
data: function ( d ) {
|
||||
return {
|
||||
json_data: JSON.stringify( d ),
|
||||
section_id: section_id,
|
||||
refresh: refresh_table
|
||||
};
|
||||
}
|
||||
};
|
||||
media_info_table = $('#media_info_table-SID-${data["section_id"]}').DataTable(media_info_table_options);
|
||||
|
||||
var colvis = new $.fn.dataTable.ColVis(media_info_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark' });
|
||||
$(colvis.button()).appendTo('#button-bar-media-info');
|
||||
|
||||
clearSearchButton('media_info_table-SID-${data["section_id"]}', media_info_table);
|
||||
}
|
||||
|
||||
$('#nav-tabs-mediainfo').on('shown.bs.tab', function() {
|
||||
if (typeof(media_info_table) === 'undefined') {
|
||||
loadMediaInfoTable();
|
||||
}
|
||||
});
|
||||
|
||||
$("#refresh-media-info-table").click(function () {
|
||||
media_info_child_table = {};
|
||||
refresh_table = true;
|
||||
refresh_child_tables = true;
|
||||
media_info_table.draw();
|
||||
refresh_table = false;
|
||||
});
|
||||
|
||||
function loadExportTable() {
|
||||
// Build export table
|
||||
export_table_options.ajax = {
|
||||
url: 'get_export_list',
|
||||
type: 'POST',
|
||||
data: function ( d ) {
|
||||
return {
|
||||
json_data: JSON.stringify( d ),
|
||||
section_id: section_id
|
||||
};
|
||||
}
|
||||
};
|
||||
export_table = $('#export_table-SID-${data["section_id"]}').DataTable(export_table_options);
|
||||
export_table.columns([7]).visible(false);
|
||||
|
||||
var colvis = new $.fn.dataTable.ColVis(export_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark' });
|
||||
$(colvis.button()).appendTo('#button-bar-export');
|
||||
|
||||
clearSearchButton('export_table-SID-${data["section_id"]}', export_table);
|
||||
}
|
||||
|
||||
$('#nav-tabs-export').on('shown.bs.tab', function() {
|
||||
if (typeof(export_table) === 'undefined') {
|
||||
loadExportTable();
|
||||
}
|
||||
});
|
||||
|
||||
$("#refresh-export-table").click(function () {
|
||||
export_table.draw();
|
||||
});
|
||||
|
||||
$("#edit-library-tooltip").tooltip();
|
||||
|
||||
// Load edit library modal
|
||||
$("#toggle-edit-library-modal").click(function() {
|
||||
$("#edit-library-tooltip").tooltip('hide');
|
||||
$.ajax({
|
||||
url: 'edit_library_dialog',
|
||||
data: { section_id: section_id },
|
||||
cache: false,
|
||||
async: true,
|
||||
complete: function(xhr, status) {
|
||||
$("#edit-library-modal").html(xhr.responseText);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(".export-button").click(function() {
|
||||
$.ajax({
|
||||
url: 'export_metadata_modal',
|
||||
data: {
|
||||
section_id: $(this).data('section_id'),
|
||||
media_type: $(this).data('media_type'),
|
||||
sub_media_type: $(this).data('sub_media_type'),
|
||||
export_type: $(this).data('export_type')
|
||||
},
|
||||
cache: false,
|
||||
async: true,
|
||||
complete: function(xhr, status) {
|
||||
$("#export-modal").html(xhr.responseText);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#row-edit-mode').on('click', function() {
|
||||
$('#row-edit-mode-alert').fadeIn(200);
|
||||
|
||||
if ($(this).hasClass('active')) {
|
||||
if (history_to_delete.length > 0) {
|
||||
$('#deleteCount').text(history_to_delete.length);
|
||||
$('#confirm-modal-delete').modal();
|
||||
$('#confirm-modal-delete').one('click', '#confirm-delete', function () {
|
||||
$.ajax({
|
||||
url: 'delete_history_rows',
|
||||
type: 'POST',
|
||||
data: { row_ids: history_to_delete.join(',') },
|
||||
async: true,
|
||||
success: function (data) {
|
||||
var msg = "History deleted";
|
||||
showMsg(msg, false, true, 2000);
|
||||
history_table.draw();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$('.delete-control').each(function () {
|
||||
$(this).addClass('hidden');
|
||||
$('#row-edit-mode-alert').fadeOut(200);
|
||||
});
|
||||
|
||||
} else {
|
||||
history_to_delete = [];
|
||||
$('.delete-control').each(function() {
|
||||
$(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger');
|
||||
$(this).removeClass('hidden');
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
% endif
|
||||
% endif
|
||||
</%def>
|
@@ -25,11 +25,11 @@ DOCUMENTATION :: END
|
||||
<div class="user-player-instance">
|
||||
<li>
|
||||
% if a['user_id']:
|
||||
<a href="${page('user', a['user_id'])}" title="${a['friendly_name']}">
|
||||
<a href="${page('user', a['user_id'])}" title="${a['username']}">
|
||||
<div class="library-user-instance-box" style="background-image: url(${a['user_thumb']});"></div>
|
||||
</a>
|
||||
<div class=" user-player-instance-name">
|
||||
<a href="${page('user', a['user_id'])}" title="${a['friendly_name']}">${a['friendly_name']}</a>
|
||||
<a href="${page('user', a['user_id'])}" title="${a['username']}">${a['friendly_name']}</a>
|
||||
</div>
|
||||
% else:
|
||||
<div class="library-user-instance-box" style="background-image: url(${a['user_thumb']});"></div>
|
||||
|
@@ -11,28 +11,28 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<link href="${http_root}css/bootstrap3/bootstrap.css" rel="stylesheet">
|
||||
<link href="${http_root}css/bootstrap3/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="${http_root}css/tautulli.css${cache_param}" rel="stylesheet">
|
||||
<link href="${http_root}css/opensans.min.css" rel="stylesheet">
|
||||
<link href="${http_root}css/font-awesome.all.min.css" rel="stylesheet">
|
||||
<link href="${http_root}css/font-awesome.v4-shims.min.css" rel="stylesheet">
|
||||
|
||||
<!-- Favicons -->
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="${http_root}images/favicon/favicon-32x32.png?v=2.0.5">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="${http_root}images/favicon/favicon-16x16.png?v=2.0.5">
|
||||
<link rel="shortcut icon" href="${http_root}images/favicon/favicon.ico?v=2.0.5">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="${http_root}images/favicon/favicon-32x32.png?v=2.6.0">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="${http_root}images/favicon/favicon-16x16.png?v=2.6.0">
|
||||
<link rel="shortcut icon" href="${http_root}images/favicon/favicon.ico?v=2.6.0">
|
||||
|
||||
<!-- ICONS -->
|
||||
<!-- Android -->
|
||||
<link rel="manifest" href="${http_root}images/favicon/manifest.json?v=2.0.5" crossorigin="use-credentials>
|
||||
<link rel="manifest" href="${http_root}images/favicon/manifest.json?v=2.6.0" crossorigin="use-credentials">
|
||||
<meta name="theme-color" content="#282a2d">
|
||||
<!-- Apple -->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="${http_root}images/favicon/apple-touch-icon.png?v=2.0.5">
|
||||
<link rel="mask-icon" href="${http_root}images/favicon/safari-pinned-tab.svg?v=2.0.5" color="#282a2d">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="${http_root}images/favicon/apple-touch-icon.png?v=2.6.0">
|
||||
<link rel="mask-icon" href="${http_root}images/favicon/safari-pinned-tab.svg?v=2.6.0" color="#282a2d">
|
||||
<meta name="apple-mobile-web-app-title" content="Tautulli">
|
||||
<!-- Microsoft -->
|
||||
<meta name="application-name" content="Tautulli">
|
||||
<meta name="msapplication-config" content="${http_root}images/favicon/browserconfig.xml?v=2.0.5">
|
||||
<meta name="msapplication-config" content="${http_root}images/favicon/browserconfig.xml?v=2.6.0">
|
||||
</head>
|
||||
|
||||
<body style="margin: 0; overflow: auto;">
|
||||
@@ -109,7 +109,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="${http_root}js/jquery-2.1.4.min.js"></script>
|
||||
<script src="${http_root}js/jquery-3.5.1.min.js"></script>
|
||||
<script src="${http_root}js/platform.min.js"></script>
|
||||
<script src="${http_root}js/script.js${cache_param}"></script>
|
||||
<script>
|
||||
@@ -159,16 +159,20 @@
|
||||
data: data,
|
||||
dataType: 'json',
|
||||
statusCode: {
|
||||
200: function() {
|
||||
200: function(xhr, status) {
|
||||
window.location = "${redirect_uri or http_root}";
|
||||
},
|
||||
401: function() {
|
||||
401: function(xhr, status) {
|
||||
if (plex) {
|
||||
$('#sign-in-alert').text('Invalid Plex Login.').show();
|
||||
} else {
|
||||
$('#sign-in-alert').text('Incorrect username or password.').show();
|
||||
$('#username').focus();
|
||||
}
|
||||
},
|
||||
429: function(xhr, status) {
|
||||
var retry = Math.ceil(xhr.getResponseHeader('Retry-After') / 60)
|
||||
$('#sign-in-alert').text('Too many login attempts. Try again in ' + retry + ' minute(s).').show();
|
||||
}
|
||||
},
|
||||
complete: function() {
|
||||
|
@@ -208,7 +208,6 @@
|
||||
<script src="${http_root}js/jquery.dataTables.min.js"></script>
|
||||
<script src="${http_root}js/dataTables.bootstrap.min.js"></script>
|
||||
<script src="${http_root}js/dataTables.bootstrap.pagination.js"></script>
|
||||
<script src="${http_root}js/moment-with-locale.js"></script>
|
||||
<script src="${http_root}js/tables/logs.js${cache_param}"></script>
|
||||
<script src="${http_root}js/tables/plex_logs.js${cache_param}"></script>
|
||||
<script src="${http_root}js/tables/notification_logs.js${cache_param}"></script>
|
||||
|
@@ -33,7 +33,7 @@
|
||||
<label for="friendly_name">OneSignal Device ID</label>
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control" id="device_id" value="${device['device_id']}" size="30" readonly>
|
||||
<input type="text" class="form-control" id="onesignal_id" value="${device['onesignal_id'] or ''}" size="30" readonly>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">Your OneSignal device ID for notifications.</p>
|
||||
|
@@ -13,7 +13,11 @@ DOCUMENTATION :: END
|
||||
% for device in sorted(devices_list, key=lambda k: k['device_name']):
|
||||
<li class="mobile-device pointer" data-id="${device['id']}" data-name="${device['device_name']}">
|
||||
<span>
|
||||
<span class="toggle-left"><i class="fa fa-lg fa-fw fa-mobile"></i></span>
|
||||
% if device['official']:
|
||||
<span class="toggle-left"><i class="fa fa-lg fa-fw fa-mobile"></i></span>
|
||||
% else:
|
||||
<span class="toggle-left officail-tooltip" data-toggle="tooltip" data-placement="top" title="Unofficial or Unknown App"><i class="fa fa-lg fa-fw fa-exclamation-triangle"></i></span>
|
||||
% endif
|
||||
${device['friendly_name'] or device['device_name']} <span class="friendly_name">(${device['id']})</span>
|
||||
<span class="toggle-right"><i class="fa fa-lg fa-fw fa-cog"></i></span>
|
||||
<span class="toggle-right friendly_name" id="device-last_seen-${device['id']}">
|
||||
@@ -117,6 +121,7 @@ DOCUMENTATION :: END
|
||||
});
|
||||
|
||||
$('#api_qr_address').change(function () {
|
||||
this.value = $.trim(this.value);
|
||||
var url = $(this).val();
|
||||
checkQRAddress(url);
|
||||
|
||||
@@ -138,4 +143,6 @@ DOCUMENTATION :: END
|
||||
}
|
||||
verifiedDevice = true;
|
||||
})
|
||||
|
||||
$('.officail-tooltip').tooltip();
|
||||
</script>
|
@@ -8,7 +8,7 @@
|
||||
<meta charset="utf-8">
|
||||
<title>Tautulli - ${title}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="${http_root}css/bootstrap3/bootstrap.css" rel="stylesheet">
|
||||
<link href="${http_root}css/bootstrap3/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="${http_root}css/tautulli.css${cache_param}" rel="stylesheet">
|
||||
<link href="${http_root}css/opensans.min.css" rel="stylesheet">
|
||||
<link href="${http_root}css/font-awesome.all.min.css" rel="stylesheet">
|
||||
|
@@ -50,7 +50,7 @@
|
||||
</div>
|
||||
<p class="help-block">
|
||||
<span id="simple_cron_message">Set the schedule for the newsletter.</span>
|
||||
<span id="custom_cron_message">Set the schedule for the newsletter using a <a href="${anon_url('https://crontab.guru')}" target="_blank">custom crontab</a>. Only standard cron values are valid.</span>
|
||||
<span id="custom_cron_message">Set the schedule for the newsletter using a <a href="${anon_url('https://crontab.guru')}" target="_blank" rel="noreferrer">custom crontab</a>. Only standard cron values are valid.</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@@ -123,7 +123,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<select class="form-control" id="${item['name']}" name="${item['name']}">
|
||||
% for key, value in sorted(item['select_options'].iteritems()):
|
||||
% for key, value in sorted(item['select_options'].items()):
|
||||
% if key == item['value']:
|
||||
<option value="${key}" selected>${value}</option>
|
||||
% else:
|
||||
@@ -144,7 +144,7 @@
|
||||
<option value="select-all">Select All</option>
|
||||
<option value="remove-all">Remove All</option>
|
||||
% if isinstance(item['select_options'], dict):
|
||||
% for section, options in item['select_options'].iteritems():
|
||||
% for section, options in item['select_options'].items():
|
||||
<optgroup label="${section}">
|
||||
% for option in sorted(options, key=lambda x: x['text'].lower()):
|
||||
<option value="${option['value']}">${option['text']}</option>
|
||||
@@ -325,7 +325,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<select class="form-control" id="${item['name']}" name="${item['name']}">
|
||||
% for key, value in sorted(item['select_options'].iteritems()):
|
||||
% for key, value in sorted(item['select_options'].items()):
|
||||
% if key == item['value']:
|
||||
<option value="${key}" selected>${value}</option>
|
||||
% else:
|
||||
@@ -346,7 +346,7 @@
|
||||
<option value="select-all">Select All</option>
|
||||
<option value="remove-all">Remove All</option>
|
||||
% if isinstance(item['select_options'], dict):
|
||||
% for section, options in item['select_options'].iteritems():
|
||||
% for section, options in item['select_options'].items():
|
||||
<optgroup label="${section}">
|
||||
% for option in sorted(options, key=lambda x: x['text'].lower()):
|
||||
<option value="${option['value']}">${option['text']}</option>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<%
|
||||
import urllib
|
||||
from six.moves.urllib.parse import urlencode
|
||||
%>
|
||||
<!doctype html>
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
<meta charset="utf-8">
|
||||
<title>Tautulli - ${title} | ${server_name}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="${http_root}images/favicon/favicon-32x32.png?v=2.0.5">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="${http_root}images/favicon/favicon-16x16.png?v=2.0.5">
|
||||
<link rel="shortcut icon" href="${http_root}images/favicon/favicon.ico?v=2.0.5">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="${http_root}images/favicon/favicon-32x32.png?v=2.6.0">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="${http_root}images/favicon/favicon-16x16.png?v=2.6.0">
|
||||
<link rel="shortcut icon" href="${http_root}images/favicon/favicon.ico?v=2.6.0">
|
||||
<link href="${http_root}css/tautulli.css${cache_param}" rel="stylesheet">
|
||||
<style>
|
||||
* {
|
||||
@@ -31,11 +31,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="${http_root}js/jquery-2.1.4.min.js"></script>
|
||||
<script src="${http_root}js/jquery-3.5.1.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
var frame = $('<iframe></iframe>', {
|
||||
src: 'real_newsletter?${urllib.urlencode(kwargs) | n}',
|
||||
src: 'real_newsletter?${urlencode(kwargs) | n}',
|
||||
frameborder: '0',
|
||||
style: 'display: none; height: 100vh; width: 100vw;'
|
||||
});
|
||||
|
@@ -9,7 +9,7 @@ Version: 0.1
|
||||
DOCUMENTATION :: END
|
||||
</%doc>
|
||||
|
||||
<% from plexpy.newsletter_handler import NEWSLETTER_SCHED %>
|
||||
<% from plexpy import newsletter_handler %>
|
||||
<ul class="stacked-configs list-unstyled">
|
||||
% for newsletter in sorted(newsletters_list, key=lambda k: (k['agent_label'], k['friendly_name'], k['id'])):
|
||||
<li class="newsletter-agent pointer" data-id="${newsletter['id']}">
|
||||
@@ -22,8 +22,8 @@ DOCUMENTATION :: END
|
||||
% endif
|
||||
<span class="toggle-right"><i class="fa fa-lg fa-fw fa-cog"></i></span>
|
||||
<span class="toggle-right friendly_name" id="newsletter-next_run-${newsletter['id']}">
|
||||
% if NEWSLETTER_SCHED.get_job('newsletter-{}'.format(newsletter['id'])):
|
||||
<% job = NEWSLETTER_SCHED.get_job('newsletter-{}'.format(newsletter['id'])) %>
|
||||
% if newsletter_handler.NEWSLETTER_SCHED.get_job('newsletter-{}'.format(newsletter['id'])):
|
||||
<% job = newsletter_handler.NEWSLETTER_SCHED.get_job('newsletter-{}'.format(newsletter['id'])) %>
|
||||
<script>
|
||||
$("#newsletter-next_run-${newsletter['id']}").text(moment("${job.next_run_time}", "YYYY-MM-DD HH:mm:ssZ").fromNow())
|
||||
</script>
|
||||
|
@@ -49,7 +49,16 @@
|
||||
<label for="${item['name']}">${item['label']}</label>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
% if notifier['agent_name'] == 'scripts' and item['name'] == 'scripts_script_folder':
|
||||
<div class="input-group">
|
||||
<input type="${item['input_type']}" class="form-control" id="${item['name']}" name="${item['name']}" value="${item['value']}" size="30" ${'readonly' if item.get('readonly') else ''}>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-form" type="button" id="${item['name']}_browse" data-toggle="browse" data-filter=".folderonly" data-target="#${item['name']}">Browse</button>
|
||||
</span>
|
||||
</div>
|
||||
% else:
|
||||
<input type="${item['input_type']}" class="form-control" id="${item['name']}" name="${item['name']}" value="${item['value']}" size="30" ${'readonly' if item.get('readonly') else ''}>
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">${item['description'] | n}</p>
|
||||
@@ -88,7 +97,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<select class="form-control" id="${item['name']}" name="${item['name']}">
|
||||
% for key, value in sorted(item['select_options'].iteritems()):
|
||||
% for key, value in sorted(item['select_options'].items()):
|
||||
% if key == item['value']:
|
||||
<option value="${key}" selected>${value}</option>
|
||||
% else:
|
||||
@@ -109,7 +118,7 @@
|
||||
<option value="select-all">Select All</option>
|
||||
<option value="remove-all">Remove All</option>
|
||||
% if isinstance(item['select_options'], dict):
|
||||
% for section, options in item['select_options'].iteritems():
|
||||
% for section, options in item['select_options'].items():
|
||||
<optgroup label="${section}">
|
||||
% for option in sorted(options, key=lambda x: x['text'].lower()):
|
||||
<option value="${option['value']}">${option['text']}</option>
|
||||
@@ -211,7 +220,7 @@
|
||||
% for action in available_notification_actions:
|
||||
<li>
|
||||
<div class="link">
|
||||
<span class="toggle-left"><i class="fa ${action['icon']} fa-fw"></i></span>
|
||||
<span class="toggle-left"><i class="fa ${action['icon']} fa-fw"></i></span>
|
||||
${action['label']}
|
||||
<span class="toggle-right"><i class="fa fa-chevron-down"></i></span>
|
||||
</div>
|
||||
@@ -237,7 +246,7 @@
|
||||
% for action in available_notification_actions:
|
||||
<li>
|
||||
<div class="link">
|
||||
<span class="toggle-left"><i class="fa ${action['icon']} fa-fw"></i></span>
|
||||
<span class="toggle-left"><i class="fa ${action['icon']} fa-fw"></i></span>
|
||||
${action['label']}
|
||||
<span class="toggle-right"><i class="fa fa-chevron-down"></i></span>
|
||||
</div>
|
||||
@@ -268,7 +277,7 @@
|
||||
% for action in available_notification_actions:
|
||||
<li>
|
||||
<div class="link">
|
||||
<span class="toggle-left"><i class="fa ${action['icon']} fa-fw"></i></span>
|
||||
<span class="toggle-left"><i class="fa ${action['icon']} fa-fw"></i></span>
|
||||
${action['label']}
|
||||
<span class="toggle-right"><i class="fa fa-chevron-down"></i></span>
|
||||
</div>
|
||||
@@ -313,7 +322,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<select class="form-control" id="test_script" name="test_script">
|
||||
% for key, value in sorted(notifier['config_options'][2]['select_options'].iteritems()):
|
||||
% for key, value in sorted(notifier['config_options'][2]['select_options'].items()):
|
||||
<option value="${key}">${value}</option>
|
||||
% endfor
|
||||
</select>
|
||||
@@ -853,10 +862,8 @@
|
||||
PNotify.prototype.options.hide = true;
|
||||
PNotify.prototype.options.delay = $('#browser_auto_hide_delay').val() * 1000;
|
||||
}
|
||||
var notification = new PNotify({
|
||||
title: $('#test_subject').val(),
|
||||
text: $('#test_body').val()
|
||||
});
|
||||
displayPNotify($('#test_subject').val(), $('#test_body').val());
|
||||
showMsg('<i class="fa fa-check"></i> Notification sent.', false, true, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -32,7 +32,7 @@ DOCUMENTATION :: END
|
||||
|
||||
% if data != None:
|
||||
<%
|
||||
from plexpy.helpers import page
|
||||
from plexpy.helpers import cast_to_int, page
|
||||
%>
|
||||
% if data:
|
||||
<div class="dashboard-recent-media-row">
|
||||
@@ -87,7 +87,7 @@ DOCUMENTATION :: END
|
||||
<a href="${page('info', item['rating_key'])}" title="${item['title']}">${item['title']}</a>
|
||||
</h3>
|
||||
<h3 class="text-muted">
|
||||
${item['child_count']} Seasons
|
||||
${item['child_count']} Season${'s' if cast_to_int(item['child_count']) > 1 else ''}
|
||||
</h3>
|
||||
<h3 class="text-muted"> </h3>
|
||||
</div>
|
||||
@@ -151,7 +151,7 @@ DOCUMENTATION :: END
|
||||
<a href="${page('info', item['rating_key'])}" title="Episode ${item['media_index']}">E${item['media_index']}</a>
|
||||
</h3>
|
||||
</div>
|
||||
% elif item['media_type'] == 'album':
|
||||
% elif item['media_type'] == 'album':
|
||||
<a href="${page('info', item['rating_key'])}" title="${item['parent_title']}">
|
||||
<div class="dashboard-recent-media-cover">
|
||||
<div class="dashboard-recent-media-cover-face" style="background-image: url(${page('pms_image_proxy', item['thumb'], item['rating_key'], 300, 300, fallback='cover')});">
|
||||
@@ -177,7 +177,7 @@ DOCUMENTATION :: END
|
||||
</h3>
|
||||
<h3 class="text-muted"> </h3>
|
||||
</div>
|
||||
% endif
|
||||
% endif
|
||||
</li>
|
||||
</div>
|
||||
% endfor
|
||||
|
@@ -28,7 +28,7 @@ DOCUMENTATION :: END
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
% for job in common.SCHEDULER_LIST:
|
||||
% for job, job_type in common.SCHEDULER_LIST.items():
|
||||
% if job in scheduled_jobs:
|
||||
<%
|
||||
sched_job = plexpy.SCHED.get_job(job)
|
||||
@@ -41,12 +41,12 @@ DOCUMENTATION :: END
|
||||
<td>${helpers.format_timedelta_Hms(sched_job.next_run_time - now)}</td>
|
||||
<td>${sched_job.next_run_time.astimezone(plexpy.SYS_TIMEZONE).strftime('%Y-%m-%d %H:%M:%S')}</td>
|
||||
</tr>
|
||||
% elif job in ('Check for server response', 'Check for active sessions', 'Check for recently added items') and plexpy.WS_CONNECTED:
|
||||
% elif job_type == 'websocket' and plexpy.WS_CONNECTED:
|
||||
<tr>
|
||||
% if job == 'Check for active sessions':
|
||||
<td><a class="queue-modal-link" href="#" data-queue="active sessions">${job}</a></td>
|
||||
<td><a class="queue-modal-link no-highlight" href="#" data-queue="active sessions">${job}</a></td>
|
||||
% elif job == 'Check for recently added items':
|
||||
<td><a class="queue-modal-link" href="#" data-queue="recently added">${job}</a></td>
|
||||
<td><a class="queue-modal-link no-highlight" href="#" data-queue="recently added">${job}</a></td>
|
||||
% else:
|
||||
<td>${job}</td>
|
||||
% endif
|
||||
|
@@ -8,7 +8,7 @@
|
||||
from plexpy.helpers import anon_url, checked
|
||||
|
||||
docker_setting = 'disabled' if plexpy.DOCKER else ''
|
||||
docker_msg = '<span class="docker-setting small">(Controlled by Docker Container)</span>' if plexpy.DOCKER else ''
|
||||
docker_msg = '<span class="setting-message small">(Controlled by Docker Container)</span>' if plexpy.DOCKER else ''
|
||||
|
||||
available_notification_agents = sorted(notifiers.available_notification_agents(), key=lambda k: k['label'].lower())
|
||||
available_newsletter_agents = sorted(newsletters.available_newsletter_agents(), key=lambda k: k['label'].lower())
|
||||
@@ -17,8 +17,6 @@
|
||||
</%def>
|
||||
|
||||
<%def name="headerIncludes()">
|
||||
<link href="${http_root}css/selectize.bootstrap3.css" rel="stylesheet" />
|
||||
<link href="${http_root}css/selectize.min.css" rel="stylesheet" />
|
||||
</%def>
|
||||
|
||||
<%def name="body()">
|
||||
@@ -48,17 +46,17 @@
|
||||
<!-- Nav tabs -->
|
||||
<div class="col-md-3">
|
||||
<ul class="nav-settings list-unstyled" role="tablist">
|
||||
<li role="presentation" class="active"><a href="#tabs-help_info" aria-controls="tabs-help_info" role="tab" data-toggle="tab">Help & Info</a></li>
|
||||
<li role="presentation"><a href="#tabs-general" aria-controls="tabs-general" role="tab" data-toggle="tab">General</a></li>
|
||||
<li role="presentation"><a href="#tabs-homepage" aria-controls="tabs-homepage" role="tab" data-toggle="tab">Homepage</a></li>
|
||||
<li role="presentation"><a href="#tabs-web_interface" aria-controls="tabs-web_interface" role="tab" data-toggle="tab">Web Interface</a></li>
|
||||
<li role="presentation"><a href="#tabs-plex_media_server" aria-controls="tabs-plex_media_server" role="tab" data-toggle="tab">Plex Media Server</a></li>
|
||||
<li role="presentation"><a href="#tabs-notifications" aria-controls="tabs-notifications" role="tab" data-toggle="tab">Notifications & Newsletters</a></li>
|
||||
<li role="presentation"><a href="#tabs-notification_agents" aria-controls="tabs-notification_agents" role="tab" data-toggle="tab">Notification Agents</a></li>
|
||||
<li role="presentation"><a href="#tabs-newsletter_agents" aria-controls="tabs-newsletter_agents" role="tab" data-toggle="tab">Newsletter Agents</a></li>
|
||||
<li role="presentation"><a href="#tabs-3rd_party_apis" aria-controls="tabs-3rd_party_apis" role="tab" data-toggle="tab">3rd Party APIs</a></li>
|
||||
<li role="presentation"><a href="#tabs-import_backups" aria-controls="tabs-import_backups" role="tab" data-toggle="tab">Import & Backups</a></li>
|
||||
<li role="presentation"><a href="#tabs-android_app" aria-controls="tabs-android_app" role="tab" data-toggle="tab">Tautulli Remote Android App <sup><small>beta</small></sup></a></li>
|
||||
<li role="presentation" class="active"><a id="nav-help_info" href="#tabs-help_info" aria-controls="tabs-help_info" role="tab" data-toggle="tab">Help & Info</a></li>
|
||||
<li role="presentation"><a id="nav-tabs-general" href="#tabs-general" aria-controls="tabs-general" role="tab" data-toggle="tab">General</a></li>
|
||||
<li role="presentation"><a id="nav-tabs-homepage" href="#tabs-homepage" aria-controls="tabs-homepage" role="tab" data-toggle="tab">Homepage</a></li>
|
||||
<li role="presentation"><a id="nav-tabs-web_interface" href="#tabs-web_interface" aria-controls="tabs-web_interface" role="tab" data-toggle="tab">Web Interface</a></li>
|
||||
<li role="presentation"><a id="nav-tabs-plex_media_server" href="#tabs-plex_media_server" aria-controls="tabs-plex_media_server" role="tab" data-toggle="tab">Plex Media Server</a></li>
|
||||
<li role="presentation"><a id="nav-tabs-notifications" href="#tabs-notifications" aria-controls="tabs-notifications" role="tab" data-toggle="tab">Notifications & Newsletters</a></li>
|
||||
<li role="presentation"><a id="nav-tabs-notification_agents" href="#tabs-notification_agents" aria-controls="tabs-notification_agents" role="tab" data-toggle="tab">Notification Agents</a></li>
|
||||
<li role="presentation"><a id="nav-tabs-newsletter_agents" href="#tabs-newsletter_agents" aria-controls="tabs-newsletter_agents" role="tab" data-toggle="tab">Newsletter Agents</a></li>
|
||||
<li role="presentation"><a id="nav-tabs-3rd_party_apis" href="#tabs-3rd_party_apis" aria-controls="tabs-3rd_party_apis" role="tab" data-toggle="tab">3rd Party APIs</a></li>
|
||||
<li role="presentation"><a id="nav-tabs-import_backups" href="#tabs-import_backups" aria-controls="tabs-import_backups" role="tab" data-toggle="tab">Import & Backups</a></li>
|
||||
<li role="presentation"><a id="nav-tabs-android_app" href="#tabs-android_app" aria-controls="tabs-android_app" role="tab" data-toggle="tab">Tautulli Remote Android App <sup><small>beta</small></sup></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
@@ -71,6 +69,13 @@
|
||||
<h3>Version ${common.RELEASE} <small><a id="changelog-modal-link" href="#"><i class="fa fa-info-circle"></i> Changelog</a></small></h3>
|
||||
</div>
|
||||
% endif
|
||||
<div class="padded-header">
|
||||
<h3>Tautulli News</h3>
|
||||
</div>
|
||||
<div id="tautulli-news">
|
||||
<div class='text-muted'><i class="fa fa-refresh fa-spin"></i> Loading news...</div>
|
||||
<br>
|
||||
</div>
|
||||
<div class="padded-header">
|
||||
<h3>Tautulli Configuration</h3>
|
||||
</div>
|
||||
@@ -215,12 +220,14 @@
|
||||
<p class="help-block">Check for Tautulli updates periodically.</p>
|
||||
</div>
|
||||
<div id="git_update_options">
|
||||
% if not plexpy.SNAP and not plexpy.FROZEN:
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" id="plexpy_auto_update" name="plexpy_auto_update" value="1" ${config['plexpy_auto_update']} ${docker_setting}> Update Automatically ${docker_msg | n}
|
||||
</label>
|
||||
<p class="help-block">Update Tautulli automatically if an update is available.</p>
|
||||
</div>
|
||||
% endif
|
||||
<div class="form-group advanced-setting">
|
||||
<label for="git_token">GitHub API Token</label>
|
||||
<div class="row">
|
||||
@@ -448,12 +455,27 @@
|
||||
</div>
|
||||
|
||||
<p class="help-block">Note: Web interface changes require a restart.</p>
|
||||
% if os.name == 'nt':
|
||||
% if common.PLATFORM in ('Windows', 'Darwin'):
|
||||
<%
|
||||
tray = {'Windows': 'System Tray', 'Darwin': 'Menu Bar'}
|
||||
tray_disabled = tray_disabled_msg = ''
|
||||
if common.PLATFORM == 'Darwin':
|
||||
from plexpy.macos import HAS_PYOBJC
|
||||
if not HAS_PYOBJC:
|
||||
tray_disabled = 'disabled'
|
||||
tray_disabled_msg = '<span class="setting-message small">(Missing pyobjc module)</span>'
|
||||
%>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" class="http-settings" name="win_sys_tray" id="win_sys_tray" value="1" ${config['win_sys_tray']}> Enable System Tray Icon
|
||||
<input type="checkbox" class="http-settings" name="sys_tray_icon" id="sys_tray_icon" value="1" ${config['sys_tray_icon']} ${tray_disabled}> Enable ${tray[common.PLATFORM]} Icon ${tray_disabled_msg | n}
|
||||
</label>
|
||||
<p class="help-block">Show Tautulli shortcut in the system tray.</p>
|
||||
<p class="help-block">Show Tautulli shortcut in the ${tray[common.PLATFORM].lower()}.</p>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="launch_startup" id="launch_startup" value="1" ${config['launch_startup']}> Launch at System Startup
|
||||
</label>
|
||||
<p class="help-block">Start Tautulli automatically after Login.</p>
|
||||
</div>
|
||||
% endif
|
||||
<div class="checkbox">
|
||||
@@ -544,29 +566,44 @@
|
||||
<div class="form-group advanced-setting">
|
||||
<label for="https_cert">HTTPS Certificate</label>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<input type="text" class="form-control http-settings" id="https_cert" name="https_cert" value="${config['https_cert']}">
|
||||
<div class="col-md-7">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control http-settings" id="https_cert" name="https_cert" value="${config['https_cert']}">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-form" type="button" id="https_cert_browse" data-toggle="browse" data-filter=".pem" data-target="#https_cert">Browse</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">The location of the SSL certificate.</p>
|
||||
<p class="help-block">The location of the SSL certificate in PEM format.</p>
|
||||
</div>
|
||||
<div class="form-group advanced-setting">
|
||||
<label for="https_cert_chain">HTTPS Certificate Chain</label>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<input type="text" class="form-control http-settings" id="https_cert_chain" name="https_cert_chain" value="${config['https_cert_chain']}">
|
||||
<div class="col-md-7">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control http-settings" id="https_cert_chain" name="https_cert_chain" value="${config['https_cert_chain']}">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-form" type="button" id="https_cert_chain_browse" data-toggle="browse" data-filter=".pem" data-target="#https_cert_chain">Browse</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">The location of the SSL certificate chain.</p>
|
||||
<p class="help-block">The location of the SSL certificate chain in PEM format.</p>
|
||||
</div>
|
||||
<div class="form-group advanced-setting">
|
||||
<label for="https_key">HTTPS Key</label>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<input type="text" class="form-control http-settings" id="https_key" name="https_key" value="${config['https_key']}">
|
||||
<div class="col-md-7">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control http-settings" id="https_key" name="https_key" value="${config['https_key']}">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-form" type="button" id="https_key_browse" data-toggle="browse" data-filter=".pem" data-target="#https_key">Browse</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">The location of the SSL key.</p>
|
||||
<p class="help-block">The location of the SSL key in PEM format.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -751,7 +788,6 @@
|
||||
<label>
|
||||
<input type="checkbox" class="pms-settings" id="pms_url_manual" name="pms_url_manual" value="1" ${config['pms_url_manual']}> Manual Connection
|
||||
</label>
|
||||
<span id="cloudManualConnection" style="display: none; color: #eb8600; padding-left: 10px;"> Not available for Plex Cloud servers.</span>
|
||||
<p class="help-block">Use the user defined connection details. Do not retrieve the server connection URL automatically.</p>
|
||||
</div>
|
||||
<div class="form-group advanced-setting">
|
||||
@@ -777,17 +813,22 @@
|
||||
<input type="checkbox" name="server_changed" id="server_changed" value="1" style="display: none;">
|
||||
|
||||
<div class="form-group advanced-setting">
|
||||
<label for="pms_logs_folder">Logs Folder</label>
|
||||
<label for="pms_logs_folder">Plex Logs Folder</label>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<input type="text" class="form-control" id="pms_logs_folder" name="pms_logs_folder" value="${config['pms_logs_folder']}" size="30" data-parsley-trigger="change" data-parsley-pattern="^[^\~\%]" data-parsley-errors-container="#pms_logs_folder_error" data-parsley-error-message="Shortcuts are not recognized.">
|
||||
<div class="col-md-7">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="pms_logs_folder" name="pms_logs_folder" value="${config['pms_logs_folder']}" size="30" data-parsley-trigger="change" data-parsley-pattern="^[^\~\%]" data-parsley-errors-container="#pms_logs_folder_error" data-parsley-error-message="Shortcuts are not recognized.">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-form" type="button" id="pms_logs_folder_browse" data-toggle="browse" data-filter=".folderonly" data-target="#pms_logs_folder">Browse</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="pms_logs_folder_error" class="alert alert-danger settings-alert" role="alert"></div>
|
||||
</div>
|
||||
<p class="help-block">
|
||||
Optional: Set your Plex logs folder to use Tautulli as a log viewer. Plex logs are not needed for Tautulli to function.
|
||||
A complete folder path is required, shortcuts are not recognized, and the logs must be accessible from the machine where Tautulli is installed.
|
||||
<a href="${anon_url('https://support.plex.tv/hc/en-us/articles/200250417-Plex-Media-Server-Log-Files')}" target="_blank">Click here</a> for help.
|
||||
<a href="${anon_url('https://support.plex.tv/hc/en-us/articles/200250417-Plex-Media-Server-Log-Files')}" target="_blank" rel="noreferrer">Click here</a> for help.
|
||||
</p>
|
||||
</div>
|
||||
<div class="checkbox advanced-setting">
|
||||
@@ -808,7 +849,6 @@
|
||||
<label>
|
||||
<input type="checkbox" id="monitor_pms_updates" name="monitor_pms_updates" value="1" ${config['monitor_pms_updates']}> Monitor Plex Updates
|
||||
</label>
|
||||
<span id="cloudMonitorUpdates" style="display: none; color: #eb8600; padding-left: 10px;"> Not available for Plex Cloud servers.</span>
|
||||
<p class="help-block">Enable to have Tautulli check if updates are available for the Plex Media Server.</p>
|
||||
</div>
|
||||
<div id="pms_update_options">
|
||||
@@ -842,36 +882,6 @@
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" id="monitor_remote_access" name="monitor_remote_access" value="1" ${config['monitor_remote_access']}> Monitor Plex Remote Access
|
||||
</label>
|
||||
<span id="cloudMonitorRemoteAccess" style="display: none; color: #eb8600; padding-left: 10px;"> Not available for Plex Cloud servers.</span>
|
||||
<span id="remoteAccessCheck" class="settings-warning"></span>
|
||||
<p class="help-block">Enable to have Tautulli check if remote access to the Plex Media Server goes down.</p>
|
||||
</div>
|
||||
<div id="monitor_remote_access_options">
|
||||
<div class="form-group advanced-setting">
|
||||
<label for="remote_access_ping_interval">Remote Access Ping Interval</label>
|
||||
<div class="row">
|
||||
<div class="col-md-2">
|
||||
<input type="text" class="form-control" data-parsley-type="integer" id="remote_access_ping_interval" name="remote_access_ping_interval" value="${config['remote_access_ping_interval']}" size="5" data-parsley-min="60" data-parsley-trigger="change" data-parsley-errors-container="#remote_access_ping_interval_error" required>
|
||||
</div>
|
||||
<div id="remote_access_ping_interval_error" class="alert alert-danger settings-alert" role="alert"></div>
|
||||
</div>
|
||||
<p class="help-block">The interval (in seconds) Tautulli will ping the Plex Media Server for the remote access status. Minimum 60.</p>
|
||||
</div>
|
||||
<div class="form-group advanced-setting">
|
||||
<label for="remote_access_ping_threshold">Remote Access Ping Threshold</label>
|
||||
<div class="row">
|
||||
<div class="col-md-2">
|
||||
<input type="text" class="form-control" data-parsley-type="integer" id="remote_access_ping_threshold" name="remote_access_ping_threshold" value="${config['remote_access_ping_threshold']}" size="5" data-parsley-min="1" data-parsley-trigger="change" data-parsley-errors-container="#remote_access_ping_threshold_error" required>
|
||||
</div>
|
||||
<div id="remote_access_ping_threshold_error" class="alert alert-danger settings-alert" role="alert"></div>
|
||||
</div>
|
||||
<p class="help-block">The number of consecutive remote access status failures to consider remote access as down. Minimum 1.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group advanced-setting">
|
||||
<label for="refresh_users_interval">Users List Refresh Interval</label>
|
||||
@@ -967,15 +977,21 @@
|
||||
</div>
|
||||
<div class="checkbox advanced-setting">
|
||||
<label>
|
||||
<input type="checkbox" name="notify_consecutive" id="notify_consecutive" value="1" ${config['notify_consecutive']}> Allow Consecutive Notifications
|
||||
<input type="checkbox" name="notify_consecutive" id="notify_consecutive" value="1" ${config['notify_consecutive']}> Allow Playback Stop Notifications Exceeding Watched Percent
|
||||
</label>
|
||||
<p class="help-block">Enable to allow sending of consecutive notifications (i.e. both watched & stopped notifications).</p>
|
||||
<p class="help-block">
|
||||
Enable to allow sending of playback stop notifications after the watched percent is exceeded.
|
||||
Disable to only send playback stop notifications below the watched percent.
|
||||
</p>
|
||||
</div>
|
||||
<div class="checkbox advanced-setting">
|
||||
<label>
|
||||
<input type="checkbox" name="notify_concurrent_by_ip" id="notify_concurrent_by_ip" value="1" ${config['notify_concurrent_by_ip']}> User Concurrent Streams Notifications by IP Address
|
||||
</label>
|
||||
<p class="help-block">Enable to only send a notification of concurrent streams by a single user from different IP addresses.</p>
|
||||
<p class="help-block">
|
||||
Enable to only send a concurrent streams notification by a single user from different IP addresses.
|
||||
Disable to send a concurrent streams notification anytime the concurrent stream threshold is exceeded regardless of IP address.
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="notify_concurrent_threshold">User Concurrent Stream Threshold</label>
|
||||
@@ -987,6 +1003,15 @@
|
||||
</div>
|
||||
<p class="help-block">The number of concurrent streams by a single user for Tautulli to trigger a notification. Minimum 2.</p>
|
||||
</div>
|
||||
<div class="checkbox advanced-setting">
|
||||
<label>
|
||||
<input type="checkbox" name="notify_new_device_initial_only" id="notify_new_device_initial_only" value="1" ${config['notify_new_device_initial_only']}> User New Device Notification First Time Only
|
||||
</label>
|
||||
<p class="help-block">
|
||||
Enable to only send a new device notification the first time a user streams from a new device.
|
||||
Disable to send a new device notification everytime a user streams from the device until it is recorded in history (i.e. exceeds the ignore interval).
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group advanced-setting">
|
||||
<label for="notify_concurrent_threshold">Continued Session Threshold</label>
|
||||
<div class="row">
|
||||
@@ -1025,14 +1050,14 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="notify_recently_added_delay">Notification Delay</label>
|
||||
<label for="notify_recently_added_delay">Recently Added Notification Delay</label>
|
||||
<div class="row">
|
||||
<div class="col-md-2">
|
||||
<input type="text" class="form-control" data-parsley-type="integer" id="notify_recently_added_delay" name="notify_recently_added_delay" value="${config['notify_recently_added_delay']}" size="5" data-parsley-min="60" data-parsley-trigger="change" data-parsley-errors-container="#notify_recently_added_delay_error" required>
|
||||
</div>
|
||||
<div id="notify_recently_added_delay_error" class="alert alert-danger settings-alert" role="alert"></div>
|
||||
</div>
|
||||
<p class="help-block">Set the delay (in seconds) to wait for consecutive recently added items to group together and to allow metadata to be processed before sending the notification. Minimum 60 seconds.</p>
|
||||
<p class="help-block">Set the delay (in seconds) to wait for consecutive recently added items to group together and to allow metadata to be processed before sending the recently added notification. Minimum 60 seconds, default 300.</p>
|
||||
</div>
|
||||
<div class="form-group advanced-setting">
|
||||
<label>Flush Recently Added</label>
|
||||
@@ -1058,6 +1083,21 @@
|
||||
</p>
|
||||
</div>-->
|
||||
|
||||
<div class="padded-header">
|
||||
<h3>Remote Access Notifications</h3>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="notify_remote_access_threshold">Remote Access Down Threshold</label>
|
||||
<div class="row">
|
||||
<div class="col-md-2">
|
||||
<input type="text" class="form-control" data-parsley-type="integer" id="notify_remote_access_threshold" name="notify_remote_access_threshold" value="${config['notify_remote_access_threshold']}" size="5" data-parsley-min="60" data-parsley-trigger="change" data-parsley-errors-container="#notify_remote_access_threshold_error" required>
|
||||
</div>
|
||||
<div id="notify_remote_access_threshold_error" class="alert alert-danger settings-alert" role="alert"></div>
|
||||
</div>
|
||||
<p class="help-block">The duration (in seconds) for Plex remote access to be down before sending a notification. Minimum 60, default 60.</p>
|
||||
</div>
|
||||
|
||||
<div class="padded-header">
|
||||
<h3>Newsletters</h3>
|
||||
</div>
|
||||
@@ -1110,10 +1150,15 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group advanced-setting">
|
||||
<label for="newsletter_dir">Custom Newsletter Templates Folder</label>
|
||||
<label for="newsletter_custom_dir">Custom Newsletter Templates Folder</label>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<input type="text" class="form-control" id="newsletter_custom_dir" name="newsletter_custom_dir" value="${config['newsletter_custom_dir']}">
|
||||
<div class="col-md-7">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="newsletter_custom_dir" name="newsletter_custom_dir" value="${config['newsletter_custom_dir']}">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-form" type="button" id="newsletter_custom_dir_browse" data-toggle="browse" data-filter=".folderonly" data-target="#newsletter_custom_dir">Browse</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">Optional: Enter the full path to your custom newsletter templates folder. Leave blank for default.</p>
|
||||
@@ -1121,8 +1166,13 @@
|
||||
<div class="form-group advanced-setting">
|
||||
<label for="newsletter_dir">Newsletter Output Directory</label> ${docker_msg | n}
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<input type="text" class="form-control" id="newsletter_dir" name="newsletter_dir" value="${config['newsletter_dir']}" ${docker_setting}>
|
||||
<div class="col-md-7">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="newsletter_dir" name="newsletter_dir" value="${config['newsletter_dir']}" ${docker_setting}>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-form" type="button" id="newsletter_dir_browse" data-toggle="browse" data-filter=".folderonly" data-target="#newsletter_dir" ${docker_setting}>Browse</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">Enter the full path to where newsletter files will be saved.</p>
|
||||
@@ -1142,7 +1192,7 @@
|
||||
Add a new notification agent, or configure an existing notification agent by clicking the settings icon on the right.
|
||||
</p>
|
||||
<p class="help-block">
|
||||
Please see the <a target='_blank' href='${anon_url('https://github.com/%s/%s-Wiki/wiki/Notification-Agents-Guide' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}'>Notification Agents Guide</a> for instructions on setting up each notification agent.
|
||||
Please see the <a href="${anon_url('https://github.com/%s/%s-Wiki/wiki/Notification-Agents-Guide' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}" target="_blank" rel="noreferrer">Notification Agents Guide</a> for instructions on setting up each notification agent.
|
||||
</p>
|
||||
<br />
|
||||
<div id="plexpy-notifiers-table">
|
||||
@@ -1204,7 +1254,7 @@
|
||||
<div id="imgur_upload_options" style="overlfow: hidden; display: ${'none' if config['notify_upload_posters'] != 1 else 'block'}">
|
||||
<div class="form-group">
|
||||
<p class="help-block">
|
||||
Please see the <a target='_blank' href='${anon_url('https://github.com/%s/%s-Wiki/wiki/3rd-Party-APIs-Guide' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}'>3rd Party APIs Guide</a> for instructions on setting up Imgur.<br>
|
||||
Please see the <a href="${anon_url('https://github.com/%s/%s-Wiki/wiki/3rd-Party-APIs-Guide' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}" target="_blank" rel="noreferrer">3rd Party APIs Guide</a> for instructions on setting up Imgur.<br>
|
||||
Warning: Imgur uploads are rate-limited and newsletters may exceed the limit. Please use Cloudinary for newsletters instead.
|
||||
</p>
|
||||
</div>
|
||||
@@ -1227,7 +1277,7 @@
|
||||
<div id="cloudinary_upload_options" style="overlfow: hidden; display: ${'none' if config['notify_upload_posters'] != 3 else 'block'}">
|
||||
<div class="form-group">
|
||||
<p class="help-block">
|
||||
Please see the <a target='_blank' href='${anon_url('https://github.com/%s/%s-Wiki/wiki/3rd-Party-APIs-Guide' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}'>3rd Party APIs Guide</a> for instructions on setting up Cloudinary.
|
||||
Please see the <a href="${anon_url('https://github.com/%s/%s-Wiki/wiki/3rd-Party-APIs-Guide' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}" target="_blank" rel="noreferrer">3rd Party APIs Guide</a> for instructions on setting up Cloudinary.
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@@ -1310,13 +1360,32 @@
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-import_backups">
|
||||
|
||||
<div class="padded-header">
|
||||
<h3>Database Import</h3>
|
||||
<h3>Import</h3>
|
||||
</div>
|
||||
|
||||
<p class="help-block">Click a button below to import an existing database from another app.</p>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-form toggle-app-import-modal" type="button" data-target="#app-import-modal" data-toggle="modal" data-app="plexwatch">PlexWatch</button>
|
||||
<button class="btn btn-form toggle-app-import-modal" type="button" data-target="#app-import-modal" data-toggle="modal" data-app="plexivity">Plexivity</button>
|
||||
<div class="form-group">
|
||||
<label for="database_import">Database Import</label>
|
||||
<p class="help-block">Click a button below to import an existing database from the selected app.</p>
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-form toggle-app-import-modal" type="button" data-target="#app-import-modal" data-toggle="modal" data-app="tautulli">Tautulli</button>
|
||||
<button class="btn btn-form toggle-app-import-modal" type="button" data-target="#app-import-modal" data-toggle="modal" data-app="plexwatch">PlexWatch</button>
|
||||
<button class="btn btn-form toggle-app-import-modal" type="button" data-target="#app-import-modal" data-toggle="modal" data-app="plexivity">Plexivity</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="config_import">Configuration Import</label>
|
||||
<p class="help-block">Click the button below to import a previous Tautulli configuration.</p>
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-form toggle-config-import-modal" type="button" data-target="#config-import-modal" data-toggle="modal">Tautulli</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="padded-header">
|
||||
@@ -1354,8 +1423,13 @@
|
||||
<div class="form-group">
|
||||
<label for="log_dir">Log Directory</label> ${docker_msg | n}
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<input type="text" class="form-control directory-settings" id="log_dir" name="log_dir" value="${config['log_dir']}" ${docker_setting}>
|
||||
<div class="col-md-7">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control directory-settings" id="log_dir" name="log_dir" value="${config['log_dir']}" ${docker_setting}>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-form" type="button" id="log_dir_browse" data-toggle="browse" data-filter=".folderonly" data-target="#log_dir" ${docker_setting}>Browse</button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-form" type="button" id="clear_logs">Clear Logs</button>
|
||||
</div>
|
||||
@@ -1365,8 +1439,13 @@
|
||||
<div class="form-group">
|
||||
<label for="backup_dir">Backup Directory</label> ${docker_msg | n}
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<input type="text" class="form-control directory-settings" id="backup_dir" name="backup_dir" value="${config['backup_dir']}" ${docker_setting}>
|
||||
<div class="col-md-7">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control directory-settings" id="backup_dir" name="backup_dir" value="${config['backup_dir']}" ${docker_setting}>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-form" type="button" id="backup_dir_browse" data-toggle="browse" data-filter=".folderonly" data-target="#backup_dir" ${docker_setting}>Browse</button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-form" type="button" id="backup_config">Backup Config</button>
|
||||
<button class="btn btn-form" type="button" id="backup_database">Backup Database</button>
|
||||
@@ -1377,8 +1456,13 @@
|
||||
<div class="form-group">
|
||||
<label for="cache_dir">Cache Directory</label> ${docker_msg | n}
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<input type="text" class="form-control directory-settings" id="cache_dir" name="cache_dir" value="${config['cache_dir']}" ${docker_setting}>
|
||||
<div class="col-md-7">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control directory-settings" id="cache_dir" name="cache_dir" value="${config['cache_dir']}" ${docker_setting}>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-form" type="button" id="cache_dir_browse" data-toggle="browse" data-filter=".folderonly" data-target="#cache_dir" ${docker_setting}>Browse</button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-form" type="button" id="clear_cache">Clear All Cache</button>
|
||||
<button class="btn btn-form" type="button" id="clear_image_cache">Clear Image Cache</button>
|
||||
@@ -1386,6 +1470,22 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="export_dir">Export Directory</label> ${docker_msg | n}
|
||||
<div class="row">
|
||||
<div class="col-md-7">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control directory-settings" id="export_dir" name="export_dir" value="${config['export_dir']}" ${docker_setting}>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-form" type="button" id="export_dir_browse" data-toggle="browse" data-filter=".folderonly" data-target="#export_dir" ${docker_setting}>Browse</button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-form" type="button" id="clear_exports">Clear All Exports</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p>
|
||||
|
||||
@@ -1400,9 +1500,9 @@
|
||||
<div class="form-group">
|
||||
<label>Get the App</label>
|
||||
<p class="help-block">
|
||||
Get the <a href="${anon_url('https://play.google.com/store/apps/details?id=com.williamcomartin.plexpyremote')}" target="_blank">Tautulli Remote</a> app on Google Play<sup>TM</sup> to access Tautulli from your Android device!<br />
|
||||
Get the <a href="${anon_url('https://play.google.com/store/apps/details?id=com.williamcomartin.plexpyremote')}" target="_blank" rel="noreferrer">Tautulli Remote</a> app on Google Play<sup>TM</sup> to access Tautulli from your Android device!<br />
|
||||
<span class="google-play-badge">
|
||||
<a href="${anon_url('https://play.google.com/store/apps/details?id=com.williamcomartin.plexpyremote')}" target="_blank"><img alt="Get it on Google Play" src="images/en-play-badge.png" /></a>
|
||||
<a href="${anon_url('https://play.google.com/store/apps/details?id=com.williamcomartin.plexpyremote')}" target="_blank" rel="noreferrer"><img alt="Get it on Google Play" src="images/en-play-badge.png" /></a>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
@@ -1410,6 +1510,7 @@
|
||||
<label>Registered Devices</label>
|
||||
<p class="help-block">Register a new device using a QR code, or configure an existing device by clicking the settings icon on the right.</p>
|
||||
<p id="app_api_msg" style="color: #eb8600;">Warning: The API must be enabled under <a data-tab-destination="web_interface" data-target="api_enabled">Web Interface</a> to use the app.</p>
|
||||
<br />
|
||||
<div class="row">
|
||||
<div id="plexpy-mobile-devices-table" class="col-md-12">
|
||||
<div class='text-muted'><i class="fa fa-refresh fa-spin"></i> Loading registered devices...</div>
|
||||
@@ -1437,14 +1538,14 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div style="text-align: center; margin-top: 20px; margin-bottom: 20px;">
|
||||
<strong>Please read the <a href="${anon_url('https://github.com/%s/%s-Issues/blob/master/README.md' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}" target="_blank">guidelines</a>
|
||||
<strong>Please read the <a href="${anon_url('https://github.com/%s/%s-Issues/blob/master/README.md' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}" target="_blank" rel="noreferrer">guidelines</a>
|
||||
in the README document <br />before submitting a new <span id="guidelines-type"></span>!</strong>
|
||||
<br /><br />
|
||||
Your post may be removed for failure to follow the guidelines.
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a href="#" target="_blank" id="guidelines-continue" class="btn btn-bright">Continue</a>
|
||||
<a href="#" target="_blank" rel="noreferrer" id="guidelines-continue" class="btn btn-bright">Continue</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1459,12 +1560,12 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div style="text-align: center; margin-top: 20px; margin-bottom: 20px;">
|
||||
<strong>Please read the <a href="${anon_url('https://github.com/%s/%s-Wiki/wiki/Frequently-Asked-Questions' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}" target="_blank">FAQ</a>
|
||||
<strong>Please read the <a href="${anon_url('https://github.com/%s/%s-Wiki/wiki/Frequently-Asked-Questions' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}" target="_blank" rel="noreferrer">FAQ</a>
|
||||
before asking for help!</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a href="#" target="_blank" id="support-continue" class="btn btn-bright">Continue</a>
|
||||
<a href="#" target="_blank" rel="noreferrer" id="support-continue" class="btn btn-bright">Continue</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1506,6 +1607,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="app-import-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="app-import-modal"></div>
|
||||
<div id="config-import-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="config-import-modal"></div>
|
||||
<div id="add-notifier-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="add-notifier-modal">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
@@ -1845,7 +1947,10 @@ Rating: {rating}/10 --> Rating: /10
|
||||
<label>Instructions</label>
|
||||
<p class="help-block">
|
||||
Scan the QR code below with the Tautulli Android app to automatically register it with the server (make sure the Tautulli Address below is correct)
|
||||
or manually enter the connection info and device token into the app settings.
|
||||
or manually enter the connection info and device token into the app settings. This window will automatically close once device registration is successful.
|
||||
</p>
|
||||
<p class="help-block">
|
||||
Note: OneSignal.com must not be blocked (e.g. in Pi-hole) for device registration.
|
||||
</p>
|
||||
<label>QR Code</label>
|
||||
<pre id="api_qr_code" style="text-align: center"></pre>
|
||||
@@ -1857,7 +1962,7 @@ Rating: {rating}/10 --> Rating: /10
|
||||
</p>
|
||||
<p class="help-block" id="api_qr_private" style="display: none;">
|
||||
Note: This is a private IP address. Tautulli will not be reachable outside of your home network.
|
||||
Access Tautulli via an externally address or manually enter the address above to generate the QR code for remote access.
|
||||
Access Tautulli via an external address or manually enter the address above to generate the QR code for remote access.
|
||||
</p>
|
||||
<p class="help-block" id="api_qr_https" style="display: none;">
|
||||
Note: This URL is not secure. Requests between the app and the server will not be encrypted.
|
||||
@@ -1873,13 +1978,44 @@ Rating: {rating}/10 --> Rating: /10
|
||||
</div>
|
||||
</div>
|
||||
<div id="mobile-device-config-modal" class="modal fade wide" tabindex="-1" role="dialog" aria-labelledby="mobile-device-config-modal"></div>
|
||||
<div id="browse-path-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="browse-path-modal">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
|
||||
<h4 class="modal-title">File Browser</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<label for="browse-path">Select a <span id="browse-path-type"></span> Below</label>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<input type="text" class="form-control" id="browse-path" name="browse-path" value="" size="30" disabled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12" style="height: 400px; overflow: auto;">
|
||||
<ul id="browse-path-list" class="stacked-configs list-unstyled">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<span id="browse-path-status-message" style="padding-right: 25px;"></span>
|
||||
<input type="button" id="select-browse-file" class="btn btn-bright" value="Select">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
<%def name="javascriptIncludes()">
|
||||
<script src="${http_root}js/parsley.min.js"></script>
|
||||
<script src="${http_root}js/Sortable.min.js"></script>
|
||||
<script src="${http_root}js/selectize.min.js"></script>
|
||||
<script src="${http_root}js/moment-with-locale.js"></script>
|
||||
<script src="${http_root}js/jquery.inputaffix.min.js"></script>
|
||||
<script src="${http_root}js/jquery.qrcode.min.js"></script>
|
||||
<script>
|
||||
function getConfigurationTable() {
|
||||
@@ -1979,13 +2115,61 @@ Rating: {rating}/10 --> Rating: /10
|
||||
});
|
||||
}
|
||||
|
||||
$("#browse-path-modal").on('hidden.bs.modal', function() {
|
||||
$("#select-browse-file").unbind('click');
|
||||
});
|
||||
|
||||
function openBrowsePath(key, path, filter_ext, file_description, select_target) {
|
||||
$("#browse-path-type").text(file_description);
|
||||
$("#browse-path-modal").modal('show');
|
||||
|
||||
$("#select-browse-file").click(function () {
|
||||
$("#browse-path-modal").modal('hide');
|
||||
$(select_target).val($("#browse-path").val()).change();
|
||||
});
|
||||
|
||||
browsePath(key, path, filter_ext);
|
||||
}
|
||||
|
||||
function browsePath(key, path, filter_ext) {
|
||||
$("#browse-path-status-message").html('<i class="fa fa-fw fa-spin fa-refresh"></i>');
|
||||
getBrowsePath(key, path, filter_ext).then(function (data) {
|
||||
if (data.result === 'error') {
|
||||
$("#browse-path-status-message").html("<i class='fa fa-exclamation-triangle'></i> " + data.message);
|
||||
} else {
|
||||
$("#browse-path-status-message").html("");
|
||||
|
||||
$('#browse-path').val(data.path);
|
||||
var browse_list = $('#browse-path-list');
|
||||
browse_list.parent().animate({ scrollTop: 0 }, 0);
|
||||
browse_list.empty();
|
||||
|
||||
$.each(data.data, function(i, item) {
|
||||
var browse_item = $('<li/>')
|
||||
.html("<span><i class='fa fa-fw fa-" + item.icon + "'></i> " + item.title + "</span>")
|
||||
.addClass(item.type + ' pointer')
|
||||
.data('key', item.key)
|
||||
.data('path', item.path)
|
||||
.appendTo(browse_list)
|
||||
});
|
||||
|
||||
$('#browse-path-list li').click(function (){
|
||||
$('#browse-path').val($(this).data('path'));
|
||||
if ($(this).hasClass('folder')) {
|
||||
browsePath($(this).data('key'), null, filter_ext)
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
// Javascript to enable link to tab
|
||||
var hash = document.location.hash;
|
||||
var prefix = "tab_";
|
||||
var prefix = "tabs_";
|
||||
if (hash) {
|
||||
$('.nav-settings a[href='+hash.replace(prefix,"")+']').tab('show');
|
||||
$('.nav-settings #nav-' + hash.replace('#' + prefix, "")).tab('show');
|
||||
}
|
||||
|
||||
// Change hash for page-reload
|
||||
@@ -2010,6 +2194,7 @@ $(document).ready(function() {
|
||||
|
||||
function preSaveChecks(_callback) {
|
||||
verifyPMSWebURL();
|
||||
setBaseURLSuffix(true);
|
||||
if (serverChanged) {
|
||||
verifyServer(_callback);
|
||||
} else if (typeof _callback === "function") {
|
||||
@@ -2029,6 +2214,7 @@ $(document).ready(function() {
|
||||
getNewslettersTable();
|
||||
getMobileDevicesTable();
|
||||
loadUpdateDistros();
|
||||
setBaseURLSuffix();
|
||||
settingsChanged = false;
|
||||
}
|
||||
|
||||
@@ -2065,7 +2251,6 @@ $(document).ready(function() {
|
||||
initConfigCheckbox('#https_create_cert');
|
||||
initConfigCheckbox('#check_github');
|
||||
initConfigCheckbox('#monitor_pms_updates');
|
||||
initConfigCheckbox('#monitor_remote_access');
|
||||
initConfigCheckbox('#newsletter_self_hosted');
|
||||
|
||||
$('#menu_link_shutdown').click(function() {
|
||||
@@ -2152,6 +2337,12 @@ $(document).ready(function() {
|
||||
confirmAjaxCall(url, msg);
|
||||
});
|
||||
|
||||
$("#clear_exports").click(function () {
|
||||
var msg = 'Are you sure you want to clear the Tautulli metadata exports?';
|
||||
var url = 'delete_export?delete_all=true';
|
||||
confirmAjaxCall(url, msg);
|
||||
});
|
||||
|
||||
$("#clear_logs").click(function () {
|
||||
var msg = 'Are you sure you want to clear the Tautulli logs?';
|
||||
var url = 'delete_logs';
|
||||
@@ -2311,7 +2502,6 @@ $(document).ready(function() {
|
||||
$('#pms_is_cloud').val(is_cloud !== 'undefined' && is_cloud === true ? 1 : 0);
|
||||
$('#pms_url_manual').prop('checked', false);
|
||||
$('#pms_url').val('Please verify your server above to retrieve the URL');
|
||||
PMSCloudCheck();
|
||||
},
|
||||
onDropdownOpen: function() {
|
||||
this.clear();
|
||||
@@ -2342,38 +2532,6 @@ $(document).ready(function() {
|
||||
}
|
||||
getServerOptions();
|
||||
|
||||
function PMSCloudCheck() {
|
||||
if ($('#pms_is_cloud').val() === "1") {
|
||||
$('#pms_port').val(443).prop('readonly', true);
|
||||
$('#pms_is_remote_checkbox').prop('checked', true).prop('disabled', true);
|
||||
$('#pms_is_remote').val(1);
|
||||
$('#pms_ssl_checkbox').prop('checked', true).prop('disabled', true);
|
||||
$('#pms_ssl').val(1);
|
||||
$('#pms_url_manual').prop('checked', false).prop('disabled', true);
|
||||
$('#monitor_pms_updates').prop('checked', false).prop('disabled', true);
|
||||
$('#pms_update_options').hide();
|
||||
$('#monitor_remote_access').prop('checked', false).prop('disabled', true);
|
||||
$('#cloudManualConnection').show();
|
||||
$('#cloudMonitorUpdates').show();
|
||||
$('#cloudMonitorRemoteAccess').show();
|
||||
$('#remoteAccessCheck').hide();
|
||||
} else {
|
||||
$('#pms_port').prop('readonly', false);
|
||||
$('#pms_is_remote_checkbox').prop('disabled', false);
|
||||
$('#pms_is_remote').val($('#pms_is_remote_checkbox').is(':checked') ? 1 : 0);
|
||||
$('#pms_ssl_checkbox').prop('disabled', false);
|
||||
$('#pms_ssl').val($('#pms_ssl_checkbox').is(':checked') ? 1 : 0);
|
||||
$('#pms_url_manual').prop('disabled', false);
|
||||
$('#monitor_pms_updates').prop('disabled', false);
|
||||
$('#monitor_remote_access').prop('disabled', false);
|
||||
$('#cloudManualConnection').hide();
|
||||
$('#cloudMonitorUpdates').hide();
|
||||
$('#cloudMonitorRemoteAccess').hide();
|
||||
remoteAccessEnabledCheck()
|
||||
}
|
||||
}
|
||||
PMSCloudCheck();
|
||||
|
||||
function verifyServer(_callback) {
|
||||
var pms_ip = $("#pms_ip").val();
|
||||
var pms_port = $("#pms_port").val();
|
||||
@@ -2461,9 +2619,7 @@ $(document).ready(function() {
|
||||
$("#token_verify").html('<i class="fa fa-refresh fa-spin"></i>').fadeIn('fast');
|
||||
}
|
||||
function OAuthSuccessCallback(authToken) {
|
||||
var x_plex_headers = getPlexHeaders();
|
||||
$("#pms_token").val(authToken);
|
||||
$("#pms_uuid").val(x_plex_headers['X-Plex-Client-Identifier']);
|
||||
$("#token_verify").html('<i class="fa fa-check"></i>').fadeIn('fast');
|
||||
getServerOptions(authToken);
|
||||
}
|
||||
@@ -2488,25 +2644,22 @@ $(document).ready(function() {
|
||||
});
|
||||
});
|
||||
|
||||
// Load config import modal
|
||||
$(".toggle-config-import-modal").click(function() {
|
||||
$.ajax({
|
||||
url: 'import_config_tool',
|
||||
cache: false,
|
||||
async: true,
|
||||
complete: function(xhr, status) {
|
||||
$("#config-import-modal").html(xhr.responseText);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
pms_version = false;
|
||||
pms_logs_debug = false;
|
||||
pms_logs = false;
|
||||
|
||||
function remoteAccessEnabledCheck() {
|
||||
$.ajax({
|
||||
url: 'get_server_pref',
|
||||
data: { pref: 'PublishServerOnPlexOnlineKey' },
|
||||
async: true,
|
||||
success: function(data) {
|
||||
if (data === 'false' || data === '0') {
|
||||
$("#remoteAccessCheck").html("Remote access must be enabled on your Plex Server. <a target='_blank' href='${anon_url('https://support.plex.tv/hc/en-us/articles/200484543-Enabling-Remote-Access-for-a-Server')}'>Click here</a> for help.");
|
||||
$("#monitor_remote_access").attr("checked", false).attr("disabled", true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
remoteAccessEnabledCheck();
|
||||
|
||||
// Sortable home_sections
|
||||
function set_home_sections() {
|
||||
var home_sections = [];
|
||||
@@ -2773,10 +2926,19 @@ $(document).ready(function() {
|
||||
});
|
||||
});
|
||||
|
||||
$('#http_base_url').change(function () {
|
||||
$(this).val($(this).val().replace(/\/*$/, ''));
|
||||
$('#http_root').change(function() {
|
||||
setBaseURLSuffix();
|
||||
});
|
||||
|
||||
function setBaseURLSuffix(clear) {
|
||||
if (clear){
|
||||
$('#http_base_url').suffix("");
|
||||
} else {
|
||||
$('#http_base_url').suffix($('#http_root').val());
|
||||
}
|
||||
}
|
||||
setBaseURLSuffix();
|
||||
|
||||
function apiEnabled() {
|
||||
var api_enabled = $('#api_enabled').prop('checked');
|
||||
$('#app_api_msg').toggle(!(api_enabled));
|
||||
@@ -2895,7 +3057,7 @@ $(document).ready(function() {
|
||||
});
|
||||
|
||||
function gotoSetting(tab, setting){
|
||||
$("a[href=#tabs-" + tab + "]").click();
|
||||
$("#nav-tabs-" + tab).click();
|
||||
if (setting) {
|
||||
_setting = '#' + setting;
|
||||
if ($(_setting).closest('.advanced-setting').length && !$('#menu_link_show_advanced_settings').hasClass('active')) {
|
||||
@@ -2917,6 +3079,56 @@ $(document).ready(function() {
|
||||
$('#resources-xml').on('tripleclick', function () {
|
||||
openPlexXML('/api/resources', true, {includeHttps: 1});
|
||||
});
|
||||
|
||||
var tautulli_news = $('#tautulli-news')
|
||||
$.ajax({
|
||||
url: 'https://tautulli.com/news/tautulli-news.json',
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
cache: false,
|
||||
async: true,
|
||||
success: function (data) {
|
||||
if (data) {
|
||||
var now = moment().endOf('day');
|
||||
var news = $('<ul/>').addClass('accordion list-unstyled')
|
||||
$.each(data, function (index, news_item) {
|
||||
var date = moment(news_item.date, "YYYY-MM-DD");
|
||||
if (index >= 5) { return false; }
|
||||
var header = $('<div/>').addClass('link').html(
|
||||
'<span class="toggle-left"><i class="fa fa-newspaper fa-fw"></i></span>' +
|
||||
'<span class="news-title">' + news_item.title + '</span>' +
|
||||
'<span class="toggle-right"><i class="fa fa-chevron-down fa-fw"></i></span>' +
|
||||
'<span class="news-date toggle-right">' + date.format($('#date_format').val()) + '</span>');
|
||||
var subtitle = $('<span/>').addClass('news-subtitle').html(news_item.subtitle);
|
||||
var body = $('<span/>').addClass('news-body').html(news_item.body);
|
||||
var content = $('<div/>').addClass('submenu');
|
||||
if (news_item.subtitle) { content.append(subtitle); }
|
||||
content.append(body);
|
||||
var li = $('<li/>').append(header).append(content)
|
||||
if (index === 0 && Math.abs(now.diff(date, 'days')) < 7) {
|
||||
li.addClass('open');
|
||||
content.css('display', 'block');
|
||||
}
|
||||
news.append(li)
|
||||
});
|
||||
tautulli_news.html(news);
|
||||
var accordion_news = new Accordion(news, false);
|
||||
} else {
|
||||
tautulli_news.html('<p class="help-block"><i class="fa fa-check"></i> No news available.</p>')
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
tautulli_news.html('<p class="help-block"><i class="fa fa-exclamation-triangle"></i> Failed to retrieve news.</p>')
|
||||
}
|
||||
});
|
||||
|
||||
$("body").on('click', '[data-toggle=browse]', function () {
|
||||
var filter = $(this).data('filter');
|
||||
var target = $(this).data('target');
|
||||
var path = $(target).val();
|
||||
var description = $(this).data('description') || $("label[for='" + target.replace('#', '') + "']").text();
|
||||
openBrowsePath(null, path, filter, description, target);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</%def>
|
||||
|
@@ -22,10 +22,10 @@
|
||||
<div class="modal-body" id="modal-text">
|
||||
<div align="center">
|
||||
% if message == "Shutting Down":
|
||||
<h3><i class="fa fa-refresh fa-spin"></i> Tautulli is ${message}.</h3>
|
||||
<h3><i class="fa fa-refresh fa-spin"></i> Tautulli is ${message.lower()}</h3>
|
||||
<br />
|
||||
% else:
|
||||
<h3><i class="fa fa-refresh fa-spin"></i> Tautulli is ${message}.</h3>
|
||||
<h3><i class="fa fa-refresh fa-spin"></i> Tautulli is ${message.lower()}</h3>
|
||||
<br />
|
||||
<h4>Restart in <span class="countdown"></span></h4>
|
||||
% endif
|
||||
|
@@ -16,10 +16,10 @@
|
||||
</div>
|
||||
<div class="button-bar">
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-dark" href="${anon_url('https://tautulli.com/discord')}" target="_blank"><i class="fab fa-discord"></i> Join Discord</a>
|
||||
<a class="btn btn-dark" href="${anon_url('https://tautulli.com/discord')}" target="_blank" rel="noreferrer"><i class="fab fa-discord"></i> Join Discord</a>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-dark" href="${anon_url('https://www.reddit.com/r/Tautulli')}" target="_blank"><i class="fab fa-reddit"></i> Join Reddit</a>
|
||||
<a class="btn btn-dark" href="${anon_url('https://www.reddit.com/r/Tautulli')}" target="_blank" rel="noreferrer"><i class="fab fa-reddit"></i> Join Reddit</a>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-dark" id="popout-iframe-button"><i class="fa fa-external-link"></i> Pop Out Chat</button>
|
||||
|
@@ -171,6 +171,7 @@ DOCUMENTATION :: END
|
||||
</p>
|
||||
<p> with </p>
|
||||
<p><span id="new_title"></span></p>
|
||||
<p>from the <strong><span id="new_library"></span></strong> library?</p>
|
||||
% if query['media_type'] != 'movie':
|
||||
<p>All items for <strong>${query['grandparent_title']}</strong> will also be updated.</p>
|
||||
% endif
|
||||
@@ -211,10 +212,12 @@ DOCUMENTATION :: END
|
||||
|
||||
$(document).on('click', '#search-results-list a', function (e) {
|
||||
e.preventDefault();
|
||||
var new_rating_key = $(this).attr('id');
|
||||
var new_rating_key = $(this).data('rating_key');
|
||||
var new_library_section = $(this).data('library_name');
|
||||
var new_href = $(this).attr('href');
|
||||
|
||||
$('#new_title').html($(this).find('.item-children-instance-text-wrapper').html());
|
||||
$('#new_library').text(new_library_section);
|
||||
|
||||
$('#confirm-modal-update').modal();
|
||||
$('#confirm-modal-update').one('click', '#confirm-update', function () {
|
||||
|
@@ -43,7 +43,9 @@ DOCUMENTATION :: END
|
||||
<div class="summary-navbar">
|
||||
<div class="col-md-12">
|
||||
<div class="summary-navbar-list">
|
||||
<ul class="list-unstyled breadcrumb"></ul>
|
||||
<ul class="list-unstyled breadcrumb">
|
||||
<li class="active">${data['friendly_name']}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -67,12 +69,16 @@ DOCUMENTATION :: END
|
||||
% endif
|
||||
</div>
|
||||
<div class="user-info-nav">
|
||||
<ul class="user-info-nav" role="tablist">
|
||||
<li class="active"><a href="#tabs-profile" role="tab" data-toggle="tab">Profile</a></li>
|
||||
<li><a id="history-tab-btn" href="#tabs-history" role="tab" data-toggle="tab">History</a></li>
|
||||
<li><a id="sync-tab-btn" href="#tabs-synceditems" role="tab" data-toggle="tab">Synced Items</a></li>
|
||||
<li><a id="ip-tab-btn" href="#tabs-ipaddresses" role="tab" data-toggle="tab">IP Addresses</a></li>
|
||||
<li><a id="login-tab-btn" href="#tabs-tautullilogins" role="tab" data-toggle="tab">Tautulli Logins</a></li>
|
||||
<ul class="nav nav-list nav-pills" role="tablist">
|
||||
<li class="active"><a id="nav-tabs-profile" href="#tabs-profile" role="tab" data-toggle="tab">Profile</a></li>
|
||||
<li><a id="nav-tabs-history" href="#tabs-history" role="tab" data-toggle="tab">History</a></li>
|
||||
<li><a id="nav-tabs-playlists" href="#tabs-playlists" role="tab" data-toggle="tab">Playlists</a></li>
|
||||
% if _session['user_group'] == 'admin':
|
||||
<li><a id="nav-tabs-export" href="#tabs-export" role="tab" data-toggle="tab">Export</a></li>
|
||||
% endif
|
||||
<li><a id="nav-tabs-synceditems" href="#tabs-synceditems" role="tab" data-toggle="tab">Synced Items</a></li>
|
||||
<li><a id="nav-tabs-ipaddresses" href="#tabs-ipaddresses" role="tab" data-toggle="tab">IP Addresses</a></li>
|
||||
<li><a id="nav-tabs-tautullilogins" href="#tabs-tautullilogins" role="tab" data-toggle="tab">Tautulli Logins</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -210,6 +216,99 @@ DOCUMENTATION :: END
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-playlists">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class='table-card-header'>
|
||||
<div class="header-bar">
|
||||
<span>
|
||||
<i class="fa fa-list-alt"></i> Playlists for <strong>
|
||||
<span class="set-username">${data['friendly_name']}</span>
|
||||
</strong>
|
||||
</span>
|
||||
</div>
|
||||
<div class="button-bar">
|
||||
% if _session['user_group'] == 'admin':
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-dark export-button" id="toggle-export-modal" data-toggle="modal" data-target="#export-modal"
|
||||
data-user_id="${data['user_id']}" data-media_type="playlist" data-sub_media_type="video,audio,photo"
|
||||
data-export_type="playlist">
|
||||
<i class="fa fa-file-export"></i> Export playlists
|
||||
</button>
|
||||
</div>
|
||||
% endif
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-dark refresh-playlists-table-button" id="refresh-playlists-table">
|
||||
<i class="fa fa-refresh"></i> Refresh playlists
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group colvis-button-bar" id="button-bar-playlists"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-card-back">
|
||||
<table class="display playlists_table" id="playlists_table-SID-${data['user_id']}" width="100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th align="left" id="playlistTitle">Playlist Title</th>
|
||||
<th align="left" id="playlistLeafCount">Playlist Items</th>
|
||||
<th align="left" id="playlistDuration">Playlist Duration</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
% if _session['user_group'] == 'admin':
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-export">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class='table-card-header'>
|
||||
<div class="header-bar">
|
||||
<span>
|
||||
<i class="fa fa-file-export"></i> Metadata Exports for <strong>
|
||||
<span class="set-username">${data['friendly_name']}</span>
|
||||
</strong>
|
||||
</span>
|
||||
</div>
|
||||
<div class="button-bar">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-dark refresh-export-table-button" id="refresh-export-table">
|
||||
<i class="fa fa-refresh"></i> Refresh exports
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group colvis-button-bar" id="button-bar-export"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-card-back">
|
||||
<table class="display export_table" id="export_table-SID-${data['user_id']}" width="100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th align="left" id="timestamp">Exported At</th>
|
||||
<th align="left" id="media_type_title">Media Type</th>
|
||||
<th align="left" id="rating_key">Rating Key</th>
|
||||
<th align="left" id="filename">Filename</th>
|
||||
<th align="left" id="file_format">File Format</th>
|
||||
<th align="left" id="metadata_level">Metadata Level</th>
|
||||
<th align="left" id="media_info_level">Media Info Level</th>
|
||||
<th align="left" id="media_info_level">Custom Fields</th>
|
||||
<th align="left" id="file_size">File Size</th>
|
||||
<th align="left" id="complete">Download</th>
|
||||
<th align="left" id="delete">Delete</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-synceditems">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
@@ -278,13 +377,15 @@ DOCUMENTATION :: END
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-dark refresh-ip-address-button" id="refresh-ip-address-list"><i class="fa fa-refresh"></i> Refresh IP addresses</button>
|
||||
</div>
|
||||
<div class="btn-group colvis-button-bar" id="button-bar-ip-address"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-card-back">
|
||||
<table class="display user_ip_table" id="user_ip_table-UID-${data['user_id']}" width="100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th align="left" id="last_seen">Last Seen</th>
|
||||
<th align="left" id="last_seen">Last Streamed</th>
|
||||
<th align="left" id="first_seen">First Streamed</th>
|
||||
<th align="left" id="ip_address">IP Address</th>
|
||||
<th align="left" id="platform">Last Platform</th>
|
||||
<th align="left" id="player">Last Player</th>
|
||||
@@ -392,6 +493,8 @@ DOCUMENTATION :: END
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="export-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="export-modal">
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
<%def name="javascriptIncludes()">
|
||||
@@ -409,8 +512,9 @@ DOCUMENTATION :: END
|
||||
|
||||
var username = '${data['username'].replace("'", "\\'")}';
|
||||
</script>
|
||||
<script src="${http_root}js/moment-with-locale.js"></script>
|
||||
<script src="${http_root}js/tables/history_table.js${cache_param}"></script>
|
||||
<script src="${http_root}js/tables/playlists_table.js${cache_param}"></script>
|
||||
<script src="${http_root}js/tables/export_table.js${cache_param}"></script>
|
||||
<script src="${http_root}js/tables/user_ips.js${cache_param}"></script>
|
||||
<script src="${http_root}js/tables/sync_table.js${cache_param}"></script>
|
||||
<script src="${http_root}js/tables/login_logs.js${cache_param}"></script>
|
||||
@@ -419,6 +523,8 @@ DOCUMENTATION :: END
|
||||
$.fn.dataTable.tables({ visible: true, api: true }).columns.adjust();
|
||||
});
|
||||
|
||||
$(".inactive-user-tooltip").tooltip();
|
||||
|
||||
function loadHistoryTable(media_type) {
|
||||
// Build watch history table
|
||||
history_table_options.ajax = {
|
||||
@@ -450,6 +556,49 @@ DOCUMENTATION :: END
|
||||
});
|
||||
}
|
||||
|
||||
$('#nav-tabs-history').on('shown.bs.tab', function() {
|
||||
if (typeof(history_table) === 'undefined') {
|
||||
var media_type = getLocalStorage('user_' + user_id + '-history_media_type', 'all');
|
||||
$('#history-' + media_type).prop('checked', true);
|
||||
$('#history-' + media_type).closest('label').addClass('active');
|
||||
loadHistoryTable(media_type);
|
||||
}
|
||||
});
|
||||
|
||||
$("#refresh-history-list").click(function () {
|
||||
history_table.draw();
|
||||
});
|
||||
|
||||
function loadPlaylistsTable() {
|
||||
// Build playlists table
|
||||
playlists_table_options.ajax = {
|
||||
url: 'get_playlists_list',
|
||||
type: 'POST',
|
||||
data: function ( d ) {
|
||||
return {
|
||||
json_data: JSON.stringify( d ),
|
||||
user_id: user_id
|
||||
};
|
||||
}
|
||||
};
|
||||
playlists_table = $('#playlists_table-SID-${data["user_id"]}').DataTable(playlists_table_options);
|
||||
|
||||
var colvis = new $.fn.dataTable.ColVis(playlists_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark' });
|
||||
$(colvis.button()).appendTo('#button-bar-playlists');
|
||||
|
||||
clearSearchButton('playlists_table-SID-${data["user_id"]}', playlists_table);
|
||||
}
|
||||
|
||||
$('#nav-tabs-playlists').on('shown.bs.tab', function() {
|
||||
if (typeof(playlists_table) === 'undefined') {
|
||||
loadPlaylistsTable();
|
||||
}
|
||||
});
|
||||
|
||||
$("#refresh-playlists-table").click(function () {
|
||||
playlists_table.draw();
|
||||
});
|
||||
|
||||
function loadSyncTable() {
|
||||
// Build user sync table
|
||||
sync_table_options.ajax = {
|
||||
@@ -465,6 +614,16 @@ DOCUMENTATION :: END
|
||||
clearSearchButton('sync_table-UID-${data["user_id"]}', sync_table);
|
||||
}
|
||||
|
||||
$('#nav-tabs-synceditems').on('shown.bs.tab', function() {
|
||||
if (typeof(sync_table) === 'undefined') {
|
||||
loadSyncTable(user_id);
|
||||
}
|
||||
});
|
||||
|
||||
$("#refresh-syncs-list").click(function() {
|
||||
sync_table.ajax.reload();
|
||||
});
|
||||
|
||||
function loadIPAddressTable() {
|
||||
// Build user IP table
|
||||
user_ip_table_options.ajax = {
|
||||
@@ -479,9 +638,22 @@ DOCUMENTATION :: END
|
||||
};
|
||||
user_ip_table = $('#user_ip_table-UID-${data["user_id"]}').DataTable(user_ip_table_options);
|
||||
|
||||
var colvis_user_ip = new $.fn.dataTable.ColVis( user_ip_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark'} );
|
||||
$( colvis_user_ip.button() ).appendTo('#button-bar-ip-address');
|
||||
|
||||
clearSearchButton('user_ip_table-UID-${data["user_id"]}', user_ip_table);
|
||||
}
|
||||
|
||||
$('#nav-tabs-ipaddresses').on('shown.bs.tab', function() {
|
||||
if (typeof(user_ip_table) === 'undefined') {
|
||||
loadIPAddressTable(user_id);
|
||||
}
|
||||
});
|
||||
|
||||
$("#refresh-ip-address-list").click(function () {
|
||||
user_ip_table.draw();
|
||||
});
|
||||
|
||||
function loadLoginTable() {
|
||||
// Build user login table
|
||||
login_log_table_options.ajax = {
|
||||
@@ -503,52 +675,141 @@ DOCUMENTATION :: END
|
||||
clearSearchButton('login_log_table-UID-${data["user_id"]}', login_log_table);
|
||||
}
|
||||
|
||||
$('a[href="#tabs-history"]').on('shown.bs.tab', function() {
|
||||
if (typeof(history_table) === 'undefined') {
|
||||
var media_type = getLocalStorage('user_' + user_id + '-history_media_type', 'all');
|
||||
$('#history-' + media_type).prop('checked', true);
|
||||
$('#history-' + media_type).closest('label').addClass('active');
|
||||
loadHistoryTable(media_type);
|
||||
}
|
||||
});
|
||||
|
||||
$('a[href="#tabs-synceditems"]').on('shown.bs.tab', function() {
|
||||
if (typeof(sync_table) === 'undefined') {
|
||||
loadSyncTable(user_id);
|
||||
}
|
||||
});
|
||||
|
||||
$('a[href="#tabs-ipaddresses"]').on('shown.bs.tab', function() {
|
||||
if (typeof(user_ip_table) === 'undefined') {
|
||||
loadIPAddressTable(user_id);
|
||||
}
|
||||
});
|
||||
|
||||
$('a[href="#tabs-tautullilogins"]').on('shown.bs.tab', function() {
|
||||
$('#nav-tabs-tautullilogins').on('shown.bs.tab', function() {
|
||||
if (typeof(login_log_table) === 'undefined') {
|
||||
loadLoginTable(user_id);
|
||||
}
|
||||
});
|
||||
|
||||
$("#refresh-history-list").click(function () {
|
||||
history_table.draw();
|
||||
});
|
||||
|
||||
$("#refresh-syncs-list").click(function() {
|
||||
sync_table.ajax.reload();
|
||||
});
|
||||
|
||||
$("#refresh-ip-address-list").click(function () {
|
||||
user_ip_table.draw();
|
||||
});
|
||||
|
||||
$("#refresh-login-list").click(function () {
|
||||
login_log_table.draw();
|
||||
});
|
||||
|
||||
$(".inactive-user-tooltip").tooltip();
|
||||
function recentlyWatched() {
|
||||
// Populate recently watched
|
||||
$.ajax({
|
||||
url: 'get_user_recently_watched',
|
||||
async: true,
|
||||
data: {
|
||||
user_id: user_id,
|
||||
limit: 50
|
||||
},
|
||||
complete: function(xhr, status) {
|
||||
$("#user-recently-watched").html(xhr.responseText);
|
||||
highlightWatchedScrollerButton();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
recentlyWatched();
|
||||
|
||||
function highlightWatchedScrollerButton() {
|
||||
var scroller = $("#recently-watched-row-scroller");
|
||||
var numElems = scroller.find("li").length;
|
||||
scroller.width(numElems * 175);
|
||||
if (scroller.width() > $("#user-recently-watched").width()) {
|
||||
$("#recently-watched-page-right").removeClass("disabled");
|
||||
} else {
|
||||
$("#recently-watched-page-right").addClass("disabled");
|
||||
}
|
||||
}
|
||||
|
||||
$(window).resize(function() {
|
||||
highlightWatchedScrollerButton();
|
||||
});
|
||||
|
||||
var leftTotal = 0;
|
||||
$(".paginate").click(function (e) {
|
||||
e.preventDefault();
|
||||
var scroller = $("#recently-watched-row-scroller");
|
||||
var containerWidth = $("#user-recently-watched").width();
|
||||
var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175;
|
||||
var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0);
|
||||
|
||||
leftTotal = Math.max(Math.min(leftTotal + scrollAmount, 0), leftMax);
|
||||
scroller.animate({ left: leftTotal }, 250);
|
||||
|
||||
if (leftTotal == 0) {
|
||||
$("#recently-watched-page-left").addClass("disabled").blur();
|
||||
} else {
|
||||
$("#recently-watched-page-left").removeClass("disabled");
|
||||
}
|
||||
|
||||
if (leftTotal == leftMax) {
|
||||
$("#recently-watched-page-right").addClass("disabled").blur();
|
||||
} else {
|
||||
$("#recently-watched-page-right").removeClass("disabled");
|
||||
}
|
||||
});
|
||||
|
||||
$(document).ready(function () {
|
||||
// Javascript to enable link to tab
|
||||
var hash = document.location.hash;
|
||||
var prefix = "tab_";
|
||||
if (hash) {
|
||||
$('.nav-list #nav-' + hash.replace('#' + prefix, "")).tab('show').trigger('show.bs.tab');
|
||||
}
|
||||
|
||||
// Change hash for page-reload
|
||||
$('.nav-list a').on('shown.bs.tab', function (e) {
|
||||
window.location.hash = e.target.hash.replace("#", "#" + prefix);
|
||||
});
|
||||
|
||||
// Populate watch time stats
|
||||
$.ajax({
|
||||
url: 'user_watch_time_stats',
|
||||
async: true,
|
||||
data: { user_id: user_id, user: username },
|
||||
complete: function(xhr, status) {
|
||||
$("#user-time-stats").html(xhr.responseText);
|
||||
}
|
||||
});
|
||||
|
||||
// Populate platform stats
|
||||
$.ajax({
|
||||
url: 'user_player_stats',
|
||||
async: true,
|
||||
data: { user_id: user_id, user: username },
|
||||
complete: function(xhr, status) {
|
||||
$("#user-player-stats").html(xhr.responseText);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
% if _session['user_group'] == 'admin':
|
||||
<script>
|
||||
function loadExportTable() {
|
||||
// Build export table
|
||||
export_table_options.ajax = {
|
||||
url: 'get_export_list',
|
||||
type: 'POST',
|
||||
data: function ( d ) {
|
||||
return {
|
||||
json_data: JSON.stringify( d ),
|
||||
user_id: user_id
|
||||
};
|
||||
}
|
||||
};
|
||||
export_table = $('#export_table-SID-${data["user_id"]}').DataTable(export_table_options);
|
||||
export_table.columns([2, 7]).visible(false);
|
||||
|
||||
var colvis = new $.fn.dataTable.ColVis(export_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark' });
|
||||
$(colvis.button()).appendTo('#button-bar-export');
|
||||
|
||||
clearSearchButton('export_table-SID-${data["user_id"]}', export_table);
|
||||
}
|
||||
|
||||
$('#nav-tabs-export').on('shown.bs.tab', function() {
|
||||
if (typeof(export_table) === 'undefined') {
|
||||
loadExportTable();
|
||||
}
|
||||
});
|
||||
|
||||
$("#refresh-export-table").click(function () {
|
||||
export_table.draw();
|
||||
});
|
||||
|
||||
% if _session['user_group'] == 'admin':
|
||||
$("#edit-user-tooltip").tooltip();
|
||||
|
||||
// Load edit user modal
|
||||
@@ -565,6 +826,23 @@ DOCUMENTATION :: END
|
||||
});
|
||||
});
|
||||
|
||||
$(".export-button").click(function() {
|
||||
$.ajax({
|
||||
url: 'export_metadata_modal',
|
||||
data: {
|
||||
user_id: $(this).data('user_id'),
|
||||
media_type: $(this).data('media_type'),
|
||||
sub_media_type: $(this).data('sub_media_type'),
|
||||
export_type: $(this).data('export_type')
|
||||
},
|
||||
cache: false,
|
||||
async: true,
|
||||
complete: function(xhr, status) {
|
||||
$("#export-modal").html(xhr.responseText);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#row-edit-mode').on('click', function() {
|
||||
$('#row-edit-mode-alert').fadeIn(200);
|
||||
|
||||
@@ -643,100 +921,7 @@ DOCUMENTATION :: END
|
||||
});
|
||||
}
|
||||
});
|
||||
% endif
|
||||
|
||||
function recentlyWatched() {
|
||||
// Populate recently watched
|
||||
$.ajax({
|
||||
url: 'get_user_recently_watched',
|
||||
async: true,
|
||||
data: {
|
||||
user_id: user_id,
|
||||
limit: 50
|
||||
},
|
||||
complete: function(xhr, status) {
|
||||
$("#user-recently-watched").html(xhr.responseText);
|
||||
highlightWatchedScrollerButton();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
recentlyWatched();
|
||||
|
||||
function highlightWatchedScrollerButton() {
|
||||
var scroller = $("#recently-watched-row-scroller");
|
||||
var numElems = scroller.find("li").length;
|
||||
scroller.width(numElems * 175);
|
||||
if (scroller.width() > $("#user-recently-watched").width()) {
|
||||
$("#recently-watched-page-right").removeClass("disabled");
|
||||
} else {
|
||||
$("#recently-watched-page-right").addClass("disabled");
|
||||
}
|
||||
}
|
||||
|
||||
$(window).resize(function() {
|
||||
highlightWatchedScrollerButton();
|
||||
});
|
||||
|
||||
var leftTotal = 0;
|
||||
$(".paginate").click(function (e) {
|
||||
e.preventDefault();
|
||||
var scroller = $("#recently-watched-row-scroller");
|
||||
var containerWidth = $("#user-recently-watched").width();
|
||||
var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175;
|
||||
var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0);
|
||||
|
||||
leftTotal = Math.max(Math.min(leftTotal + scrollAmount, 0), leftMax);
|
||||
scroller.animate({ left: leftTotal }, 250);
|
||||
|
||||
if (leftTotal == 0) {
|
||||
$("#recently-watched-page-left").addClass("disabled").blur();
|
||||
} else {
|
||||
$("#recently-watched-page-left").removeClass("disabled");
|
||||
}
|
||||
|
||||
if (leftTotal == leftMax) {
|
||||
$("#recently-watched-page-right").addClass("disabled").blur();
|
||||
} else {
|
||||
$("#recently-watched-page-right").removeClass("disabled");
|
||||
}
|
||||
});
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
// Javascript to enable link to tab
|
||||
var hash = document.location.hash;
|
||||
var prefix = "tab_";
|
||||
if (hash) {
|
||||
$('.user-info-nav a[href='+hash.replace(prefix,"")+']').tab('show').trigger('show.bs.tab');
|
||||
}
|
||||
|
||||
// Change hash for page-reload
|
||||
$('.user-info-nav a').on('shown.bs.tab', function (e) {
|
||||
window.location.hash = e.target.hash.replace("#", "#" + prefix);
|
||||
});
|
||||
|
||||
// Populate watch time stats
|
||||
$.ajax({
|
||||
url: 'user_watch_time_stats',
|
||||
async: true,
|
||||
data: { user_id: user_id, user: username },
|
||||
complete: function(xhr, status) {
|
||||
$("#user-time-stats").html(xhr.responseText);
|
||||
}
|
||||
});
|
||||
|
||||
// Populate platform stats
|
||||
$.ajax({
|
||||
url: 'user_player_stats',
|
||||
async: true,
|
||||
data: { user_id: user_id, user: username },
|
||||
complete: function(xhr, status) {
|
||||
$("#user-player-stats").html(xhr.responseText);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
% endif
|
||||
% endif
|
||||
</%def>
|
@@ -27,7 +27,7 @@ DOCUMENTATION :: END
|
||||
<div id="user-player-image-${a['result_id']}">
|
||||
<div class="user-player-instance-box svg-icon platform-${a['platform_name']}"></div>
|
||||
</div>
|
||||
<div class="user-player-instance-name">
|
||||
<div class="user-player-instance-name" title="${a['player_name']}">
|
||||
${a['player_name']}
|
||||
</div>
|
||||
<div class="user-player-instance-playcount">
|
||||
|
@@ -34,7 +34,7 @@
|
||||
<th align="left" id="edit_row">Edit</th>
|
||||
<th align="right" id="avatar"></th>
|
||||
<th align="left" id="friendly_name">User</th>
|
||||
<th align="left" id="last_seen">Last Seen</th>
|
||||
<th align="left" id="last_seen">Last Streamed</th>
|
||||
<th align="left" id="last_known_ip">Last Known IP</th>
|
||||
<th align="left" id="last_platform">Last Platform</th>
|
||||
<th align="left" id="last_player">Last Player</th>
|
||||
@@ -82,7 +82,6 @@
|
||||
<script src="${http_root}js/dataTables.colVis.js"></script>
|
||||
<script src="${http_root}js/dataTables.bootstrap.min.js"></script>
|
||||
<script src="${http_root}js/dataTables.bootstrap.pagination.js"></script>
|
||||
<script src="${http_root}js/moment-with-locale.js"></script>
|
||||
<script src="${http_root}js/tables/users.js${cache_param}"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
|
@@ -12,7 +12,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<link href="${http_root}css/bootstrap3/bootstrap.css" rel="stylesheet">
|
||||
<link href="${http_root}css/bootstrap3/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="${http_root}css/bootstrap-wizard.css" rel="stylesheet">
|
||||
<link href="${http_root}css/tautulli.css${cache_param}" rel="stylesheet">
|
||||
<link href="${http_root}css/selectize.bootstrap3.css" rel="stylesheet">
|
||||
@@ -21,21 +21,21 @@
|
||||
<link href="${http_root}css/font-awesome.v4-shims.min.css" rel="stylesheet">
|
||||
|
||||
<!-- Favicons -->
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="${http_root}images/favicon/favicon-32x32.png?v=2.0.5">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="${http_root}images/favicon/favicon-16x16.png?v=2.0.5">
|
||||
<link rel="shortcut icon" href="${http_root}images/favicon/favicon.ico?v=2.0.5">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="${http_root}images/favicon/favicon-32x32.png?v=2.6.0">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="${http_root}images/favicon/favicon-16x16.png?v=2.6.0">
|
||||
<link rel="shortcut icon" href="${http_root}images/favicon/favicon.ico?v=2.6.0">
|
||||
|
||||
<!-- ICONS -->
|
||||
<!-- Android -->
|
||||
<link rel="manifest" href="${http_root}images/favicon/manifest.json?v=2.0.5" crossorigin="use-credentials">
|
||||
<link rel="manifest" href="${http_root}images/favicon/manifest.json?v=2.6.0" crossorigin="use-credentials">
|
||||
<meta name="theme-color" content="#282a2d">
|
||||
<!-- Apple -->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="${http_root}images/favicon/apple-touch-icon.png?v=2.0.5">
|
||||
<link rel="mask-icon" href="${http_root}images/favicon/safari-pinned-tab.svg?v=2.0.5" color="#282a2d">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="${http_root}images/favicon/apple-touch-icon.png?v=2.6.0">
|
||||
<link rel="mask-icon" href="${http_root}images/favicon/safari-pinned-tab.svg?v=2.6.0" color="#282a2d">
|
||||
<meta name="apple-mobile-web-app-title" content="Tautulli">
|
||||
<!-- Microsoft -->
|
||||
<meta name="application-name" content="Tautulli">
|
||||
<meta name="msapplication-config" content="${http_root}images/favicon/browserconfig.xml?v=2.0.5">
|
||||
<meta name="msapplication-config" content="${http_root}images/favicon/browserconfig.xml?v=2.6.0">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -203,7 +203,7 @@
|
||||
<h3>Database Import</h3>
|
||||
<div class="wizard-input-section">
|
||||
<p class="help-block">
|
||||
If you have an existing PlexWatch/Plexivity database, you can import the data into Tautulli.
|
||||
If you have an existing Tautulli, PlexWatch, or Plexivity database, you can import the data into Tautulli.
|
||||
</p>
|
||||
<p class="help-block">
|
||||
To import a database, navigate to the <strong>Settings</strong> page
|
||||
@@ -216,7 +216,8 @@
|
||||
<input type="checkbox" name="first_run" id="first_run" value="1" checked>
|
||||
<input type="checkbox" name="group_history_tables" id="group_history_tables" value="1" checked>
|
||||
<input type="checkbox" name="history_table_activity" id="history_table_activity" value="1" checked>
|
||||
<input type="checkbox" name="win_sys_tray" id="win_sys_tray" value="1" checked>
|
||||
<input type="checkbox" name="sys_tray_icon" id="sys_tray_icon" value="1" checked>
|
||||
<input type="checkbox" name="launch_startup" id="launch_startup" value="1" checked>
|
||||
<input type="checkbox" name="launch_browser" id="launch_browser" value="1" checked>
|
||||
<input type="checkbox" name="api_enabled" id="api_enabled" value="1" checked>
|
||||
<input type="checkbox" name="refresh_users_on_startup" id="refresh_users_on_startup" value="1" checked>
|
||||
@@ -247,7 +248,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="${http_root}js/jquery-2.1.4.min.js"></script>
|
||||
<script src="${http_root}js/jquery-3.5.1.min.js"></script>
|
||||
<script src="${http_root}js/bootstrap.min.js"></script>
|
||||
<script src="${http_root}js/selectize.min.js"></script>
|
||||
<script src="${http_root}js/platform.min.js"></script>
|
||||
@@ -494,7 +495,7 @@ $(document).ready(function() {
|
||||
var pms_ssl = $("#pms_ssl").val();
|
||||
var pms_is_remote = $("#pms_is_remote").val();
|
||||
if ((pms_ip !== '') || (pms_port !== '')) {
|
||||
$("#pms-verify-status").html('<i class="fa fa-refresh fa-spin"></i> Validating server...');
|
||||
$("#pms-verify-status").html('<i class="fa fa-refresh fa-spin"></i> Verifying server...');
|
||||
$('#pms-verify-status').fadeIn('fast');
|
||||
$.ajax({
|
||||
url: 'get_server_id',
|
||||
@@ -509,7 +510,7 @@ $(document).ready(function() {
|
||||
async: true,
|
||||
timeout: 5000,
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
$("#pms-verify-status").html('<i class="fa fa-exclamation-circle"></i> This is not a Plex Server!');
|
||||
$("#pms-verify-status").html('<i class="fa fa-exclamation-circle"></i> Error verifying server: ' + textStatus);
|
||||
$('#pms-verify-status').fadeIn('fast');
|
||||
},
|
||||
success: function(xhr, status) {
|
||||
|
@@ -521,7 +521,7 @@
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
.apple-link a {
|
||||
a[x-apple-data-detectors] {
|
||||
color: inherit !important;
|
||||
font-family: inherit !important;
|
||||
font-size: inherit !important;
|
||||
@@ -546,7 +546,7 @@
|
||||
<span class="preheader" style="color: transparent;display: none;height: 0;max-height: 0;max-width: 0;opacity: 0;overflow: hidden;mso-hide: all;visibility: hidden;width: 0;">Tautulli Newsletter - ${subject}</span>
|
||||
% if base_url and not preview:
|
||||
<div class="view-full" style="clear: both;color: #282A2D;font-size: 12px;margin-bottom: 10px;text-align: center;width: 100%;"> <!-- IGNORE SAVE -->
|
||||
<a href="${base_url + uuid}" title="View full newsletter" target="_blank" style="text-decoration: underline;color: #282A2D;">Click here to view the full newsletter.</a> <!-- IGNORE SAVE -->
|
||||
<a href="${base_url + uuid}" title="View full newsletter" target="_blank" rel="noreferrer" style="text-decoration: underline;color: #282A2D;">Click here to view the full newsletter.</a> <!-- IGNORE SAVE -->
|
||||
</div> <!-- IGNORE SAVE -->
|
||||
% endif
|
||||
<table border="0" cellpadding="3" cellspacing="0" class="main" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;background: #282A2D;border-radius: 3px;color: #ffffff;">
|
||||
@@ -599,7 +599,7 @@
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-poster" style="background-image: url(${(base_url_image + movie['thumb_hash']) if base_url_image else movie['thumb_url']});border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;background-color: #3F4245;background-position: center;background-size: cover;background-repeat: no-repeat;background-clip: padding-box;border: 1px solid rgba(255,255,255,.1);">
|
||||
<tr>
|
||||
<td style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;">
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${movie['rating_key']}" title="${movie['title']}" target="_blank" style="text-decoration: underline;">
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${movie['rating_key']}" title="${movie['title']}" target="_blank" rel="noreferrer" style="text-decoration: underline;">
|
||||
<img class="card-poster-overlay" src="${base_url_image + 'images/newsletter/view-on-plex-poster.png' if base_url_image else 'https://tautulli.com/images/newsletter/view-on-plex-poster.png'}" width="150" height="225" style="border: none;-ms-interpolation-mode: bicubic;max-width: 100%;display: block;">
|
||||
</a>
|
||||
</td>
|
||||
@@ -610,7 +610,7 @@
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-info-container-table" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;height: 100%;">
|
||||
<tr>
|
||||
<td class="card-info-title nowrap" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.9rem;vertical-align: top;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;border-bottom: 1px solid rgba(255, 255, 255, .1);line-height: 1.2rem;padding: 5px;max-width: 320px;">
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${movie['rating_key']}" title="${movie['title']}" target="_blank" style="text-decoration: none;color: #ffffff;">${movie['title']}</a>
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${movie['rating_key']}" title="${movie['title']}" target="_blank" rel="noreferrer" style="text-decoration: none;color: #ffffff;">${movie['title']}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -728,7 +728,7 @@
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-poster" style="background-image: url(${(base_url_image + show['thumb_hash']) if base_url_image else show['thumb_url']});border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;background-color: #3F4245;background-position: center;background-size: cover;background-repeat: no-repeat;background-clip: padding-box;border: 1px solid rgba(255,255,255,.1);">
|
||||
<tr>
|
||||
<td style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;">
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${link_rating_key}" title="${show['title']}" target="_blank" style="text-decoration: underline;">
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${link_rating_key}" title="${show['title']}" target="_blank" rel="noreferrer" style="text-decoration: underline;">
|
||||
<img class="card-poster-overlay" src="${base_url_image + 'images/newsletter/view-on-plex-poster.png' if base_url_image else 'https://tautulli.com/images/newsletter/view-on-plex-poster.png'}" width="150" height="225" style="border: none;-ms-interpolation-mode: bicubic;max-width: 100%;display: block;">
|
||||
</a>
|
||||
</td>
|
||||
@@ -739,7 +739,7 @@
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-info-container-table" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;height: 100%;">
|
||||
<tr>
|
||||
<td class="card-info-title nowrap" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.9rem;vertical-align: top;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;border-bottom: 1px solid rgba(255, 255, 255, .1);line-height: 1.2rem;padding: 5px;max-width: 320px;">
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${link_rating_key}" title="${show['title']}" target="_blank" style="text-decoration: none;color: #ffffff;">${show['title']}</a>
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${link_rating_key}" title="${show['title']}" target="_blank" rel="noreferrer" style="text-decoration: none;color: #ffffff;">${show['title']}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -872,7 +872,7 @@
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-poster" style="background-image: url(${(base_url_image + album['thumb_hash']) if base_url_image else album['thumb_url']});border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;background-color: #3F4245;background-position: center;background-size: cover;background-repeat: no-repeat;background-clip: padding-box;border: 1px solid rgba(255,255,255,.1);">
|
||||
<tr>
|
||||
<td style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;">
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${album['rating_key']}" title="${album['title']}" target="_blank" style="text-decoration: underline;">
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${album['rating_key']}" title="${album['title']}" target="_blank" rel="noreferrer" style="text-decoration: underline;">
|
||||
<img class="card-poster-overlay" src="${base_url_image + 'images/newsletter/view-on-plex-cover.png' if base_url_image else 'https://tautulli.com/images/newsletter/view-on-plex-cover.png'}" width="150" height="150" style="border: none;-ms-interpolation-mode: bicubic;max-width: 100%;display: block;">
|
||||
</a>
|
||||
</td>
|
||||
@@ -883,7 +883,7 @@
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-info-container-table" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;height: 100%;">
|
||||
<tr>
|
||||
<td class="card-info-title nowrap" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.9rem;vertical-align: top;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;border-bottom: 1px solid rgba(255, 255, 255, .1);line-height: 1.2rem;padding: 5px;max-width: 320px;">
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${album['rating_key']}" title="${album['title']}" target="_blank" style="text-decoration: none;color: #ffffff;">${album['title']}</a>
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${album['rating_key']}" title="${album['title']}" target="_blank" rel="noreferrer" style="text-decoration: none;color: #ffffff;">${album['title']}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -986,7 +986,7 @@
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-poster" style="background-image: url(${(base_url_image + video['thumb_hash']) if base_url_image else video['thumb_url']});border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;background-color: #3F4245;background-position: center;background-size: cover;background-repeat: no-repeat;background-clip: padding-box;border: 1px solid rgba(255,255,255,.1);">
|
||||
<tr>
|
||||
<td style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;">
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${video['rating_key']}" title="${video['title']}" target="_blank" style="text-decoration: underline;">
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${video['rating_key']}" title="${video['title']}" target="_blank" rel="noreferrer" style="text-decoration: underline;">
|
||||
<img class="card-poster-overlay" src="${base_url_image + 'images/newsletter/view-on-plex-poster.png' if base_url_image else 'https://tautulli.com/images/newsletter/view-on-plex-poster.png'}" width="150" height="225" style="border: none;-ms-interpolation-mode: bicubic;max-width: 100%;display: block;">
|
||||
</a>
|
||||
</td>
|
||||
@@ -997,7 +997,7 @@
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="card-info-container-table" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;height: 100%;">
|
||||
<tr>
|
||||
<td class="card-info-title nowrap" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.9rem;vertical-align: top;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;border-bottom: 1px solid rgba(255, 255, 255, .1);line-height: 1.2rem;padding: 5px;max-width: 320px;">
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${video['rating_key']}" title="${video['title']}" target="_blank" style="text-decoration: none;color: #ffffff;">${video['title']}</a>
|
||||
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${video['rating_key']}" title="${video['title']}" target="_blank" rel="noreferrer" style="text-decoration: none;color: #ffffff;">${video['title']}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -1087,4 +1087,4 @@
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
% endif
|
||||
% endif
|
||||
|