Compare commits
592 Commits
v2.0.1-bet
...
v2.1.9
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8e4aba7ed4 | ||
![]() |
8c0ef75d4c | ||
![]() |
76c4b3bb71 | ||
![]() |
112b1c7984 | ||
![]() |
c22a2513e3 | ||
![]() |
f336782fc1 | ||
![]() |
c19afa06de | ||
![]() |
e003850d31 | ||
![]() |
23cf790079 | ||
![]() |
e7f930bd0f | ||
![]() |
348707b6b9 | ||
![]() |
7ad78b4536 | ||
![]() |
a408a62234 | ||
![]() |
a1e9e7e87f | ||
![]() |
fa99f6e684 | ||
![]() |
11e9bd2d54 | ||
![]() |
50165af4b7 | ||
![]() |
5dd22c23f2 | ||
![]() |
79b45c1c46 | ||
![]() |
af917c4915 | ||
![]() |
c3238b5a83 | ||
![]() |
908dbc3243 | ||
![]() |
14b6df8c25 | ||
![]() |
d3e53cb97f | ||
![]() |
445eea5c1e | ||
![]() |
c5918d7d6c | ||
![]() |
b8e025193e | ||
![]() |
85772cdd83 | ||
![]() |
7f2bab3082 | ||
![]() |
8185cc1c40 | ||
![]() |
63bfe96124 | ||
![]() |
88b640f5e2 | ||
![]() |
26b2342956 | ||
![]() |
883280be09 | ||
![]() |
7c76b0678a | ||
![]() |
e91ba46265 | ||
![]() |
62104c95e3 | ||
![]() |
178bd89e7c | ||
![]() |
365260401c | ||
![]() |
04029bd4d3 | ||
![]() |
9cf1128712 | ||
![]() |
2eebce9f6c | ||
![]() |
b08e071b81 | ||
![]() |
7778d84728 | ||
![]() |
8e3fe7bfa2 | ||
![]() |
6f22c823be | ||
![]() |
34d7c67813 | ||
![]() |
862ed5ce9f | ||
![]() |
84406e6797 | ||
![]() |
19cf567366 | ||
![]() |
8af697a157 | ||
![]() |
76122bea5d | ||
![]() |
1a12422908 | ||
![]() |
2df9f0b48b | ||
![]() |
8540b80e57 | ||
![]() |
8ad565a444 | ||
![]() |
f91b6481b3 | ||
![]() |
826db082c9 | ||
![]() |
f3d64a7886 | ||
![]() |
031d078bc2 | ||
![]() |
04fcd78102 | ||
![]() |
53d1e0f541 | ||
![]() |
9719f0b25b | ||
![]() |
6d1d5bc822 | ||
![]() |
0d7bbe044d | ||
![]() |
b1dc5816a4 | ||
![]() |
476011a783 | ||
![]() |
e038c57c4c | ||
![]() |
a989a53750 | ||
![]() |
d8cfdea704 | ||
![]() |
ed4722c4ce | ||
![]() |
17ab5f05ed | ||
![]() |
71ab2248d7 | ||
![]() |
4fb4410552 | ||
![]() |
a915d2333f | ||
![]() |
aaf5a18251 | ||
![]() |
b90026801b | ||
![]() |
e9676e3651 | ||
![]() |
c16d3288d8 | ||
![]() |
0d7ade8ca4 | ||
![]() |
87b1118e98 | ||
![]() |
9f6422cc8d | ||
![]() |
df1a42a4ee | ||
![]() |
6554136a8f | ||
![]() |
81e04269fd | ||
![]() |
b6c6590a12 | ||
![]() |
136260a822 | ||
![]() |
5710bcb43c | ||
![]() |
30bc3f8a66 | ||
![]() |
e0e7d68df2 | ||
![]() |
cf73639281 | ||
![]() |
008e04d5cf | ||
![]() |
5f7991665c | ||
![]() |
5e000162c6 | ||
![]() |
ea1aba2c87 | ||
![]() |
f321bb869c | ||
![]() |
abe496668a | ||
![]() |
9cefc7f701 | ||
![]() |
1d3cd431eb | ||
![]() |
8f8318da6d | ||
![]() |
36ce751875 | ||
![]() |
858ea33680 | ||
![]() |
eee759d0d0 | ||
![]() |
dbe3b492fd | ||
![]() |
4e4fde2e9a | ||
![]() |
5283126608 | ||
![]() |
df72ecebf5 | ||
![]() |
d316aa34e2 | ||
![]() |
405aec8bb8 | ||
![]() |
4a62f8c395 | ||
![]() |
eabea2deeb | ||
![]() |
3742021dcc | ||
![]() |
9c4219b42e | ||
![]() |
f624908302 | ||
![]() |
ab9132cdd4 | ||
![]() |
0186363753 | ||
![]() |
653ad36f17 | ||
![]() |
5073f82d53 | ||
![]() |
833937eced | ||
![]() |
32df79bb83 | ||
![]() |
fabced9942 | ||
![]() |
8aa34321c9 | ||
![]() |
b144ded87b | ||
![]() |
ef8c91ee56 | ||
![]() |
d76ded3ebe | ||
![]() |
c4fc94ea34 | ||
![]() |
ad61e23d92 | ||
![]() |
fcd7593764 | ||
![]() |
8465df5095 | ||
![]() |
95697a3367 | ||
![]() |
978ae7d8cb | ||
![]() |
366e8514b6 | ||
![]() |
45c646c062 | ||
![]() |
4b482938a1 | ||
![]() |
9699129a38 | ||
![]() |
5ef8947532 | ||
![]() |
f335ffa8d5 | ||
![]() |
793665d62a | ||
![]() |
7da5730c73 | ||
![]() |
1f587ed698 | ||
![]() |
1032fdfe7a | ||
![]() |
35e3f7dccc | ||
![]() |
909cbc90df | ||
![]() |
77ed94bbef | ||
![]() |
c260543586 | ||
![]() |
a4de63095f | ||
![]() |
817335b42e | ||
![]() |
818e7723ff | ||
![]() |
a69008e179 | ||
![]() |
91c647f9ae | ||
![]() |
36b80aa6d3 | ||
![]() |
c35fcc727c | ||
![]() |
749e1fcebe | ||
![]() |
80506b8541 | ||
![]() |
80df2b0fad | ||
![]() |
084732706d | ||
![]() |
2aff7713cd | ||
![]() |
683a782723 | ||
![]() |
5108e1bb09 | ||
![]() |
d8298a12eb | ||
![]() |
dec5931fd4 | ||
![]() |
71d79266f6 | ||
![]() |
d3f6812178 | ||
![]() |
042b48c1fd | ||
![]() |
38613f24fe | ||
![]() |
e23b1a0603 | ||
![]() |
90f3d597dc | ||
![]() |
d166b77ea9 | ||
![]() |
feb74b157f | ||
![]() |
4aeafdae2d | ||
![]() |
f12de78370 | ||
![]() |
d2415c92ea | ||
![]() |
646ca1d9fa | ||
![]() |
c8c93c69ab | ||
![]() |
2c8c20af02 | ||
![]() |
a877da3de8 | ||
![]() |
1b7cfd7f8a | ||
![]() |
3f7edc3635 | ||
![]() |
8fac54aa71 | ||
![]() |
244008d539 | ||
![]() |
502b807e45 | ||
![]() |
35914b9a48 | ||
![]() |
24ac34d5e2 | ||
![]() |
e1035a49fd | ||
![]() |
511f4a916b | ||
![]() |
1f10668838 | ||
![]() |
a9a08a959c | ||
![]() |
341f4040ff | ||
![]() |
e9a1b2ea38 | ||
![]() |
7f67213ff7 | ||
![]() |
e9bdbb863c | ||
![]() |
04641c7c63 | ||
![]() |
15cc96a005 | ||
![]() |
b712874ed2 | ||
![]() |
5b1ff402bc | ||
![]() |
eda0e73eb6 | ||
![]() |
a5807f21b4 | ||
![]() |
e3b71a729e | ||
![]() |
f810f50ea9 | ||
![]() |
2b0f83e036 | ||
![]() |
4977b3def1 | ||
![]() |
1cb5f0b635 | ||
![]() |
7e11af1fd0 | ||
![]() |
6f6fb485fe | ||
![]() |
964f24d6ab | ||
![]() |
1474f144fe | ||
![]() |
8d25b0c973 | ||
![]() |
50b37d6b3a | ||
![]() |
b9b82b23f7 | ||
![]() |
b6bd305694 | ||
![]() |
ebb287e1ee | ||
![]() |
bd3497b2bf | ||
![]() |
034f3ee308 | ||
![]() |
a946879fc1 | ||
![]() |
9f964b5a87 | ||
![]() |
2245e38d40 | ||
![]() |
c9618322c2 | ||
![]() |
960e147e10 | ||
![]() |
bbca0b3b42 | ||
![]() |
ed0b41cd19 | ||
![]() |
dc87591992 | ||
![]() |
1f7be7a4d5 | ||
![]() |
d05e80e573 | ||
![]() |
003e890844 | ||
![]() |
afa16cd656 | ||
![]() |
9aff61f670 | ||
![]() |
8b1c7df3ce | ||
![]() |
25355f29ce | ||
![]() |
09ea81ccd2 | ||
![]() |
28efaf73c7 | ||
![]() |
0057481efb | ||
![]() |
827b012978 | ||
![]() |
0e419695cf | ||
![]() |
46f26cc307 | ||
![]() |
46f7a92c97 | ||
![]() |
2a24ea4cdf | ||
![]() |
8e13bf4f93 | ||
![]() |
aa844b76fc | ||
![]() |
0e5bb7b188 | ||
![]() |
49a6cf8809 | ||
![]() |
2adad24684 | ||
![]() |
d4d5ff9de7 | ||
![]() |
33c2315384 | ||
![]() |
4577704f19 | ||
![]() |
a13d93f239 | ||
![]() |
5ac5b3cd29 | ||
![]() |
d104ec216c | ||
![]() |
32645c374e | ||
![]() |
d1f982847b | ||
![]() |
7770431b67 | ||
![]() |
edeb6ae4e4 | ||
![]() |
af3501a6a6 | ||
![]() |
0f39201774 | ||
![]() |
b73d2ff1f7 | ||
![]() |
6009fb24b6 | ||
![]() |
522684b2ab | ||
![]() |
feab16b351 | ||
![]() |
ee041db63d | ||
![]() |
2479533d07 | ||
![]() |
d045fd5834 | ||
![]() |
8407f27fed | ||
![]() |
b505286caf | ||
![]() |
feb762ce8b | ||
![]() |
8acdb5af83 | ||
![]() |
5af1294f71 | ||
![]() |
87d2d273d3 | ||
![]() |
b5c52ac71e | ||
![]() |
efe9a15f72 | ||
![]() |
525f1e4b0b | ||
![]() |
d18820b832 | ||
![]() |
7e024fd736 | ||
![]() |
c9c5989474 | ||
![]() |
ce9f96d3be | ||
![]() |
7362dd0bf4 | ||
![]() |
9905ebc144 | ||
![]() |
8f8010884b | ||
![]() |
37afd141be | ||
![]() |
a3643b4302 | ||
![]() |
02cfd8d9b7 | ||
![]() |
941ce439b4 | ||
![]() |
a08bce2073 | ||
![]() |
4e9c8322c3 | ||
![]() |
89bfe85be3 | ||
![]() |
98d994591c | ||
![]() |
a29bc7f4f9 | ||
![]() |
288f4c5f7f | ||
![]() |
a6bf78ed56 | ||
![]() |
8dbb05931e | ||
![]() |
ac8a712ff0 | ||
![]() |
39406c25c3 | ||
![]() |
48d7c2c54c | ||
![]() |
0217188274 | ||
![]() |
fd762e71de | ||
![]() |
4d5c3b6df0 | ||
![]() |
7df54e4d1b | ||
![]() |
5d085de9d3 | ||
![]() |
a8a4299086 | ||
![]() |
86f0e8425c | ||
![]() |
d2e879be4a | ||
![]() |
544114fffe | ||
![]() |
3b3e207b11 | ||
![]() |
84aad638ac | ||
![]() |
2bb691966e | ||
![]() |
8f5e788270 | ||
![]() |
7c43ea2f46 | ||
![]() |
8146e1e3cf | ||
![]() |
51b1ff6d4a | ||
![]() |
403e8dfbea | ||
![]() |
9d08717c83 | ||
![]() |
66167d5960 | ||
![]() |
624863d826 | ||
![]() |
d4b3810fbc | ||
![]() |
6056e1d3b9 | ||
![]() |
1a293d525f | ||
![]() |
b87eb68bdd | ||
![]() |
8620546d07 | ||
![]() |
a082109045 | ||
![]() |
559a9b393e | ||
![]() |
ae41b22e59 | ||
![]() |
754fd24421 | ||
![]() |
ab34a74210 | ||
![]() |
cfa6de4d91 | ||
![]() |
a5608c7a1e | ||
![]() |
88a7b52e51 | ||
![]() |
e444bad4de | ||
![]() |
5403b0b547 | ||
![]() |
51b5e615f5 | ||
![]() |
700547b63b | ||
![]() |
3f3d1962c7 | ||
![]() |
655a359ef4 | ||
![]() |
90647628c9 | ||
![]() |
681c3ed6e3 | ||
![]() |
7f255943c6 | ||
![]() |
b6e73b5dea | ||
![]() |
eacb7f6ae5 | ||
![]() |
7b300bb87e | ||
![]() |
a81ad27d85 | ||
![]() |
8eed14ff3b | ||
![]() |
82446acdf0 | ||
![]() |
88770b8805 | ||
![]() |
f9f05bbea3 | ||
![]() |
17dd767c22 | ||
![]() |
25b1dc6dd8 | ||
![]() |
b2b1277e37 | ||
![]() |
8e1a588ced | ||
![]() |
9eddfafeae | ||
![]() |
d24a922ccb | ||
![]() |
bbc6482c99 | ||
![]() |
36ff1fb674 | ||
![]() |
f0aa793262 | ||
![]() |
681627a656 | ||
![]() |
87c6ad66fb | ||
![]() |
4ab9eb3bfa | ||
![]() |
2d56ac027b | ||
![]() |
836c4293d6 | ||
![]() |
07092e8aa5 | ||
![]() |
66743c1401 | ||
![]() |
bfe34e060b | ||
![]() |
5ed4236a22 | ||
![]() |
868aeb3902 | ||
![]() |
cbcdac5b04 | ||
![]() |
d473bb3058 | ||
![]() |
066a95d209 | ||
![]() |
c7cc476623 | ||
![]() |
bd44eb7fe4 | ||
![]() |
6ec4f51077 | ||
![]() |
b4a4f60b04 | ||
![]() |
dc4e6edc9a | ||
![]() |
60b362b19e | ||
![]() |
7e81ce8c06 | ||
![]() |
c7f9e2f721 | ||
![]() |
cab8b1c041 | ||
![]() |
16f270691d | ||
![]() |
d94a1efe75 | ||
![]() |
12755970b7 | ||
![]() |
93e4853ea2 | ||
![]() |
5e0c0365fb | ||
![]() |
c2713c53dd | ||
![]() |
90443b4028 | ||
![]() |
e0109ed179 | ||
![]() |
a53afe05a2 | ||
![]() |
a5d2467bfe | ||
![]() |
8447663e27 | ||
![]() |
64d67d8209 | ||
![]() |
78034b82a9 | ||
![]() |
f77bd6c17b | ||
![]() |
2621da7d36 | ||
![]() |
e1dca1509a | ||
![]() |
df016243dd | ||
![]() |
be72693fec | ||
![]() |
33a1ebdb1a | ||
![]() |
030f9d334b | ||
![]() |
dc743ac378 | ||
![]() |
0010cbe21f | ||
![]() |
3a5d5918de | ||
![]() |
3380e39de2 | ||
![]() |
7d31079897 | ||
![]() |
c287b6df77 | ||
![]() |
dab1f8ba20 | ||
![]() |
a26de7f6c2 | ||
![]() |
ab32b2cbc2 | ||
![]() |
503c249fc3 | ||
![]() |
2a03ce757e | ||
![]() |
373a15524f | ||
![]() |
13036183d3 | ||
![]() |
170591c79e | ||
![]() |
a15d225a5f | ||
![]() |
a0106874e2 | ||
![]() |
ab157d1c0e | ||
![]() |
0b95c9fe2e | ||
![]() |
d693514ca9 | ||
![]() |
56987b3aaa | ||
![]() |
3ca1bd5d78 | ||
![]() |
5d2219f2f8 | ||
![]() |
56dc28eed3 | ||
![]() |
3e723d4373 | ||
![]() |
f5e341e655 | ||
![]() |
3c81100957 | ||
![]() |
304378f93b | ||
![]() |
de6b6e8124 | ||
![]() |
d15223fb1a | ||
![]() |
d29a12b6db | ||
![]() |
9100e25a21 | ||
![]() |
7672f1955e | ||
![]() |
5f52171fc4 | ||
![]() |
31ac82ad71 | ||
![]() |
38ca4e37a6 | ||
![]() |
3c55550702 | ||
![]() |
7dff6b121b | ||
![]() |
d77d889695 | ||
![]() |
318a21438f | ||
![]() |
7175b57a28 | ||
![]() |
e1e5a050c2 | ||
![]() |
58996c1115 | ||
![]() |
7301fe5f6e | ||
![]() |
a27c423569 | ||
![]() |
19680d3bc7 | ||
![]() |
ecaca4e5dc | ||
![]() |
191de0b577 | ||
![]() |
ebcc073b32 | ||
![]() |
043b3fd57b | ||
![]() |
dd50502dcb | ||
![]() |
f159a1014d | ||
![]() |
abb801535c | ||
![]() |
2732dbf1b1 | ||
![]() |
095d893005 | ||
![]() |
5d8455d141 | ||
![]() |
aa3450bfcc | ||
![]() |
770f12b632 | ||
![]() |
45c2ccdffe | ||
![]() |
fc14c3165f | ||
![]() |
0fad245148 | ||
![]() |
79609c384e | ||
![]() |
09054ddb4b | ||
![]() |
6f912d4aa2 | ||
![]() |
96033a8214 | ||
![]() |
5ca65f4797 | ||
![]() |
d2fccbde68 | ||
![]() |
e6b48d7baf | ||
![]() |
3e51310511 | ||
![]() |
32b43202c2 | ||
![]() |
446170f8de | ||
![]() |
c5a9ecd4ac | ||
![]() |
2af5f817a3 | ||
![]() |
4e55cf3cd4 | ||
![]() |
eeb0478813 | ||
![]() |
33739f1cb2 | ||
![]() |
515e6a8071 | ||
![]() |
2b22f8eb4f | ||
![]() |
e9725a0081 | ||
![]() |
8fd159d2fe | ||
![]() |
3d7e6c8b2c | ||
![]() |
0c048d61b1 | ||
![]() |
f05b8e5cd1 | ||
![]() |
0b38fec827 | ||
![]() |
547dc9ed33 | ||
![]() |
896a37bea9 | ||
![]() |
3f90037db3 | ||
![]() |
380ca11ced | ||
![]() |
ab3a288e49 | ||
![]() |
638e225f80 | ||
![]() |
5089ede207 | ||
![]() |
a3e6e76158 | ||
![]() |
7c4c7bfc90 | ||
![]() |
644fea6665 | ||
![]() |
a1349ff8a6 | ||
![]() |
71c20002b8 | ||
![]() |
157af84226 | ||
![]() |
9b4536f132 | ||
![]() |
29ab470e42 | ||
![]() |
c67fa480a7 | ||
![]() |
0a1a691c73 | ||
![]() |
48588f23bf | ||
![]() |
cf14fbc3f0 | ||
![]() |
e471d5207d | ||
![]() |
5722a52082 | ||
![]() |
08c32e875e | ||
![]() |
7d3ee3afb3 | ||
![]() |
def8600f5c | ||
![]() |
74a68f3c7d | ||
![]() |
64c9247dd1 | ||
![]() |
1bfcd34247 | ||
![]() |
19864e97e6 | ||
![]() |
ec5c5e1420 | ||
![]() |
803f4e14ca | ||
![]() |
6cc254b80a | ||
![]() |
59593ab1aa | ||
![]() |
65a0a0eb7d | ||
![]() |
f4206b401f | ||
![]() |
99f8d24b3e | ||
![]() |
26b06e453d | ||
![]() |
54ab646048 | ||
![]() |
12c9aa3d6a | ||
![]() |
1ae8544f2d | ||
![]() |
eae9e66c75 | ||
![]() |
ad041a1691 | ||
![]() |
1aee3b6c8f | ||
![]() |
04d4ffb63d | ||
![]() |
80b318b45c | ||
![]() |
19969a8b1f | ||
![]() |
b84888356f | ||
![]() |
c9436195f3 | ||
![]() |
98cfb50571 | ||
![]() |
b67884ea7f | ||
![]() |
c7081b4417 | ||
![]() |
83f1519932 | ||
![]() |
6c6493fe31 | ||
![]() |
1564cdf55e | ||
![]() |
ffcb1144b6 | ||
![]() |
4ad644ab7f | ||
![]() |
f1783df8d6 | ||
![]() |
b1a3dd1a46 | ||
![]() |
52eac2b437 | ||
![]() |
ca20eda606 | ||
![]() |
4e3b95950f | ||
![]() |
9d01335395 | ||
![]() |
63814584e9 | ||
![]() |
bf7e432c0c | ||
![]() |
5a14c229a1 | ||
![]() |
79581fb83e | ||
![]() |
90c587e6e1 | ||
![]() |
b2b728a3cc | ||
![]() |
645ef86c75 | ||
![]() |
450fda18f1 | ||
![]() |
f6162a8df8 | ||
![]() |
a8c5f48b60 | ||
![]() |
edc0b96167 | ||
![]() |
9b4b3e0ecb | ||
![]() |
7bd2c00636 | ||
![]() |
15faccfa2f | ||
![]() |
11e10e7d0e | ||
![]() |
8ef48fc548 | ||
![]() |
29632b0805 | ||
![]() |
e908c45cf2 | ||
![]() |
c37e934f42 | ||
![]() |
41f91956b8 | ||
![]() |
8b2bd5ce79 | ||
![]() |
28e4151157 | ||
![]() |
66d45293e6 | ||
![]() |
243eeeff67 | ||
![]() |
18520e24d1 | ||
![]() |
87743171b7 | ||
![]() |
452f0747c8 | ||
![]() |
a20747044c | ||
![]() |
e60241ca73 | ||
![]() |
c69e68a4a8 | ||
![]() |
561d994328 | ||
![]() |
33acf9402d | ||
![]() |
0a42cb4135 | ||
![]() |
19695c220b | ||
![]() |
1fbc0165e6 | ||
![]() |
4dd05e217a | ||
![]() |
b77a409414 | ||
![]() |
b2157df026 | ||
![]() |
0b8d5954f9 | ||
![]() |
0d60c7b728 | ||
![]() |
de58136314 | ||
![]() |
4e718056b6 | ||
![]() |
480913362e | ||
![]() |
ad2f61132a | ||
![]() |
0e2df4ba14 | ||
![]() |
c342273742 | ||
![]() |
0cb147cd8f | ||
![]() |
11b2a17fb3 | ||
![]() |
23486ff235 | ||
![]() |
a9e24bc0c5 | ||
![]() |
3c22496ea2 | ||
![]() |
b8164ca556 | ||
![]() |
19565b3d0a | ||
![]() |
50ce17ac72 | ||
![]() |
e51a425389 | ||
![]() |
3df7642193 |
3
.gitignore
vendored
@@ -12,9 +12,12 @@
|
||||
*.db*
|
||||
*.db-journal
|
||||
*.ini
|
||||
release.lock
|
||||
version.lock
|
||||
logs/*
|
||||
backups/*
|
||||
cache/*
|
||||
newsletters/*
|
||||
*.mmdb
|
||||
|
||||
# HTTPS Cert/Key #
|
||||
|
458
CHANGELOG.md
@@ -1,5 +1,463 @@
|
||||
# Changelog
|
||||
|
||||
## v2.1.9 (2018-05-21)
|
||||
|
||||
* Notifications:
|
||||
* New: Added "live" to notification parameters.
|
||||
|
||||
|
||||
## v2.1.8-beta (2018-05-19)
|
||||
|
||||
* Newsletters:
|
||||
* New: Added authentication options for self-hosted newsletters.
|
||||
* Change: Check if the Tautulli footer has been removed in custom newsletter templates.
|
||||
* Notifications:
|
||||
* Fix: Cloudinary images not working for Twitter notifications.
|
||||
* API:
|
||||
* Fix: Return proper HTTP status codes for errors.
|
||||
|
||||
|
||||
## v2.1.7-beta (2018-05-13)
|
||||
|
||||
* Newsletters:
|
||||
* New: Option to toggle between inline or internal CSS style templates.
|
||||
* New: Button to delete all uploaded images from Imgur/Cloudinary.
|
||||
* Fix: Long titles overflowing the newsletter cards.
|
||||
* Change: Self-hosted images on newsletters to use the /image endpoint instead of proxying through /newsletter/image.
|
||||
* Change: Strip whitespace from newsletter for smaller file size before sending to email.
|
||||
* API:
|
||||
* New: Added get_stream_data command to API.
|
||||
* New: Added newsletter API commands to documentation.
|
||||
|
||||
|
||||
## v2.1.6-beta (2018-05-09)
|
||||
|
||||
* Newsletters:
|
||||
* Change: Setting to specify static URL ID name instead of using the newsletter ID number.
|
||||
* Change: Reorganize newsletter config options.
|
||||
|
||||
|
||||
## v2.1.5-beta (2018-05-07)
|
||||
|
||||
* Newsletters:
|
||||
* New: Added setting for a custom newsletter template folder.
|
||||
* New: Added option to enable static newsletter URLs to retrieve the last sent scheduled newsletter.
|
||||
* New: Added ability to change the newsletter output directory and filenames.
|
||||
* New: Added option to save the newsletter file without sending it to a notification agent.
|
||||
* Fix: Check for disabled image hosting setting.
|
||||
* Fix: Cache newsletter images when refreshing the page.
|
||||
* Fix: Refresh image from the Plex server when uploading to image hosting.
|
||||
* Change: Allow all image hosting options with self-hosted newsletters.
|
||||
* UI:
|
||||
* Change: Don't retrieve recently added on the homepage if the Plex Cloud server is sleeping.
|
||||
* Other:
|
||||
* Fix: Imgur database upgrade migration.
|
||||
|
||||
|
||||
## v2.1.4 (2018-05-05)
|
||||
|
||||
* Newsletters:
|
||||
* Fix: Newsletter URL without an HTTP root.
|
||||
|
||||
|
||||
## v2.1.3-beta (2018-05-04)
|
||||
|
||||
* Newsletters:
|
||||
* Fix: HTTP root doubled in newsletter URL.
|
||||
* Fix: Configuration would not open with failed hostname resolution.
|
||||
* Fix: Schedule one day off when using weekday names in cron.
|
||||
* Fix: Images not refreshing when changed in Plex.
|
||||
* Fix: Cloudinary upload with non-ASCII image titles.
|
||||
* Other:
|
||||
* Fix: Potential XSS vulnerability in search.
|
||||
|
||||
|
||||
## v2.1.2-beta (2018-05-01)
|
||||
|
||||
* Newsletters:
|
||||
* New: Added Cloudinary option for image hosting.
|
||||
* Notifications:
|
||||
* New: Added Message-ID to Email header (Thanks @Dam64)
|
||||
* Fix: Posters not showing up on Twitter with self-hosted images.
|
||||
* Fix: Incorrect action parameter for new device notifications.
|
||||
* Change: Hardcode Pushover sound list instead of fetching the list every time.
|
||||
* API:
|
||||
* Fix: Success result for empty response data.
|
||||
* Change: Do not send notification when checking for Tautulli updates via the API.
|
||||
|
||||
|
||||
## v2.1.1-beta (2018-04-11)
|
||||
|
||||
* Monitoring:
|
||||
* Fix: Live TV transcoding showing incorrectly as direct play.
|
||||
* Newsletters:
|
||||
* New: Added week number as parameter. (Thanks @samip5)
|
||||
* Fix: Fallback to cover art on the newsletter cards.
|
||||
* Change: Option to set newsletter time frame by calendar days or hours.
|
||||
* Notifications:
|
||||
* New: Added week number as parameter. (Thanks @samip5)
|
||||
* Other:
|
||||
* New: Added plexapi library for custom scripts.
|
||||
|
||||
|
||||
## v2.1.0-beta (2018-04-07)
|
||||
|
||||
* Newsletters:
|
||||
* New: A completely new scheduled newsletter system.
|
||||
* Beautiful HTML formatted newsletter for recently added movies, TV shows, or music.
|
||||
* Send newsletters on a daily, weekly, or monthly schedule to your users.
|
||||
* Customize the number of days of recently added content and the libraries to include on the newsletter.
|
||||
* Add a custom message to be included on the newsletter.
|
||||
* Option to either send an HTML formatted email, or a link to a self-hosted newsletter on your own domain to any notification agent.
|
||||
* Notifications:
|
||||
* New: Ability to use self-hosted images on your own domain instead of using Imgur.
|
||||
|
||||
|
||||
## v2.0.28 (2018-04-02)
|
||||
|
||||
* Monitoring:
|
||||
* Fix: Homepage activity header text.
|
||||
|
||||
|
||||
## v2.0.27 (2018-04-02)
|
||||
|
||||
* Monitoring:
|
||||
* Change: Move activity refresh interval setting to the settings page.
|
||||
|
||||
|
||||
## v2.0.26-beta (2018-03-30)
|
||||
|
||||
* Monitoring:
|
||||
* New: Setting to change the refresh interval on the homepage.
|
||||
* Fix: Identify extras correctly on the activity cards.
|
||||
* Notifications:
|
||||
* Change: Send Telegram image and text separately if the caption is longer than 200 characters.
|
||||
* UI:
|
||||
* Fix: Error when clicking on synced playlist links.
|
||||
|
||||
|
||||
## v2.0.25 (2018-03-22)
|
||||
|
||||
* Monitoring:
|
||||
* Fix: Websocket not reconnecting causing activity monitoring and notifications to not work.
|
||||
* Fix: Error checking for synced streams without Plex Pass.
|
||||
|
||||
|
||||
## v2.0.24 (2018-03-18)
|
||||
|
||||
* Monitoring:
|
||||
* Fix: Fix stream data not showing for history recorded before v2.
|
||||
* Notifications:
|
||||
* Fix: Set all environment variables for scripts.
|
||||
* Change: Moved all notification agent instructions to the wiki.
|
||||
* Change: XBMC notification agent renamed to Kodi.
|
||||
* Change: OSX Notify notification agent renamed to macOS Notification Center.
|
||||
|
||||
|
||||
## v2.0.23-beta (2018-03-16)
|
||||
|
||||
* Monitoring:
|
||||
* Fix: Certain transcode stream showing incorrectly as direct play in history. Fix is not retroactive.
|
||||
* Notifications:
|
||||
* New: Added season/episode/album/track count to notification parameters.
|
||||
* New: Added "Value 3" setting for IFTTT notifications.
|
||||
* New: Set PLEX_URL, PLEX_TOKEN, TAUTULLI_URL, and TAUTULLI_APIKEY environment variables for scripts.
|
||||
* Fix: Notifications failing to send with invalid custom conditions json.
|
||||
* Fix: Email notifications failing with unicode username/passwords.
|
||||
* Change: Facebook Graph API version updated to v2.12.
|
||||
* UI:
|
||||
* New: Show the Plex Server URL in the settings.
|
||||
* Fix: Incorrect info displayed in the Tautulli login logs.
|
||||
* API:
|
||||
* Fix: API returning empty data if a message was in the original data.
|
||||
* Change: get_server_id command returns json instead of string.
|
||||
* Other:
|
||||
* Fix: Forgot git pull when changing branches in the web UI.
|
||||
|
||||
|
||||
## v2.0.22 (2018-03-10)
|
||||
|
||||
* Tautulli v2 release!
|
||||
|
||||
|
||||
## v2.0.22-beta (2018-03-09)
|
||||
|
||||
* Notifications:
|
||||
* Fix: Pushover notifications failing with priority 2 is set.
|
||||
* Fix: Expanding selectize box for some notification agent settings.
|
||||
* Other:
|
||||
* Fix: Update check failing when an update is available.
|
||||
* Fix: Item count incorrect for photo libraries.
|
||||
|
||||
|
||||
## v2.0.21-beta (2018-03-04)
|
||||
|
||||
* Monitoring:
|
||||
* New: Identify if a stream is using Plex Relay.
|
||||
* Change: Don't ping the Plex server if the websocket is disconnected.
|
||||
* Notifications:
|
||||
* Fix: Pause/resume state not being sent correctly in some instances.
|
||||
* Other:
|
||||
* New: Add Patreon donation method.
|
||||
* Fix: Catch failure to send analytics.
|
||||
* Fix: IP address connection lookup error when the country is missing.
|
||||
* Change: Updated all init scripts to Tautulli.
|
||||
* Change: Move database to tautulli.db.
|
||||
* Change: Move logs to tautulli.log.
|
||||
* Change: Move startup file to Tautulli.py.
|
||||
|
||||
|
||||
## v2.0.20-beta (2018-02-24)
|
||||
|
||||
* Notifications:
|
||||
* New: Add poster support for Pushover notifications.
|
||||
* New: Add poster support for Pushbullet notifications.
|
||||
* Fix: Incorrect Plex/Tautulli update notification parameter types.
|
||||
* Change: Poster and text sent as a single message for Telegram.
|
||||
* Change: Posters uploaded directly to Telegram without Imgur.
|
||||
* UI:
|
||||
* New: Add "Delete" button to synced items table on user pages.
|
||||
* Fix: Button spacing/positioning on mobile site.
|
||||
* Fix: Music statistic cards not using the fallback thumbnail.
|
||||
* Fix: Logo not showing up when using an SVG.
|
||||
* Change: Graphs now respect the "Group History" setting.
|
||||
* API:
|
||||
* New: Add grouping to graph API commands.
|
||||
* Other:
|
||||
* New: Added Google Analytics to collect installation metrics.
|
||||
* Fix: Reconnecting to the Plex server when server settings are not changed.
|
||||
|
||||
|
||||
## v2.0.19-beta (2018-02-16)
|
||||
|
||||
* Monitoring:
|
||||
* Fix: Connect to Plex Cloud server without keeping it awake.
|
||||
* Fix: Reconnect to Plex Cloud server after the server wakes up from sleeping.
|
||||
* Notifications:
|
||||
* Fix: Don't send Plex Server Up/Down notifications when Tautulli starts up.
|
||||
* Change: Better handling of Watched notifications.
|
||||
* UI:
|
||||
* New: Added Plex server selection dropdown in the settings.
|
||||
* Fix: Libraries and Users tables not refreshing properly.
|
||||
* Change: Updated the masked info shown to guests.
|
||||
* Change: Check for updates without refreshing to the homepage.
|
||||
* API:
|
||||
* New: Added update_check to the API.
|
||||
* Fix: delete_media_info_cache not deleting the cache.
|
||||
* Change: Document "refresh" parameter for get_library_media_info.
|
||||
* Other:
|
||||
* Fix: Show the full changelog since v2 on a fresh install.
|
||||
|
||||
|
||||
## v2.0.18-beta (2018-02-12)
|
||||
|
||||
* Notifications:
|
||||
* Fix: Default text for Tautulli update notifications using the wrong parameter.
|
||||
* Fix: Playback pause and resume notifications only triggering once.
|
||||
* Change: Negative operators for custom conditions now use "and" instead of "or".
|
||||
* UI:
|
||||
* New: Added button to delete the 3rd party lookup info from the info pages.
|
||||
* Fix: Missing host info in the login logs when logging in using Firefox.
|
||||
* Change: Cleaned up settings. Advanced settings are now hidden behind a toggle.
|
||||
* API:
|
||||
* New: Updated API documentation for v2.
|
||||
* Other:
|
||||
* Fix: DeprecationWarning when using HTTPS with self-signed certificates.
|
||||
* Change: Deleting the Imgur poster URL also deletes the poster from Imgur (only available for new uploads).
|
||||
* Change: GitHub repository moved to Tautulli/Tautulli. Old GitHub URLs will still work.
|
||||
|
||||
|
||||
## v2.0.17-beta (2018-02-03)
|
||||
|
||||
* Notifications:
|
||||
* Fix: Unable to use @ mentions tags for Discord and Slack.
|
||||
* New: Added Zapier notification agent.
|
||||
* API:
|
||||
* Fix: get_synced_items returning no results.
|
||||
* Fix: get_library_media_info returning incorrect media type for photo albums.
|
||||
* Fix: get_library_media_info not being able to sort by title.
|
||||
|
||||
|
||||
## v2.0.16-beta (2018-01-30)
|
||||
|
||||
* Monitoring:
|
||||
* Fix: Timestamp sometimes showing as "0:60" on the activity cards.
|
||||
* Fix: Incorrect session information being shown for playback of synced content.
|
||||
* Fix: Sessions not being stopped when "Playback Stopped" notifications were enabled.
|
||||
* UI:
|
||||
* Fix: Stream resolution showing up as "unknown" on the graphs.
|
||||
* New: Added user filter to the Synced Items table.
|
||||
* Other:
|
||||
* New: Option to use the Plex server update channel when checking for updates.
|
||||
|
||||
|
||||
## v2.0.15-beta (2018-01-27)
|
||||
|
||||
* Monitoring:
|
||||
* Fix: Live TV sessions not being stopped in History.
|
||||
* Fix: Stream location showing as "unknown" on the activity cards.
|
||||
* New: Improved Live TV details on the activity cards.
|
||||
* Notifications:
|
||||
* New: Added labels and collections to notification parameters.
|
||||
* New: Added more server details to notification parameters.
|
||||
* Change: Renamed "PlexPy" update notification parameters to "Tautulli".
|
||||
|
||||
|
||||
## v2.0.14-beta (2018-01-20)
|
||||
|
||||
* Monitoring:
|
||||
* Change: Added "Cellular" bandwidth to "WAN" in activity header.
|
||||
* Notifications:
|
||||
* Fix: Plex Web URL for tracks now go to the album page.
|
||||
* Fix: Recently added notifications being sent for the entire library when DVR EPG data was refreshed.
|
||||
* Fix: Notifier settings not loading with an apostrophe in the custom condition values.
|
||||
* Fix: Custom email addresses not being saved when closing the notifier settings.
|
||||
* Change: Re-enabled Browser notifications.
|
||||
* Change: Renamed "PlexPy" update notification parameters to "Tautulli".
|
||||
* Change: Emails no longer automatically insert HTML line breaks.
|
||||
* Change: "Date" header added to email notifications.
|
||||
* UI:
|
||||
* Change: Show all changelogs since the previous version when updating.
|
||||
|
||||
|
||||
## v2.0.13-beta (2018-01-13)
|
||||
|
||||
* Notifications:
|
||||
* New: Added dropdown selection for email addresses of shared users.
|
||||
* New: Added more notification options for Join.
|
||||
* Change: Show "OR" between custom condition values.
|
||||
* Other:
|
||||
* New: Use JSON Web Tokens for authentication. Login now works with SSO applications.
|
||||
* New: Allow the Plex server admin to login as a Tautulli admin using their Plex.tv account.
|
||||
|
||||
|
||||
## v2.0.12-beta (2018-01-07)
|
||||
|
||||
* Notifications:
|
||||
* Fix: Incorrect Plex URL parameter value.
|
||||
* Change: Custom condition logic is now optional. An implicit "and" is applied between all conditions if the logic is blank.
|
||||
* UI:
|
||||
* New: Added separate required LAN/WAN bandwidth in the activity header.
|
||||
* API:
|
||||
* Fix: Notify API command not sending notifications.
|
||||
|
||||
|
||||
## v2.0.11-beta (2018-01-05)
|
||||
|
||||
* Notifications:
|
||||
* Fix: Some notification parameters showing up blank.
|
||||
* UI:
|
||||
* Fix: Stream data showing up as "None" for pre-v2 history.
|
||||
* Other:
|
||||
* Fix: Ability to login using the hashed password.
|
||||
|
||||
|
||||
## v2.0.10-beta (2018-01-04)
|
||||
|
||||
* Monitoring:
|
||||
* Fix: HW transcoding indicator on activity cards incorrect after refreshing.
|
||||
* Notifications:
|
||||
* Remove: Notification toggles from library and user settings. Use custom conditions to filter out notifications instead.
|
||||
* UI:
|
||||
* Fix: Incorrect examples for some date format options. Also added a few missing date format options. (Thanks @Tommatheussen)
|
||||
|
||||
|
||||
## v2.0.9-beta (2018-01-03)
|
||||
|
||||
* Notifications:
|
||||
* Fix: Notifications failing due to incorrect season/episode number types.
|
||||
|
||||
|
||||
## v2.0.8-beta (2018-01-03)
|
||||
|
||||
* Monitoring:
|
||||
* Fix: Incorrect HW transcoding indicator on activity cards.
|
||||
* Fix: Long product/player names hidden behind platform icon on activity cards.
|
||||
* Notifications:
|
||||
* Fix: Notifications failing due to some missing notification parameters.
|
||||
|
||||
|
||||
## v2.0.7-beta (2018-01-01)
|
||||
|
||||
* Monitoring:
|
||||
* Fix: Incorrect LAN/WAN location on activity cards.
|
||||
* Fix: Paused time not recording correctly.
|
||||
* Other:
|
||||
* Fix: Failed to retrieve synced items when there are special characters in the title.
|
||||
|
||||
|
||||
## v2.0.6-beta (2017-12-31)
|
||||
|
||||
* Monitoring:
|
||||
* New: Beta Plex Cloud support.
|
||||
* Fix: Update paused time while still paused.
|
||||
* UI:
|
||||
* Fix: Stopped time showing as "n/a" on history table.
|
||||
|
||||
|
||||
## v2.0.5-beta (2017-12-31)
|
||||
|
||||
* Monitoring:
|
||||
* Fix: IPv6 addresses overflowing on the activity cards.
|
||||
* Notifications:
|
||||
* Fix: Error sending Join notifications.
|
||||
* UI:
|
||||
* New: Added total required bandwidth in the activity header.
|
||||
* Other:
|
||||
* Fix: Failing to retrieve releases from GitHub.
|
||||
* Fix: CherryPy SSL connection warning. (Thanks @felixbuenemann)
|
||||
* Fix: Sanitize script output in logs.
|
||||
* Change: Login sessions persists across server restarts.
|
||||
|
||||
|
||||
## v2.0.4-beta (2017-12-29)
|
||||
|
||||
* Monitoring:
|
||||
* Fix: Current activity cards duplicating on the homepage.
|
||||
* Notifications:
|
||||
* Fix: Concurrent stream notifications being sent when there is an incorrect number of streams.
|
||||
* UI:
|
||||
* New: Info pages for collections.
|
||||
* New: Button to test Plex Web URL override.
|
||||
* Fix: Library and User pages return to the correct tab when pressing back.
|
||||
|
||||
|
||||
## v2.0.3-beta (2017-12-25)
|
||||
|
||||
* Monitoring:
|
||||
* Fix: Missing sync ID error causing logging to crash.
|
||||
* Fix: Incorrect optimized version title column name causing logging to crash.
|
||||
* Notifications:
|
||||
* Fix: Report correct beta version for Tautulli update notifications.
|
||||
* UI:
|
||||
* Fix: Missing CSS for stream info modal.
|
||||
|
||||
|
||||
## v2.0.2-beta (2017-12-24)
|
||||
|
||||
* Monitoring:
|
||||
* Fix: Websocket connection fails to start with existing streams when upgrading to v2.
|
||||
* Fix: Long request URI for refreshing current activity on the homepage.
|
||||
* Fix: Missing subtitle database columns.
|
||||
* Fix: Details for synced and optimized versions reporting incorrectly.
|
||||
* Notifications:
|
||||
* Fix: Recently added notifications sending for previously added items. It is now limited to past 24 hours only.
|
||||
* Fix: Source video/audio/subtitle parameters showing up as blank.
|
||||
* Change: Validate condition logic when saving a notification agent.
|
||||
* API:
|
||||
* Change: API is enabled by default on new installs.
|
||||
* UI:
|
||||
* New: Add logo svg files. (Thanks @Fish2)
|
||||
* New: Updated stream info modal.
|
||||
* Change: Media info tables sort by sort title instead of title.
|
||||
* Other:
|
||||
* Fix: Updating library IDs message on libraries page.
|
||||
* Fix: Wtched percentage settings not saving after restart.
|
||||
* Remove: Video Preview Thumbnails setting no longer used.
|
||||
* Change: Add back HTTP Proxy setting under the Web Interface settings tab.
|
||||
* Change: "Group Table and Watch Statistics History" and "Current Activity in History Tables" enabled by default on new installs.
|
||||
|
||||
|
||||
## v2.0.1-beta (2017-12-19)
|
||||
|
||||
* Monitoring:
|
||||
|
@@ -1,61 +1,20 @@
|
||||
# Contributing to PlexPy
|
||||
|
||||
## Issues
|
||||
In case you read this because you are posting an issue, please take a minute and conside the things below. The issue tracker is not a support forum. It is primarily intended to submit bugs. However, we are glad to help you, and make sure the problem is not caused by PlexPy, but don't expect step-by-step answers.
|
||||
|
||||
##### Many issues can simply be solved by:
|
||||
|
||||
- Making sure you update to the latest version.
|
||||
- Turning your device off and on again.
|
||||
- Analyzing your logs, you just might find the solution yourself!
|
||||
- Using the **search** function to see if this issue has already been reported/solved.
|
||||
- Checking the [Wiki](https://github.com/JonnyWong16/plexpy/wiki) for
|
||||
[ [Installation] ](https://github.com/JonnyWong16/plexpy/wiki/Installation) and
|
||||
[ [FAQs] ](https://github.com/JonnyWong16/plexpy/wiki/Frequently-Asked-Questions-(FAQ)).
|
||||
- For basic questions try asking on [Gitter](https://gitter.im/plexpy/general) or the [Plex Forums](https://forums.plex.tv/discussion/169591/plexpy-another-plex-monitoring-program) first before opening an issue.
|
||||
|
||||
##### If nothing has worked:
|
||||
|
||||
1. Open a new issue on the GitHub [issue tracker](http://github.com/JonnyWong16/plexpy/issues).
|
||||
2. Provide a clear title to easily help identify your problem.
|
||||
3. Use proper [markdown syntax](https://help.github.com/articles/github-flavored-markdown) to structure your post (i.e. code/log in code blocks).
|
||||
4. Make sure you provide the following information:
|
||||
- [ ] Version
|
||||
- [ ] Branch
|
||||
- [ ] Commit hash
|
||||
- [ ] Operating system
|
||||
- [ ] Python version
|
||||
- [ ] What you did?
|
||||
- [ ] What happened?
|
||||
- [ ] What you expected?
|
||||
- [ ] How can we reproduce your issue?
|
||||
- [ ] What are your (relevant) settings?
|
||||
- [ ] Include a link to your **FULL** (not just a few lines!) log file that has the error. Please use [Gist](http://gist.github.com) or [Pastebin](http://pastebin.com/).
|
||||
5. Close your issue when it's solved! If you found the solution yourself please comment so that others benefit from it.
|
||||
|
||||
## Feature Requests
|
||||
|
||||
Feature requests are handled on [FeatHub](http://feathub.com/JonnyWong16/plexpy).
|
||||
|
||||
1. Search the existing requests to see if your suggestion has already been submitted.
|
||||
2. If a similar request exists, give it a thumbs up (+1), or add additional comments to the request.
|
||||
3. If no similar requests exist, you can create a new one. Make sure to provide a clear title to easily identify the feature request.
|
||||
# Contributing to Tautulli
|
||||
|
||||
## Pull Requests
|
||||
If you think you can contribute code to the PlexPy repository, do not hesitate to submit a pull request.
|
||||
If you think you can contribute code to the Tautulli repository, do not hesitate to submit a pull request.
|
||||
|
||||
### Branches
|
||||
All pull requests should be based on the `dev` branch, to minimize cross merges. When you want to develop a new feature, clone the repository with `git clone origin/dev -b FEATURE_NAME`. Use meaningful commit messages.
|
||||
All pull requests should be based on the `nightly` branch, to minimize cross merges. When you want to develop a new feature, clone the repository with `git clone origin/nightly -b FEATURE_NAME`. Use meaningful commit messages.
|
||||
|
||||
### Python Code
|
||||
|
||||
#### Compatibility
|
||||
The code should work with Python 2.6 and 2.7. Note that PlexPy runs on different platforms, including Network Attached Storage devices such as Synology.
|
||||
The code should work with Python 2.7. 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.
|
||||
|
||||
#### Code conventions
|
||||
Although PlexPy did not adapt a code convention in the past, we try to follow the [PEP8](http://legacy.python.org/dev/peps/pep-0008/) conventions for future code. A short summary to remind you (copied from http://wiki.ros.org/PyStyleGuide):
|
||||
Although Tautulli did not adapt a code convention in the past, we try to follow the [PEP8](http://legacy.python.org/dev/peps/pep-0008/) conventions for future code. A short summary to remind you (copied from http://wiki.ros.org/PyStyleGuide):
|
||||
|
||||
* 4 space indentation
|
||||
* 80 characters per line
|
||||
@@ -70,13 +29,10 @@ Although PlexPy did not adapt a code convention in the past, we try to follow th
|
||||
#### Documentation
|
||||
Document your code. Use docstrings See [PEP-257](https://www.python.org/dev/peps/pep-0257/) for more information.
|
||||
|
||||
#### Continuous Integration
|
||||
PlexPy has a configuration file for [travis-ci](https://travis-ci.org/). You can add your forked repo to Travis to have it check your code against PEP8, PyLint, and PyFlakes for you. Your pull request will show a green check mark or a red cross on each tested commit, depending on if linting passes.
|
||||
|
||||
### HTML/Template code
|
||||
|
||||
#### Compatibility
|
||||
HTML5 compatible browsers are targetted. There is no specific mobile version of PlexPy yet.
|
||||
HTML5 compatible browsers are targeted.
|
||||
|
||||
#### Conventions
|
||||
* 4 space indentation
|
||||
|
@@ -8,7 +8,7 @@ Reporting Issues:
|
||||
Please use [Gist](http://gist.github.com) or [Pastebin](http://pastebin.com/).
|
||||
|
||||
Feature Requests:
|
||||
* Feature requests are handled on FeatHub: http://feathub.com/JonnyWong16/plexpy
|
||||
* Feature requests are handled on FeatHub: http://feathub.com/Tautulli/Tautulli
|
||||
* Do not post them on the GitHub issues tracker.
|
||||
-->
|
||||
|
||||
|
242
PlexPy.py
@@ -21,246 +21,8 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Tautulli. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import sys
|
||||
from Tautulli import main
|
||||
|
||||
# Ensure lib added to path, before any other imports
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'lib/'))
|
||||
|
||||
import argparse
|
||||
import locale
|
||||
import signal
|
||||
import time
|
||||
|
||||
import plexpy
|
||||
from plexpy import config, database, logger, web_socket, webstart
|
||||
|
||||
|
||||
# Register signals, such as CTRL + C
|
||||
signal.signal(signal.SIGINT, plexpy.sig_handler)
|
||||
signal.signal(signal.SIGTERM, plexpy.sig_handler)
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Tautulli application entry point. Parses arguments, setups encoding and
|
||||
initializes the application.
|
||||
"""
|
||||
|
||||
# Fixed paths to Tautulli
|
||||
if hasattr(sys, 'frozen'):
|
||||
plexpy.FULL_PATH = os.path.abspath(sys.executable)
|
||||
else:
|
||||
plexpy.FULL_PATH = os.path.abspath(__file__)
|
||||
|
||||
plexpy.PROG_DIR = os.path.dirname(plexpy.FULL_PATH)
|
||||
plexpy.ARGS = sys.argv[1:]
|
||||
|
||||
# From sickbeard
|
||||
plexpy.SYS_PLATFORM = sys.platform
|
||||
plexpy.SYS_ENCODING = None
|
||||
|
||||
try:
|
||||
locale.setlocale(locale.LC_ALL, "")
|
||||
plexpy.SYS_ENCODING = locale.getpreferredencoding()
|
||||
except (locale.Error, IOError):
|
||||
pass
|
||||
|
||||
# for OSes that are poorly configured I'll just force UTF-8
|
||||
if not plexpy.SYS_ENCODING or plexpy.SYS_ENCODING in ('ANSI_X3.4-1968', 'US-ASCII', 'ASCII'):
|
||||
plexpy.SYS_ENCODING = 'UTF-8'
|
||||
|
||||
# Set up and gather command line arguments
|
||||
parser = argparse.ArgumentParser(
|
||||
description='A Python based monitoring and tracking tool for Plex Media Server.')
|
||||
|
||||
parser.add_argument(
|
||||
'-v', '--verbose', action='store_true', help='Increase console logging verbosity')
|
||||
parser.add_argument(
|
||||
'-q', '--quiet', action='store_true', help='Turn off console logging')
|
||||
parser.add_argument(
|
||||
'-d', '--daemon', action='store_true', help='Run as a daemon')
|
||||
parser.add_argument(
|
||||
'-p', '--port', type=int, help='Force Tautulli to run on a specified port')
|
||||
parser.add_argument(
|
||||
'--dev', action='store_true', help='Start Tautulli in the development environment')
|
||||
parser.add_argument(
|
||||
'--datadir', help='Specify a directory where to store your data files')
|
||||
parser.add_argument(
|
||||
'--config', help='Specify a config file to use')
|
||||
parser.add_argument(
|
||||
'--nolaunch', action='store_true', help='Prevent browser from launching on startup')
|
||||
parser.add_argument(
|
||||
'--pidfile', help='Create a pid file (only relevant when running as a daemon)')
|
||||
parser.add_argument(
|
||||
'--nofork', action='store_true', help='Start Tautulli as a service, do not fork when restarting')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.verbose:
|
||||
plexpy.VERBOSE = True
|
||||
if args.quiet:
|
||||
plexpy.QUIET = True
|
||||
|
||||
# Do an intial setup of the logger.
|
||||
logger.initLogger(console=not plexpy.QUIET, log_dir=False,
|
||||
verbose=plexpy.VERBOSE)
|
||||
|
||||
if args.dev:
|
||||
plexpy.DEV = True
|
||||
logger.debug(u"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")
|
||||
else:
|
||||
plexpy.DAEMON = True
|
||||
plexpy.QUIET = True
|
||||
|
||||
if args.nofork:
|
||||
plexpy.NOFORK = True
|
||||
logger.info("Tautulli is running as a service, it will not fork when restarted.")
|
||||
|
||||
if args.pidfile:
|
||||
plexpy.PIDFILE = str(args.pidfile)
|
||||
|
||||
# If the pidfile already exists, plexpy may still be running, so
|
||||
# exit
|
||||
if os.path.exists(plexpy.PIDFILE):
|
||||
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)
|
||||
except OSError:
|
||||
logger.warn("PID file '%s' already exists, but PID %d is " \
|
||||
"not running. Ignoring PID file." %
|
||||
(plexpy.PIDFILE, pid))
|
||||
else:
|
||||
# The pidfile exists and points to a live PID. plexpy may
|
||||
# still be running, so exit.
|
||||
raise SystemExit("PID file '%s' already exists. Exiting." %
|
||||
plexpy.PIDFILE)
|
||||
|
||||
# The pidfile is only useful in daemon mode, make sure we can write the
|
||||
# file properly
|
||||
if plexpy.DAEMON:
|
||||
plexpy.CREATEPID = True
|
||||
|
||||
try:
|
||||
with open(plexpy.PIDFILE, 'w') as fp:
|
||||
fp.write("pid\n")
|
||||
except IOError as e:
|
||||
raise SystemExit("Unable to write PID file: %s", e)
|
||||
else:
|
||||
logger.warn("Not running in daemon mode. PID file creation " \
|
||||
"disabled.")
|
||||
|
||||
# Determine which data directory and config file to use
|
||||
if args.datadir:
|
||||
plexpy.DATA_DIR = args.datadir
|
||||
else:
|
||||
plexpy.DATA_DIR = plexpy.PROG_DIR
|
||||
|
||||
if args.config:
|
||||
config_file = args.config
|
||||
else:
|
||||
config_file = os.path.join(plexpy.DATA_DIR, config.FILENAME)
|
||||
|
||||
# Try to create the DATA_DIR if it doesn't exist
|
||||
if not os.path.exists(plexpy.DATA_DIR):
|
||||
try:
|
||||
os.makedirs(plexpy.DATA_DIR)
|
||||
except OSError:
|
||||
raise SystemExit(
|
||||
'Could not create data directory: ' + plexpy.DATA_DIR + '. Exiting....')
|
||||
|
||||
# Make sure the DATA_DIR is writeable
|
||||
if not os.access(plexpy.DATA_DIR, os.W_OK):
|
||||
raise SystemExit(
|
||||
'Cannot write to the data directory: ' + plexpy.DATA_DIR + '. Exiting...')
|
||||
|
||||
# Put the database in the DATA_DIR
|
||||
plexpy.DB_FILE = os.path.join(plexpy.DATA_DIR, database.FILENAME)
|
||||
|
||||
if plexpy.DAEMON:
|
||||
plexpy.daemonize()
|
||||
|
||||
# Read config and start logging
|
||||
plexpy.initialize(config_file)
|
||||
|
||||
# Start the background threads
|
||||
plexpy.start()
|
||||
|
||||
# Open connection for websocket
|
||||
try:
|
||||
web_socket.start_thread()
|
||||
except:
|
||||
logger.warn(u"Websocket :: Unable to open connection.")
|
||||
plexpy.initialize_scheduler()
|
||||
|
||||
# Force the http port if neccessary
|
||||
if args.port:
|
||||
http_port = args.port
|
||||
logger.info('Using forced web server port: %i', http_port)
|
||||
else:
|
||||
http_port = int(plexpy.CONFIG.HTTP_PORT)
|
||||
|
||||
# Check if pyOpenSSL is installed. It is required for certificate generation
|
||||
# and for CherryPy.
|
||||
if plexpy.CONFIG.ENABLE_HTTPS:
|
||||
try:
|
||||
import OpenSSL
|
||||
except ImportError:
|
||||
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.
|
||||
web_config = {
|
||||
'http_port': http_port,
|
||||
'http_host': plexpy.CONFIG.HTTP_HOST,
|
||||
'http_root': plexpy.CONFIG.HTTP_ROOT,
|
||||
'http_environment': plexpy.CONFIG.HTTP_ENVIRONMENT,
|
||||
'http_proxy': plexpy.CONFIG.HTTP_PROXY,
|
||||
'enable_https': plexpy.CONFIG.ENABLE_HTTPS,
|
||||
'https_cert': plexpy.CONFIG.HTTPS_CERT,
|
||||
'https_cert_chain': plexpy.CONFIG.HTTPS_CERT_CHAIN,
|
||||
'https_key': plexpy.CONFIG.HTTPS_KEY,
|
||||
'http_username': plexpy.CONFIG.HTTP_USERNAME,
|
||||
'http_password': plexpy.CONFIG.HTTP_PASSWORD,
|
||||
'http_basic_auth': plexpy.CONFIG.HTTP_BASIC_AUTH
|
||||
}
|
||||
webstart.initialize(web_config)
|
||||
|
||||
# Open webbrowser
|
||||
if plexpy.CONFIG.LAUNCH_BROWSER and not args.nolaunch and not plexpy.DEV:
|
||||
plexpy.launch_browser(plexpy.CONFIG.HTTP_HOST, http_port,
|
||||
plexpy.CONFIG.HTTP_ROOT)
|
||||
|
||||
# Wait endlessy for a signal to happen
|
||||
while True:
|
||||
if not plexpy.SIGNAL:
|
||||
try:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
plexpy.SIGNAL = 'shutdown'
|
||||
else:
|
||||
logger.info('Received signal: %s', plexpy.SIGNAL)
|
||||
|
||||
if plexpy.SIGNAL == 'shutdown':
|
||||
plexpy.shutdown()
|
||||
elif plexpy.SIGNAL == 'restart':
|
||||
plexpy.shutdown(restart=True)
|
||||
elif plexpy.SIGNAL == 'checkout':
|
||||
plexpy.shutdown(restart=True, checkout=True)
|
||||
else:
|
||||
plexpy.shutdown(restart=True, update=True)
|
||||
|
||||
plexpy.SIGNAL = None
|
||||
|
||||
# Call main()
|
||||
# Call main() from Tautulli.py
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
55
README.md
@@ -1,8 +1,8 @@
|
||||
# Tautulli
|
||||
|
||||
[](https://discord.gg/36ggawe)
|
||||
[](https://discord.gg/tQcWEUp)
|
||||
[](https://www.reddit.com/r/Tautulli/)
|
||||
[](https://forums.plex.tv/discussion/169591/plexpy-another-plex-monitoring-program)
|
||||
[](https://forums.plex.tv/discussion/307821/tautulli-monitor-your-plex-media-server)
|
||||
|
||||
A python based web application for monitoring, analytics and notifications for [Plex Media Server](https://plex.tv).
|
||||
|
||||
@@ -27,56 +27,19 @@ This project is based on code from [Headphones](https://github.com/rembo10/headp
|
||||
|
||||
## Preview
|
||||
|
||||
* [Full preview gallery on our website](http://tautulli.com)
|
||||
* [Full preview gallery available on our website](https://tautulli.com)
|
||||
|
||||

|
||||

|
||||
|
||||
## Installation and Support
|
||||
|
||||
* [Installation Guides](https://github.com/JonnyWong16/plexpy/wiki/Installation) shows you how to install Tautulli.
|
||||
* [FAQs](https://github.com/JonnyWong16/plexpy/wiki/Frequently-Asked-Questions-(FAQ)) in the wiki can help you with common problems.
|
||||
* Read the [Installation Guides](https://github.com/Tautulli/Tautulli-Wiki/wiki/Installation) for instructions to install Tautulli.
|
||||
* The [Frequently Asked Questions](https://github.com/Tautulli/Tautulli-Wiki/wiki/Frequently-Asked-Questions) in the wiki can help you with common problems.
|
||||
* Support is available on [Discord](https://discord.gg/tQcWEUp), [Reddit](https://www.reddit.com/r/Tautulli), or the [Plex Forums](https://forums.plex.tv/discussion/307821/tautulli-monitor-your-plex-media-server).
|
||||
|
||||
**Support** the project by implementing new features, solving support tickets and provide bug fixes.
|
||||
## Issues & Feature Requests
|
||||
|
||||
## Issues
|
||||
|
||||
##### Many issues can simply be solved by:
|
||||
|
||||
- Making sure you update to the latest version.
|
||||
- Turning your device off and on again.
|
||||
- Analyzing your logs, you just might find the solution yourself!
|
||||
- Using the **search** function to see if this issue has already been reported/solved.
|
||||
- Checking the [Wiki](https://github.com/JonnyWong16/plexpy/wiki) for
|
||||
[ [Installation] ](https://github.com/JonnyWong16/plexpy/wiki/Installation) and
|
||||
[ [FAQs] ](https://github.com/JonnyWong16/plexpy/wiki/Frequently-Asked-Questions-(FAQ)).
|
||||
- For basic questions try asking on [Discord](https://discord.gg/36ggawe), [Reddit](https://www.reddit.com/r/Tautulli), or the [Plex Forums](https://forums.plex.tv/discussion/169591/plexpy-another-plex-monitoring-program) first before opening an issue.
|
||||
|
||||
##### If nothing has worked:
|
||||
|
||||
1. Open a new issue on the GitHub [issue tracker](http://github.com/JonnyWong16/plexpy/issues).
|
||||
2. Provide a clear title to easily help identify your problem.
|
||||
3. Use proper [markdown syntax](https://help.github.com/articles/github-flavored-markdown) to structure your post (i.e. code/log in code blocks).
|
||||
4. Make sure you provide the following information:
|
||||
- [ ] Version
|
||||
- [ ] Branch
|
||||
- [ ] Commit hash
|
||||
- [ ] Operating system
|
||||
- [ ] Python version
|
||||
- [ ] What you did?
|
||||
- [ ] What happened?
|
||||
- [ ] What you expected?
|
||||
- [ ] How can we reproduce your issue?
|
||||
- [ ] What are your (relevant) settings?
|
||||
- [ ] Include a link to your **FULL** (not just a few lines!) log file that has the error. Please use [Gist](http://gist.github.com) or [Pastebin](http://pastebin.com/).
|
||||
5. Close your issue when it's solved! If you found the solution yourself please comment so that others benefit from it.
|
||||
|
||||
## Feature Requests
|
||||
|
||||
Feature requests are handled on [FeatHub](http://feathub.com/JonnyWong16/plexpy).
|
||||
|
||||
1. Search the existing requests to see if your suggestion has already been submitted.
|
||||
2. If a similar request exists, give it a thumbs up (+1), or add additional comments to the request.
|
||||
3. If no similar requests exist, you can create a new one. Make sure to provide a clear title to easily identify the feature request.
|
||||
* Please see the [Issues Repository](https://github.com/Tautulli/Tautulli-Issues).
|
||||
|
||||
## License
|
||||
|
||||
|
267
Tautulli.py
Executable file
@@ -0,0 +1,267 @@
|
||||
#!/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!" # '''
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This file is part of Tautulli.
|
||||
#
|
||||
# Tautulli is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Tautulli is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Tautulli. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Ensure lib added to path, before any other imports
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'lib/'))
|
||||
|
||||
import argparse
|
||||
import locale
|
||||
import signal
|
||||
import time
|
||||
|
||||
import plexpy
|
||||
from plexpy import config, database, logger, webstart
|
||||
|
||||
|
||||
# Register signals, such as CTRL + C
|
||||
signal.signal(signal.SIGINT, plexpy.sig_handler)
|
||||
signal.signal(signal.SIGTERM, plexpy.sig_handler)
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Tautulli application entry point. Parses arguments, setups encoding and
|
||||
initializes the application.
|
||||
"""
|
||||
|
||||
# Fixed paths to Tautulli
|
||||
if hasattr(sys, 'frozen'):
|
||||
plexpy.FULL_PATH = os.path.abspath(sys.executable)
|
||||
else:
|
||||
plexpy.FULL_PATH = os.path.abspath(__file__)
|
||||
|
||||
plexpy.PROG_DIR = os.path.dirname(plexpy.FULL_PATH)
|
||||
plexpy.ARGS = sys.argv[1:]
|
||||
|
||||
# From sickbeard
|
||||
plexpy.SYS_PLATFORM = sys.platform
|
||||
plexpy.SYS_ENCODING = None
|
||||
|
||||
try:
|
||||
locale.setlocale(locale.LC_ALL, "")
|
||||
plexpy.SYS_LANGUAGE, plexpy.SYS_ENCODING = locale.getdefaultlocale()
|
||||
except (locale.Error, IOError):
|
||||
pass
|
||||
|
||||
# for OSes that are poorly configured I'll just force UTF-8
|
||||
if not plexpy.SYS_ENCODING or plexpy.SYS_ENCODING in ('ANSI_X3.4-1968', 'US-ASCII', 'ASCII'):
|
||||
plexpy.SYS_ENCODING = 'UTF-8'
|
||||
|
||||
# Set up and gather command line arguments
|
||||
parser = argparse.ArgumentParser(
|
||||
description='A Python based monitoring and tracking tool for Plex Media Server.')
|
||||
|
||||
parser.add_argument(
|
||||
'-v', '--verbose', action='store_true', help='Increase console logging verbosity')
|
||||
parser.add_argument(
|
||||
'-q', '--quiet', action='store_true', help='Turn off console logging')
|
||||
parser.add_argument(
|
||||
'-d', '--daemon', action='store_true', help='Run as a daemon')
|
||||
parser.add_argument(
|
||||
'-p', '--port', type=int, help='Force Tautulli to run on a specified port')
|
||||
parser.add_argument(
|
||||
'--dev', action='store_true', help='Start Tautulli in the development environment')
|
||||
parser.add_argument(
|
||||
'--datadir', help='Specify a directory where to store your data files')
|
||||
parser.add_argument(
|
||||
'--config', help='Specify a config file to use')
|
||||
parser.add_argument(
|
||||
'--nolaunch', action='store_true', help='Prevent browser from launching on startup')
|
||||
parser.add_argument(
|
||||
'--pidfile', help='Create a pid file (only relevant when running as a daemon)')
|
||||
parser.add_argument(
|
||||
'--nofork', action='store_true', help='Start Tautulli as a service, do not fork when restarting')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.verbose:
|
||||
plexpy.VERBOSE = True
|
||||
if args.quiet:
|
||||
plexpy.QUIET = True
|
||||
|
||||
# Do an intial setup of the logger.
|
||||
logger.initLogger(console=not plexpy.QUIET, log_dir=False,
|
||||
verbose=plexpy.VERBOSE)
|
||||
|
||||
if args.dev:
|
||||
plexpy.DEV = True
|
||||
logger.debug(u"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")
|
||||
else:
|
||||
plexpy.DAEMON = True
|
||||
plexpy.QUIET = True
|
||||
|
||||
if args.nofork:
|
||||
plexpy.NOFORK = True
|
||||
logger.info("Tautulli is running as a service, it will not fork when restarted.")
|
||||
|
||||
if args.pidfile:
|
||||
plexpy.PIDFILE = str(args.pidfile)
|
||||
|
||||
# If the pidfile already exists, plexpy may still be running, so
|
||||
# exit
|
||||
if os.path.exists(plexpy.PIDFILE):
|
||||
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)
|
||||
except OSError:
|
||||
logger.warn("PID file '%s' already exists, but PID %d is " \
|
||||
"not running. Ignoring PID file." %
|
||||
(plexpy.PIDFILE, pid))
|
||||
else:
|
||||
# The pidfile exists and points to a live PID. plexpy may
|
||||
# still be running, so exit.
|
||||
raise SystemExit("PID file '%s' already exists. Exiting." %
|
||||
plexpy.PIDFILE)
|
||||
|
||||
# The pidfile is only useful in daemon mode, make sure we can write the
|
||||
# file properly
|
||||
if plexpy.DAEMON:
|
||||
plexpy.CREATEPID = True
|
||||
|
||||
try:
|
||||
with open(plexpy.PIDFILE, 'w') as fp:
|
||||
fp.write("pid\n")
|
||||
except IOError as e:
|
||||
raise SystemExit("Unable to write PID file: %s", e)
|
||||
else:
|
||||
logger.warn("Not running in daemon mode. PID file creation " \
|
||||
"disabled.")
|
||||
|
||||
# Determine which data directory and config file to use
|
||||
if args.datadir:
|
||||
plexpy.DATA_DIR = args.datadir
|
||||
else:
|
||||
plexpy.DATA_DIR = plexpy.PROG_DIR
|
||||
|
||||
if args.config:
|
||||
config_file = args.config
|
||||
else:
|
||||
config_file = os.path.join(plexpy.DATA_DIR, config.FILENAME)
|
||||
|
||||
# Try to create the DATA_DIR if it doesn't exist
|
||||
if not os.path.exists(plexpy.DATA_DIR):
|
||||
try:
|
||||
os.makedirs(plexpy.DATA_DIR)
|
||||
except OSError:
|
||||
raise SystemExit(
|
||||
'Could not create data directory: ' + plexpy.DATA_DIR + '. Exiting....')
|
||||
|
||||
# Make sure the DATA_DIR is writeable
|
||||
if not os.access(plexpy.DATA_DIR, os.W_OK):
|
||||
raise SystemExit(
|
||||
'Cannot write to the data directory: ' + plexpy.DATA_DIR + '. Exiting...')
|
||||
|
||||
# Put the database in the DATA_DIR
|
||||
plexpy.DB_FILE = os.path.join(plexpy.DATA_DIR, database.FILENAME)
|
||||
|
||||
# Move 'plexpy.db' to 'tautulli.db'
|
||||
if os.path.isfile(os.path.join(plexpy.DATA_DIR, 'plexpy.db')) and \
|
||||
not os.path.isfile(os.path.join(plexpy.DATA_DIR, plexpy.DB_FILE)):
|
||||
try:
|
||||
os.rename(os.path.join(plexpy.DATA_DIR, 'plexpy.db'), plexpy.DB_FILE)
|
||||
except OSError as e:
|
||||
raise SystemExit("Unable to rename plexpy.db to tautulli.db: %s", e)
|
||||
|
||||
if plexpy.DAEMON:
|
||||
plexpy.daemonize()
|
||||
|
||||
# Read config and start logging
|
||||
plexpy.initialize(config_file)
|
||||
|
||||
# Start the background threads
|
||||
plexpy.start()
|
||||
|
||||
# Force the http port if neccessary
|
||||
if args.port:
|
||||
http_port = args.port
|
||||
logger.info('Using forced web server port: %i', http_port)
|
||||
else:
|
||||
http_port = int(plexpy.CONFIG.HTTP_PORT)
|
||||
|
||||
# Check if pyOpenSSL is installed. It is required for certificate generation
|
||||
# and for CherryPy.
|
||||
if plexpy.CONFIG.ENABLE_HTTPS:
|
||||
try:
|
||||
import OpenSSL
|
||||
except ImportError:
|
||||
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.
|
||||
web_config = {
|
||||
'http_port': http_port,
|
||||
'http_host': plexpy.CONFIG.HTTP_HOST,
|
||||
'http_root': plexpy.CONFIG.HTTP_ROOT,
|
||||
'http_environment': plexpy.CONFIG.HTTP_ENVIRONMENT,
|
||||
'http_proxy': plexpy.CONFIG.HTTP_PROXY,
|
||||
'enable_https': plexpy.CONFIG.ENABLE_HTTPS,
|
||||
'https_cert': plexpy.CONFIG.HTTPS_CERT,
|
||||
'https_cert_chain': plexpy.CONFIG.HTTPS_CERT_CHAIN,
|
||||
'https_key': plexpy.CONFIG.HTTPS_KEY,
|
||||
'http_username': plexpy.CONFIG.HTTP_USERNAME,
|
||||
'http_password': plexpy.CONFIG.HTTP_PASSWORD,
|
||||
'http_basic_auth': plexpy.CONFIG.HTTP_BASIC_AUTH
|
||||
}
|
||||
webstart.initialize(web_config)
|
||||
|
||||
# Open webbrowser
|
||||
if plexpy.CONFIG.LAUNCH_BROWSER and not args.nolaunch and not plexpy.DEV:
|
||||
plexpy.launch_browser(plexpy.CONFIG.HTTP_HOST, http_port,
|
||||
plexpy.CONFIG.HTTP_ROOT)
|
||||
|
||||
# Wait endlessy for a signal to happen
|
||||
while True:
|
||||
if not plexpy.SIGNAL:
|
||||
try:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
plexpy.SIGNAL = 'shutdown'
|
||||
else:
|
||||
logger.info('Received signal: %s', plexpy.SIGNAL)
|
||||
|
||||
if plexpy.SIGNAL == 'shutdown':
|
||||
plexpy.shutdown()
|
||||
elif plexpy.SIGNAL == 'restart':
|
||||
plexpy.shutdown(restart=True)
|
||||
elif plexpy.SIGNAL == 'checkout':
|
||||
plexpy.shutdown(restart=True, checkout=True)
|
||||
else:
|
||||
plexpy.shutdown(restart=True, update=True)
|
||||
|
||||
plexpy.SIGNAL = None
|
||||
|
||||
# Call main()
|
||||
if __name__ == "__main__":
|
||||
main()
|
@@ -2,6 +2,7 @@
|
||||
import plexpy
|
||||
from plexpy import version
|
||||
from plexpy.helpers import anon_url
|
||||
from plexpy.notifiers import BROWSER_NOTIFIERS
|
||||
%>
|
||||
<!doctype html>
|
||||
|
||||
@@ -14,34 +15,27 @@
|
||||
<meta name="author" content="">
|
||||
<link href="${http_root}css/bootstrap3/bootstrap.css" rel="stylesheet">
|
||||
<link href="${http_root}css/pnotify.custom.min.css" rel="stylesheet" />
|
||||
<link href="${http_root}css/plexpy.css${cache_param}" 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.min.css" rel="stylesheet">
|
||||
${next.headIncludes()}
|
||||
|
||||
<!-- Favicons -->
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="${http_root}images/favicon/favicon-32x32.png?v=2.0.0">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="${http_root}images/favicon/favicon-16x16.png?v=2.0.0">
|
||||
<link rel="shortcut icon" href="${http_root}images/favicon/favicon.ico?v=2.0.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">
|
||||
|
||||
<!-- ICONS -->
|
||||
<!-- Android >M39 icon -->
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="${http_root}images/favicon/android-chrome-192x192.png?v=2.0.0">
|
||||
<link rel="manifest" href="${http_root}json/Android-manifest.json?v=2.0.0">
|
||||
<meta name="theme-color" content="#1f1f1f">
|
||||
<!-- Android -->
|
||||
<link rel="manifest" href="${http_root}images/favicon/manifest.json?v=2.0.5">
|
||||
<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.0">
|
||||
<link rel="mask-icon" href="${http_root}images/favicon/safari-pinned-tab.svg?v=2.0.0" color="#1f1f1f">
|
||||
<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">
|
||||
<meta name="apple-mobile-web-app-title" content="Tautulli">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="viewport" content="initial-scale=1">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<!-- IE10 icon -->
|
||||
<!-- Microsoft -->
|
||||
<meta name="application-name" content="Tautulli">
|
||||
<meta name="msapplication-TileColor" content="#1f1f1f">
|
||||
<meta name="msapplication-TileImage" content="${http_root}images/favicon/mstile-144x144.png?v=2.0.0">
|
||||
<meta name="msapplication-config" content="${http_root}xml/IEconfig.xml?v=2.0.0" />
|
||||
<meta name="msapplication-config" content="${http_root}images/favicon/browserconfig.xml?v=2.0.5">
|
||||
</head>
|
||||
|
||||
<body class="content">
|
||||
@@ -50,16 +44,24 @@
|
||||
% if _session['user_group'] == 'admin':
|
||||
% if plexpy.CONFIG.CHECK_GITHUB and not plexpy.CURRENT_VERSION:
|
||||
<div id="updatebar" style="display: none;">
|
||||
You're running an unknown version of Tautulli.<br />
|
||||
<a href="update">Update</a> or <a href="#" id="updateDismiss">Close</a>
|
||||
You are running an unknown version of Tautulli.<br />
|
||||
<a href="update">Update</a> or <a href="#" id="updateDismiss">Dismiss</a>
|
||||
</div>
|
||||
% elif plexpy.CONFIG.CHECK_GITHUB and plexpy.CURRENT_VERSION != plexpy.LATEST_VERSION and plexpy.COMMITS_BEHIND > 0 and plexpy.INSTALL_TYPE != 'win':
|
||||
% elif plexpy.CONFIG.CHECK_GITHUB and plexpy.COMMITS_BEHIND > 0 and plexpy.common.BRANCH in ('master', 'beta') and plexpy.common.RELEASE != plexpy.LATEST_RELEASE:
|
||||
<div id="updatebar" style="display: none;">
|
||||
A <a href="${anon_url('https://github.com/%s/plexpy/compare/%s...%s' % (plexpy.CONFIG.GIT_USER, plexpy.CURRENT_VERSION, plexpy.LATEST_VERSION))}" target="_blank">
|
||||
newer version</a> is available.<br />
|
||||
You're ${plexpy.COMMITS_BEHIND} commits behind.<br />
|
||||
<a href="update">Update</a> or <a href="#" id="updateDismiss">Close</a>
|
||||
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">
|
||||
new release (${plexpy.LATEST_RELEASE})</a> of Tautulli is available!<br />
|
||||
<a href="update">Update</a> or <a href="#" id="updateDismiss">Dismiss</a>
|
||||
</div>
|
||||
% elif plexpy.CONFIG.CHECK_GITHUB and plexpy.COMMITS_BEHIND > 0 and plexpy.CURRENT_VERSION != plexpy.LATEST_VERSION and plexpy.INSTALL_TYPE != 'win':
|
||||
<div id="updatebar" style="display: none;">
|
||||
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">
|
||||
newer version</a> of Tautulli is available!<br />
|
||||
You are ${plexpy.COMMITS_BEHIND} commit${'s' if plexpy.COMMITS_BEHIND > 1 else ''} behind.<br />
|
||||
<a href="update">Update</a> or <a href="#" id="updateDismiss">Dismiss</a>
|
||||
</div>
|
||||
% else:
|
||||
<div id="updatebar" style="display: none;"></div>
|
||||
% endif
|
||||
% endif
|
||||
<nav class="navbar navbar-fixed-top">
|
||||
@@ -71,8 +73,8 @@
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="home">
|
||||
<img alt="Tautulli" src="${http_root}images/logo-tautulli-50.png" height="40">
|
||||
<a class="navbar-brand" href="home" title="Tautulli">
|
||||
<img src="${http_root}images/logo-tautulli-45.png" height="45" alt="PlexPy">
|
||||
</a>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse navbar-right" id="navbar-collapse-1">
|
||||
@@ -131,7 +133,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/plexpy/wiki/Frequently-Asked-Questions-(FAQ)' % plexpy.CONFIG.GIT_USER)}" 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"><i class="fa fa-fw fa-question-circle"></i> FAQ</a></li>
|
||||
<li><a href="settings?support=true"><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>
|
||||
@@ -145,7 +147,7 @@
|
||||
<li><a href="#" data-target="#admin-login-modal" data-toggle="modal"><i class="fa fa-fw fa-lock"></i> Admin Login</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
% endif
|
||||
% if _session['expiry']:
|
||||
% if _session['exp']:
|
||||
<li><a href="${http_root}auth/logout"><i class="fa fa-fw fa-sign-out"></i> Sign Out</a></li>
|
||||
% endif
|
||||
</ul>
|
||||
@@ -168,7 +170,7 @@ ${next.modalIncludes()}
|
||||
<div id="admin-login-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="admin-login-modal">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<form action="${http_root}auth/login" method="post">
|
||||
<form id="login-form">
|
||||
<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">Admin Login</h4>
|
||||
@@ -197,7 +199,8 @@ ${next.modalIncludes()}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-bright login-button"><i class="fa fa-sign-in"></i> Sign In</button>
|
||||
<span id="incorrect-login" style="padding-right: 25px; display: none;">Incorrect username or password.</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" />
|
||||
</form>
|
||||
@@ -224,15 +227,23 @@ ${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="#paypal-donation" role="tab" data-toggle="tab">PayPal</a></li>
|
||||
<li><a href="#flattr-donation" role="tab" data-toggle="tab">Flattr</a></li>
|
||||
<li class="active"><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>
|
||||
<li><a href="#crypto-donation" role="tab" data-toggle="tab" class="crypto-donation" data-coin="bitcoin" data-name="Bitcoin" data-address="3FdfJAyNWU15Sf11U9FTgPHuP1hPz32eEN">Bitcoin</a></li>
|
||||
<li><a href="#crypto-donation" role="tab" data-toggle="tab" class="crypto-donation" data-coin="bitcoincash" data-name="Bitcoin Cash" data-address="1H2atabxAQGaFAWYQEiLkXKSnK9CZZvt2n">Bitcoin Cash</a></li>
|
||||
<li><a href="#crypto-donation" role="tab" data-toggle="tab" class="crypto-donation" data-coin="ethereum" data-name="Ethereum" data-address="0x77ae4c2b8de1a1ccfa93553db39971da58c873d3">Ethereum</a></li>
|
||||
<li><a href="#crypto-donation" role="tab" data-toggle="tab" class="crypto-donation" data-coin="litecoin" data-name="Litecoin" data-address="LWpPmUqQYHBhMV83XSCsHzPmKLhJt6r57J">Litecoin</a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="paypal-donation" style="text-align: center">
|
||||
<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/bePatron?u=10078609')}" 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="paypal-donation" style="text-align: center">
|
||||
<p>
|
||||
Click the button below to continue to PayPal.
|
||||
</p>
|
||||
@@ -240,14 +251,6 @@ ${next.modalIncludes()}
|
||||
<img src="images/gold-rect-paypal-34px.png" alt="PayPal">
|
||||
</a>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="flattr-donation" style="text-align: center">
|
||||
<p>
|
||||
Click the button below to continue to Flattr.
|
||||
</p>
|
||||
<a href="${anon_url('https://flattr.com/submit/auto?user_id=JonnyWong16&url=https://github.com/JonnyWong16/plexpy&title=Tautulli&language=en_GB&tags=github&category=software')}" target="_blank">
|
||||
<img src="images/flattr-badge-large.png" alt="Flattr">
|
||||
</a>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="crypto-donation">
|
||||
<label>QR Code</label>
|
||||
<pre id="crypto_qr_code" style="text-align: center"></pre>
|
||||
@@ -289,16 +292,53 @@ ${next.modalIncludes()}
|
||||
<script src="${http_root}js/pnotify.custom.min.js"></script>
|
||||
<script src="${http_root}js/script.js${cache_param}"></script>
|
||||
<script src="${http_root}js/jquery.qrcode.min.js"></script>
|
||||
% if _session['user_group'] == 'admin' and BROWSER_NOTIFIERS:
|
||||
<script src="${http_root}js/ajaxNotifications.js"></script>
|
||||
% endif
|
||||
<script>
|
||||
% if _session['user_group'] == 'admin':
|
||||
$('#updateDismiss').click(function() {
|
||||
$('#updatebar').slideUp('slow');
|
||||
$('body').on('click', '#updateDismiss', function() {
|
||||
$('#updatebar').fadeOut();
|
||||
// Set cookie to remember dismiss decision for 1 hour.
|
||||
setCookie('updateDismiss', 'true', 1/24);
|
||||
});
|
||||
|
||||
if (!getCookie('updateDismiss')) {
|
||||
$('#updatebar').show();
|
||||
if ($('#updatebar').html().length > 0) {
|
||||
$('#updatebar').show();
|
||||
}
|
||||
}
|
||||
|
||||
function checkUpdate(_callback) {
|
||||
// Allow the update bar to show again if previously dismissed.
|
||||
setCookie('updateDismiss', 'true', 0);
|
||||
$.ajax({
|
||||
url: 'update_check',
|
||||
complete: function (xhr, status) {
|
||||
var result = $.parseJSON(xhr.responseText);
|
||||
var msg = '';
|
||||
if (result.update === null) {
|
||||
msg = 'You are running an unknown version of Tautulli.<br />' +
|
||||
'<a href="update">Update</a> or <a href="#" id="updateDismiss">Dismiss</a>';
|
||||
$('#updatebar').html(msg).fadeIn();
|
||||
} 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 />' +
|
||||
'<a href="update">Update</a> or <a href="#" id="updateDismiss">Dismiss</a>';
|
||||
$('#updatebar').html(msg).fadeIn();
|
||||
} 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 />' +
|
||||
'You are '+ result.commits_behind + ' commit' + (result.commits_behind > 1 ? 's' : '') + ' behind.<br />' +
|
||||
'<a href="update">Update</a> or <a href="#" id="updateDismiss">Dismiss</a>';
|
||||
$('#updatebar').html(msg).fadeIn();
|
||||
} else if (result.update === false) {
|
||||
showMsg('<i class="fa fa-check"></i> ' + result.message, false, true, 2000);
|
||||
}
|
||||
|
||||
if (_callback) {
|
||||
_callback();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$("#nav-shutdown").click(function() {
|
||||
@@ -317,11 +357,9 @@ ${next.modalIncludes()}
|
||||
});
|
||||
});
|
||||
|
||||
$("#nav-update").first().one("click", function () {
|
||||
// Allow the update bar to show again if previously dismissed.
|
||||
setCookie('updateDismiss', 'true', 0);
|
||||
$(this).html('<i class="fa fa-spin fa-refresh"></i> Checking');
|
||||
window.location.href = "checkGithub";
|
||||
$('#nav-update').click(function () {
|
||||
$(this).html('<i class="fa fa-fw fa-spin fa-refresh"></i> Checking');
|
||||
checkUpdate(function () { $('#nav-update').html('<i class="fa fa-fw fa-arrow-circle-up"></i> Check for Updates'); });
|
||||
});
|
||||
|
||||
$('#donation_type a.crypto-donation').on('shown.bs.tab', function () {
|
||||
@@ -393,6 +431,29 @@ ${next.modalIncludes()}
|
||||
$('#admin-login-modal').on('shown.bs.modal', function () {
|
||||
$('#admin-login-modal #username').focus()
|
||||
})
|
||||
|
||||
$('#login-form').submit(function(event) {
|
||||
event.preventDefault();
|
||||
$('#sign-in').prop('disabled', true).html('<i class="fa fa-refresh fa-spin"></i> Sign In');
|
||||
$.ajax({
|
||||
url: '${http_root}auth/signin',
|
||||
type: 'POST',
|
||||
data: $(this).serialize(),
|
||||
dataType: 'json',
|
||||
statusCode: {
|
||||
200: function() {
|
||||
window.location = "${http_root}";
|
||||
},
|
||||
401: function() {
|
||||
$('#incorrect-login').show();
|
||||
$('#username').focus();
|
||||
}
|
||||
},
|
||||
complete: function() {
|
||||
$('#sign-in').prop('disabled', false).html('<i class="fa fa-sign-in"></i> Sign In');
|
||||
}
|
||||
});
|
||||
});
|
||||
% endif
|
||||
</script>
|
||||
${next.javascriptIncludes()}
|
||||
|
@@ -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/plexpy/tree/%s' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_BRANCH))}">${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))}">${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/plexpy/commit/%s' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_BRANCH))}">${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))}">${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>Newsletter Directory:</td>
|
||||
<td>${plexpy.CONFIG.NEWSLETTER_DIR}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>GeoLite2 Database:</td>
|
||||
% if plexpy.CONFIG.GEOIP_DB:
|
||||
@@ -65,7 +69,7 @@ DOCUMENTATION :: END
|
||||
% endif
|
||||
<tr>
|
||||
<td>Platform:</td>
|
||||
<td>${common.PLATFORM} ${common.PLATFORM_VERSION}</td>
|
||||
<td>${common.PLATFORM} ${common.PLATFORM_RELEASE} (${common.PLATFORM_VERSION + (' - {}'.format(common.PLATFORM_LINUX_DISTRO) if common.PLATFORM_LINUX_DISTRO else '')})</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Python Version:</td>
|
||||
@@ -74,19 +78,19 @@ DOCUMENTATION :: END
|
||||
<tr>
|
||||
<td class="top-line">Resources:</td>
|
||||
<td class="top-line">
|
||||
<a id="source-link" class="no-highlight" href="${anon_url('https://github.com/%s/plexpy' % plexpy.CONFIG.GIT_USER)}" target="_blank">GitHub Source</a> |
|
||||
<a class="no-highlight guidelines-modal-link" href="${anon_url('https://github.com/%s/plexpy/issues' % plexpy.CONFIG.GIT_USER)}" data-id="issue">GitHub Issues</a> |
|
||||
<a class="no-highlight guidelines-modal-link" href="${anon_url('http://feathub.com/%s/plexpy' % plexpy.CONFIG.GIT_USER)}" data-id="feature request">FeatHub Feature Requests</a> |
|
||||
<a class="no-highlight" href="${anon_url('https://github.com/%s/plexpy/wiki' % plexpy.CONFIG.GIT_USER)}" target="_blank">Tautulli Wiki</a> |
|
||||
<a id="faq-source-link" class="no-highlight" href="${anon_url('https://github.com/%s/plexpy/wiki/Frequently-Asked-Questions-(FAQ)' % plexpy.CONFIG.GIT_USER)}" target="_blank">Tautulli FAQ</a>
|
||||
<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>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Support:</td>
|
||||
<td>
|
||||
<a class="no-highlight support-modal-link" href="${anon_url('https://discord.gg/36ggawe')}" target="_blank">Tautulli Discord Server</a> |
|
||||
<a class="no-highlight support-modal-link" href="${anon_url('https://discord.gg/tQcWEUp')}" 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/discussion/169591/plexpy-another-plex-monitoring-program')}" target="_blank">Plex Forums</a>
|
||||
<a class="no-highlight support-modal-link" href="${anon_url('https://forums.plex.tv/discussion/307821/tautulli-monitor-your-plex-media-server')}" target="_blank">Plex Forums</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -112,7 +116,6 @@ DOCUMENTATION :: END
|
||||
|
||||
$('.guidelines-modal-link').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
$('#guidelines-link').attr('href', $('#source-link').attr('href'));
|
||||
$('#guidelines-type').text($(this).data('id'))
|
||||
$('#guidelines-modal').modal();
|
||||
$('#guidelines-continue').attr('href', $(this).attr('href')).on('click', function () {
|
||||
@@ -121,7 +124,6 @@ DOCUMENTATION :: END
|
||||
});
|
||||
$('.support-modal-link').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
$('#faq-link').attr('href', $('#faq-source-link').attr('href'));
|
||||
$('#support-modal').modal();
|
||||
$('#support-continue').attr('href', $(this).attr('href')).on('click', function () {
|
||||
$('#support-modal').modal('hide');
|
||||
|
@@ -359,11 +359,13 @@ table.display tr.shown + tr:hover {
|
||||
}
|
||||
table.display tr.shown + tr:hover a,
|
||||
table.display tr.shown + tr td:hover a,
|
||||
table.display tr.shown + tr td:hover a .fa,
|
||||
table.display tr.shown + tr .pagination > .active > a,
|
||||
table.display tr.shown + tr .pagination > .active > a:hover {
|
||||
color: #fff;
|
||||
}
|
||||
table.display tr.shown + tr table[id^='history_child'] td:hover a,
|
||||
table.display tr.shown + tr table[id^='history_child'] td:hover a .fa,
|
||||
table.display tr.shown + tr table[id^='media_info_child'] > tr > td:hover a,
|
||||
table.display tr.shown + tr table[id^='media_info_child'] tr.shown + tr table[id^='media_info_child'] td:hover a {
|
||||
color: #cc7b19;
|
@@ -64,43 +64,54 @@ DOCUMENTATION :: END
|
||||
from collections import defaultdict
|
||||
from urllib import quote
|
||||
from plexpy import helpers
|
||||
from plexpy.common import VIDEO_RESOLUTION_OVERRIDES, AUDIO_CODEC_OVERRIDES, EXTRA_TYPES
|
||||
import plexpy
|
||||
%>
|
||||
<% data = defaultdict(lambda: 'Unknown', **session) %>
|
||||
<% sk = data['session_key'] %>
|
||||
<%
|
||||
data = defaultdict(lambda: 'Unknown', **session)
|
||||
sk = data['session_key']
|
||||
|
||||
href = 'info?rating_key={}'.format(data['rating_key']) if data['rating_key'] else '#'
|
||||
parent_href = 'info?rating_key={}'.format(data['parent_rating_key']) if data['parent_rating_key'] else '#'
|
||||
grandparent_href = 'info?rating_key={}'.format(data['grandparent_rating_key']) if data['grandparent_rating_key'] else '#'
|
||||
user_href = 'user?user_id={}'.format(data['user_id']) if data['user_id'] else '#'
|
||||
%>
|
||||
<div class="dashboard-activity-instance" id="activity-instance-${sk}" data-key="${sk}" data-id="${data['session_id']}"
|
||||
data-rating_key="${data['rating_key']}" data-parent_rating_key="${data['parent_rating_key']}" data-grandparent_rating_key="${data['grandparent_rating_key']}">
|
||||
<div class="dashboard-activity-container">
|
||||
<div class="dashboard-activity-background-overlay">
|
||||
% if data['channel_stream'] == '0':
|
||||
<div id="background-${sk}" class="dashboard-activity-background" style="background-image: url(pms_image_proxy?img=${data['art']}&width=500&height=280&fallback=art&refresh=true);"></div>
|
||||
% else:
|
||||
% if (data['art'] and data['art'].startswith('http')) or (data['thumb'] and data['thumb'].startswith('http')):
|
||||
<div id="background-${sk}" class="dashboard-activity-background" style="background-image: url(${data['art']});"></div>
|
||||
% else:
|
||||
<!--Hacky solution to escape the image url until I come up with something better-->
|
||||
<div id="background-${sk}" class="dashboard-activity-background" style="background-image: url(pms_image_proxy?img=${quote(data['art'] or data['thumb'])}&width=500&height=280&fallback=art&refresh=true&clip=true);"></div>
|
||||
% endif
|
||||
% endif
|
||||
<%
|
||||
if data['channel_stream'] == 0:
|
||||
background_url = 'pms_image_proxy?img=' + data['art'] + '&width=500&height=280&opacity=40&background=282828&blur=3&fallback=art&refresh=true'
|
||||
else:
|
||||
if (data['art'] and data['art'].startswith('http')) or (data['thumb'] and data['thumb'].startswith('http')):
|
||||
background_url = data['art']
|
||||
else:
|
||||
background_url = 'pms_image_proxy?img=' + quote(data['art'] or data['thumb']) + '&width=500&height=280&fallback=art&refresh=true&clip=true'
|
||||
%>
|
||||
<div id="background-${sk}" class="dashboard-activity-background" style="background-image: url(${background_url});">
|
||||
<div class="dashboard-activity-poster-container hidden-xs">
|
||||
% if data['media_type'] == 'track':
|
||||
<div id="poster-${sk}-bg" class="dashboard-activity-poster-blur" style="background-image: url(pms_image_proxy?img=${data['parent_thumb']}&width=300&height=300&fallback=cover&refresh=true);"></div>
|
||||
<div id="poster-${sk}-bg" class="dashboard-activity-poster" style="background-image: url(pms_image_proxy?img=${data['parent_thumb']}&width=300&height=300&opacity=60&background=282828&blur=3&fallback=cover&refresh=true);"></div>
|
||||
% endif
|
||||
% if data['channel_stream'] == '0':
|
||||
% if data['channel_stream'] == 0:
|
||||
% if data['media_type'] == 'movie':
|
||||
<a id="poster-url-${sk}" href="info?rating_key=${data['rating_key']}" title="${data['title']}">
|
||||
<a id="poster-url-${sk}" href="${href}" title="${data['title']}">
|
||||
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(pms_image_proxy?img=${data['thumb']}&width=300&height=450&fallback=poster&refresh=true);"></div>
|
||||
</a>
|
||||
% elif data['media_type'] == 'episode':
|
||||
<a id="poster-url-${sk}" href="info?rating_key=${data['grandparent_rating_key']}" title="${data['grandparent_title']}">
|
||||
<a id="poster-url-${sk}" href="${grandparent_href}" title="${data['grandparent_title']}">
|
||||
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(pms_image_proxy?img=${data['grandparent_thumb']}&width=300&height=450&fallback=poster&refresh=true);"></div>
|
||||
</a>
|
||||
% elif data['media_type'] == 'track':
|
||||
<a id="poster-url-${sk}" href="info?rating_key=${data['parent_rating_key']}" title="${data['parent_title']}">
|
||||
<a id="poster-url-${sk}" href="${parent_href}" title="${data['parent_title']}">
|
||||
<div id="poster-${sk}" class="dashboard-activity-cover" style="background-image: url(pms_image_proxy?img=${data['parent_thumb']}&width=300&height=300&fallback=cover&refresh=true);"></div>
|
||||
</a>
|
||||
% elif data['media_type'] in ('photo', 'clip'):
|
||||
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(pms_image_proxy?img=${data['parent_thumb']}&width=300&height=450&fallback=poster&refresh=true);"></div>
|
||||
% if data['extra_type']:
|
||||
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(pms_image_proxy?img=${data['art'].replace('/art', '/thumb') or data['thumb']}&width=300&height=450&fallback=poster&refresh=true);"></div>
|
||||
% else:
|
||||
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(pms_image_proxy?img=${data['parent_thumb'] or data['thumb']}&width=300&height=450&fallback=poster&refresh=true);"></div>
|
||||
% endif
|
||||
% else:
|
||||
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(images/art.png);"></div>
|
||||
% endif
|
||||
@@ -109,7 +120,7 @@ DOCUMENTATION :: END
|
||||
<div id="poster-${sk}" class="dashboard-activity-poster-blur" style="background-image: url(${data['channel_icon']});"></div>
|
||||
<div id="poster-${sk}" class="dashboard-activity-cover" style="background-image: url(${data['channel_icon']});"></div>
|
||||
% else:
|
||||
<div id="poster-${sk}" class="dashboard-activity-poster-blur" style="background-image: url(pms_image_proxy?img=${data['channel_icon']}&width=300&height=300&fallback=cover&refresh=true);"></div>
|
||||
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(pms_image_proxy?img=${data['channel_icon']}&width=300&height=300&opacity=60&background=282828&blur=3&fallback=cover&refresh=true);"></div>
|
||||
<div id="poster-${sk}" class="dashboard-activity-cover" style="background-image: url(pms_image_proxy?img=${data['channel_icon']}&width=300&height=300&fallback=cover&refresh=true);"></div>
|
||||
% endif
|
||||
% endif
|
||||
@@ -134,15 +145,15 @@ DOCUMENTATION :: END
|
||||
<ul class="list-unstyled dashboard-activity-info-list">
|
||||
<li class="dashboard-activity-info-item">
|
||||
<div class="sub-heading">Product</div>
|
||||
<div class="sub-value">${data['product']}</div>
|
||||
<div class="sub-value platform-right">${data['product']}</div>
|
||||
</li>
|
||||
<li class="dashboard-activity-info-item">
|
||||
<div class="sub-heading">Player</div>
|
||||
<div class="sub-value">${data['player']}</div>
|
||||
<div class="sub-value platform-right">${data['player']}</div>
|
||||
</li>
|
||||
<li class="dashboard-activity-info-item">
|
||||
<div class="sub-heading">Quality</div>
|
||||
<div class="sub-value" id="stream_quality-${sk}">
|
||||
<div class="sub-value platform-right" id="stream_quality-${sk}">
|
||||
% if data['media_type'] != 'photo' and data['quality_profile'] != 'Unknown':
|
||||
<%
|
||||
br = helpers.cast_to_int(data['stream_bitrate']) or ''
|
||||
@@ -158,11 +169,19 @@ DOCUMENTATION :: END
|
||||
% endif
|
||||
</div>
|
||||
</li>
|
||||
% if data['optimized_version'] == '1':
|
||||
% if data['optimized_version'] == 1:
|
||||
<li class="dashboard-activity-info-item">
|
||||
<div class="sub-heading">Optimized</div>
|
||||
<div class="sub-value" id="optimized_version-${sk}">
|
||||
${data['optimized_version_profile']}
|
||||
${data['optimized_version_profile']} (${data['optimized_version_title']})
|
||||
</div>
|
||||
</li>
|
||||
% endif
|
||||
% if data['synced_version'] == 1:
|
||||
<li class="dashboard-activity-info-item">
|
||||
<div class="sub-heading">Synced</div>
|
||||
<div class="sub-value" id="synced_version-${sk}">
|
||||
${data['synced_version_profile']}
|
||||
</div>
|
||||
</li>
|
||||
% endif
|
||||
@@ -170,14 +189,14 @@ DOCUMENTATION :: END
|
||||
<ul class="list-unstyled dashboard-activity-info-list">
|
||||
<li class="dashboard-activity-info-item">
|
||||
% if _session['user_group'] == 'admin':
|
||||
<div class="sub-heading"><span class="raw-stream-info-modal" data-toggle="modal" data-target="#raw-stream-info-modal" data-key="${sk}">Stream</span></div>\
|
||||
<div class="sub-heading"><span class="raw-stream-info-modal" data-toggle="modal" data-target="#raw-stream-info-modal" data-key="${sk}">Stream</span></div>
|
||||
% else:
|
||||
<div class="sub-heading">Stream</div>
|
||||
% endif
|
||||
<div class="sub-value" id="transcode_decision-${sk}">
|
||||
% if data['transcode_decision'] == 'transcode':
|
||||
Transcode
|
||||
% if data['transcode_throttled'] == '1':
|
||||
% if data['transcode_throttled'] == 1:
|
||||
(Throttled)
|
||||
% else:
|
||||
(Speed: ${data['transcode_speed']})
|
||||
@@ -185,15 +204,15 @@ DOCUMENTATION :: END
|
||||
% elif data['transcode_decision'] == 'copy':
|
||||
Direct Stream
|
||||
% else:
|
||||
Direct Play ${'(Synced)' if data['synced_version'] == '1' else ''}
|
||||
Direct Play
|
||||
% endif
|
||||
</div>
|
||||
</li>
|
||||
<li class="dashboard-activity-info-item">
|
||||
<div class="sub-heading">Container</div>
|
||||
<div class="sub-value" id="transcode_container-${sk}">
|
||||
% if data.get('stream_container_decision') == 'transcode':
|
||||
Transcode (${data['container'].upper()} → ${data['stream_container'].upper()})
|
||||
% if data['stream_container_decision'] == 'transcode':
|
||||
Transcode (${data['container'].upper()} <i class="fa fa-long-arrow-right"></i> ${data['stream_container'].upper()})
|
||||
% else:
|
||||
Direct Play (${data['container'].upper()})
|
||||
% endif
|
||||
@@ -204,19 +223,16 @@ DOCUMENTATION :: END
|
||||
<div class="sub-heading">Video</div>
|
||||
<div class="sub-value" id="video_decision-${sk}">
|
||||
% if data['media_type'] in ('movie', 'episode', 'clip'):
|
||||
% if data.get('stream_video_decision') == 'transcode':
|
||||
% if data['stream_video_decision'] == 'transcode':
|
||||
<%
|
||||
hw_d = hw_e = ''
|
||||
if data['transcode_hw_requested'] == '1' and data['transcode_hw_full_pipeline'] == '0':
|
||||
hw_d = ' (HW)'
|
||||
elif data['transcode_hw_requested'] == '1' and data['transcode_hw_full_pipeline'] == '1':
|
||||
hw_d = hw_e = ' (HW)'
|
||||
hw_d = ' (HW)' if data['transcode_hw_decoding'] else ''
|
||||
hw_e = ' (HW)' if data['transcode_hw_encoding'] else ''
|
||||
%>
|
||||
Transcode (${data['video_codec'].upper()}${hw_d} ${plexpy.common.VIDEO_RESOLUTION_OVERRIDES.get(data['video_resolution'], data['video_resolution'])} → ${data['stream_video_codec'].upper()}${hw_e} ${plexpy.common.VIDEO_RESOLUTION_OVERRIDES.get(data['stream_video_resolution'], data['stream_video_resolution'])})
|
||||
% elif data.get('stream_video_decision') == 'copy':
|
||||
Direct Stream (${data['stream_video_codec'].upper()} ${plexpy.common.VIDEO_RESOLUTION_OVERRIDES.get(data['stream_video_resolution'], data['stream_video_resolution'])})
|
||||
Transcode (${data['video_codec'].upper()}${hw_d} ${VIDEO_RESOLUTION_OVERRIDES.get(data['video_resolution'], data['video_resolution'])} <i class="fa fa-long-arrow-right"></i> ${data['stream_video_codec'].upper()}${hw_e} ${VIDEO_RESOLUTION_OVERRIDES.get(data['stream_video_resolution'], data['stream_video_resolution'])})
|
||||
% elif data['stream_video_decision'] == 'copy':
|
||||
Direct Stream (${data['stream_video_codec'].upper()} ${VIDEO_RESOLUTION_OVERRIDES.get(data['stream_video_resolution'], data['stream_video_resolution'])})
|
||||
% else:
|
||||
Direct Play (${data['video_codec'].upper()} ${plexpy.common.VIDEO_RESOLUTION_OVERRIDES.get(data['video_resolution'], data['video_resolution'])})
|
||||
Direct Play (${data['video_codec'].upper()} ${VIDEO_RESOLUTION_OVERRIDES.get(data['video_resolution'], data['video_resolution'])})
|
||||
% endif
|
||||
% elif data['media_type'] == 'photo':
|
||||
Direct Play (${data['width']}x${data['height']})
|
||||
@@ -228,12 +244,12 @@ DOCUMENTATION :: END
|
||||
<li class="dashboard-activity-info-item">
|
||||
<div class="sub-heading">Audio</div>
|
||||
<div class="sub-value" id="audio_decision-${sk}">
|
||||
% if data.get('stream_audio_decision') == 'transcode':
|
||||
Transcode (${plexpy.common.AUDIO_CODEC_OVERRIDES.get(data['audio_codec'], data['audio_codec'].upper())} ${data['audio_channel_layout'].split('(')[0].capitalize()} → ${plexpy.common.AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()})
|
||||
% elif data.get('stream_audio_decision') == 'copy':
|
||||
Direct Stream (${plexpy.common.AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()})
|
||||
% if data['stream_audio_decision'] == 'transcode':
|
||||
Transcode (${AUDIO_CODEC_OVERRIDES.get(data['audio_codec'], data['audio_codec'].upper())} ${data['audio_channel_layout'].split('(')[0].capitalize()} <i class="fa fa-long-arrow-right"></i> ${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()})
|
||||
% elif data['stream_audio_decision'] == 'copy':
|
||||
Direct Stream (${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()})
|
||||
% else:
|
||||
Direct Play (${plexpy.common.AUDIO_CODEC_OVERRIDES.get(data['audio_codec'], data['audio_codec'].upper())} ${data['audio_channel_layout'].split('(')[0].capitalize()})
|
||||
Direct Play (${AUDIO_CODEC_OVERRIDES.get(data['audio_codec'], data['audio_codec'].upper())} ${data['audio_channel_layout'].split('(')[0].capitalize()})
|
||||
% endif
|
||||
</div>
|
||||
</li>
|
||||
@@ -242,15 +258,15 @@ DOCUMENTATION :: END
|
||||
<li class="dashboard-activity-info-item">
|
||||
<div class="sub-heading">Subtitle</div>
|
||||
<div class="sub-value" id="subtitle_decision-${sk}">
|
||||
% if data['subtitles'] == '1':
|
||||
% if data['subtitles'] == 1:
|
||||
% if data['stream_subtitle_decision'] == 'transcode':
|
||||
Transcode (${data['subtitle_codec'].upper()} → ${data['stream_subtitle_codec'].upper()})
|
||||
Transcode (${data['subtitle_codec'].upper()} <i class="fa fa-long-arrow-right"></i> ${data['stream_subtitle_codec'].upper()})
|
||||
% elif data['stream_subtitle_decision'] == 'copy':
|
||||
Direct Stream (${data['subtitle_codec'].upper()})
|
||||
% elif data['stream_subtitle_decision'] == 'burn':
|
||||
Burn (${data['subtitle_codec'].upper()})
|
||||
% else:
|
||||
Direct Play (${data['subtitle_codec'].upper()})
|
||||
Direct Play (${data['stream_subtitle_codec'].upper() if data['synced_version'] else data['subtitle_codec'].upper()})
|
||||
% endif
|
||||
% else:
|
||||
None
|
||||
@@ -262,19 +278,24 @@ DOCUMENTATION :: END
|
||||
<ul class="list-unstyled dashboard-activity-info-list">
|
||||
<li class="dashboard-activity-info-item">
|
||||
<div class="sub-heading">Location</div>
|
||||
<div class="sub-value">
|
||||
<div class="sub-value time-right">
|
||||
<span id="location-${sk}">${data['location'].upper()}</span>:
|
||||
% if data['ip_address'] != 'N/A':
|
||||
${'LAN' if data['local'] == '1' else 'WAN'}: ${data['ip_address']}
|
||||
<a href="#" class="external_ip-modal" data-toggle="modal" data-target="#ip-info-modal" data-ip="${data['ip_address']}">
|
||||
<span id="external_ip-${sk}" class="external-ip-tooltip" data-toggle="tooltip" title="Lookup IP" style="display: none;"><i class="fa fa-map-marker"></i></span>
|
||||
</a>
|
||||
<script>
|
||||
isPrivateIP("${data['ip_address']}").then(function () {
|
||||
$("#external_ip-${sk}").hide();
|
||||
}, function () {
|
||||
$("#external_ip-${sk}").show();
|
||||
});
|
||||
</script>
|
||||
<span class="ip-container"><span class="ip-address">${data['ip_address']}</span></span>
|
||||
% if data['relay']:
|
||||
<span data-toggle="tooltip" title="Plex Relay"><i class="fa fa-exclamation-circle"></i></span>
|
||||
% else:
|
||||
<a href="#" class="external_ip-modal" data-toggle="modal" data-target="#ip-info-modal" data-ip="${data['ip_address']}">
|
||||
<span id="external_ip-${sk}" class="external-ip-tooltip" data-toggle="tooltip" title="Lookup External IP" style="display: none;"><i class="fa fa-map-marker"></i></span>
|
||||
</a>
|
||||
<script>
|
||||
isPrivateIP("${data['ip_address']}").then(function () {
|
||||
$("#external_ip-${sk}").hide();
|
||||
}, function () {
|
||||
$("#external_ip-${sk}").show();
|
||||
});
|
||||
</script>
|
||||
% endif
|
||||
% else:
|
||||
N/A
|
||||
% endif
|
||||
@@ -282,19 +303,18 @@ DOCUMENTATION :: END
|
||||
</li>
|
||||
<li class="dashboard-activity-info-item">
|
||||
<div class="sub-heading">Bandwidth</div>
|
||||
<div class="sub-value">
|
||||
% if data['media_type'] != 'photo' and helpers.cast_to_int(data['bandwidth']):
|
||||
<div class="sub-value time-right">
|
||||
% if data['media_type'] != 'photo' and data['bandwidth'] != 'Unknown':
|
||||
<%
|
||||
bw = helpers.cast_to_int(data['bandwidth'])
|
||||
if bw != "Unknown":
|
||||
if bw > 1000:
|
||||
bw = str(round(bw / 1000.0, 1)) + ' Mbps'
|
||||
else:
|
||||
bw = str(bw) + ' kbps'
|
||||
if bw > 1000:
|
||||
bw = str(round(bw / 1000.0, 1)) + ' Mbps'
|
||||
else:
|
||||
bw = str(bw) + ' kbps'
|
||||
%>
|
||||
<span id="stream-bandwidth-${sk}">${bw}</span>
|
||||
<span id="streaming-brain-${sk}" data-toggle="tooltip" title="Streaming Brain Estimate"><i class="fa fa-info-circle"></i></span>
|
||||
% elif data['synced_version'] == '1' or data['channel_stream'] == '1':
|
||||
<span id="streaming-brain-${sk}" data-toggle="tooltip" title="Streaming Brain Estimate (Required Bandwidth)"><i class="fa fa-info-circle"></i></span>
|
||||
% elif data['synced_version'] == 1 or data['channel_stream'] == 1:
|
||||
<span id="stream-bandwidth-${sk}">None</span>
|
||||
% else:
|
||||
<span id="stream-bandwidth-${sk}">Unknown</span>
|
||||
@@ -306,7 +326,9 @@ DOCUMENTATION :: END
|
||||
</div>
|
||||
% if data['media_type'] != 'photo':
|
||||
<div class="dashboard-activity-info-time">
|
||||
% if data['view_offset']:
|
||||
% if data['live'] == 1:
|
||||
<br />Live
|
||||
% elif data['view_offset']:
|
||||
ETA:
|
||||
<span id="stream-eta-${sk}">
|
||||
<script>
|
||||
@@ -334,19 +356,19 @@ DOCUMENTATION :: END
|
||||
</div>
|
||||
<div class="dashboard-activity-progress">
|
||||
<div class="dashboard-activity-progress-bar">
|
||||
% if data['live'] == 1:
|
||||
<div id="progress-bar-${sk}" class="progress-bar" style="width: 100%" data-toggle="tooltip" title="Stream Progress Live">Live</div>
|
||||
% else:
|
||||
<div id="buffer-bar-${sk}" class="buffer-bar" style="width: ${data['transcode_progress']}%" data-toggle="tooltip" title="Transcoder Progress ${data['transcode_progress']}%">${data['transcode_progress']}%</div>
|
||||
<div id="progress-bar-${sk}" class="progress-bar" style="width: ${data['progress_percent']}%" data-last_view_offset="${data['view_offset']}" data-view_offset="${data['view_offset']}" data-stream_duration="${data['stream_duration']}" data-state="${data['state']}" data-toggle="tooltip" title="Stream Progress ${data['progress_percent']}%">${data['progress_percent']}%</div>
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard-activity-metadata-wrapper">
|
||||
% if data['user_id']:
|
||||
<a href="user?user_id=${data['user_id']}" title="${data['friendly_name']}">
|
||||
<a href="${user_href}" title="${data['friendly_name']}">
|
||||
<div class="dashboard-activity-metadata-user-thumb" style="background-image: url(${data['user_thumb']});"></div>
|
||||
</a>
|
||||
% else:
|
||||
<div class="dashboard-activity-metadata-user-thumb" style="background-image: url(${data['user_thumb']});"></div>
|
||||
% endif
|
||||
<div class="dashboard-activity-metadata-title-container">
|
||||
<div id="play-state-${sk}" class="dashboard-activity-metadata-play_state-icon" title="${data['state'].capitalize()}">
|
||||
% if data['state'] == 'playing':
|
||||
@@ -358,22 +380,22 @@ DOCUMENTATION :: END
|
||||
% endif
|
||||
</div>
|
||||
<div class="dashboard-activity-metadata-title">
|
||||
% if data['channel_stream'] == '0':
|
||||
% if data['media_type'] == 'movie':
|
||||
<a href="info?rating_key=${data['rating_key']}" title="${data['title']}">${data['title']}</a>
|
||||
% elif data['media_type'] == 'episode':
|
||||
<a href="info?rating_key=${data['grandparent_rating_key']}" title="${data['grandparent_title']}">${data['grandparent_title']}</a>
|
||||
- <a href="info?rating_key=${data['rating_key']}" title="${data['title']}">${data['title']}</a>
|
||||
% elif data['media_type'] == 'track':
|
||||
<a id="metadata-grandparent_title-${sk}" href="info?rating_key=${data['grandparent_rating_key']}" title="${data['grandparent_title']}">${data['grandparent_title']}</a>
|
||||
- <a id="metadata-title-${sk}" href="info?rating_key=${data['rating_key']}" title="${data['title']}">${data['title']}</a>
|
||||
% elif data['media_type'] == 'photo':
|
||||
<span title="${data['parent_title']}">${data['parent_title']}</span>
|
||||
% elif data['media_type'] == 'clip':
|
||||
<span title="${data['title']}">${data['title']}</span>
|
||||
% else:
|
||||
<span title="${data['title']}">${data['title']}</span>
|
||||
% endif
|
||||
% if data['channel_stream'] == 0:
|
||||
% if data['media_type'] == 'movie':
|
||||
<a href="${href}" title="${data['title']}">${data['title']}</a>
|
||||
% elif data['media_type'] == 'episode':
|
||||
<a href="${grandparent_href}" title="${data['grandparent_title']}">${data['grandparent_title']}</a>
|
||||
- <a href="${href}" title="${data['title']}">${data['title']}</a>
|
||||
% elif data['media_type'] == 'track':
|
||||
<a id="metadata-grandparent_title-${sk}" href="${grandparent_href}" title="${data['grandparent_title']}">${data['grandparent_title']}</a>
|
||||
- <a id="metadata-title-${sk}" href="${href}" title="${data['title']}">${data['title']}</a>
|
||||
% elif data['media_type'] == 'photo':
|
||||
<span title="${data['parent_title']}">${data['parent_title']}</span>
|
||||
% elif data['media_type'] == 'clip':
|
||||
<span title="${data['title']}">${data['title']}</span>
|
||||
% else:
|
||||
<span title="${data['title']}">${data['title']}</span>
|
||||
% endif
|
||||
% elif data['media_type'] == 'episode' and data['grandparent_title']:
|
||||
<span title="${data['grandparent_title']}">${data['grandparent_title']}</span>
|
||||
- <span title="${data['title']}">${data['title']}</span>
|
||||
@@ -383,7 +405,11 @@ DOCUMENTATION :: END
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard-activity-metadata-subtitle-container">
|
||||
% if data['channel_stream'] == '0':
|
||||
% if data['live'] == 1:
|
||||
<div id="media-type-${sk}" class="dashboard-activity-metadata-media_type-icon" title="Plex Live TV">
|
||||
<i class="fa fa-fw fa-television"></i>
|
||||
</div>
|
||||
% elif data['channel_stream'] == 0:
|
||||
<div id="media-type-${sk}" class="dashboard-activity-metadata-media_type-icon" title="${data['media_type'].capitalize()}">
|
||||
% if data['media_type'] == 'movie':
|
||||
<i class="fa fa-fw fa-film"></i>
|
||||
@@ -398,23 +424,30 @@ DOCUMENTATION :: END
|
||||
% endif
|
||||
</div>
|
||||
% else:
|
||||
<div id="media-type-${sk}" title="Channel">
|
||||
<div id="media-type-${sk}" class="dashboard-activity-metadata-media_type-icon" title="Channel">
|
||||
<i class="fa fa-fw fa-cloud"></i>
|
||||
</div>
|
||||
% endif
|
||||
<div class="dashboard-activity-metadata-subtitle">
|
||||
% if data['channel_stream'] == '0':
|
||||
% if data['live'] == 1:
|
||||
<span title="Plex Live TV" class="sub-heading">Plex Live TV</span>
|
||||
% elif data['channel_stream'] == 0:
|
||||
% if data['media_type'] == 'movie':
|
||||
<span title="${data['year']}" class="sub-heading">${data['year']}</span>
|
||||
% elif data['media_type'] == 'episode':
|
||||
<a href="info?rating_key=${data['parent_rating_key']}" title="Season ${data['parent_media_index']}" class="sub-heading">S${data['parent_media_index']}</a>
|
||||
· <a href="info?rating_key=${data['rating_key']}" title="Episode ${data['media_index']}" class="sub-heading">E${data['media_index']}</a>
|
||||
<a href="${parent_href}" title="Season ${data['parent_media_index']}" class="sub-heading">S${data['parent_media_index']}</a>
|
||||
· <a href="${href}" title="Episode ${data['media_index']}" class="sub-heading">E${data['media_index']}</a>
|
||||
% elif data['media_type'] == 'track':
|
||||
<a id="metadata-parent_title-${sk}" href="info?rating_key=${data['parent_rating_key']}" title="${data['parent_title']}" class="sub-heading">${data['parent_title']}</a>
|
||||
<a id="metadata-parent_title-${sk}" href="${parent_href}" title="${data['parent_title']}" class="sub-heading">${data['parent_title']}</a>
|
||||
% elif data['media_type'] == 'photo':
|
||||
<span title="${data['title']}" class="sub-heading">${data['title']}</span>
|
||||
% else:
|
||||
<span title="${data['year']}" class="sub-heading">${data['year']}</span>
|
||||
% if data['extra_type']:
|
||||
<% extra_type = EXTRA_TYPES.get(data['extra_type'], data['sub_type'].capitalize()) %>
|
||||
<span title="${data['year']} (${extra_type})" class="sub-heading">${data['year']} (${extra_type})</span>
|
||||
% else:
|
||||
<span title="${data['year']}" class="sub-heading">${data['year']}</span>
|
||||
% endif
|
||||
% endif
|
||||
% elif data['channel_title']:
|
||||
<span title="${data['channel_title']}" class="sub-heading">${data['channel_title']}</span>
|
||||
@@ -435,11 +468,7 @@ DOCUMENTATION :: END
|
||||
% endif
|
||||
</div>
|
||||
<div class="dashboard-activity-metadata-user">
|
||||
% if data['user_id']:
|
||||
<a href="user?user_id=${data['user_id']}" title="${data['friendly_name']}">${data['friendly_name']}</a>
|
||||
% else:
|
||||
${data['friendly_name']}
|
||||
% endif
|
||||
<a href="${user_href}" title="${data['friendly_name']}">${data['friendly_name']}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -47,24 +47,12 @@ DOCUMENTATION :: END
|
||||
</div>
|
||||
<p class="help-block">Change the library's picture in Tautulli. To reset to default, leave this field empty and save.</p>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" id="do_notify" name="do_notify" value="1" ${helpers.checked(data['do_notify'])}> Enable notifications
|
||||
</label>
|
||||
<p class="help-block">Uncheck this if you do not want to receive notifications for this library's activity.</p>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" id="keep_history" name="keep_history" value="1" ${helpers.checked(data['keep_history'])}> Keep history
|
||||
</label>
|
||||
<p class="help-block">Uncheck this if you do not want to keep any history on this library's activity.</p>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" id="do_notify_created" name="do_notify_created" value="1" ${helpers.checked(data['do_notify_created'])}> Enable recently added notifications
|
||||
</label>
|
||||
<p class="help-block">Uncheck this if you do not want to receive recently added notifications for this library.</p>
|
||||
</div>
|
||||
% if data['section_id']:
|
||||
<div class="form-group">
|
||||
<button class="btn btn-danger" id="delete-all-history">Purge</button>
|
||||
@@ -85,15 +73,7 @@ DOCUMENTATION :: END
|
||||
// Save library options
|
||||
$("#save_library").on('click', function () {
|
||||
var custom_thumb = $("#custom_thumb_url").val();
|
||||
var do_notify = 0;
|
||||
var do_notify_created = 0;
|
||||
var keep_history = 0;
|
||||
if ($("#do_notify").is(":checked")) {
|
||||
do_notify = 1;
|
||||
}
|
||||
if ($("#do_notify_created").is(":checked")) {
|
||||
do_notify_created = 1;
|
||||
}
|
||||
if ($("#keep_history").is(":checked")) {
|
||||
keep_history = 1;
|
||||
}
|
||||
@@ -103,8 +83,6 @@ DOCUMENTATION :: END
|
||||
data: {
|
||||
section_id: '${data["section_id"]}',
|
||||
custom_thumb: custom_thumb,
|
||||
do_notify: do_notify,
|
||||
do_notify_created: do_notify_created,
|
||||
keep_history: keep_history
|
||||
},
|
||||
cache: false,
|
||||
|
@@ -56,12 +56,6 @@ DOCUMENTATION :: END
|
||||
</div>
|
||||
<p class="help-block">Change the users profile picture in Tautulli. To reset to default, leave this field empty and save.</p>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" id="do_notify" name="do_notify" value="1" ${helpers.checked(data['do_notify'])}> Enable notifications
|
||||
</label>
|
||||
<p class="help-block">Uncheck this if you do not want to receive notifications for this user's activity.</p>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" id="keep_history" name="keep_history" value="1" ${helpers.checked(data['keep_history'])}> Keep history
|
||||
@@ -95,12 +89,8 @@ DOCUMENTATION :: END
|
||||
$("#save_user").on('click', function () {
|
||||
var friendly_name = $("input#friendly_name").val();
|
||||
var custom_thumb = $("#custom_avatar_url").val();
|
||||
var do_notify = 0;
|
||||
var keep_history = 0;
|
||||
var allow_guest = 0;
|
||||
if ($("#do_notify").is(":checked")) {
|
||||
do_notify = 1;
|
||||
}
|
||||
if ($("#keep_history").is(":checked")) {
|
||||
keep_history = 1;
|
||||
}
|
||||
@@ -114,7 +104,6 @@ DOCUMENTATION :: END
|
||||
user_id: '${data["user_id"]}',
|
||||
friendly_name: friendly_name,
|
||||
custom_thumb: custom_thumb,
|
||||
do_notify: do_notify,
|
||||
keep_history: keep_history,
|
||||
allow_guest: allow_guest
|
||||
},
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
<%def name="headIncludes()">
|
||||
<link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css">
|
||||
<link rel="stylesheet" href="${http_root}css/plexpy-dataTables.css">
|
||||
<link rel="stylesheet" href="${http_root}css/tautulli-dataTables.css">
|
||||
</%def>
|
||||
|
||||
<%def name="body()">
|
||||
@@ -11,7 +11,7 @@
|
||||
<div class="header-bar">
|
||||
<span><i class="fa fa-bar-chart"></i> Graphs</span>
|
||||
</div>
|
||||
<div class="button-bar hidden-xs">
|
||||
<div class="button-bar">
|
||||
<div class="btn-group" id="user-selection">
|
||||
<label>
|
||||
<select name="graph-user" id="graph-user" class="btn" style="color: inherit;">
|
||||
@@ -39,12 +39,12 @@
|
||||
</div>
|
||||
<div class="input-group pull-right" style="width: 1px;" id="days-selection">
|
||||
<span class="input-group-addon btn-dark inactive">Last</span>
|
||||
<input type="number" class="form-control" name="graph-days" id="graph-days" value="${config['graph_days']}" min="1" data-default="7" data-toggle="tooltip" title="Min: 1 day" />
|
||||
<input type="number" class="form-control number-input" name="graph-days" id="graph-days" value="${config['graph_days']}" min="1" data-default="7" data-toggle="tooltip" title="Min: 1 day" />
|
||||
<span class="input-group-addon btn-dark inactive">days</span>
|
||||
</div>
|
||||
<div class="input-group pull-right" style="width: 1px;" id="months-selection">
|
||||
<span class="input-group-addon btn-dark inactive">Last</span>
|
||||
<input type="number" class="form-control" name="graph-months" id="graph-months" value="${config['graph_months']}" min="1" data-default="12" data-toggle="tooltip" title="Min: 1 month" />
|
||||
<input type="number" class="form-control number-input" name="graph-months" id="graph-months" value="${config['graph_months']}" min="1" data-default="12" data-toggle="tooltip" title="Min: 1 month" />
|
||||
<span class="input-group-addon btn-dark inactive">months</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -3,7 +3,7 @@
|
||||
<%def name="headIncludes()">
|
||||
<link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css">
|
||||
<link rel="stylesheet" href="${http_root}css/dataTables.colVis.css">
|
||||
<link rel="stylesheet" href="${http_root}css/plexpy-dataTables.css">
|
||||
<link rel="stylesheet" href="${http_root}css/tautulli-dataTables.css">
|
||||
</%def>
|
||||
|
||||
<%def name="body()">
|
||||
@@ -113,8 +113,8 @@
|
||||
// Load user ids and names (for the selector)
|
||||
$.ajax({
|
||||
url: 'get_user_names',
|
||||
type: 'get',
|
||||
dataType: "json",
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
success: function (data) {
|
||||
var select = $('#history-user');
|
||||
data.sort(function (a, b) {
|
||||
@@ -130,7 +130,7 @@
|
||||
function loadHistoryTable(media_type, selected_user_id) {
|
||||
history_table_options.ajax = {
|
||||
url: 'get_history',
|
||||
type: 'post',
|
||||
type: 'POST',
|
||||
data: function (d) {
|
||||
return {
|
||||
json_data: JSON.stringify(d),
|
||||
@@ -138,9 +138,13 @@
|
||||
user_id: selected_user_id
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
history_table = $('#history_table').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, 11] });
|
||||
var colvis = new $.fn.dataTable.ColVis(history_table, {
|
||||
buttonText: '<i class="fa fa-columns"></i> Select columns',
|
||||
buttonClass: 'btn btn-dark',
|
||||
exclude: [0, 11]
|
||||
});
|
||||
$(colvis.button()).appendTo('div.colvis-button-bar');
|
||||
|
||||
clearSearchButton('history_table', history_table);
|
||||
@@ -160,7 +164,7 @@
|
||||
}
|
||||
|
||||
var media_type = null;
|
||||
var selected_user_id = "${_session['user_id']}" == "None" ? null : "${_session['user_id']}"
|
||||
var selected_user_id = "${_session['user_group']}" == "admin" ? null : "${_session['user_id']}";
|
||||
loadHistoryTable(media_type, selected_user_id);
|
||||
|
||||
% if _session['user_group'] == 'admin':
|
||||
|
@@ -5,7 +5,15 @@
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
|
||||
<h4 class="modal-title" id="myModalLabel">
|
||||
<strong><span id="modal_header_ip_address">
|
||||
% if data.get('media_type'):
|
||||
<% h = {'episode': 'TV Show', 'track': 'Music'} %>
|
||||
<i class="fa fa-history"></i> ${h.get(data['media_type'], data['media_type'].title())} History for <span id="date-header">${data['start_date']}</span>
|
||||
% elif data.get('transcode_decision'):
|
||||
<% h = {'copy': 'Direct Stream'} %>
|
||||
<i class="fa fa-history"></i> ${h.get(data['transcode_decision'], data['transcode_decision'].title())} History for <span id="date-header">${data['start_date']}</span>
|
||||
% else:
|
||||
<i class="fa fa-history"></i> History for <span id="date-header">${data['start_date']}</span>
|
||||
% endif
|
||||
</span></strong>
|
||||
</h4>
|
||||
</div>
|
||||
@@ -13,11 +21,18 @@
|
||||
<table class="display history_table" id="history_table_modal" width="100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th align="left" id="started">Started</th>
|
||||
<th align="left" id="stopped">Stopped</th>
|
||||
<th align="left" id="delete_row">Delete</th>
|
||||
<th align="left" id="date">Date</th>
|
||||
<th align="left" id="friendly_name">User</th>
|
||||
<th align="left" id="player">Player</th>
|
||||
<th align="left" id="ip_address">IP Address</th>
|
||||
<th align="left" id="platform">Platform</th>
|
||||
<th align="left" id="device">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>
|
||||
@@ -28,28 +43,31 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="${http_root}js/tables/history_table_modal.js${cache_param}"></script>
|
||||
<script src="${http_root}js/tables/history_table.js${cache_param}"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('#date-header').html(moment('${data["start_date"]}','YYYY-MM-DD').format('ddd MMM Do YYYY'));
|
||||
history_table_modal_options.ajax = {
|
||||
history_table_options.ajax = {
|
||||
url: 'get_history',
|
||||
type: 'post',
|
||||
data: function ( d ) {
|
||||
return {
|
||||
json_data: JSON.stringify(d),
|
||||
grouping: false,
|
||||
user_id: "${data['user_id']}",
|
||||
start_date: "${data['start_date']}",
|
||||
media_type: "${data.get('media_type')}",
|
||||
transcode_decision: "${data.get('transcode_decision')}"
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
history_table = $('#history_table_modal').DataTable(history_table_options);
|
||||
history_table.columns([0, 3, 4, 8, 10, 11]).visible(false);
|
||||
|
||||
history_table = $('#history_table_modal').DataTable(history_table_modal_options);
|
||||
|
||||
clearSearchButton('history_table_modal', history_table);
|
||||
|
||||
$('#history-modal').on('shown.bs.modal', function() {
|
||||
history_table.columns.adjust().draw();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
% else:
|
||||
|
@@ -71,34 +71,35 @@ DOCUMENTATION :: END
|
||||
%>
|
||||
<div class="dashboard-stats-instance" id="stats-instance-${stat_id}" data-stat_id="${stat_id}">
|
||||
<div class="dashboard-stats-container">
|
||||
<div class="dashboard-stats-background-overlay">
|
||||
% if stat_id in ('top_movies', 'popular_movies', 'top_tv', 'popular_tv', 'top_music', 'popular_music', 'last_watched'):
|
||||
% if row0['art']:
|
||||
<div id="stats-background-${stat_id}" class="dashboard-stats-background" style="background-image: url(pms_image_proxy?img=${row0['art']}&width=500&height=280&fallback=art);"></div>
|
||||
% else:
|
||||
<div id="stats-background-${stat_id}" class="dashboard-stats-background" style="background-image: url(images/art.png);"></div>
|
||||
% endif
|
||||
% elif stat_id == 'top_platforms':
|
||||
<div id="stats-background-${stat_id}" class="dashboard-stats-background platform-${row0['platform_name']} no-image"></div>
|
||||
% else:
|
||||
<div id="stats-background-${stat_id}" class="dashboard-stats-background flat"></div>
|
||||
% endif
|
||||
% if stat_id in ('top_movies', 'popular_movies', 'top_tv', 'popular_tv', 'top_music', 'popular_music', 'last_watched'):
|
||||
% if row0['art']:
|
||||
<div id="stats-background-${stat_id}" class="dashboard-stats-background" style="background-image: url(pms_image_proxy?img=${row0['art']}&width=500&height=280&opacity=40&background=282828&blur=3&fallback=art);">
|
||||
% else:
|
||||
<div id="stats-background-${stat_id}" class="dashboard-stats-background" style="background-image: url(images/art.png);">
|
||||
% endif
|
||||
% elif stat_id == 'top_platforms':
|
||||
<div id="stats-background-${stat_id}" class="dashboard-stats-background platform-${row0['platform_name']}-rgba no-image">
|
||||
% else:
|
||||
<div id="stats-background-${stat_id}" class="dashboard-stats-background flat">
|
||||
% endif
|
||||
% if stat_id in ('top_movies', 'popular_movies', 'top_tv', 'popular_tv', 'top_music', 'popular_music', 'last_watched'):
|
||||
<div class="dashboard-stats-poster-container hidden-xs">
|
||||
% if stat_id in ('top_music', 'popular_music'):
|
||||
<div id="stats-thumb-${stat_id}-bg" class="dashboard-stats-poster-blur" style="background-image: url(pms_image_proxy?img=${row0['thumb']}&width=300&height=300&fallback=cover);"></div>
|
||||
<div id="stats-thumb-${stat_id}-bg" class="dashboard-stats-poster" style="background-image: url(pms_image_proxy?img=${row0['thumb']}&width=300&height=300&opacity=60&background=282828&blur=3&fallback=cover);"></div>
|
||||
% endif
|
||||
<a id="stats-thumb-url-${stat_id}" href="info?rating_key=${row0['rating_key']}" title="${row0['title']}">
|
||||
<% type = 'cover' if stat_id in ('top_music', 'popular_music') else 'poster' %>
|
||||
<% height, type = ('300', 'cover') if stat_id in ('top_music', 'popular_music') else ('450', 'poster') %>
|
||||
<% href = 'info?rating_key={}'.format(row0['rating_key']) if row0['rating_key'] else '#' %>
|
||||
<a id="stats-thumb-url-${stat_id}" href="${href}" title="${row0['title']}">
|
||||
% if row0['thumb']:
|
||||
<div id="stats-thumb-${stat_id}" class="dashboard-stats-${type}" style="background-image: url(pms_image_proxy?img=${row0['thumb']}&width=300&height=300&fallback=${type});"></div>
|
||||
<div id="stats-thumb-${stat_id}" class="dashboard-stats-${type}" style="background-image: url(pms_image_proxy?img=${row0['thumb']}&width=300&height=${height}&fallback=${type});"></div>
|
||||
% else:
|
||||
<div id="stats-thumb-${stat_id}" class="dashboard-stats-${type}" style="background-image: url(images/${type}.png);"></div>
|
||||
% endif
|
||||
</a>
|
||||
</div>
|
||||
% elif stat_id == 'top_users':
|
||||
<a id="stats-thumb-url-${stat_id}" href="user?user_id=${row0['user_id']}" title="${row0['friendly_name']}" class="hidden-xs">
|
||||
<% user_href = 'user?user_id={}'.format(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">
|
||||
<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':
|
||||
@@ -127,26 +128,20 @@ DOCUMENTATION :: END
|
||||
% for row in top_stat['rows']:
|
||||
<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-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_id="${row.get('user_id')}" 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')}">
|
||||
<div class="sub-list">${loop.index + 1}</div>
|
||||
<div class="sub-value">
|
||||
% if stat_id in ('top_movies', 'popular_movies', 'top_tv', 'popular_tv', 'top_music', 'popular_music', 'last_watched'):
|
||||
% if top_stat['rows'][loop.index]['rating_key']:
|
||||
<a href="info?rating_key=${row['rating_key']}" title="${row['title']}">
|
||||
<% href = 'info?rating_key={}'.format(row['rating_key']) if row['rating_key'] else '#' %>
|
||||
<a href="${href}" title="${row['title']}">
|
||||
${row['title']}
|
||||
</a>
|
||||
% else:
|
||||
${row['title']}
|
||||
% endif
|
||||
% elif stat_id == 'top_users':
|
||||
% if top_stat['rows'][loop.index]['user_id']:
|
||||
<a href="user?user_id=${row['user_id']}" title="${row['friendly_name']}">
|
||||
<% user_href = 'user?user_id={}'.format(row['user_id']) if row['user_id'] else '#' %>
|
||||
<a href="${user_href}" title="${row['friendly_name']}">
|
||||
${row['friendly_name']}
|
||||
</a>
|
||||
% else:
|
||||
${row['friendly_name']}
|
||||
% endif
|
||||
% elif stat_id == 'top_platforms':
|
||||
${row['platform']}
|
||||
% elif stat_id == 'most_concurrent':
|
||||
@@ -182,32 +177,47 @@ DOCUMENTATION :: END
|
||||
var stat_id = $(elem).data('stat_id');
|
||||
var art = $(elem).data('art');
|
||||
var thumb = $(elem).data('thumb');
|
||||
var user_id = $(elem).data('user_id');
|
||||
var user_thumb = $(elem).data('user_thumb');
|
||||
var rating_key = $(elem).data('rating_key');
|
||||
var [height, fallback] = ($.inArray(stat_id, ['top_music', 'popular_music']) > -1) ? [300, 'cover'] : [450, 'poster'];
|
||||
var href;
|
||||
|
||||
if (stat_id == 'most_concurrent') {
|
||||
return
|
||||
} else if (stat_id == 'top_users') {
|
||||
$('#stats-thumb-' + stat_id).css('background-image', 'url(' + (thumb || 'images/gravatar-default.png') + ')');
|
||||
$('#stats-thumb-url-' + stat_id).attr('href', 'user?user_id=' + $(elem).data('user_id')).prop('title', $(elem).data('friendly_name'));
|
||||
$('#stats-thumb-' + stat_id).css('background-image', 'url(' + (user_thumb || 'images/gravatar-default.png') + ')');
|
||||
if (user_id) {
|
||||
href = 'user?user_id=' + user_id;
|
||||
} else {
|
||||
href = '#';
|
||||
}
|
||||
$('#stats-thumb-url-' + stat_id).attr('href', href).prop('title', $(elem).data('friendly_name'));
|
||||
} else if (stat_id == 'top_platforms') {
|
||||
$('#stats-thumb-' + stat_id).removeClass(function (index, className) {
|
||||
return (className.match (/(^|\s)platform-\S+/g) || []).join(' ');
|
||||
}).addClass('platform-' + $(elem).data('platform'));
|
||||
$('#stats-background-' + stat_id).removeClass(function (index, className) {
|
||||
return (className.match (/(^|\s)platform-\S+/g) || []).join(' ');
|
||||
}).addClass('platform-' + $(elem).data('platform'));
|
||||
}).addClass('platform-' + $(elem).data('platform') + '-rgba');
|
||||
} else {
|
||||
$('#stats-thumb-url-' + stat_id).attr('href', 'info?rating_key=' + $(elem).data('rating_key')).prop('title', $(elem).data('title'));
|
||||
if (rating_key) {
|
||||
href = 'info?rating_key=' + rating_key;
|
||||
} else {
|
||||
href = '#';
|
||||
}
|
||||
$('#stats-thumb-url-' + stat_id).attr('href', href).prop('title', $(elem).data('title'));
|
||||
if (art) {
|
||||
$('#stats-background-' + stat_id).css('background-image', 'url(pms_image_proxy?img=' + art + '&width=500&height=280&fallback=art)');
|
||||
$('#stats-background-' + stat_id).css('background-image', 'url(pms_image_proxy?img=' + art + '&width=500&height=280&opacity=40&background=282828&blur=3&fallback=art)');
|
||||
} else {
|
||||
$('#stats-background-' + stat_id).css('background-image', 'url(images/art.png)');
|
||||
}
|
||||
if (thumb) {
|
||||
$('#stats-thumb-' + stat_id).css('background-image', 'url(pms_image_proxy?img=' + thumb + '&width=300&height=' + height + '&fallback=' + fallback + ')');
|
||||
$('#stats-thumb-' + stat_id + '-bg').css('background-image', 'url(pms_image_proxy?img=' + thumb + '&width=300&height=' + height + '&fallback=' + fallback + ')');
|
||||
$('#stats-thumb-' + stat_id + '-bg').css('background-image', 'url(pms_image_proxy?img=' + thumb + '&width=300&height=' + height + '&opacity=60&background=282828&blur=3&fallback=' + fallback + ')');
|
||||
} else {
|
||||
$('#stats-background-' + stat_id).css('background-image', 'url(images/' + fallback + '.png)');
|
||||
$('#stats-thumb-' + stat_id).css('background-image', 'url(images/' + fallback + '.png)');
|
||||
$('#stats-thumb-' + stat_id + '-bg').css('background-image', 'url(images/' + fallback + '.png)');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
BIN
data/interfaces/default/images/13AD46AC.png
Normal file
After Width: | Height: | Size: 9.9 KiB |
BIN
data/interfaces/default/images/13AD46AD.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
data/interfaces/default/images/93124B54.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
data/interfaces/default/images/93124B55.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
data/interfaces/default/images/become_a_patron_button.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 8.9 KiB |
Before Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 9.3 KiB |
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 3.3 KiB |
9
data/interfaces/default/images/favicon/browserconfig.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="${http_root}images/favicon/mstile-150x150.png?v=2.0.5"/>
|
||||
<TileColor>#282a2d</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 553 B |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 971 B |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -2,17 +2,17 @@
|
||||
"name": "Tautulli",
|
||||
"icons": [
|
||||
{
|
||||
"src": "${http_root}images/favicon/android-chrome-192x192.png?v=2.0.0",
|
||||
"src": "${http_root}images/favicon/android-chrome-192x192.png?v=2.0.5",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "${http_root}images/favicon/android-chrome-256x256.png?v=2.0.0",
|
||||
"src": "${http_root}images/favicon/android-chrome-256x256.png?v=2.0.5",
|
||||
"sizes": "256x256",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#1f1f1f",
|
||||
"background_color": "#1f1f1f",
|
||||
"theme_color": "#282a2d",
|
||||
"background_color": "#282a2d",
|
||||
"display": "standalone"
|
||||
}
|
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 7.9 KiB |
@@ -1,70 +1 @@
|
||||
<?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="275.000000pt" height="275.000000pt" viewBox="0 0 275.000000 275.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.11, written by Peter Selinger 2001-2013
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,275.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M2200 2569 c0 -4 -12 -11 -27 -17 -57 -20 -143 -130 -149 -187 -7
|
||||
-82 -6 -105 6 -140 7 -22 23 -56 36 -75 13 -19 20 -37 16 -40 -5 -3 -11 -13
|
||||
-15 -23 -4 -11 -12 -15 -19 -11 -7 4 -10 4 -6 -1 10 -11 -20 -65 -37 -65 -8 0
|
||||
-12 -6 -9 -14 3 -7 -2 -16 -11 -20 -9 -3 -14 -10 -11 -15 4 -5 -5 -18 -19 -30
|
||||
-15 -11 -26 -23 -27 -28 0 -4 -4 -7 -9 -6 -4 2 -6 -3 -4 -10 3 -6 -6 -24 -20
|
||||
-39 -14 -16 -23 -28 -20 -28 4 0 0 -7 -7 -15 -30 -33 -38 -45 -38 -55 0 -5 -4
|
||||
-10 -8 -10 -5 0 -17 -16 -28 -35 -10 -19 -22 -35 -26 -35 -5 0 -8 -5 -8 -10 0
|
||||
-10 -8 -22 -37 -55 -7 -7 -13 -17 -13 -22 0 -5 -13 -23 -30 -41 -16 -18 -31
|
||||
-36 -32 -40 -2 -4 -6 -6 -10 -5 -4 2 -4 -5 -1 -14 4 -10 2 -14 -4 -10 -6 4
|
||||
-13 -3 -17 -16 -3 -12 -16 -34 -30 -48 -14 -15 -22 -29 -19 -33 3 -3 -2 -8
|
||||
-11 -12 -9 -3 -16 -12 -16 -19 0 -6 -11 -24 -25 -39 -13 -14 -24 -30 -24 -33
|
||||
0 -4 -5 -9 -12 -12 -6 -2 -9 1 -6 6 3 5 -2 9 -12 8 -10 0 -45 2 -79 5 -34 3
|
||||
-62 2 -62 -2 0 -4 -7 -8 -15 -8 -21 0 -95 -33 -95 -43 0 -4 -5 -5 -12 -1 -7 5
|
||||
-8 3 -4 -5 5 -8 0 -14 -13 -18 -11 -3 -18 -9 -15 -14 3 -5 1 -9 -4 -9 -8 0
|
||||
-43 -60 -48 -82 -1 -5 -3 -10 -4 -13 -1 -3 -4 -9 -5 -15 -1 -5 -4 -13 -5 -17
|
||||
-1 -5 -1 -17 0 -28 1 -16 -5 -13 -28 15 -17 19 -28 38 -25 43 2 4 1 6 -4 5
|
||||
-14 -5 -114 112 -107 124 4 6 3 8 -3 5 -5 -3 -44 33 -85 81 -42 48 -90 103
|
||||
-107 122 -55 61 -72 88 -61 95 34 21 50 139 30 210 -6 22 -12 48 -13 57 -1 9
|
||||
-5 14 -9 12 -4 -3 -8 -2 -8 3 0 17 -70 84 -110 104 -26 14 -67 24 -110 27 -57
|
||||
4 -76 1 -121 -20 -30 -14 -67 -38 -83 -54 -33 -34 -70 -84 -61 -84 3 0 -1 -10
|
||||
-9 -22 -23 -37 -18 -147 8 -205 61 -130 212 -195 343 -149 38 13 41 13 59 -7
|
||||
10 -12 17 -23 15 -25 -1 -2 8 -13 21 -24 13 -11 22 -25 20 -32 -2 -6 -1 -8 3
|
||||
-4 7 7 35 -15 35 -27 0 -4 14 -20 30 -35 17 -15 30 -32 30 -38 0 -5 7 -13 16
|
||||
-16 8 -3 12 -11 8 -17 -4 -7 -3 -9 3 -6 11 7 49 -42 45 -56 -1 -5 1 -6 6 -3 8
|
||||
5 49 -35 43 -43 -1 -2 4 -8 13 -13 9 -5 13 -14 10 -20 -4 -6 -3 -8 3 -5 11 7
|
||||
45 -28 36 -37 -4 -3 2 -6 12 -6 10 0 15 -3 12 -6 -4 -4 4 -16 17 -28 13 -12
|
||||
31 -33 40 -47 15 -24 15 -27 0 -33 -8 -3 -21 -3 -26 1 -7 3 -8 1 -4 -5 4 -7
|
||||
-2 -16 -16 -22 -13 -6 -43 -24 -68 -40 -25 -16 -56 -35 -70 -42 -14 -7 -28
|
||||
-17 -31 -22 -4 -5 -12 -5 -19 -1 -9 5 -11 4 -6 -3 4 -7 3 -12 -1 -12 -5 0 -33
|
||||
-16 -63 -35 -61 -40 -63 -40 -82 -22 -7 8 -24 19 -36 26 -12 7 -20 17 -16 23
|
||||
4 7 3 8 -4 4 -6 -4 -31 -2 -54 5 -49 14 -128 18 -128 7 0 -5 -7 -8 -16 -8 -28
|
||||
0 -80 -30 -120 -69 -43 -43 -65 -68 -64 -76 1 -27 -1 -35 -9 -31 -5 3 -8 -32
|
||||
-7 -82 2 -128 46 -201 155 -256 96 -49 228 -27 309 52 17 16 34 27 37 24 4 -4
|
||||
5 -2 4 3 -4 15 11 56 20 51 6 -4 11 18 14 62 0 6 4 12 8 12 4 0 5 8 3 18 -2 9
|
||||
-5 36 -7 59 -4 40 -2 44 32 65 20 12 39 21 44 20 4 -1 7 3 7 9 0 6 7 8 17 5
|
||||
10 -4 14 -4 11 0 -6 5 54 54 67 54 2 0 24 14 49 31 25 17 51 27 57 23 8 -4 9
|
||||
-3 5 5 -4 6 0 14 11 18 10 3 30 14 44 24 14 10 29 16 33 12 3 -3 6 -1 6 5 0 7
|
||||
7 12 15 12 8 0 15 -7 15 -15 0 -8 -5 -15 -12 -15 -6 0 -9 -2 -6 -5 3 -3 0 -24
|
||||
-6 -47 -7 -23 -10 -59 -6 -83 4 -23 6 -52 5 -63 -1 -12 2 -19 6 -17 3 3 13
|
||||
-10 20 -28 19 -47 95 -113 152 -131 123 -40 255 4 319 106 22 36 45 44 60 23
|
||||
5 -6 8 -5 8 2 0 9 7 10 24 3 13 -5 44 -11 67 -15 24 -3 76 -12 114 -19 39 -8
|
||||
86 -17 106 -20 33 -6 38 -11 44 -43 12 -67 50 -124 113 -176 12 -9 26 -17 30
|
||||
-17 4 0 19 -6 34 -14 15 -8 56 -14 95 -14 245 0 365 295 190 468 -31 31 -64
|
||||
53 -92 61 -24 7 -42 15 -40 18 2 4 -20 5 -48 4 -29 -1 -52 -2 -52 -3 0 -1 -10
|
||||
-3 -23 -6 -61 -11 -182 -105 -182 -141 0 -9 -8 -12 -22 -8 -26 5 -184 33 -238
|
||||
41 -19 3 -42 8 -50 11 -15 6 -35 10 -70 12 -14 1 -20 8 -20 26 0 29 -39 115
|
||||
-60 132 -12 10 -13 15 -2 30 24 34 61 117 56 125 -3 4 0 8 6 8 6 0 9 4 6 8 -3
|
||||
5 -6 34 -7 65 -1 31 -6 57 -11 57 -4 0 -8 7 -8 16 0 9 -9 29 -21 45 -17 24
|
||||
-18 33 -9 44 7 8 16 12 21 9 5 -3 6 2 3 10 -3 9 -1 16 5 16 6 0 10 6 8 13 -1
|
||||
6 4 11 11 9 8 -1 11 1 8 6 -7 12 39 74 48 65 4 -4 4 0 0 10 -3 10 -1 17 5 17
|
||||
6 0 11 7 11 15 0 8 7 15 15 15 8 0 14 3 13 8 -3 10 110 180 199 297 5 6 26 36
|
||||
47 67 21 32 43 55 47 52 5 -3 6 1 4 8 -3 8 3 23 13 34 11 11 30 36 43 56 13
|
||||
19 40 58 59 86 l35 52 45 -7 c25 -3 50 -6 55 -6 78 5 153 38 199 87 68 72 96
|
||||
198 63 279 -9 20 -14 37 -11 37 5 0 -43 55 -76 86 -14 13 -28 24 -33 24 -4 0
|
||||
-5 5 -1 12 4 7 3 8 -4 4 -6 -4 -24 0 -40 8 -28 15 -192 19 -192 5z"/>
|
||||
<path d="M2046 2132 c-3 -5 1 -9 9 -9 8 0 12 4 9 9 -3 4 -7 8 -9 8 -2 0 -6 -4
|
||||
-9 -8z"/>
|
||||
</g>
|
||||
</svg>
|
||||
<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>
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 2.2 KiB |
BIN
data/interfaces/default/images/libraries/artist.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
data/interfaces/default/images/libraries/movie.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
data/interfaces/default/images/libraries/photo.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
data/interfaces/default/images/libraries/playlist.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
data/interfaces/default/images/libraries/show.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
data/interfaces/default/images/libraries/video.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
data/interfaces/default/images/logo-circle.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
data/interfaces/default/images/logo-tautulli-100.png
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
data/interfaces/default/images/logo-tautulli-45.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 5.1 KiB |
BIN
data/interfaces/default/images/logo-tautulli-newsletter.png
Normal file
After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 22 KiB |
73
data/interfaces/default/images/logo-tautulli.svg
Normal file
@@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="900px"
|
||||
height="300px" viewBox="0 0 900 300" enable-background="new 0 0 900 300" xml:space="preserve">
|
||||
<g id="Layer_1">
|
||||
<g>
|
||||
<path fill="none" d="M401.343,203.396c7.493,0,14.836,0,22.407,0c-3.766-12.354-7.495-24.586-11.333-37.176
|
||||
C408.665,178.818,405.017,191.064,401.343,203.396z"/>
|
||||
<path fill="#FFFFFF" d="M436.772,189.627c-4.399-12.521-8.814-25.034-13.224-37.553c-7.552,0-15.105,0-22.657,0.002
|
||||
c-0.14,0.611-0.221,1.246-0.428,1.836c-8.502,24.335-17.017,48.666-25.526,72.996c-1.45,4.146-2.887,8.298-4.362,12.535
|
||||
c6.686,0,13.198,0,19.792,0c2.095-6.873,4.172-13.693,6.248-20.514c10.593,0,21.056,0,31.594,0
|
||||
c2.102,6.894,4.177,13.707,6.245,20.489c6.66,0,13.146,0,19.772,0c-0.229-0.676-0.408-1.213-0.597-1.747
|
||||
C448.014,221.656,442.4,205.639,436.772,189.627z M401.343,203.396c3.674-12.332,7.322-24.578,11.074-37.176
|
||||
c3.837,12.59,7.567,24.82,11.333,37.176C416.179,203.396,408.836,203.396,401.343,203.396z"/>
|
||||
<path fill="#FFFFFF" d="M371.072,152.459c-21.916,0-43.668,0-65.421,0c0,5.111,0,10.227,0,15.341c7.836,0,15.672,0,23.691,0
|
||||
c0,24.046,0,47.786,0,71.621c6.164,0,12.144,0,18.361,0c0-23.901,0-47.698,0-71.682c7.948,0,15.66,0,23.369,0
|
||||
C371.072,162.549,371.072,157.549,371.072,152.459z"/>
|
||||
<path fill="#FFFFFF" d="M532.615,152.555c-6.176,0-12.154,0-18.349,0c0,0.842,0,1.592,0,2.34c0,16.42,0.021,32.838-0.021,49.258
|
||||
c-0.006,2.675-0.119,5.373-0.494,8.018c-0.997,7.07-4.634,11.153-11.06,12.587c-3.731,0.83-7.491,0.754-11.238,0
|
||||
c-5.745-1.157-9.344-4.668-10.58-10.297c-0.678-3.088-0.958-6.317-0.976-9.487c-0.094-16.738-0.045-33.478-0.045-50.217
|
||||
c0-0.732,0-1.467,0-2.148c-6.296,0-12.275,0-18.329,0c0,0.591,0,1.035,0,1.48c0,18.209-0.013,36.416,0.018,54.625
|
||||
c0.003,1.908,0.127,3.835,0.396,5.727c1.632,11.408,7.683,19.563,18.562,23.516c11.146,4.052,22.489,3.83,33.593-0.393
|
||||
c7.937-3.017,13.483-8.627,16.483-16.617c1.474-3.93,2.044-8.024,2.042-12.211c-0.006-18.146-0.002-36.289-0.002-54.436
|
||||
C532.615,153.744,532.615,153.188,532.615,152.555z"/>
|
||||
<path fill="#FFFFFF" d="M688.532,152.555c-6.16,0-12.14,0-18.349,0c0,0.924,0,1.62,0,2.318c0,16.482,0.021,32.967-0.021,49.449
|
||||
c-0.006,2.609-0.125,5.244-0.49,7.824c-1.003,7.127-4.74,11.291-11.226,12.654c-3.674,0.771-7.373,0.702-11.056-0.037
|
||||
c-5.74-1.156-9.354-4.652-10.596-10.281c-0.682-3.086-0.964-6.317-0.979-9.487c-0.094-16.738-0.044-33.478-0.044-50.216
|
||||
c0-0.739,0-1.478,0-2.18c-6.278,0-12.26,0-18.332,0c0,0.582,0,1.024,0,1.467c0.003,18.271-0.011,36.543,0.021,54.815
|
||||
c0.002,1.847,0.132,3.707,0.391,5.533c1.626,11.411,7.669,19.567,18.546,23.533c11.213,4.09,22.621,3.836,33.771-0.445
|
||||
c7.851-3.019,13.345-8.612,16.315-16.541c1.521-4.052,2.06-8.274,2.056-12.592c-0.018-17.953-0.008-35.905-0.008-53.858
|
||||
C688.532,153.893,688.532,153.27,688.532,152.555z"/>
|
||||
<path fill="#FFFFFF" d="M542.405,167.793c7.813,0,15.522,0,23.479,0c0,23.979,0,47.771,0,71.648c6.172,0,12.102,0,18.425,0
|
||||
c0-23.948,0-47.738,0-71.729c7.947,0,15.655,0,23.378,0c0-5.158,0-10.115,0-15.115c-21.819,0-43.53,0-65.281,0
|
||||
C542.405,157.691,542.405,162.648,542.405,167.793z"/>
|
||||
<path fill="#FFFFFF" d="M725.407,152.58c-6.199,0-12.177,0-18.193,0c0,29.021,0,57.92,0,86.857c17.832,0,35.528,0,53.335,0
|
||||
c0-5.191,0-10.146,0-15.307c-11.72,0-23.322,0-35.142,0C725.407,200.143,725.407,176.361,725.407,152.58z"/>
|
||||
<path fill="#FFFFFF" d="M790.668,152.593c-6.197,0-12.172,0-18.216,0c0,28.985,0,57.833,0,86.831c17.905,0,35.6,0,53.371,0
|
||||
c0-5.105,0-10.107,0-15.316c-11.729,0-23.335,0-35.155,0C790.668,200.119,790.668,176.339,790.668,152.593z"/>
|
||||
<path fill="#FFFFFF" d="M838.183,239.464c6.083,0,12.021,0,18.096,0c0-29.09,0-57.987,0-86.879c-6.12,0-12.098,0-18.096,0
|
||||
C838.183,181.611,838.183,210.514,838.183,239.464z"/>
|
||||
<ellipse fill="#E5A00D" cx="847.229" cy="129.416" rx="12.459" ry="12.44"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Layer_2_1_">
|
||||
<g>
|
||||
|
||||
<image overflow="visible" opacity="0.2" width="279" height="216" xlink:href="13AD46AC.png" transform="matrix(1 0 0 1 15.5 73.5)">
|
||||
</image>
|
||||
<g>
|
||||
<path fill="#FFFFFF" d="M243.77,212.601c-10.482,0-19.533,6.118-23.783,14.977l-37.755-5.478
|
||||
c-0.133-14.445-11.881-26.114-26.356-26.114c-5.163,0-9.975,1.489-14.041,4.051l-53.649-60.787
|
||||
c3.091-4.319,4.914-9.605,4.914-15.319c0-14.56-11.802-26.362-26.363-26.362c-14.56,0-26.363,11.803-26.363,26.362
|
||||
c0,14.559,11.803,26.361,26.363,26.361c4.484,0,8.704-1.122,12.399-3.096l54.223,61.438c-2.439,3.994-3.846,8.688-3.846,13.712
|
||||
c0,14.56,11.803,26.363,26.364,26.363c10.422,0,19.43-6.05,23.708-14.828l37.832,5.491c0.22,14.369,11.929,25.953,26.354,25.953
|
||||
c14.56,0,26.363-11.804,26.363-26.362S258.33,212.601,243.77,212.601z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
|
||||
<image overflow="visible" opacity="0.2" width="279" height="279" xlink:href="13AD46AD.png" transform="matrix(1 0 0 1 15.5 10.5)">
|
||||
</image>
|
||||
<g>
|
||||
<path fill="#E5A00D" d="M243.77,34.779c-14.56,0-26.363,11.802-26.363,26.362c0,7.17,2.867,13.667,7.512,18.42l-58.424,82.548
|
||||
c-3.25-1.431-6.84-2.23-10.619-2.23c-14.56,0-26.363,11.8-26.363,26.36c0,2.592,0.38,5.094,1.078,7.46L86.2,221.181
|
||||
c-4.82-5.271-11.754-8.58-19.461-8.58c-14.56,0-26.363,11.803-26.363,26.361s11.803,26.362,26.363,26.362
|
||||
c14.561,0,26.363-11.804,26.363-26.362c0-2.518-0.36-4.951-1.019-7.256l44.477-27.534c4.815,5.183,11.686,8.429,19.318,8.429
|
||||
c14.561,0,26.364-11.803,26.364-26.361c0-6.424-2.301-12.31-6.119-16.883l58.973-83.322c2.72,0.948,5.637,1.468,8.677,1.468
|
||||
c14.561,0,26.363-11.802,26.363-26.361C270.135,46.582,258.33,34.779,243.77,34.779z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 17 KiB |
32
data/interfaces/default/images/logo.svg
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="298.961px" height="300px" viewBox="0 0 298.961 300" enable-background="new 0 0 298.961 300" xml:space="preserve">
|
||||
<g>
|
||||
|
||||
<image overflow="visible" opacity="0.2" width="289" height="224" xlink:href="93124B54.png" transform="matrix(1 0 0 1 4.9805 70.5)">
|
||||
</image>
|
||||
<g>
|
||||
<path fill="#FFFFFF" d="M241.811,215.25c-10.935,0-20.375,6.382-24.809,15.623l-39.383-5.715
|
||||
c-0.139-15.068-12.393-27.242-27.493-27.242c-5.385,0-10.405,1.554-14.646,4.229l-55.963-63.414
|
||||
c3.224-4.505,5.127-10.02,5.127-15.981c0-15.188-12.312-27.5-27.5-27.5s-27.5,12.312-27.5,27.5s12.312,27.5,27.5,27.5
|
||||
c4.677,0,9.079-1.171,12.934-3.23l56.56,64.089c-2.544,4.168-4.012,9.064-4.012,14.307c0,15.188,12.312,27.5,27.5,27.5
|
||||
c10.872,0,20.269-6.311,24.731-15.467l39.463,5.727c0.229,14.99,12.443,27.074,27.49,27.074c15.188,0,27.5-12.313,27.5-27.5
|
||||
S256.998,215.25,241.811,215.25z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
|
||||
<image overflow="visible" opacity="0.2" width="289" height="289" xlink:href="93124B55.png" transform="matrix(1 0 0 1 4.9805 5.5)">
|
||||
</image>
|
||||
<g>
|
||||
<path fill="#E5A00D" d="M241.811,29.75c-15.188,0-27.5,12.312-27.5,27.5c0,7.48,2.99,14.258,7.836,19.216l-60.943,86.113
|
||||
c-3.389-1.493-7.135-2.329-11.076-2.329c-15.188,0-27.5,12.313-27.5,27.5c0,2.704,0.397,5.314,1.125,7.783l-46.306,28.668
|
||||
c-5.028-5.5-12.261-8.951-20.3-8.951c-15.188,0-27.5,12.313-27.5,27.5s12.312,27.5,27.5,27.5s27.5-12.313,27.5-27.5
|
||||
c0-2.627-0.376-5.165-1.064-7.57l46.396-28.724c5.022,5.407,12.189,8.794,20.15,8.794c15.188,0,27.5-12.313,27.5-27.5
|
||||
c0-6.701-2.399-12.84-6.382-17.611l61.515-86.92c2.837,0.988,5.88,1.532,9.052,1.532c15.188,0,27.5-12.312,27.5-27.5
|
||||
S256.998,29.75,241.811,29.75z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
BIN
data/interfaces/default/images/newsletter/newsletter-header.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
data/interfaces/default/images/newsletter/view-on-plex-cover.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
data/interfaces/default/images/newsletter/view-on-plex-flat.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 8.9 KiB |
44
data/interfaces/default/images/tautulli.svg
Normal file
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="956.251px" height="214.137px" viewBox="0 -12.083 956.251 214.137" enable-background="new 0 -12.083 956.251 214.137"
|
||||
xml:space="preserve">
|
||||
<g>
|
||||
<path fill="none" d="M165.163,137.282c12.933,0,25.607,0,38.673,0c-6.5-21.354-12.936-42.492-19.56-64.253
|
||||
C177.798,94.804,171.503,115.967,165.163,137.282z"/>
|
||||
<path fill="#FFFFFF" d="M226.314,113.482c-7.594-21.64-15.215-43.269-22.824-64.903c-13.035,0-26.072,0-39.106,0.002
|
||||
c-0.241,1.061-0.381,2.153-0.737,3.174c-14.675,42.061-29.37,84.111-44.058,126.164c-2.503,7.166-4.984,14.34-7.53,21.666
|
||||
c11.54,0,22.78,0,34.162,0c3.614-11.881,7.2-23.67,10.784-35.455c18.283,0,36.34,0,54.529,0
|
||||
c3.627,11.914,7.212,23.691,10.781,35.412c11.493,0,22.693,0,34.128,0c-0.397-1.166-0.705-2.094-1.029-3.02
|
||||
C245.716,168.84,236.026,141.157,226.314,113.482z M165.163,137.282c6.34-21.314,12.635-42.478,19.113-64.253
|
||||
c6.624,21.761,13.06,42.899,19.56,64.253C190.77,137.282,178.096,137.282,165.163,137.282z"/>
|
||||
<path fill="#FFFFFF" d="M112.916,49.243c-37.826,0-75.371,0-112.916,0c0,8.836,0,17.674,0.001,26.514c13.525,0,27.05,0,40.889,0
|
||||
c0,41.563,0,82.594,0,123.787c10.64,0,20.961,0,31.692,0c0-41.311,0-82.438,0-123.892c13.718,0,27.028,0,40.334,0
|
||||
C112.916,66.68,112.916,58.04,112.916,49.243z"/>
|
||||
<path fill="#FFFFFF" d="M391.734,49.409c-10.657,0-20.976,0-31.667,0c0,1.457,0,2.752,0,4.045c0,28.379,0.037,56.758-0.037,85.135
|
||||
c-0.012,4.623-0.206,9.287-0.851,13.857c-1.723,12.219-7.998,19.277-19.089,21.752c-6.441,1.436-12.932,1.305-19.396,0.002
|
||||
c-9.917-2-16.129-8.066-18.262-17.797c-1.171-5.336-1.655-10.92-1.685-16.398c-0.158-28.928-0.074-57.859-0.074-86.791
|
||||
c0-1.268,0-2.534,0-3.717c-10.869,0-21.188,0-31.639,0c0,1.023,0,1.793,0,2.563c0,31.471-0.018,62.939,0.032,94.41
|
||||
c0.005,3.301,0.217,6.629,0.683,9.896c2.816,19.719,13.26,33.813,32.036,40.645c19.239,7.002,38.813,6.619,57.978-0.678
|
||||
c13.699-5.215,23.273-14.91,28.45-28.723c2.545-6.791,3.528-13.869,3.526-21.104c-0.011-31.363-0.005-62.723-0.005-94.082
|
||||
C391.734,51.463,391.734,50.502,391.734,49.409z"/>
|
||||
<path fill="#FFFFFF" d="M660.845,52.793c0-1.073,0-2.149,0-3.385c-10.631,0-20.953,0-31.668,0c0,1.598,0,2.803,0,4.008
|
||||
c0,28.487,0.035,56.979-0.037,85.467c-0.012,4.51-0.215,9.064-0.844,13.525c-1.734,12.313-8.184,19.514-19.379,21.869
|
||||
c-6.34,1.332-12.723,1.215-19.076-0.064c-9.912-1.996-16.146-8.039-18.291-17.77c-1.174-5.334-1.662-10.92-1.689-16.398
|
||||
c-0.16-28.928-0.076-57.86-0.076-86.791c0-1.277,0-2.553,0-3.77c-10.834,0-21.158,0-31.641,0c0,1.01,0,1.771,0,2.537
|
||||
c0.004,31.581-0.016,63.16,0.033,94.742c0.006,3.189,0.23,6.406,0.678,9.564c2.805,19.723,13.236,33.82,32.006,40.674
|
||||
c19.354,7.068,39.047,6.631,58.289-0.77c13.549-5.217,23.031-14.889,28.16-28.59c2.623-7.006,3.557-14.305,3.551-21.764
|
||||
C660.828,114.852,660.845,83.823,660.845,52.793z"/>
|
||||
<path fill="#FFFFFF" d="M408.633,75.745c13.484,0,26.794,0,40.523,0c0,41.445,0,82.566,0,123.836c10.655,0,20.887,0,31.802,0
|
||||
c0-41.391,0-82.51,0-123.974c13.717,0,27.02,0,40.35,0c0-8.916,0-17.481,0-26.125c-37.66,0-75.133,0-112.675,0
|
||||
C408.633,58.288,408.633,66.853,408.633,75.745z"/>
|
||||
<path fill="#FFFFFF" d="M724.49,49.454c-10.699,0-21.014,0-31.4,0c0,50.158,0,100.104,0,150.119c30.779,0,61.322,0,92.055,0
|
||||
c0-8.973,0-17.535,0-26.453c-20.227,0-40.254,0-60.652,0C724.49,131.659,724.49,90.556,724.49,49.454z"/>
|
||||
<path fill="#FFFFFF" d="M837.128,49.476c-10.697,0-21.01,0-31.441,0c0,50.094,0,99.954,0,150.073c30.904,0,61.445,0,92.119,0
|
||||
c0-8.826,0-17.469,0-26.473c-20.246,0-40.273,0-60.678,0C837.128,131.618,837.128,90.515,837.128,49.476z"/>
|
||||
<path fill="#FFFFFF" d="M919.136,199.62c10.502,0,20.748,0,31.232,0c0-50.277,0-100.226,0-150.158c-10.564,0-20.879,0-31.232,0
|
||||
C919.136,99.631,919.136,149.583,919.136,199.62z"/>
|
||||
<circle fill="#E5A00D" cx="934.751" cy="9.417" r="21.5"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.0 KiB |
@@ -5,16 +5,35 @@
|
||||
</%def>
|
||||
|
||||
<%def name="body()">
|
||||
<% from plexpy import PLEX_SERVER_UP %>
|
||||
<div class="container-fluid">
|
||||
% for section in config['home_sections']:
|
||||
% if section == 'current_activity':
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="padded-header" id="current-activity-header">
|
||||
<h3>Activity <small><span id="currentActivityHeader"></span></small></h3>
|
||||
<h3><span id="sessions-shortcut">Activity</span>
|
||||
<small>
|
||||
<span id="currentActivityHeader" style="display: none;">
|
||||
Streams: <span id="currentActivityHeader-streams"></span> |
|
||||
Bandwidth: <span id="currentActivityHeader-bandwidth"></span>
|
||||
<span id="currentActivityHeader-bandwidth-tooltip" data-toggle="tooltip" title="Streaming Brain Estimate (Required Bandwidth)"><i class="fa fa-info-circle"></i></span>
|
||||
</span>
|
||||
</small>
|
||||
</h3>
|
||||
</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>
|
||||
% elif config['pms_is_cloud']:
|
||||
<div id="dashboard-no-activity" class="text-muted">Plex Cloud server is sleeping.</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>
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -23,27 +42,29 @@
|
||||
<div class="col-md-12">
|
||||
<div class="home-padded-header padded-header">
|
||||
<h3 class="pull-left">Watch Statistics</h3>
|
||||
<div class="btn-group pull-left" data-toggle="buttons" id="watch-stats-toggles" style="margin-right: 3px">
|
||||
% if config['home_stats_type'] == 0:
|
||||
<label class="btn btn-dark active">
|
||||
<input type="radio" class="watched-stats-toggle" name="watched-stats-type" id="watched-stats-plays" value="0" autocomplete="off" checked> Play Count
|
||||
</label>
|
||||
<label class="btn btn-dark">
|
||||
<input type="radio" class="watched-stats-toggle" name="watched-stats-type" id="watched-stats-duration" value="1" autocomplete="off"> Play Duration
|
||||
</label>
|
||||
% else:
|
||||
<label class="btn btn-dark">
|
||||
<input type="radio" class="watched-stats-toggle" name="watched-stats-type" id="watched-stats-plays" value="0" autocomplete="off"> Play Count
|
||||
</label>
|
||||
<label class="btn btn-dark active">
|
||||
<input type="radio" class="watched-stats-toggle" name="watched-stats-type" id="watched-stats-duration" value="1" autocomplete="off" checked> Play Duration
|
||||
</label>
|
||||
% endif
|
||||
</div>
|
||||
<div class="input-group pull-left" style="width: 1px; margin-right: 3px" id="watched-stats-days-selection">
|
||||
<span class="input-group-addon btn-dark inactive">Last</span>
|
||||
<input type="number" class="form-control" name="watched-stats-days" id="watched-stats-days" value="${config['home_stats_length']}" min="1" data-default="30" data-toggle="tooltip" title="Min: 1 day" />
|
||||
<span class="input-group-addon btn-dark inactive">days</span>
|
||||
<div class="button-bar">
|
||||
<div class="btn-group pull-left" data-toggle="buttons" id="watch-stats-toggles" style="margin-right: 3px">
|
||||
% if config['home_stats_type'] == 0:
|
||||
<label class="btn btn-dark active">
|
||||
<input type="radio" class="watched-stats-toggle" name="watched-stats-type" id="watched-stats-plays" value="0" autocomplete="off" checked> Play Count
|
||||
</label>
|
||||
<label class="btn btn-dark">
|
||||
<input type="radio" class="watched-stats-toggle" name="watched-stats-type" id="watched-stats-duration" value="1" autocomplete="off"> Play Duration
|
||||
</label>
|
||||
% else:
|
||||
<label class="btn btn-dark">
|
||||
<input type="radio" class="watched-stats-toggle" name="watched-stats-type" id="watched-stats-plays" value="0" autocomplete="off"> Play Count
|
||||
</label>
|
||||
<label class="btn btn-dark active">
|
||||
<input type="radio" class="watched-stats-toggle" name="watched-stats-type" id="watched-stats-duration" value="1" autocomplete="off" checked> Play Duration
|
||||
</label>
|
||||
% endif
|
||||
</div>
|
||||
<div class="input-group pull-left" style="width: 1px; margin-right: 3px" id="watched-stats-days-selection">
|
||||
<span class="input-group-addon btn-dark inactive">Last</span>
|
||||
<input type="number" class="form-control number-input" name="watched-stats-days" id="watched-stats-days" value="${config['home_stats_length']}" min="1" data-default="30" data-toggle="tooltip" title="Min: 1 day" />
|
||||
<span class="input-group-addon btn-dark inactive">days</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -61,7 +82,9 @@
|
||||
<div class="col-md-12">
|
||||
<div class="home-padded-header padded-header" id="library-statistics-header">
|
||||
<h3 class="pull-left">Library Statistics</h3>
|
||||
<span class="btn btn-dark active" style="cursor: default">${config['pms_name']}</span>
|
||||
<div class="button-bar">
|
||||
<span class="btn btn-dark active" style="cursor: default">${config['pms_name']}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -86,23 +109,25 @@
|
||||
<a href="#" id="recently-added-page-right" class="paginate btn-gray disabled" data-id="-1"><i class="fa fa-lg fa-chevron-right"></i></a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="btn-group pull-left" data-toggle="buttons" id="recently-added-toggles" style="margin-right: 3px">
|
||||
<label class="btn btn-dark active" id="recently-added-label-all">
|
||||
<input type="radio" name="recently-added-toggle" id="recently-added-toggle-all" value="" autocomplete="off"> All
|
||||
</label>
|
||||
<label class="btn btn-dark" id="recently-added-label-movies">
|
||||
<input type="radio" name="recently-added-toggle" id="recently-added-toggle-movie" value="movie" autocomplete="off"> Movies
|
||||
</label>
|
||||
<label class="btn btn-dark" id="recently-added-label-tv">
|
||||
<input type="radio" name="recently-added-toggle" id="recently-added-toggle-show" value="show" autocomplete="off"> TV Shows
|
||||
</label>
|
||||
<label class="btn btn-dark" id="recently-added-label-music">
|
||||
<input type="radio" name="recently-added-toggle" id="recently-added-toggle-music" value="artist" autocomplete="off"> Music
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group pull-left" style="width: 1px;" id="recently-added-count-selection">
|
||||
<input type="number" class="form-control" name="recently-added-count" id="recently-added-count" value="${config['home_stats_recently_added_count']}" min="1" max="100" data-default="50" data-toggle="tooltip" title="Min: 1 item<br>Max: 100 items" />
|
||||
<span class="input-group-addon btn-dark inactive">items</span>
|
||||
<div class="button-bar">
|
||||
<div class="btn-group pull-left" data-toggle="buttons" id="recently-added-toggles" style="margin-right: 3px">
|
||||
<label class="btn btn-dark active" id="recently-added-label-all">
|
||||
<input type="radio" name="recently-added-toggle" id="recently-added-toggle-all" value="" autocomplete="off"> All
|
||||
</label>
|
||||
<label class="btn btn-dark" id="recently-added-label-movies">
|
||||
<input type="radio" name="recently-added-toggle" id="recently-added-toggle-movie" value="movie" autocomplete="off"> Movies
|
||||
</label>
|
||||
<label class="btn btn-dark" id="recently-added-label-tv">
|
||||
<input type="radio" name="recently-added-toggle" id="recently-added-toggle-show" value="show" autocomplete="off"> TV Shows
|
||||
</label>
|
||||
<label class="btn btn-dark" id="recently-added-label-music">
|
||||
<input type="radio" name="recently-added-toggle" id="recently-added-toggle-music" value="artist" autocomplete="off"> Music
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group pull-left" style="width: 1px;" id="recently-added-count-selection">
|
||||
<input type="number" class="form-control number-input" name="recently-added-count" id="recently-added-count" value="${config['home_stats_recently_added_count']}" min="1" max="50" data-default="50" data-toggle="tooltip" title="Min: 1 item<br>Max: 50 items" />
|
||||
<span class="input-group-addon btn-dark inactive">items</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -110,7 +135,17 @@
|
||||
<div class="row">
|
||||
<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>
|
||||
% 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>
|
||||
% endif
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
@@ -123,12 +158,13 @@
|
||||
<%def name="modalIncludes()">
|
||||
|
||||
% if _session['user_group'] == 'admin' and config['update_show_changelog']:
|
||||
<% from plexpy.common import RELEASE %>
|
||||
<div id="changelog-modal" class="modal fade wide" tabindex="-1" role="dialog" aria-labelledby="changelog-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">Tautulli Updated</h4>
|
||||
<h4 class="modal-title">Tautulli Updated to <strong>${RELEASE}</strong></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
</div>
|
||||
@@ -196,9 +232,11 @@
|
||||
</%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 src="${http_root}js/jquery.tripleclick.min.js"></script>
|
||||
<script>
|
||||
var date_format = 'YYYY-MM-DD';
|
||||
var time_format = 'hh:mm a';
|
||||
@@ -225,9 +263,9 @@
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
</script>
|
||||
% if 'current_activity' in config['home_sections']:
|
||||
% if 'current_activity' in config['home_sections'] and PLEX_SERVER_UP:
|
||||
<script>
|
||||
var defaultHandler = {
|
||||
get: function(target, name) {
|
||||
@@ -235,7 +273,14 @@
|
||||
}
|
||||
};
|
||||
|
||||
var create_instances = [];
|
||||
var activity_ready = true;
|
||||
|
||||
$('#currentActivityHeader-bandwidth-tooltip').tooltip({ container: 'body', placement: 'right', delay: 50 });
|
||||
|
||||
function getCurrentActivity() {
|
||||
activity_ready = false;
|
||||
|
||||
$.ajax({
|
||||
url: 'get_activity',
|
||||
type: 'GET',
|
||||
@@ -243,6 +288,7 @@
|
||||
async: true,
|
||||
error: function (xhr, status, error) {
|
||||
console.log(status + ': ' + error);
|
||||
activity_ready = true;
|
||||
},
|
||||
complete: function (xhr, status) {
|
||||
$('#dashboard-checking-activity').remove();
|
||||
@@ -256,8 +302,13 @@
|
||||
}
|
||||
|
||||
if (!(current_activity)) {
|
||||
$('#currentActivityHeader').text('');
|
||||
$('#currentActivity').html('<div id="dashboard-no-activity" class="text-muted">There was an error communicating with your Plex Server.</div>');
|
||||
% 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>');
|
||||
return
|
||||
}
|
||||
|
||||
@@ -270,63 +321,82 @@
|
||||
// Update the header stream counts
|
||||
var sc_dp = current_activity.stream_count_direct_play,
|
||||
sc_ds = current_activity.stream_count_direct_stream,
|
||||
sc_tc = current_activity.stream_count_transcode;
|
||||
var header_count = stream_count + ' stream' + (stream_count > 1 ? 's' : '') + ' (';
|
||||
sc_tc = current_activity.stream_count_transcode,
|
||||
total_bw = current_activity.total_bandwidth,
|
||||
lan_bw = current_activity.lan_bandwidth,
|
||||
wan_bw = current_activity.wan_bandwidth;
|
||||
var streams_header = stream_count + ' stream' + (stream_count > 1 ? 's' : '') + ' (';
|
||||
if (sc_dp) {
|
||||
header_count += sc_dp + ' direct play' + (sc_dp > 1 ? 's' : '') + ', ';
|
||||
streams_header += sc_dp + ' direct play' + (sc_dp > 1 ? 's' : '') + ', ';
|
||||
}
|
||||
if (sc_ds) {
|
||||
header_count += sc_ds + ' direct stream' + (sc_ds > 1 ? 's' : '') + ', ';
|
||||
streams_header += sc_ds + ' direct stream' + (sc_ds > 1 ? 's' : '') + ', ';
|
||||
}
|
||||
if (sc_tc) {
|
||||
header_count += sc_tc + ' transcode' + (sc_tc > 1 ? 's' : '') + ', ';
|
||||
streams_header += sc_tc + ' transcode' + (sc_tc > 1 ? 's' : '') + ', ';
|
||||
}
|
||||
header_count = header_count.replace(/, $/, '');
|
||||
header_count += ')';
|
||||
$('#currentActivityHeader').text(header_count);
|
||||
streams_header = streams_header.replace(/, $/, '') + ')';
|
||||
$('#currentActivityHeader-streams').text(streams_header);
|
||||
|
||||
var bandwidth_header = ((total_bw > 1000) ? ((total_bw / 1000).toFixed(1) + ' Mbps') : (total_bw + ' kbps'));
|
||||
var lan_wan_bandwidth_header = '';
|
||||
if (lan_bw) {
|
||||
lan_wan_bandwidth_header += 'LAN: ' + ((lan_bw > 1000) ? ((lan_bw / 1000).toFixed(1) + ' Mbps') : (lan_bw + ' kbps')) + ', ';
|
||||
}
|
||||
if (wan_bw) {
|
||||
lan_wan_bandwidth_header += 'WAN: ' + ((wan_bw > 1000) ? ((wan_bw / 1000).toFixed(1) + ' Mbps') : (wan_bw + ' kbps')) + ', ';
|
||||
}
|
||||
if (lan_wan_bandwidth_header) {
|
||||
bandwidth_header += ' (' + lan_wan_bandwidth_header.replace(/, $/, '') + ')';
|
||||
}
|
||||
$('#currentActivityHeader-bandwidth').text(bandwidth_header);
|
||||
|
||||
$('#currentActivityHeader').show();
|
||||
|
||||
sessions.forEach(function (session) {
|
||||
var s = new Proxy(session, defaultHandler);
|
||||
var s = (typeof Proxy === "function") ? new Proxy(session, defaultHandler) : session;
|
||||
var key = s.session_key;
|
||||
var session_id = s.session_id;
|
||||
var instance = $('#activity-instance-' + key);
|
||||
|
||||
// Create a new instance if it doesn't exist
|
||||
if (!(instance.length)) {
|
||||
getActivityInstance(session);
|
||||
create_instances.push(key);
|
||||
getActivityInstance(key);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update play state icon
|
||||
var state_icon = '';
|
||||
switch (s.state) {
|
||||
case 'playing':
|
||||
var state_icon = '<i class="fa fa-fw fa-play"></i> ';
|
||||
state_icon = '<i class="fa fa-fw fa-play"></i> ';
|
||||
break;
|
||||
case 'paused':
|
||||
var state_icon = '<i class="fa fa-fw fa-pause"></i> ';
|
||||
state_icon = '<i class="fa fa-fw fa-pause"></i> ';
|
||||
break;
|
||||
case 'buffering':
|
||||
var state_icon = '<i class="fa fa-fw fa-spinner"></i> ';
|
||||
state_icon = '<i class="fa fa-fw fa-spinner"></i> ';
|
||||
break;
|
||||
default:
|
||||
var state_icon = '<i class="fa fa-fw fa-question-circle"></i> ';
|
||||
state_icon = '<i class="fa fa-fw fa-question-circle"></i> ';
|
||||
}
|
||||
$('#play-state-' + key).html(state_icon).attr('title', capitalizeFirstLetter(s.state));
|
||||
|
||||
// Switching tracks can be under the same session key, so need to update the info.
|
||||
if (s.media_type === 'track') {
|
||||
// Update if artist changed
|
||||
if (s.grandparent_rating_key != instance.data('grandparent_rating_key')) {
|
||||
$('#background-' + key).css('background-image', 'url(pms_image_proxy?img=' + s.art + '&width=500&height=280&fallback=art&refresh=true)');
|
||||
if (s.grandparent_rating_key !== instance.data('grandparent_rating_key')) {
|
||||
$('#background-' + key).css('background-image', 'url(pms_image_proxy?img=' + s.art + '&width=500&height=280&opacity=40&background=282828&blur=3&fallback=art&refresh=true)');
|
||||
$('#metadata-grandparent_title-' + key)
|
||||
.attr('href', 'info?rating_key=' + s.grandparent_rating_key)
|
||||
.attr('title', s.grandparent_title)
|
||||
.text(s.grandparent_title);
|
||||
}
|
||||
// Update cover if album changed
|
||||
if (s.parent_rating_key != instance.data('parent_rating_key')) {
|
||||
if (s.parent_rating_key !== instance.data('parent_rating_key')) {
|
||||
$('#poster-' + key).css('background-image', 'url(pms_image_proxy?img=' + s.parent_thumb + '&width=300&height=300&fallback=poster&refresh=true)');
|
||||
$('#poster-' + key + '-bg').css('background-image', 'url(pms_image_proxy?img=' + s.parent_thumb + '&width=300&height=300&fallback=poster&refresh=true)');
|
||||
$('#poster-' + key + '-bg').css('background-image', 'url(pms_image_proxy?img=' + s.parent_thumb + '&width=300&height=300&opacity=60&background=282828&blur=3&fallback=poster&refresh=true)');
|
||||
$('#poster-url-' + key)
|
||||
.attr('href', 'info?rating_key=' + s.parent_rating_key)
|
||||
.attr('title', s.parent_title);
|
||||
@@ -336,7 +406,7 @@
|
||||
.text(s.parent_title);
|
||||
}
|
||||
// Update cover if track changed
|
||||
if (s.parent_rating_key != instance.data('parent_rating_key')) {
|
||||
if (s.parent_rating_key !== instance.data('parent_rating_key')) {
|
||||
$('#metadata-title-' + key)
|
||||
.attr('href', 'info?rating_key=' + s.rating_key)
|
||||
.attr('title', s.title)
|
||||
@@ -347,55 +417,51 @@
|
||||
// Update the transcode state
|
||||
var transcode_decision = '';
|
||||
if (s.transcode_decision === 'transcode') {
|
||||
var throttled = (s.transcode_throttled == 1) ? ' (Throttled)' : ' (Speed: ' + s.transcode_speed + ')';
|
||||
var throttled = (s.transcode_throttled === 1) ? ' (Throttled)' : ' (Speed: ' + s.transcode_speed + ')';
|
||||
transcode_decision = 'Transcode' + throttled;
|
||||
} else if (s.transcode_decision === 'copy') {
|
||||
transcode_decision = 'Direct Stream';
|
||||
} else {
|
||||
transcode_decision = 'Direct Play' + ((s.synced_version == 1) ? ' (Synced)' : '');
|
||||
transcode_decision = 'Direct Play';
|
||||
}
|
||||
$('#transcode_decision-' + key).html(transcode_decision);
|
||||
|
||||
var transcode_container = '';
|
||||
if (s.stream_container_decision === 'transcode') {
|
||||
transcode_container = 'Transcode (' + s.container.toUpperCase() + ' → ' + s.stream_container.toUpperCase() + ')';
|
||||
transcode_container = 'Transcode (' + s.container.toUpperCase() + ' <i class="fa fa-long-arrow-right"></i> ' + s.stream_container.toUpperCase() + ')';
|
||||
} else {
|
||||
transcode_container = 'Direct Play (' + s.container.toUpperCase() + ')';
|
||||
}
|
||||
$('#transcode_container-' + key).html(transcode_container);
|
||||
|
||||
var video_decision = '';
|
||||
if (['movie', 'episode', 'clip'].indexOf(s.media_type) > -1 && s.video_decision != '') {
|
||||
if (['movie', 'episode', 'clip'].indexOf(s.media_type) > -1 && s.video_decision !== '') {
|
||||
var v_res= '';
|
||||
switch (s.video_resolution.toLowerCase()) {
|
||||
case 'sd':
|
||||
var v_res = 'SD';
|
||||
v_res = 'SD';
|
||||
break;
|
||||
case '4k':
|
||||
var v_res = '4k';
|
||||
v_res = '4k';
|
||||
break;
|
||||
default:
|
||||
var v_res = s.video_resolution + 'p'
|
||||
v_res = s.video_resolution + 'p'
|
||||
}
|
||||
var sv_res = '';
|
||||
switch (s.stream_video_resolution.toLowerCase()) {
|
||||
case 'sd':
|
||||
var sv_res = 'SD';
|
||||
sv_res = 'SD';
|
||||
break;
|
||||
case '4k':
|
||||
var sv_res = '4k';
|
||||
sv_res = '4k';
|
||||
break;
|
||||
default:
|
||||
var sv_res = s.stream_video_resolution + 'p'
|
||||
sv_res = s.stream_video_resolution + 'p'
|
||||
}
|
||||
if (s.stream_video_decision === 'transcode') {
|
||||
var hw_d = '';
|
||||
var hw_e = '';
|
||||
if (s.transcode_hw_requested === 1 && s.transcode_hw_full_pipeline === 0) {
|
||||
hw_d = ' (HW)';
|
||||
} else if (s.transcode_hw_requested === 1 && s.transcode_hw_full_pipeline === 1) {
|
||||
hw_d = ' (HW)';
|
||||
hw_e = ' (HW)';
|
||||
}
|
||||
video_decision = 'Transcode (' + s.video_codec.toUpperCase() + hw_d + ' ' + v_res + ' → ' + s.stream_video_codec.toUpperCase() + hw_e + ' ' + sv_res + ')';
|
||||
var hw_d = (s.transcode_hw_decoding === 1) ? ' (HW)' : '';
|
||||
var hw_e = (s.transcode_hw_encoding === 1) ? ' (HW)' : '';
|
||||
video_decision = 'Transcode (' + s.video_codec.toUpperCase() + hw_d + ' ' + v_res + ' <i class="fa fa-long-arrow-right"></i> ' + s.stream_video_codec.toUpperCase() + hw_e + ' ' + sv_res + ')';
|
||||
} else if (s.stream_video_decision === 'copy') {
|
||||
video_decision = 'Direct Stream (' + s.stream_video_codec.toUpperCase() + ' ' + sv_res + ')';
|
||||
} else {
|
||||
@@ -407,11 +473,11 @@
|
||||
$('#video_decision-' + key).html(video_decision);
|
||||
|
||||
var audio_decision = '';
|
||||
if (['movie', 'episode', 'clip', 'track'].indexOf(s.media_type) > -1 && s.audio_codec) {
|
||||
if (['movie', 'episode', 'clip', 'track'].indexOf(s.media_type) > -1 && s.audio_decision) {
|
||||
var a_codec = (s.audio_codec === 'truehd') ? 'TrueHD' : s.audio_codec.toUpperCase();
|
||||
var sa_codec = (s.stream_audio_codec === 'truehd') ? 'TrueHD' : s.stream_audio_codec.toUpperCase();
|
||||
if (s.stream_audio_decision === 'transcode') {
|
||||
audio_decision = 'Transcode (' + a_codec + ' ' + capitalizeFirstLetter(s.audio_channel_layout.split('(')[0]) + ' → ' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')';
|
||||
audio_decision = 'Transcode (' + a_codec + ' ' + capitalizeFirstLetter(s.audio_channel_layout.split('(')[0]) + ' <i class="fa fa-long-arrow-right"></i> ' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')';
|
||||
} else if (s.stream_audio_decision === 'copy') {
|
||||
audio_decision = 'Direct Stream (' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')';
|
||||
} else {
|
||||
@@ -423,19 +489,19 @@
|
||||
var subtitle_decision = 'None';
|
||||
if (['movie', 'episode', 'clip'].indexOf(s.media_type) > -1 && s.subtitles === 1) {
|
||||
if (s.stream_subtitle_decision === 'transcode') {
|
||||
subtitle_decision = 'Transcode (' + s.subtitle_codec.toUpperCase() + ' → ' + s.stream_subtitle_codec.toUpperCase() + ')';
|
||||
subtitle_decision = 'Transcode (' + s.subtitle_codec.toUpperCase() + ' <i class="fa fa-long-arrow-right"></i> ' + s.stream_subtitle_codec.toUpperCase() + ')';
|
||||
} else if (s.stream_subtitle_decision === 'copy') {
|
||||
subtitle_decision = 'Direct Stream (' + s.subtitle_codec.toUpperCase() + ')';
|
||||
} else if (s.stream_subtitle_decision === 'burn') {
|
||||
subtitle_decision = 'Burn (' + s.subtitle_codec.toUpperCase() + ')';
|
||||
} else {
|
||||
subtitle_decision = 'Direct Play (' + s.subtitle_codec.toUpperCase() + ')';
|
||||
subtitle_decision = 'Direct Play (' + ((s.synced_version === '1') ? s.stream_subtitle_codec.toUpperCase() : s.subtitle_codec.toUpperCase()) + ')';
|
||||
}
|
||||
}
|
||||
$('#subtitle_decision-' + key).html(subtitle_decision);
|
||||
|
||||
// Update the stream quality profile and bandwidth
|
||||
if (s.media_type != 'photo' && s.quality_profile != 'Unknown') {
|
||||
if (s.media_type !== 'photo' && s.quality_profile !== 'Unknown') {
|
||||
var br = parseInt(s.stream_bitrate) || '';
|
||||
if (br) {
|
||||
if (br > 1000) {
|
||||
@@ -448,33 +514,36 @@
|
||||
} else {
|
||||
$('#stream_quality-' + key).html(s.quality_profile);
|
||||
}
|
||||
$('#optimized_version-' + key).html(s.optimized_version_profile);
|
||||
$('#optimized_version-' + key).html(s.optimized_version_profile + ' (' + s.optimized_version_title + ')');
|
||||
$('#synced_quality_profile-' + key).html(s.synced_quality_profile);
|
||||
|
||||
if (s.media_type != 'photo' && parseInt(s.bandwidth)) {
|
||||
var bw = parseInt(s.bandwidth);
|
||||
if (bw != "Unknown") {
|
||||
if (bw > 1000) {
|
||||
bw = (bw / 1000).toFixed(1) + ' Mbps';
|
||||
} else {
|
||||
bw = bw + ' kbps'
|
||||
}
|
||||
$('#location-' + key).html(s.location.toUpperCase());
|
||||
|
||||
if (s.media_type !== 'photo' && s.bandwidth !== 'Unknown') {
|
||||
var bw = parseInt(s.bandwidth) || 0;
|
||||
if (bw > 1000) {
|
||||
bw = (bw / 1000).toFixed(1) + ' Mbps';
|
||||
} else {
|
||||
bw = bw + ' kbps'
|
||||
}
|
||||
$('#stream-bandwidth-' + key).html(bw);
|
||||
}
|
||||
};
|
||||
|
||||
// Update the stream progress times
|
||||
$('#stream-eta-' + key).html(moment().add(parseInt(s.duration) - parseInt(s.view_offset), 'milliseconds').format(time_format));
|
||||
$('#stream-view-offset-' + key).data('state', s.state);
|
||||
if ($('#stream-view-offset-' + key).data('last_view_offset') != s.view_offset) {
|
||||
$('#stream-view-offset-' + key).data('last_view_offset', s.view_offset).data('view_offset', s.view_offset);
|
||||
var stream_view_offset = $('#stream-view-offset-' + key);
|
||||
stream_view_offset.data('state', s.state);
|
||||
if (stream_view_offset.data('last_view_offset') !== s.view_offset) {
|
||||
stream_view_offset.data('last_view_offset', s.view_offset).data('view_offset', s.view_offset);
|
||||
}
|
||||
|
||||
// Update the progress bars, percent - 3 because of 3px padding-right
|
||||
$('#buffer-bar-' + key).width(parseInt(s.transcode_progress) - 3 + '%').html(s.transcode_progress + '%')
|
||||
.attr('data-original-title', 'Transcoder Progress ' + s.transcode_progress + '%');
|
||||
$('#progress-bar-' + key).data('state', s.state);
|
||||
if ($('#progress-bar-' + key).data('last_view_offset') != s.view_offset) {
|
||||
$('#progress-bar-' + key).data('last_view_offset', s.view_offset).data('view_offset', s.view_offset);
|
||||
var progress_bar = $('#progress-bar-' + key);
|
||||
progress_bar.data('state', s.state);
|
||||
if (progress_bar.data('last_view_offset') && progress_bar.data('last_view_offset') !== s.view_offset) {
|
||||
progress_bar.data('last_view_offset', s.view_offset).data('view_offset', s.view_offset);
|
||||
}
|
||||
|
||||
// Add temporary class so we know which instances are still active
|
||||
@@ -492,34 +561,45 @@
|
||||
});
|
||||
|
||||
} else {
|
||||
$('#currentActivityHeader').text('');
|
||||
$('#currentActivityHeader').hide();
|
||||
$('#currentActivity').html('<div id="dashboard-no-activity" class="text-muted">Nothing is currently being played.</div>');
|
||||
}
|
||||
|
||||
activity_ready = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getActivityInstance(session) {
|
||||
function getActivityInstance(session_key) {
|
||||
$.ajax({
|
||||
url: 'get_current_activity_instance',
|
||||
type: 'GET',
|
||||
cache: false,
|
||||
async: true,
|
||||
data: session,
|
||||
data: {
|
||||
session_key: session_key
|
||||
},
|
||||
complete: function(xhr, status) {
|
||||
$('#currentActivity').append(xhr.responseText);
|
||||
$('#activity-instance-' + session.session_key + ' .dashboard-activity-info-scroller').scrollbar();
|
||||
$('#activity-instance-' + session.session_key + ' [data-toggle=tooltip]').tooltip({ container: 'body', placement: 'right', delay: 50 })
|
||||
$('#terminate-button-' + session.session_key).tooltip('destroy').tooltip({ container: 'body', placement: 'left', delay: 50 });
|
||||
lockScroll('#activity-instance-' + session.session_key + ' .dashboard-activity-info-scroller');
|
||||
$('#activity-instance-' + session_key + ' .dashboard-activity-info-scroller').scrollbar();
|
||||
$('#activity-instance-' + session_key + ' [data-toggle=tooltip]').tooltip({ container: 'body', placement: 'right', delay: 50 });
|
||||
$('#terminate-button-' + session_key).tooltip('destroy').tooltip({ container: 'body', placement: 'left', delay: 50 });
|
||||
lockScroll('#activity-instance-' + session_key + ' .dashboard-activity-info-scroller');
|
||||
|
||||
var index = create_instances.indexOf(session_key);
|
||||
if (index > -1) {
|
||||
create_instances.splice(index, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getCurrentActivity();
|
||||
setInterval(function () {
|
||||
getCurrentActivity();
|
||||
}, 2000);
|
||||
if (!(create_instances.length) && activity_ready) {
|
||||
getCurrentActivity();
|
||||
}
|
||||
}, ${config['home_refresh_interval'] * 1000});
|
||||
|
||||
setInterval(function(){
|
||||
$('.progress_time_offset').each(function () {
|
||||
@@ -534,7 +614,7 @@
|
||||
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.min(Math.trunc(view_offset / stream_duration * 100), 100)
|
||||
var progress_percent = Math.min(Math.trunc(view_offset / stream_duration * 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));
|
||||
@@ -609,6 +689,12 @@
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$('#sessions-shortcut').on('tripleclick', function () {
|
||||
$.getJSON('return_sessions_url', function(sessions_url) {
|
||||
window.open(sessions_url, '_blank');
|
||||
});
|
||||
});
|
||||
% endif
|
||||
</script>
|
||||
% endif
|
||||
@@ -670,7 +756,7 @@
|
||||
getLibraryStats();
|
||||
</script>
|
||||
% endif
|
||||
% if 'recently_added' in config['home_sections']:
|
||||
% if 'recently_added' in config['home_sections'] and PLEX_SERVER_UP:
|
||||
<script>
|
||||
function recentlyAdded(recently_added_count, recently_added_type) {
|
||||
showMsg("Loading recently added items...", true, false, 0);
|
||||
@@ -726,13 +812,13 @@
|
||||
leftTotal = Math.max(Math.min(leftTotal + scrollAmount, 0), leftMax);
|
||||
scroller.animate({ left: leftTotal }, 250);
|
||||
|
||||
if (leftTotal == 0) {
|
||||
if (leftTotal === 0) {
|
||||
$("#recently-added-page-left").addClass("disabled").blur();
|
||||
} else {
|
||||
$("#recently-added-page-left").removeClass("disabled");
|
||||
}
|
||||
|
||||
if (leftTotal == leftMax) {
|
||||
if (leftTotal === leftMax) {
|
||||
$("#recently-added-page-right").addClass("disabled").blur();
|
||||
} else {
|
||||
$("#recently-added-page-right").removeClass("disabled");
|
||||
@@ -764,7 +850,7 @@
|
||||
$.ajax({
|
||||
url: 'get_changelog',
|
||||
data: {
|
||||
latest_only: true,
|
||||
since_prev_release: true,
|
||||
update_shown: true
|
||||
},
|
||||
cache: false,
|
||||
|
@@ -38,20 +38,21 @@ DOCUMENTATION :: END
|
||||
<%!
|
||||
import re
|
||||
|
||||
from plexpy import common, notifiers
|
||||
from plexpy import notifiers
|
||||
from plexpy.common import MEDIA_TYPE_HEADERS, MEDIA_FLAGS_AUDIO, MEDIA_FLAGS_VIDEO
|
||||
|
||||
# Get audio codec file
|
||||
def af(codec):
|
||||
for pattern, file in common.MEDIA_FLAGS_AUDIO.iteritems():
|
||||
for pattern, file_type in MEDIA_FLAGS_AUDIO.iteritems():
|
||||
if re.match(pattern, codec):
|
||||
return file
|
||||
return file_type
|
||||
return codec
|
||||
|
||||
# Get audio codec file
|
||||
def vf(codec):
|
||||
for pattern, file in common.MEDIA_FLAGS_VIDEO.iteritems():
|
||||
for pattern, file_type in MEDIA_FLAGS_VIDEO.iteritems():
|
||||
if re.match(pattern, codec):
|
||||
return file
|
||||
return file_type
|
||||
return codec
|
||||
|
||||
def br(text):
|
||||
@@ -63,7 +64,7 @@ DOCUMENTATION :: END
|
||||
<%def name="headIncludes()">
|
||||
<link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css">
|
||||
<link rel="stylesheet" href="${http_root}css/dataTables.colVis.css">
|
||||
<link rel="stylesheet" href="${http_root}css/plexpy-dataTables.css">
|
||||
<link rel="stylesheet" href="${http_root}css/tautulli-dataTables.css">
|
||||
</%def>
|
||||
|
||||
<%def name="body()">
|
||||
@@ -80,7 +81,7 @@ DOCUMENTATION :: END
|
||||
<div class="col-md-12">
|
||||
<div class="summary-navbar-list">
|
||||
<ul class="list-unstyled breadcrumb">
|
||||
% if data['media_type'] == 'movie':
|
||||
% if data['media_type'] in ('movie', 'collection'):
|
||||
<li><a href="library?section_id=${data['section_id']}">${data['library_name']}</a></li>
|
||||
<li class="active">${data['title']}</li>
|
||||
% elif data['media_type'] == 'show':
|
||||
@@ -116,9 +117,9 @@ DOCUMENTATION :: END
|
||||
<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'] or 'https://app.plex.tv/desktop'}#!/server/${config['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${data['parent_rating_key']}" target="_blank" title="View in Plex Web">
|
||||
<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">
|
||||
% else:
|
||||
<a href="${config['pms_web_url'] or 'https://app.plex.tv/desktop'}#!/server/${config['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${data['rating_key']}" target="_blank" title="View in Plex Web">
|
||||
<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">
|
||||
% endif
|
||||
% if data['media_type'] == 'episode':
|
||||
<div class="summary-poster-face-episode" style="background-image: url(pms_image_proxy?img=${data['thumb']}&width=500&height=280&fallback=art);">
|
||||
@@ -151,7 +152,7 @@ DOCUMENTATION :: END
|
||||
</a>
|
||||
</div>
|
||||
<div class="summary-content-title">
|
||||
% if data['media_type'] in ('movie', 'show', 'artist'):
|
||||
% if data['media_type'] in ('movie', 'show', 'artist', 'collection'):
|
||||
<h1> </h1><h1>${data['title']}</h1>
|
||||
% elif data['media_type'] == 'season':
|
||||
<h1> </h1><h1><a href="info?rating_key=${data['parent_rating_key']}">${data['parent_title']}</a></h1>
|
||||
@@ -175,7 +176,7 @@ DOCUMENTATION :: END
|
||||
<div class="col-md-9">
|
||||
% if data['media_type'] == 'movie':
|
||||
<div class="summary-content-padding hidden-xs hidden-sm" style="height: 305px;">
|
||||
% elif data['media_type'] == 'show' or data['media_type'] == 'season':
|
||||
% 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;">
|
||||
@@ -235,6 +236,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'] == 'collection':
|
||||
Year <strong> ${data['min_year']} - ${data['max_year']}</strong>
|
||||
% endif
|
||||
</div>
|
||||
<div class="summary-content-details-tag">
|
||||
@@ -308,51 +311,65 @@ DOCUMENTATION :: END
|
||||
</div>
|
||||
% if data['media_type'] == 'show':
|
||||
<div class="col-md-12">
|
||||
<div class='table-card-header'>
|
||||
<div class="table-card-header">
|
||||
<div class="header-bar">
|
||||
<span>Season List for <strong>${data['title']}</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class='table-card-back'>
|
||||
<div id="children-list"><i class="fa fa-refresh fa-spin"></i> Loading season list...</div>
|
||||
<div class="table-card-back">
|
||||
<div id="children-list" class="children-list"><i class="fa fa-refresh fa-spin"></i> Loading season list...</div>
|
||||
</div>
|
||||
</div>
|
||||
% elif data['media_type'] == 'season':
|
||||
<div class="col-md-12">
|
||||
<div class='table-card-header'>
|
||||
<div class="table-card-header">
|
||||
<div class="header-bar">
|
||||
<span>Episode List for <strong>${data['title']}</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class='table-card-back'>
|
||||
<div id="children-list"><i class="fa fa-refresh fa-spin"></i> Loading episode list...</div>
|
||||
<div class="table-card-back">
|
||||
<div id="children-list" class="children-list"><i class="fa fa-refresh fa-spin"></i> Loading episode list...</div>
|
||||
</div>
|
||||
</div>
|
||||
% elif data['media_type'] == 'artist':
|
||||
<div class="col-md-12">
|
||||
<div class='table-card-header'>
|
||||
<div class="table-card-header">
|
||||
<div class="header-bar">
|
||||
<span>Album List for <strong>${data['title']}</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class='table-card-back'>
|
||||
<div id="children-list"><i class="fa fa-refresh fa-spin"></i> Loading album list...</div>
|
||||
<div class="table-card-back">
|
||||
<div id="children-list" class="children-list"><i class="fa fa-refresh fa-spin"></i> Loading album list...</div>
|
||||
</div>
|
||||
</div>
|
||||
% elif data['media_type'] == 'album':
|
||||
<div class="col-md-12">
|
||||
<div class='table-card-header'>
|
||||
<div class="table-card-header">
|
||||
<div class="header-bar">
|
||||
<span>Track List for <strong>${data['title']}</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class='table-card-back'>
|
||||
<div id="children-list"><i class="fa fa-refresh fa-spin"></i> Loading track list...</div>
|
||||
<div class="table-card-back">
|
||||
<div id="children-list" class="children-list"><i class="fa fa-refresh fa-spin"></i> Loading track list...</div>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
% elif data['media_type'] == 'collection':
|
||||
<div class="col-md-12">
|
||||
<div class='table-card-header'>
|
||||
<div class="table-card-header">
|
||||
<div class="header-bar">
|
||||
<span>${MEDIA_TYPE_HEADERS[data['sub_media_type']]} in <strong>${data['title']}</strong> collection</span>
|
||||
</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>
|
||||
</div>
|
||||
<div id="collection-related-list-container" style="display: none;">
|
||||
</div>
|
||||
% endif
|
||||
% if data['media_type'] != 'collection':
|
||||
<div class="col-md-12">
|
||||
<div class="table-card-header">
|
||||
<div class="header-bar">
|
||||
<span>Watch History for <strong>${data['title']}</strong></span>
|
||||
</div>
|
||||
@@ -371,16 +388,26 @@ DOCUMENTATION :: END
|
||||
</a>
|
||||
</div>
|
||||
% endif
|
||||
% if data.get('tvmaze_id') or data.get('themoviedb_id'):
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-danger btn-edit" data-toggle="modal" aria-pressed="false" autocomplete="off" id="delete-lookup-info"
|
||||
data-id="${data['grandparent_rating_key'] if data['media_type'] in ('episode', 'track') else data['parent_rating_key'] if data['media_type'] in ('season', 'album') else data['rating_key']}"
|
||||
data-title="${data['grandparent_title'] if data['media_type'] in ('episode', 'track') else data['parent_title'] if data['media_type'] in ('season', 'album') else data['title']}">
|
||||
<i class="fa fa-search"></i> Delete Lookup Info
|
||||
</button>
|
||||
</div>
|
||||
% endif
|
||||
% if data.get('poster_url'):
|
||||
<div class="btn-group">
|
||||
% if data['media_type'] == 'artist' or data['media_type'] == 'album' or data['media_type'] == 'track':
|
||||
<span class="imgur-poster-tooltip" data-toggle="popover" data-img="${data['poster_url']}" data-height="80" data-width="80" style="display: inline-flex;">
|
||||
<span class="hosted-poster-tooltip" data-toggle="popover" data-img="${data['poster_url']}" data-height="80" data-width="80" style="display: inline-flex;">
|
||||
% else:
|
||||
<span class="imgur-poster-tooltip" data-toggle="popover" data-img="${data['poster_url']}" data-height="120" data-width="80" style="display: inline-flex;">
|
||||
<span class="hosted-poster-tooltip" data-toggle="popover" data-img="${data['poster_url']}" data-height="120" data-width="80" style="display: inline-flex;">
|
||||
% endif
|
||||
<button class="btn btn-danger btn-edit" data-toggle="modal" aria-pressed="false" autocomplete="off" id="delete-imgur-poster"
|
||||
data-id="${data['parent_rating_key'] if data['media_type'] in ('episode', 'track') else data['rating_key']}">
|
||||
<i class="fa fa-picture-o"></i> Reset Imgur Poster
|
||||
<button class="btn btn-danger btn-edit" data-toggle="modal" aria-pressed="false" autocomplete="off" id="delete-hosted-poster"
|
||||
data-id="${data['parent_rating_key'] if data['media_type'] in ('episode', 'track') else data['rating_key']}"
|
||||
data-title="${data["poster_title"]}">
|
||||
<i class="fa fa-picture-o"></i> Delete ${data['img_service']} Poster
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
@@ -420,6 +447,7 @@ DOCUMENTATION :: END
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -428,7 +456,7 @@ DOCUMENTATION :: END
|
||||
</%def>
|
||||
|
||||
<%def name="modalIncludes()">
|
||||
<div id="info-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="info-modal">
|
||||
<div class="modal fade" id="info-modal" tabindex="-1" role="dialog" aria-labelledby="info-modal">
|
||||
</div>
|
||||
<div class="modal fade" id="ip-info-modal" tabindex="-1" role="dialog" aria-labelledby="ip-info-modal">
|
||||
</div>
|
||||
@@ -493,6 +521,7 @@ DOCUMENTATION :: END
|
||||
% endfor
|
||||
</select>
|
||||
</div>
|
||||
<p class="help-block">Note: All custom notification conditions will be bypassed.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@@ -518,12 +547,12 @@ DOCUMENTATION :: END
|
||||
function get_history() {
|
||||
history_table_options.ajax = {
|
||||
url: 'get_history',
|
||||
type: 'post',
|
||||
type: 'POST',
|
||||
data: function ( d ) {
|
||||
return {
|
||||
json_data: JSON.stringify( d ),
|
||||
grandparent_rating_key: "${data['rating_key']}",
|
||||
user_id: "${_session['user_id']}" == "None" ? null : "${_session['user_id']}"
|
||||
user_id: "${_session['user_group']}" == "admin" ? null : "${_session['user_id']}"
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -534,12 +563,12 @@ DOCUMENTATION :: END
|
||||
function get_history() {
|
||||
history_table_options.ajax = {
|
||||
url: 'get_history',
|
||||
type: 'post',
|
||||
type: 'POST',
|
||||
data: function ( d ) {
|
||||
return {
|
||||
json_data: JSON.stringify( d ),
|
||||
parent_rating_key: "${data['rating_key']}",
|
||||
user_id: "${_session['user_id']}" == "None" ? null : "${_session['user_id']}"
|
||||
user_id: "${_session['user_group']}" == "admin" ? null : "${_session['user_id']}"
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -550,18 +579,19 @@ DOCUMENTATION :: END
|
||||
function get_history() {
|
||||
history_table_options.ajax = {
|
||||
url: 'get_history',
|
||||
type: 'post',
|
||||
type: 'POST',
|
||||
data: function ( d ) {
|
||||
return {
|
||||
json_data: JSON.stringify( d ),
|
||||
rating_key: "${data['rating_key']}",
|
||||
user_id: "${_session['user_id']}" == "None" ? null : "${_session['user_id']}"
|
||||
user_id: "${_session['user_group']}" == "admin" ? null : "${_session['user_id']}"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
% endif
|
||||
% if data['media_type'] != 'collection':
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
get_history();
|
||||
@@ -638,7 +668,8 @@ DOCUMENTATION :: END
|
||||
});
|
||||
});
|
||||
</script>
|
||||
% if data['media_type'] in ('show', 'season', 'artist', 'album'):
|
||||
% endif
|
||||
% if data['media_type'] in ('show', 'season', 'artist', 'album', 'collection'):
|
||||
<script>
|
||||
$.ajax({
|
||||
url: 'get_item_children',
|
||||
@@ -646,7 +677,24 @@ DOCUMENTATION :: END
|
||||
async: true,
|
||||
data: { rating_key : "${data['rating_key']}" },
|
||||
complete: function(xhr, status) {
|
||||
$("#children-list").html(xhr.responseText); }
|
||||
$("#children-list").html(xhr.responseText);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
% endif
|
||||
% if data['media_type'] == 'collection':
|
||||
<script>
|
||||
$.ajax({
|
||||
url: 'get_item_children_related',
|
||||
type: 'GET',
|
||||
async: true,
|
||||
data: {
|
||||
rating_key : "${data['rating_key']}",
|
||||
title: "${data['title']}"
|
||||
},
|
||||
complete: function(xhr, status) {
|
||||
$("#collection-related-list-container").html(xhr.responseText).show();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
% endif
|
||||
@@ -657,7 +705,7 @@ DOCUMENTATION :: END
|
||||
</script>
|
||||
% if data.get('poster_url'):
|
||||
<script>
|
||||
$('.imgur-poster-tooltip').popover({
|
||||
$('.hosted-poster-tooltip').popover({
|
||||
html: true,
|
||||
container: 'body',
|
||||
trigger: 'hover',
|
||||
@@ -668,14 +716,29 @@ DOCUMENTATION :: END
|
||||
}
|
||||
});
|
||||
|
||||
$('#delete-imgur-poster').on('click', function () {
|
||||
var msg = 'Are you sure you want to reset the Imgur poster for <strong>${data["poster_title"]}</strong>?';
|
||||
var url = 'delete_poster_url';
|
||||
var data = { rating_key: $(this).data('id') }
|
||||
$('#delete-hosted-poster').on('click', function () {
|
||||
var msg = 'Are you sure you want to delete the ${data['img_service']} poster for <strong>' + $(this).data('title') + '</strong>?<br><br>' +
|
||||
'All previous links to this image will no longer work.';
|
||||
var url = 'delete_hosted_images';
|
||||
var data = { rating_key: $(this).data('id') };
|
||||
var callback = function () {
|
||||
$('.imgur-poster-tooltip').popover('destroy');
|
||||
$('#delete-imgur-poster').closest('span').remove();
|
||||
}
|
||||
$('.hosted-poster-tooltip').popover('destroy');
|
||||
$('#delete-hosted-poster').closest('.btn-group').remove();
|
||||
};
|
||||
confirmAjaxCall(url, msg, data, false, callback);
|
||||
});
|
||||
</script>
|
||||
% endif
|
||||
% if data.get('tvmaze_id') or data.get('themoviedb_id'):
|
||||
<script>
|
||||
$('#delete-lookup-info').on('click', function () {
|
||||
var msg = 'Are you sure you want to delete the 3rd party API lookup for <strong>' + $(this).data('title') + '</strong>?<br><br>' +
|
||||
'The info will be looked up again the next time a notification is sent.';
|
||||
var url = 'delete_lookup_info';
|
||||
var data = { rating_key: $(this).data('id'), title: $(this).data('title') };
|
||||
var callback = function () {
|
||||
$('#delete-lookup-info').closest('.btn-group').remove();
|
||||
};
|
||||
confirmAjaxCall(url, msg, data, false, callback);
|
||||
});
|
||||
</script>
|
||||
|
@@ -37,13 +37,42 @@ DOCUMENTATION :: END
|
||||
% else:
|
||||
<li>
|
||||
% endif
|
||||
%if data['children_type'] == 'season':
|
||||
% if data['children_type'] == 'movie':
|
||||
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">
|
||||
<div class="item-children-poster">
|
||||
<div class="item-children-poster-face poster-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div>
|
||||
% if _session['user_group'] == 'admin':
|
||||
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
|
||||
% endif
|
||||
</div>
|
||||
</a>
|
||||
<div class="item-children-instance-text-wrapper poster-item">
|
||||
<h3>
|
||||
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">${child['title']}</a>
|
||||
</h3>
|
||||
<h3 class="text-muted">${child['year']}</h3>
|
||||
</div>
|
||||
% elif data['children_type'] == 'show':
|
||||
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">
|
||||
<div class="item-children-poster">
|
||||
<div class="item-children-poster-face poster-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div>
|
||||
% if _session['user_group'] == 'admin':
|
||||
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
|
||||
% endif
|
||||
</div>
|
||||
</a>
|
||||
<div class="item-children-instance-text-wrapper poster-item">
|
||||
<h3>
|
||||
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">${child['title']}</a>
|
||||
</h3>
|
||||
</div>
|
||||
% elif data['children_type'] == 'season':
|
||||
<a href="info?rating_key=${child['rating_key']}" title="Season ${child['media_index']}">
|
||||
<div class="item-children-poster">
|
||||
% if child['thumb']:
|
||||
<div class="item-children-poster-face season-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);">
|
||||
<div class="item-children-poster-face poster-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);">
|
||||
% else:
|
||||
<div class="item-children-poster-face season-poster" style="background-image: url(pms_image_proxy?img=${child['parent_thumb']}&width=300&height=450&fallback=poster);">
|
||||
<div class="item-children-poster-face poster-item" style="background-image: url(pms_image_proxy?img=${child['parent_thumb']}&width=300&height=450&fallback=poster);">
|
||||
% endif
|
||||
<div class="item-children-card-overlay">
|
||||
<div class="item-children-overlay-text">
|
||||
@@ -59,10 +88,10 @@ DOCUMENTATION :: END
|
||||
% elif data['children_type'] == 'episode':
|
||||
<a href="info?rating_key=${child['rating_key']}" title="Episode ${child['media_index']}">
|
||||
<div class="item-children-poster">
|
||||
<div class="item-children-poster-face episode-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=500&height=250&fallback=art);">
|
||||
<div class="item-children-poster-face episode-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=500&height=250&fallback=art);">
|
||||
<div class="item-children-card-overlay">
|
||||
<div class="item-children-overlay-text">
|
||||
Episode ${child['media_index']}
|
||||
Episode ${child['media_index'] or child['originally_available_at']}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -79,13 +108,13 @@ DOCUMENTATION :: END
|
||||
% elif data['children_type'] == 'album':
|
||||
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">
|
||||
<div class="item-children-poster">
|
||||
<div class="item-children-poster-face album-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=300&fallback=cover);"></div>
|
||||
<div class="item-children-poster-face cover-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=300&fallback=cover);"></div>
|
||||
% if _session['user_group'] == 'admin':
|
||||
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
|
||||
% endif
|
||||
</div>
|
||||
</a>
|
||||
<div class="item-children-instance-text-wrapper album-item">
|
||||
<div class="item-children-instance-text-wrapper cover-item">
|
||||
<h3>
|
||||
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">${child['title']}</a>
|
||||
</h3>
|
||||
|
92
data/interfaces/default/info_collection_list.html
Normal file
@@ -0,0 +1,92 @@
|
||||
<%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: info_collection_list.html
|
||||
Version: 0.1
|
||||
Variable names: data [list]
|
||||
|
||||
data :: Usable parameters
|
||||
|
||||
== Global keys ==
|
||||
children_type Returns the type of children in the array.
|
||||
children_count Returns the number of episodes in the array.
|
||||
children_list Returns an array of episodes.
|
||||
|
||||
data['children_list'] :: Usable paramaters
|
||||
|
||||
== Global keys ==
|
||||
rating_key Returns the unique identifier for the media item.
|
||||
media_index Returns the episode number.
|
||||
title Returns the name of the episode.
|
||||
thumb Returns the location of the item's thumbnail. Use with pms_image_proxy.
|
||||
parent_thumb Returns the location of the item's parent thumbnail. Use with pms_image_proxy.
|
||||
|
||||
DOCUMENTATION :: END
|
||||
</%doc>
|
||||
|
||||
% if data != None:
|
||||
<%
|
||||
from plexpy.common import MEDIA_TYPE_HEADERS
|
||||
types = ('movie', 'show', 'artist', 'album')
|
||||
%>
|
||||
% for media_type in types:
|
||||
% if data['results_list'][media_type]:
|
||||
<div class="col-md-12">
|
||||
<div class="table-card-header">
|
||||
<div class="header-bar">
|
||||
<span>${MEDIA_TYPE_HEADERS[media_type]} in <strong>${title}</strong> collection</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-card-back">
|
||||
<div id="children-list" class="children-list">
|
||||
<div class="item-children-wrapper">
|
||||
<ul class="item-children-instance list-unstyled">
|
||||
% for child in data['results_list'][media_type]:
|
||||
<li>
|
||||
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">
|
||||
<div class="item-children-poster">
|
||||
% if media_type in ('artist', 'album'):
|
||||
<div class="item-children-poster-face cover-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=300&fallback=cover);"></div>
|
||||
% else:
|
||||
<div class="item-children-poster-face poster-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div>
|
||||
% endif
|
||||
% if _session['user_group'] == 'admin':
|
||||
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
|
||||
% endif
|
||||
</div>
|
||||
</a>
|
||||
% if media_type == 'artist':
|
||||
<div class="item-children-instance-text-wrapper cover-item">
|
||||
<h3>
|
||||
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">${child['title']}</a>
|
||||
</h3>
|
||||
</div>
|
||||
% elif media_type == 'album':
|
||||
<div class="item-children-instance-text-wrapper cover-item">
|
||||
<h3>
|
||||
<a href="info?rating_key=${child['parent_rating_key']}" title="${child['parent_title']}">${child['parent_title']}</a>
|
||||
</h3>
|
||||
<h3>
|
||||
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">${child['title']}</a>
|
||||
</h3>
|
||||
</div>
|
||||
% else:
|
||||
<div class="item-children-instance-text-wrapper poster-item">
|
||||
<h3>
|
||||
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">${child['title']}</a>
|
||||
</h3>
|
||||
<h3 class="text-muted">${child['year']}</h3>
|
||||
</div>
|
||||
% endif
|
||||
</li>
|
||||
% endfor
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
% endfor
|
||||
% endif
|
@@ -54,6 +54,31 @@ DOCUMENTATION :: END
|
||||
|
||||
% if data != None:
|
||||
% if data['results_count'] > 0:
|
||||
% if 'collection' in data['results_list'] and data['results_list']['collection']:
|
||||
<div class="item-children-wrapper">
|
||||
<div class="item-children-section-title">
|
||||
<h4>Collections</h4>
|
||||
</div>
|
||||
<ul class="item-children-instance list-unstyled">
|
||||
% for child in data['results_list']['collection']:
|
||||
<li>
|
||||
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
|
||||
<div class="item-children-poster">
|
||||
<div class="item-children-poster-face poster-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div>
|
||||
% if _session['user_group'] == 'admin':
|
||||
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
|
||||
% endif
|
||||
</div>
|
||||
<div class="item-children-instance-text-wrapper poster-item">
|
||||
<h3 title="${child['title']}">${child['title']}</h3>
|
||||
<h3 class="text-muted">${child['min_year']} - ${child['max_year']}</h3>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
% endfor
|
||||
</ul>
|
||||
</div>
|
||||
% endif
|
||||
% if 'movie' in data['results_list'] and data['results_list']['movie']:
|
||||
<div class="item-children-wrapper">
|
||||
<div class="item-children-section-title">
|
||||
@@ -64,12 +89,12 @@ DOCUMENTATION :: END
|
||||
<li>
|
||||
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
|
||||
<div class="item-children-poster">
|
||||
<div class="item-children-poster-face season-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div>
|
||||
<div class="item-children-poster-face poster-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div>
|
||||
% if _session['user_group'] == 'admin':
|
||||
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
|
||||
% endif
|
||||
</div>
|
||||
<div class="item-children-instance-text-wrapper season-item">
|
||||
<div class="item-children-instance-text-wrapper poster-item">
|
||||
<h3 title="${child['title']}">${child['title']}</h3>
|
||||
<h3 class="text-muted">${child['year']}</h3>
|
||||
</div>
|
||||
@@ -89,12 +114,12 @@ DOCUMENTATION :: END
|
||||
<li>
|
||||
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
|
||||
<div class="item-children-poster">
|
||||
<div class="item-children-poster-face season-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div>
|
||||
<div class="item-children-poster-face poster-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div>
|
||||
% if _session['user_group'] == 'admin':
|
||||
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
|
||||
% endif
|
||||
</div>
|
||||
<div class="item-children-instance-text-wrapper season-item">
|
||||
<div class="item-children-instance-text-wrapper poster-item">
|
||||
<h3 title="${child['title']}">${child['title']}</h3>
|
||||
<h3 class="text-muted">${child['year']}</h3>
|
||||
</div>
|
||||
@@ -114,12 +139,12 @@ DOCUMENTATION :: END
|
||||
<li>
|
||||
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
|
||||
<div class="item-children-poster">
|
||||
<div class="item-children-poster-face season-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div>
|
||||
<div class="item-children-poster-face poster-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div>
|
||||
% if _session['user_group'] == 'admin':
|
||||
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
|
||||
% endif
|
||||
</div>
|
||||
<div class="item-children-instance-text-wrapper season-item">
|
||||
<div class="item-children-instance-text-wrapper poster-item">
|
||||
<h3 title="${child['parent_title']}">${child['parent_title']}</h3>
|
||||
<h3 class="text-muted">S${child['media_index']}</h3>
|
||||
</div>
|
||||
@@ -139,7 +164,7 @@ DOCUMENTATION :: END
|
||||
<li>
|
||||
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
|
||||
<div class="item-children-poster">
|
||||
<div class="item-children-poster-face episode-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=500&height=250&fallback=art);"></div>
|
||||
<div class="item-children-poster-face episode-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=500&height=250&fallback=art);"></div>
|
||||
% if _session['user_group'] == 'admin':
|
||||
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
|
||||
% endif
|
||||
@@ -165,7 +190,7 @@ DOCUMENTATION :: END
|
||||
<li>
|
||||
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
|
||||
<div class="item-children-poster">
|
||||
<div class="item-children-poster-face album-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=300&fallback=cover);"></div>
|
||||
<div class="item-children-poster-face cover-item style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=300&fallback=cover);"></div>
|
||||
% if _session['user_group'] == 'admin':
|
||||
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
|
||||
% endif
|
||||
@@ -189,7 +214,7 @@ DOCUMENTATION :: END
|
||||
<li>
|
||||
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
|
||||
<div class="item-children-poster">
|
||||
<div class="item-children-poster-face album-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=300&fallback=cover);"></div>
|
||||
<div class="item-children-poster-face cover-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=300&fallback=cover);"></div>
|
||||
% if _session['user_group'] == 'admin':
|
||||
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
|
||||
% endif
|
||||
@@ -214,7 +239,7 @@ DOCUMENTATION :: END
|
||||
<li>
|
||||
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
|
||||
<div class="item-children-poster">
|
||||
<div class="item-children-poster-face album-poster" style="background-image: url(pms_image_proxy?img=${child['parent_thumb']}&width=300&height=300&fallback=cover);">
|
||||
<div class="item-children-poster-face cover-item" style="background-image: url(pms_image_proxy?img=${child['parent_thumb']}&width=300&height=300&fallback=cover);">
|
||||
<div class="item-children-card-overlay">
|
||||
<div class="item-children-overlay-text">
|
||||
Track ${child['media_index']}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
PNotify.prototype.options.addclass = "stack-bottomright";
|
||||
PNotify.prototype.options.buttons.closer_hover = false;
|
||||
PNotify.prototype.options.desktop = { desktop: true, icon: 'images/logo.png' }
|
||||
PNotify.prototype.options.desktop = { desktop: true, icon: 'images/logo-circle.png' };
|
||||
PNotify.prototype.options.history = false;
|
||||
PNotify.prototype.options.shadow = false;
|
||||
PNotify.prototype.options.stack = { dir1: 'up', dir2: 'left', firstpos1: 25, firstpos2: 25 };
|
||||
@@ -21,7 +21,7 @@ function check_notifications() {
|
||||
$.getJSON('get_browser_notifications', function (data) {
|
||||
if (data) {
|
||||
$.each(data, function (i, notification) {
|
||||
if (notification.delay == 0) {
|
||||
if (notification.delay === 0) {
|
||||
PNotify.prototype.options.hide = false;
|
||||
} else {
|
||||
PNotify.prototype.options.hide = true;
|
||||
@@ -34,7 +34,7 @@ function check_notifications() {
|
||||
setTimeout(function () {
|
||||
"use strict";
|
||||
check_notifications();
|
||||
}, 3000);
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
|
1
data/interfaces/default/js/jquery-cron-min.js
vendored
Normal file
1
data/interfaces/default/js/jquery.tripleclick.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!function(t){function i(t){var i=jQuery(this);settings=jQuery.extend({},e,t.data);var a=i.data("triclick_clicks")||0,c=i.data("triclick_start")||0;0===a&&(c=t.timeStamp),0!=c&&t.timeStamp>c+settings.threshold&&(a=0,c=t.timeStamp),a+=1,3===a&&(a=0,c=0,t.type="tripleclick",void 0===jQuery.event.handle?jQuery.event.dispatch.apply(this,arguments):jQuery.event.handle.apply(this,arguments)),i.data("triclick_clicks",a),i.data("triclick_start",c)}var e={threshold:1e3};t.event.special.tripleclick={setup:function(e,a){t(this).bind("touchstart click.triple",e,i)},teardown:function(e){t(this).unbind("touchstart click.triple",i)}}}(jQuery);
|
@@ -1,17 +1,17 @@
|
||||
function initConfigCheckbox(elem) {
|
||||
var config = $(elem).closest('div').next();
|
||||
function initConfigCheckbox(elem, toggleElem = null, reverse = false) {
|
||||
var config = toggleElem ? $(toggleElem) : $(elem).closest('div').next();
|
||||
config.css('overflow', 'hidden');
|
||||
if ($(elem).is(":checked")) {
|
||||
config.show();
|
||||
config.toggle(!reverse);
|
||||
} else {
|
||||
config.hide();
|
||||
config.toggle(reverse);
|
||||
}
|
||||
$(elem).click(function () {
|
||||
var config = $(this).closest('div').next();
|
||||
var config = toggleElem ? $(toggleElem) : $(this).closest('div').next();
|
||||
if ($(this).is(":checked")) {
|
||||
config.slideDown();
|
||||
config.slideToggleBool(!reverse);
|
||||
} else {
|
||||
config.slideUp();
|
||||
config.slideToggleBool(reverse);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -26,7 +26,7 @@ function refreshTab() {
|
||||
|
||||
function showMsg(msg, loader, timeout, ms, error) {
|
||||
var feedback = $("#ajaxMsg");
|
||||
update = $("#updatebar");
|
||||
var update = $("#updatebar");
|
||||
if (update.is(":visible")) {
|
||||
var height = update.height() + 35;
|
||||
feedback.css("bottom", height + "px");
|
||||
@@ -35,7 +35,7 @@ function showMsg(msg, loader, timeout, ms, error) {
|
||||
}
|
||||
var message = $("<div class='msg'>" + msg + "</div>");
|
||||
if (loader) {
|
||||
var message = $("<i class='fa fa-refresh fa-spin'></i> " + msg + "</div>");
|
||||
message = $("<i class='fa fa-refresh fa-spin'></i> " + msg + "</div>");
|
||||
feedback.css("padding", "14px 10px")
|
||||
}
|
||||
if (error) {
|
||||
@@ -290,19 +290,13 @@ String.prototype.toProperCase = function () {
|
||||
|
||||
function millisecondsToMinutes(ms, roundToMinute) {
|
||||
if (ms > 0) {
|
||||
seconds = ms / 1000;
|
||||
minutes = seconds / 60;
|
||||
if (roundToMinute) {
|
||||
output = Math.round(minutes, 0)
|
||||
} else {
|
||||
minutesFloor = Math.floor(minutes);
|
||||
secondsReal = Math.round((seconds - (minutesFloor * 60)), 0);
|
||||
if (secondsReal < 10) {
|
||||
secondsReal = '0' + secondsReal;
|
||||
}
|
||||
output = minutesFloor + ':' + secondsReal;
|
||||
}
|
||||
return output;
|
||||
var minutes = Math.floor(ms / 60000);
|
||||
var seconds = ((ms % 60000) / 1000).toFixed(0);
|
||||
if (roundToMinute) {
|
||||
return (seconds >= 30 ? (minutes + 1) : minutes);
|
||||
} else {
|
||||
return (seconds == 60 ? (minutes + 1) + ":00" : minutes + ":" + (seconds < 10 ? "0" : "") + seconds);
|
||||
}
|
||||
} else {
|
||||
if (roundToMinute) {
|
||||
return '0';
|
||||
@@ -455,4 +449,8 @@ function forceMinMax(elem) {
|
||||
|
||||
function capitalizeFirstLetter(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
|
||||
$.fn.slideToggleBool = function(bool, options) {
|
||||
return bool ? $(this).slideDown(options) : $(this).slideUp(options);
|
||||
}
|
@@ -21,7 +21,7 @@ history_table_options = {
|
||||
"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,
|
||||
"processing": false,
|
||||
@@ -59,6 +59,8 @@ history_table_options = {
|
||||
state = '<span class="current-activity-tooltip" data-toggle="tooltip" title="Currently Paused"><i class="fa fa-pause fa-fw"></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>';
|
||||
} 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>';
|
||||
}
|
||||
$(td).html('<div><div style="float: left;">' + state + ' ' + date + '</div></div>');
|
||||
} else if (rowData['group_count'] > 1) {
|
||||
@@ -170,7 +172,7 @@ history_table_options = {
|
||||
},
|
||||
"width": "33%",
|
||||
"className": "datatable-wrap"
|
||||
},
|
||||
},
|
||||
{
|
||||
"targets": [7],
|
||||
"data":"started",
|
||||
@@ -203,7 +205,7 @@ history_table_options = {
|
||||
"targets": [9],
|
||||
"data":"stopped",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData === null || rowData['state'] != null) {
|
||||
if (cellData === null || (rowData['state'] != null && rowData['state'] != "stopped")) {
|
||||
$(td).html('n/a');
|
||||
} else {
|
||||
$(td).html(moment(cellData,"X").format(time_format));
|
||||
@@ -268,7 +270,7 @@ history_table_options = {
|
||||
});
|
||||
|
||||
if ($('#row-edit-mode').hasClass('active')) {
|
||||
$('.delete-control').each(function () {
|
||||
$('.history_table .delete-control').each(function () {
|
||||
$(this).removeClass('hidden');
|
||||
});
|
||||
}
|
||||
@@ -288,7 +290,9 @@ history_table_options = {
|
||||
},
|
||||
"preDrawCallback": function(settings) {
|
||||
var msg = "<i class='fa fa-refresh fa-spin'></i> Fetching rows...";
|
||||
showMsg(msg, false, false, 0)
|
||||
showMsg(msg, false, false, 0);
|
||||
$('[data-toggle="tooltip"]').tooltip('destroy');
|
||||
$('[data-toggle="popover"]').popover('destroy');
|
||||
},
|
||||
"rowCallback": function (row, rowData, rowIndex) {
|
||||
if (rowData['group_count'] == 1) {
|
||||
@@ -320,7 +324,7 @@ history_table_options = {
|
||||
$(row).addClass('current-activity-row');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Parent table platform modal
|
||||
$('.history_table').on('click', '> tbody > tr > td.modal-control', function () {
|
||||
@@ -462,7 +466,7 @@ function childTableOptions(rowData) {
|
||||
});
|
||||
|
||||
if ($('#row-edit-mode').hasClass('active')) {
|
||||
$('.delete-control').each(function () {
|
||||
$('.history_table .delete-control').each(function () {
|
||||
$(this).removeClass('hidden');
|
||||
});
|
||||
}
|
||||
|
@@ -28,9 +28,7 @@ libraries_list_table_options = {
|
||||
$(td).html('<div class="edit-library-toggles">' +
|
||||
'<button class="btn btn-xs btn-warning delete-library" data-id="' + rowData['section_id'] + '" data-toggle="button"><i class="fa fa-trash-o fa-fw"></i> Delete</button> ' +
|
||||
'<button class="btn btn-xs btn-warning purge-library" data-id="' + rowData['section_id'] + '" data-toggle="button"><i class="fa fa-eraser fa-fw"></i> Purge</button>   ' +
|
||||
'<input type="checkbox" id="do_notify-' + rowData['section_id'] + '" name="do_notify" value="1" ' + rowData['do_notify'] + '><label class="edit-tooltip" for="do_notify-' + rowData['section_id'] + '" data-toggle="tooltip" title="Toggle Notifications"><i class="fa fa-bell fa-lg fa-fw"></i></label> ' +
|
||||
'<input type="checkbox" id="keep_history-' + rowData['section_id'] + '" name="keep_history" value="1" ' + rowData['keep_history'] + '><label class="edit-tooltip" for="keep_history-' + rowData['section_id'] + '" data-toggle="tooltip" title="Toggle History"><i class="fa fa-history fa-lg fa-fw"></i></label> ' +
|
||||
'<input type="checkbox" id="do_notify_created-' + rowData['section_id'] + '" name="do_notify_created" value="1" ' + rowData['do_notify_created'] + '><label class="edit-tooltip" for="do_notify_created-' + rowData['section_id'] + '" data-toggle="tooltip" title="Toggle Recently Added"><i class="fa fa-download fa-lg fa-fw"></i></label> ' +
|
||||
'</div>');
|
||||
},
|
||||
"width": "7%",
|
||||
@@ -258,15 +256,7 @@ $('#libraries_list_table').on('change', 'td.edit-control > .edit-library-toggles
|
||||
var row = libraries_list_table.row(tr);
|
||||
var rowData = row.data();
|
||||
|
||||
var do_notify = 0;
|
||||
var do_notify_created = 0;
|
||||
var keep_history = 0;
|
||||
if ($('#do_notify-' + rowData['section_id']).is(':checked')) {
|
||||
do_notify = 1;
|
||||
}
|
||||
if ($('#do_notify_created-' + rowData['section_id']).is(':checked')) {
|
||||
do_notify_created = 1;
|
||||
}
|
||||
if ($('#keep_history-' + rowData['section_id']).is(':checked')) {
|
||||
keep_history = 1;
|
||||
}
|
||||
@@ -280,8 +270,6 @@ $('#libraries_list_table').on('change', 'td.edit-control > .edit-library-toggles
|
||||
url: 'edit_library',
|
||||
data: {
|
||||
section_id: rowData['section_id'],
|
||||
do_notify: do_notify,
|
||||
do_notify_created: do_notify_created,
|
||||
keep_history: keep_history,
|
||||
custom_thumb: custom_thumb
|
||||
},
|
||||
|
@@ -113,7 +113,7 @@ login_log_table_options = {
|
||||
var msg = "<i class='fa fa-refresh fa-spin'></i> Fetching rows...";
|
||||
showMsg(msg, false, false, 0)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$('.login_log_table').on('click', '> tbody > tr > td.modal-control-ip', function () {
|
||||
var tr = $(this).closest('tr');
|
||||
|
@@ -54,7 +54,7 @@ media_info_table_options = {
|
||||
} else if (rowData['media_type'] === 'album') {
|
||||
expand_details = '<span class="expand-media-info-tooltip" data-toggle="tooltip" title="Show Tracks"><i class="fa fa-plus-circle fa-fw"></i></span>';
|
||||
$(td).html('<div><a href="#"><div style="float: left;">' + expand_details + ' ' + date + '</div></a></div>');
|
||||
} else if (rowData['media_type'] === 'photo' && rowData['parent_rating_key'] == '') {
|
||||
} else if (rowData['media_type'] === 'photo_album') {
|
||||
expand_details = '<span class="expand-media-info-tooltip" data-toggle="tooltip" title="Show Photos"><i class="fa fa-plus-circle fa-fw"></i></span>';
|
||||
$(td).html('<div><a href="#"><div style="float: left;">' + expand_details + ' ' + date + '</div></a></div>');
|
||||
} else {
|
||||
@@ -68,7 +68,7 @@ media_info_table_options = {
|
||||
},
|
||||
{
|
||||
"targets": [1],
|
||||
"data": "title",
|
||||
"data": "sort_title",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== null && cellData !== '') {
|
||||
var parent_info = '';
|
||||
@@ -77,32 +77,44 @@ media_info_table_options = {
|
||||
if (rowData['media_type'] === 'movie') {
|
||||
if (rowData['year']) { parent_info = ' (' + rowData['year'] + ')'; }
|
||||
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Movie"><i class="fa fa-film fa-fw"></i></span>';
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + rowData['title'] + parent_info + '</span>';
|
||||
$(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
||||
} else if (rowData['media_type'] === 'show') {
|
||||
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="TV Show"><i class="fa fa-television fa-fw"></i></span>';
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + '</span>'
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + rowData['title'] + '</span>';
|
||||
$(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
||||
} else if (rowData['media_type'] === 'season') {
|
||||
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Season"><i class="fa fa-television fa-fw"></i></span>';
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + '</span>'
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + rowData['title'] + '</span>';
|
||||
$(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left; padding-left: 15px;">' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
||||
} else if (rowData['media_type'] === 'episode') {
|
||||
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Episode"><i class="fa fa-television fa-fw"></i></span>';
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=art" data-height="80" data-width="140">E' + rowData['media_index'] + ' - ' + cellData + '</span>'
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=art" data-height="80" data-width="140">E' + rowData['media_index'] + ' - ' + rowData['title'] + '</span>';
|
||||
$(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left; padding-left: 30px;">' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
||||
} else if (rowData['media_type'] === 'artist') {
|
||||
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Artist"><i class="fa fa-music fa-fw"></i></span>';
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=300&fallback=cover" data-height="80" data-width="80">' + cellData + '</span>'
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=300&fallback=cover" data-height="80" data-width="80">' + rowData['title'] + '</span>';
|
||||
$(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
||||
} else if (rowData['media_type'] === 'album') {
|
||||
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Album"><i class="fa fa-music fa-fw"></i></span>';
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=300&fallback=cover" data-height="80" data-width="80">' + cellData + '</span>'
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=300&fallback=cover" data-height="80" data-width="80">' + rowData['title'] + '</span>';
|
||||
$(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left; padding-left: 15px;">' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
||||
} else if (rowData['media_type'] === 'track') {
|
||||
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Track"><i class="fa fa-music fa-fw"></i></span>';
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=300&fallback=cover" data-height="80" data-width="80">T' + rowData['media_index'] + ' - ' + cellData + '</span>'
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=300&fallback=cover" data-height="80" data-width="80">T' + rowData['media_index'] + ' - ' + rowData['title'] + '</span>';
|
||||
$(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left; padding-left: 30px;">' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
||||
} 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="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=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>');
|
||||
} 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="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=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>');
|
||||
} 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="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=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>');
|
||||
} else {
|
||||
$(td).html(cellData);
|
||||
}
|
||||
@@ -335,7 +347,7 @@ function childTableOptionsMedia(rowData) {
|
||||
case 'album':
|
||||
section_type = 'track';
|
||||
break;
|
||||
case 'photo':
|
||||
case 'photo_album':
|
||||
section_type = 'picture';
|
||||
break;
|
||||
}
|
||||
|
146
data/interfaces/default/js/tables/newsletter_logs.js
Normal file
@@ -0,0 +1,146 @@
|
||||
newsletter_log_table_options = {
|
||||
"destroy": true,
|
||||
"serverSide": true,
|
||||
"processing": false,
|
||||
"pagingType": "full_numbers",
|
||||
"order": [ 0, 'desc'],
|
||||
"pageLength": 50,
|
||||
"stateSave": true,
|
||||
"language": {
|
||||
"search":"Search: ",
|
||||
"lengthMenu": "Show _MENU_ lines per page",
|
||||
"emptyTable": "No log information available",
|
||||
"info" :"Showing _START_ to _END_ of _TOTAL_ lines",
|
||||
"infoEmpty": "Showing 0 to 0 of 0 lines",
|
||||
"infoFiltered": "(filtered from _MAX_ total lines)",
|
||||
"loadingRecords": '<i class="fa fa-refresh fa-spin"></i> Loading items...</div>'
|
||||
},
|
||||
"autoWidth": false,
|
||||
"scrollX": true,
|
||||
"columnDefs": [
|
||||
{
|
||||
"targets": [0],
|
||||
"data": "timestamp",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
$(td).html(moment(cellData, "X").format('YYYY-MM-DD HH:mm:ss'));
|
||||
}
|
||||
},
|
||||
"width": "10%",
|
||||
"className": "no-wrap"
|
||||
},
|
||||
{
|
||||
"targets": [1],
|
||||
"data": "newsletter_id",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
$(td).html(cellData);
|
||||
}
|
||||
},
|
||||
"width": "5%",
|
||||
"className": "no-wrap"
|
||||
},
|
||||
{
|
||||
"targets": [2],
|
||||
"data": "agent_name",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
$(td).html(cellData);
|
||||
}
|
||||
},
|
||||
"width": "5%",
|
||||
"className": "no-wrap"
|
||||
},
|
||||
{
|
||||
"targets": [3],
|
||||
"data": "notify_action",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
$(td).html(cellData);
|
||||
}
|
||||
},
|
||||
"width": "5%",
|
||||
"className": "no-wrap"
|
||||
},
|
||||
{
|
||||
"targets": [4],
|
||||
"data": "subject_text",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
$(td).html(cellData);
|
||||
}
|
||||
},
|
||||
"width": "23%"
|
||||
},
|
||||
{
|
||||
"targets": [5],
|
||||
"data": "body_text",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
$(td).html(cellData);
|
||||
}
|
||||
},
|
||||
"width": "35%"
|
||||
},
|
||||
{
|
||||
"targets": [6],
|
||||
"data": "start_date",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
$(td).html(cellData);
|
||||
}
|
||||
},
|
||||
"width": "5%"
|
||||
},
|
||||
{
|
||||
"targets": [7],
|
||||
"data": "end_date",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
$(td).html(cellData);
|
||||
}
|
||||
},
|
||||
"width": "5%"
|
||||
},
|
||||
{
|
||||
"targets": [8],
|
||||
"data": "uuid",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
$(td).html('<a href="newsletter/' + rowData['uuid'] + '" target="_blank">' + cellData + '</a>');
|
||||
}
|
||||
},
|
||||
"width": "5%"
|
||||
},
|
||||
{
|
||||
"targets": [9],
|
||||
"data": "success",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData === 1) {
|
||||
$(td).html('<span class="success-tooltip" data-toggle="tooltip" title="Newsletter Sent"><i class="fa fa-lg fa-fw fa-check"></i></span>');
|
||||
} else {
|
||||
$(td).html('<span class="success-tooltip" data-toggle="tooltip" title="Newsletter Failed"><i class="fa fa-lg fa-fw fa-times"></i></span>');
|
||||
}
|
||||
},
|
||||
"searchable": false,
|
||||
"orderable": false,
|
||||
"className": "no-wrap",
|
||||
"width": "2%"
|
||||
},
|
||||
],
|
||||
"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'
|
||||
});
|
||||
},
|
||||
"preDrawCallback": function(settings) {
|
||||
var msg = "<i class='fa fa-refresh fa-spin'></i> Fetching rows...";
|
||||
showMsg(msg, false, false, 0)
|
||||
}
|
||||
};
|
@@ -86,7 +86,7 @@ notification_log_table_options = {
|
||||
"targets": [6],
|
||||
"data": "success",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData == 1) {
|
||||
if (cellData === 1) {
|
||||
$(td).html('<span class="success-tooltip" data-toggle="tooltip" title="Notification Sent"><i class="fa fa-lg fa-fw fa-check"></i></span>');
|
||||
} else {
|
||||
$(td).html('<span class="success-tooltip" data-toggle="tooltip" title="Notification Failed"><i class="fa fa-lg fa-fw fa-times"></i></span>');
|
||||
@@ -113,4 +113,4 @@ notification_log_table_options = {
|
||||
var msg = "<i class='fa fa-refresh fa-spin'></i> Fetching rows...";
|
||||
showMsg(msg, false, false, 0)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -37,7 +37,6 @@ sync_table_options = {
|
||||
"data": "state",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData === 'pending') {
|
||||
$(td).addClass('currentlyWatching');
|
||||
$(td).html('Pending...');
|
||||
} else {
|
||||
$(td).html(cellData.toProperCase());
|
||||
@@ -47,7 +46,7 @@ sync_table_options = {
|
||||
},
|
||||
{
|
||||
"targets": [2],
|
||||
"data": "friendly_name",
|
||||
"data": "user",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
if (rowData['user_id']) {
|
||||
@@ -63,10 +62,10 @@ sync_table_options = {
|
||||
},
|
||||
{
|
||||
"targets": [3],
|
||||
"data": "title",
|
||||
"data": "sync_title",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
if (rowData['metadata_type'] !== '') {
|
||||
if (rowData['rating_key']) {
|
||||
$(td).html('<a href="info?rating_key=' + rowData['rating_key'] + '">' + cellData + '</a>');
|
||||
} else {
|
||||
$(td).html(cellData);
|
||||
@@ -74,7 +73,7 @@ sync_table_options = {
|
||||
}
|
||||
},
|
||||
"className": "datatable-wrap"
|
||||
},
|
||||
},
|
||||
{
|
||||
"targets": [4],
|
||||
"data": "metadata_type",
|
||||
@@ -98,7 +97,7 @@ sync_table_options = {
|
||||
"data": "total_size",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData > 0 ) {
|
||||
megabytes = Math.round((cellData/1024)/1024, 0)
|
||||
megabytes = Math.round((cellData/1024)/1024, 0);
|
||||
$(td).html(megabytes + 'MB');
|
||||
} else {
|
||||
$(td).html('0MB');
|
||||
@@ -139,19 +138,33 @@ sync_table_options = {
|
||||
// $('html,body').scrollTop(0);
|
||||
|
||||
$('#ajaxMsg').fadeOut();
|
||||
|
||||
if ($('#sync-row-edit-mode').hasClass('active')) {
|
||||
$('.sync_table .delete-control').each(function () {
|
||||
$(this).removeClass('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
"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) {
|
||||
if (rowData['state'] === 'pending') {
|
||||
$(row).addClass('current-activity-row');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$('#sync_table').on('click', 'td.delete-control > .edit-sync-toggles > button.delete-sync', function () {
|
||||
$('.sync_table').on('click', 'td.delete-control > .edit-sync-toggles > button.delete-sync', function () {
|
||||
var tr = $(this).parents('tr');
|
||||
var row = sync_table.row(tr);
|
||||
var rowData = row.data();
|
||||
|
||||
var index_delete = syncs_to_delete.findIndex(x => x.client_id == rowData['client_id'] && x.sync_id == rowData['sync_id']);
|
||||
var index_delete = syncs_to_delete.findIndex(function (x) {
|
||||
return x.client_id === rowData['client_id'] && x.sync_id === rowData['sync_id'];
|
||||
});
|
||||
|
||||
if (index_delete === -1) {
|
||||
syncs_to_delete.push({ client_id: rowData['client_id'], sync_id: rowData['sync_id'] });
|
||||
|
@@ -45,7 +45,6 @@ users_list_table_options = {
|
||||
$(td).html('<div class="edit-user-toggles">' +
|
||||
'<button class="btn btn-xs btn-warning delete-user" data-id="' + rowData['user_id'] + '" data-toggle="button"><i class="fa fa-trash-o fa-fw"></i> Delete</button> ' +
|
||||
'<button class="btn btn-xs btn-warning purge-user" data-id="' + rowData['user_id'] + '" data-toggle="button"><i class="fa fa-eraser fa-fw"></i> Purge</button>   ' +
|
||||
'<input type="checkbox" id="do_notify-' + rowData['user_id'] + '" name="do_notify" value="1" ' + rowData['do_notify'] + '><label class="edit-tooltip" for="do_notify-' + rowData['user_id'] + '" data-toggle="tooltip" title="Toggle Notifications"><i class="fa fa-bell fa-lg fa-fw"></i></label> ' +
|
||||
'<input type="checkbox" id="keep_history-' + rowData['user_id'] + '" name="keep_history" value="1" ' + rowData['keep_history'] + '><label class="edit-tooltip" for="keep_history-' + rowData['user_id'] + '" data-toggle="tooltip" title="Toggle History"><i class="fa fa-history fa-lg fa-fw"></i></label> ' +
|
||||
'<input type="checkbox" id="allow_guest-' + rowData['user_id'] + '" name="allow_guest" value="1" ' + rowData['allow_guest'] + '><label class="edit-tooltip" for="allow_guest-' + rowData['user_id'] + '" data-toggle="tooltip" title="Toggle Guest Access"><i class="fa fa-unlock-alt fa-lg fa-fw"></i></label> ' +
|
||||
'</div>');
|
||||
@@ -284,12 +283,8 @@ $('#users_list_table').on('change', 'td.edit-control > .edit-user-toggles > inpu
|
||||
var row = users_list_table.row(tr);
|
||||
var rowData = row.data();
|
||||
|
||||
var do_notify = 0;
|
||||
var keep_history = 0;
|
||||
var allow_guest = 0;
|
||||
if ($('#do_notify-' + rowData['user_id']).is(':checked')) {
|
||||
do_notify = 1;
|
||||
}
|
||||
if ($('#keep_history-' + rowData['user_id']).is(':checked')) {
|
||||
keep_history = 1;
|
||||
}
|
||||
@@ -304,7 +299,6 @@ $('#users_list_table').on('change', 'td.edit-control > .edit-user-toggles > inpu
|
||||
data: {
|
||||
user_id: rowData['user_id'],
|
||||
friendly_name: friendly_name,
|
||||
do_notify: do_notify,
|
||||
keep_history: keep_history,
|
||||
allow_guest: allow_guest,
|
||||
thumb: rowData['user_thumb']
|
||||
|
@@ -3,22 +3,11 @@
|
||||
<%def name="headIncludes()">
|
||||
<link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css">
|
||||
<link rel="stylesheet" href="${http_root}css/dataTables.colVis.css">
|
||||
<link rel="stylesheet" href="${http_root}css/plexpy-dataTables.css">
|
||||
<link rel="stylesheet" href="${http_root}css/tautulli-dataTables.css">
|
||||
</%def>
|
||||
|
||||
<%def name="body()">
|
||||
<div class='container-fluid'>
|
||||
% if config['update_section_ids'] == 1:
|
||||
<div id="update_section_ids_message" style="text-align: center; margin-top: 20px;">
|
||||
<i class="fa fa-exclamation-triangle"></i> Tautulli needs to update the Library IDs in your databse. Click the "<strong>Refresh libraries</strong>" button below to begin the update.
|
||||
</div>
|
||||
% elif config['update_section_ids'] == -1:
|
||||
<div id="update_section_ids_message" style="text-align: center; margin-top: 20px;">
|
||||
<i class="fa fa-refresh fa-spin"></i> Tautulli is updating library IDs in the database. This could take a few minutes to hours depending on the size of your database.
|
||||
<br />
|
||||
You may leave this page and come back later.
|
||||
</div>
|
||||
% endif
|
||||
<div class='table-card-header'>
|
||||
<div class="header-bar">
|
||||
<span><i class="fa fa-book"></i> All Libraries</span>
|
||||
@@ -31,16 +20,10 @@
|
||||
<i class="fa fa-pencil"></i> Edit mode
|
||||
</button> 
|
||||
</div>
|
||||
% if config['update_section_ids'] == -1:
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-dark refresh-libraries-button" id="refresh-libraries-list" disabled><i class="fa fa-refresh"></i> Refresh libraries</button>
|
||||
</div>
|
||||
% else:
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-dark refresh-libraries-button" id="refresh-libraries-list"><i class="fa fa-refresh"></i> Refresh libraries</button>
|
||||
</div>
|
||||
% endif
|
||||
% endif
|
||||
<div class="btn-group colvis-button-bar"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -108,7 +91,7 @@
|
||||
json_data: JSON.stringify(d)
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
libraries_list_table = $('#libraries_list_table').DataTable(libraries_list_table_options);
|
||||
var colvis = new $.fn.dataTable.ColVis(libraries_list_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 1] });
|
||||
@@ -197,26 +180,20 @@
|
||||
|
||||
% if _session['user_group'] == 'admin':
|
||||
$("#refresh-libraries-list").click(function () {
|
||||
if ("${config['update_section_ids']}" == "1") {
|
||||
$('#update_section_ids_message').html(
|
||||
'<i class="fa fa-refresh fa-spin"></i> Tautulli is updating library IDs in the database. This could take a few minutes to hours depending on the size of your database.' +
|
||||
'<br />' +
|
||||
'You may leave this page and come back later.');
|
||||
$(this).prop('disabled', true);
|
||||
}
|
||||
|
||||
showMsg('Refreshing libraries list...', true, false);
|
||||
$.ajax({
|
||||
url: 'refresh_libraries_list',
|
||||
cache: false,
|
||||
async: true,
|
||||
success: function (data) {
|
||||
showMsg('<i class="fa fa-refresh"></i> Libraries list refresh started...', false, true, 2000, false);
|
||||
},
|
||||
complete: function (data) {
|
||||
showMsg('<i class="fa fa-check"></i> Libraries list refreshed.', false, true, 2000, false);
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
showMsg('<i class="fa fa-exclamation-circle"></i> Unable to refresh libraries list.', false, true, 2000, true);
|
||||
complete: function (xhr, status) {
|
||||
var result = $.parseJSON(xhr.responseText);
|
||||
var msg = result.message;
|
||||
if (result.result == 'success') {
|
||||
showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 2000, false);
|
||||
libraries_list_table.draw();
|
||||
} else {
|
||||
showMsg('<i class="fa fa-times"></i> ' + msg, false, true, 2000, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@@ -30,7 +30,7 @@ DOCUMENTATION :: END
|
||||
<%def name="headIncludes()">
|
||||
<link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css">
|
||||
<link rel="stylesheet" href="${http_root}css/dataTables.colVis.css">
|
||||
<link rel="stylesheet" href="${http_root}css/plexpy-dataTables.css">
|
||||
<link rel="stylesheet" href="${http_root}css/tautulli-dataTables.css">
|
||||
</%def>
|
||||
|
||||
<%def name="body()">
|
||||
@@ -71,11 +71,11 @@ DOCUMENTATION :: END
|
||||
% endif
|
||||
</div>
|
||||
<div class="user-info-nav">
|
||||
<ul class="user-info-nav">
|
||||
<li class="active"><a href="#profile" data-toggle="tab">Profile</a></li>
|
||||
<li><a id="history-tab-btn" href="#libraryHistory" data-toggle="tab">History</a></li>
|
||||
<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':
|
||||
<li><a id="media-info-tab-btn" href="#libraryMediaInfo" data-toggle="tab">Media Info</a></li>
|
||||
<li><a id="media-info-tab-btn" href="#tabs-mediainfo" role="tab" data-toggle="tab">Media Info</a></li>
|
||||
% endif
|
||||
</ul>
|
||||
</div>
|
||||
@@ -83,7 +83,7 @@ DOCUMENTATION :: END
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="profile">
|
||||
<div role="tabpanel" class="tab-pane active" id="tabs-profile">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
@@ -169,7 +169,7 @@ DOCUMENTATION :: END
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane" id="libraryHistory">
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-history">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
@@ -221,7 +221,7 @@ DOCUMENTATION :: END
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane" id="libraryMediaInfo">
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-mediainfo">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
@@ -366,8 +366,257 @@ DOCUMENTATION :: END
|
||||
<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>
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
$.fn.dataTable.tables({ visible: true, api: true }).columns.adjust();
|
||||
});
|
||||
|
||||
function loadHistoryTable() {
|
||||
// Build watch history table
|
||||
history_table_options.ajax = {
|
||||
url: 'get_history',
|
||||
type: 'POST',
|
||||
data: function ( d ) {
|
||||
return {
|
||||
json_data: JSON.stringify( d ),
|
||||
section_id: section_id,
|
||||
user_id: "${_session['user_group']}" == "admin" ? null : "${_session['user_id']}"
|
||||
};
|
||||
}
|
||||
};
|
||||
history_table = $('#history_table-SID-${data["section_id"]}').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, 11] });
|
||||
$(colvis.button()).appendTo('#button-bar-history');
|
||||
|
||||
clearSearchButton('history_table-SID-${data["section_id"]}', history_table);
|
||||
}
|
||||
|
||||
$('a[href="#tabs-history"]').on('shown.bs.tab', function() {
|
||||
if (typeof(history_table) === 'undefined') {
|
||||
loadHistoryTable();
|
||||
}
|
||||
});
|
||||
|
||||
$("#refresh-history-list").click(function () {
|
||||
history_table.draw();
|
||||
});
|
||||
|
||||
% if _session['user_group'] == 'admin':
|
||||
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);
|
||||
}
|
||||
|
||||
$('a[href="#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;
|
||||
});
|
||||
|
||||
$("#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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#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 () {
|
||||
history_to_delete.forEach(function(row, idx) {
|
||||
$.ajax({
|
||||
url: 'delete_history_rows',
|
||||
type: 'POST',
|
||||
data: { row_id: row },
|
||||
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');
|
||||
});
|
||||
}
|
||||
});
|
||||
% endif
|
||||
|
||||
function recentlyWatched() {
|
||||
// Populate recently watched
|
||||
$.ajax({
|
||||
url: 'library_recently_watched',
|
||||
async: true,
|
||||
data: {
|
||||
section_id: section_id,
|
||||
limit: 50
|
||||
},
|
||||
complete: function(xhr, status) {
|
||||
$("#library-recently-watched").html(xhr.responseText);
|
||||
highlightWatchedScrollerButton();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function recentlyAdded() {
|
||||
// Populate recently added
|
||||
$.ajax({
|
||||
url: 'library_recently_added',
|
||||
async: true,
|
||||
data: {
|
||||
section_id: section_id,
|
||||
limit: 50
|
||||
},
|
||||
complete: function(xhr, status) {
|
||||
$("#library-recently-added").html(xhr.responseText);
|
||||
highlightAddedScrollerButton();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
recentlyWatched();
|
||||
recentlyAdded();
|
||||
|
||||
function highlightWatchedScrollerButton() {
|
||||
var scroller = $("#recently-watched-row-scroller");
|
||||
var numElems = scroller.find("li").length;
|
||||
scroller.width(numElems * 175);
|
||||
if (scroller.width() > $("#library-recently-watched").width()) {
|
||||
$("#recently-watched-page-right").removeClass("disabled");
|
||||
} else {
|
||||
$("#recently-watched-page-right").addClass("disabled");
|
||||
}
|
||||
}
|
||||
|
||||
function highlightAddedScrollerButton() {
|
||||
var scroller = $("#recently-added-row-scroller");
|
||||
var numElems = scroller.find("li").length;
|
||||
scroller.width(numElems * 175);
|
||||
if (scroller.width() > $("#library-recently-added").width()) {
|
||||
$("#recently-added-page-right").removeClass("disabled");
|
||||
} else {
|
||||
$("#recently-added-page-right").addClass("disabled");
|
||||
}
|
||||
}
|
||||
|
||||
$(window).resize(function() {
|
||||
highlightWatchedScrollerButton();
|
||||
highlightAddedScrollerButton();
|
||||
});
|
||||
|
||||
$('div.art-face').animate({ opacity: 0.2 }, { duration: 1000 });
|
||||
|
||||
var leftTotalWatched = 0;
|
||||
$(".paginate-watched").click(function (e) {
|
||||
e.preventDefault();
|
||||
var scroller = $("#recently-watched-row-scroller");
|
||||
var containerWidth = $("#library-recently-watched").width();
|
||||
var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175;
|
||||
var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0);
|
||||
|
||||
leftTotalWatched = Math.max(Math.min(leftTotalWatched + scrollAmount, 0), leftMax);
|
||||
scroller.animate({ left: leftTotalWatched }, 250);
|
||||
|
||||
if (leftTotalWatched == 0) {
|
||||
$("#recently-watched-page-left").addClass("disabled").blur();
|
||||
} else {
|
||||
$("#recently-watched-page-left").removeClass("disabled");
|
||||
}
|
||||
|
||||
if (leftTotalWatched == leftMax) {
|
||||
$("#recently-watched-page-right").addClass("disabled").blur();
|
||||
} else {
|
||||
$("#recently-watched-page-right").removeClass("disabled");
|
||||
}
|
||||
});
|
||||
|
||||
var leftTotalAdded = 0;
|
||||
$(".paginate-added").click(function (e) {
|
||||
e.preventDefault();
|
||||
var scroller = $("#recently-added-row-scroller");
|
||||
var containerWidth = $("#library-recently-added").width();
|
||||
var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175;
|
||||
var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0);
|
||||
|
||||
leftTotalAdded = Math.max(Math.min(leftTotalAdded + scrollAmount, 0), leftMax);
|
||||
scroller.animate({ left: leftTotalAdded }, 250);
|
||||
|
||||
if (leftTotalAdded == 0) {
|
||||
$("#recently-added-page-left").addClass("disabled").blur();
|
||||
} else {
|
||||
$("#recently-added-page-left").removeClass("disabled");
|
||||
}
|
||||
|
||||
if (leftTotalAdded == leftMax) {
|
||||
$("#recently-added-page-right").addClass("disabled").blur();
|
||||
} else {
|
||||
$("#recently-added-page-right").removeClass("disabled");
|
||||
}
|
||||
});
|
||||
|
||||
$(document).ready(function () {
|
||||
$("#edit-library-tooltip").tooltip();
|
||||
|
||||
// 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({
|
||||
@@ -389,237 +638,6 @@ DOCUMENTATION :: END
|
||||
}
|
||||
});
|
||||
|
||||
function loadHistoryTable() {
|
||||
// Build watch history table
|
||||
history_table_options.ajax = {
|
||||
url: 'get_history',
|
||||
type: 'post',
|
||||
data: function ( d ) {
|
||||
return {
|
||||
json_data: JSON.stringify( d ),
|
||||
section_id: section_id,
|
||||
user_id: "${_session['user_id']}" == "None" ? null : "${_session['user_id']}"
|
||||
};
|
||||
}
|
||||
}
|
||||
history_table = $('#history_table-SID-${data["section_id"]}').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, 11] });
|
||||
$(colvis.button()).appendTo('#button-bar-history');
|
||||
|
||||
clearSearchButton('history_table-SID-${data["section_id"]}', history_table);
|
||||
}
|
||||
|
||||
$( "#history-tab-btn" ).one( "click", function() {
|
||||
loadHistoryTable();
|
||||
});
|
||||
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
$.fn.dataTable.tables({ visible: true, api: true }).columns.adjust();
|
||||
});
|
||||
|
||||
% if _session['user_group'] == 'admin':
|
||||
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);
|
||||
}
|
||||
|
||||
$( "#media-info-tab-btn" ).one( "click", function() {
|
||||
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;
|
||||
});
|
||||
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#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 () {
|
||||
history_to_delete.forEach(function(row, idx) {
|
||||
$.ajax({
|
||||
url: 'delete_history_rows',
|
||||
type: 'POST',
|
||||
data: { row_id: row },
|
||||
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');
|
||||
});
|
||||
}
|
||||
});
|
||||
% endif
|
||||
|
||||
$("#refresh-history-list").click(function () {
|
||||
history_table.draw();
|
||||
});
|
||||
|
||||
function recentlyWatched() {
|
||||
// Populate recently watched
|
||||
$.ajax({
|
||||
url: 'library_recently_watched',
|
||||
async: true,
|
||||
data: {
|
||||
section_id: section_id,
|
||||
limit: 50
|
||||
},
|
||||
complete: function(xhr, status) {
|
||||
$("#library-recently-watched").html(xhr.responseText);
|
||||
highlightWatchedScrollerButton();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function recentlyAdded() {
|
||||
// Populate recently added
|
||||
$.ajax({
|
||||
url: 'library_recently_added',
|
||||
async: true,
|
||||
data: {
|
||||
section_id: section_id,
|
||||
limit: 50
|
||||
},
|
||||
complete: function(xhr, status) {
|
||||
$("#library-recently-added").html(xhr.responseText);
|
||||
highlightAddedScrollerButton();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
recentlyWatched();
|
||||
recentlyAdded();
|
||||
|
||||
function highlightWatchedScrollerButton() {
|
||||
var scroller = $("#recently-watched-row-scroller");
|
||||
var numElems = scroller.find("li").length;
|
||||
scroller.width(numElems * 175);
|
||||
if (scroller.width() > $("#library-recently-watched").width()) {
|
||||
$("#recently-watched-page-right").removeClass("disabled");
|
||||
} else {
|
||||
$("#recently-watched-page-right").addClass("disabled");
|
||||
}
|
||||
}
|
||||
|
||||
function highlightAddedScrollerButton() {
|
||||
var scroller = $("#recently-added-row-scroller");
|
||||
var numElems = scroller.find("li").length;
|
||||
scroller.width(numElems * 175);
|
||||
if (scroller.width() > $("#library-recently-added").width()) {
|
||||
$("#recently-added-page-right").removeClass("disabled");
|
||||
} else {
|
||||
$("#recently-added-page-right").addClass("disabled");
|
||||
}
|
||||
}
|
||||
|
||||
$(window).resize(function() {
|
||||
highlightWatchedScrollerButton();
|
||||
highlightAddedScrollerButton();
|
||||
});
|
||||
|
||||
$('div.art-face').animate({ opacity: 0.2 }, { duration: 1000 });
|
||||
|
||||
var leftTotalWatched = 0;
|
||||
$(".paginate-watched").click(function (e) {
|
||||
e.preventDefault();
|
||||
var scroller = $("#recently-watched-row-scroller");
|
||||
var containerWidth = $("#library-recently-watched").width();
|
||||
var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175;
|
||||
var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0);
|
||||
|
||||
leftTotalWatched = Math.max(Math.min(leftTotalWatched + scrollAmount, 0), leftMax);
|
||||
scroller.animate({ left: leftTotalWatched }, 250);
|
||||
|
||||
if (leftTotalWatched == 0) {
|
||||
$("#recently-watched-page-left").addClass("disabled").blur();
|
||||
} else {
|
||||
$("#recently-watched-page-left").removeClass("disabled");
|
||||
}
|
||||
|
||||
if (leftTotalWatched == leftMax) {
|
||||
$("#recently-watched-page-right").addClass("disabled").blur();
|
||||
} else {
|
||||
$("#recently-watched-page-right").removeClass("disabled");
|
||||
}
|
||||
});
|
||||
|
||||
var leftTotalAdded = 0;
|
||||
$(".paginate-added").click(function (e) {
|
||||
e.preventDefault();
|
||||
var scroller = $("#recently-added-row-scroller");
|
||||
var containerWidth = $("#library-recently-added").width();
|
||||
var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175;
|
||||
var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0);
|
||||
|
||||
leftTotalAdded = Math.max(Math.min(leftTotalAdded + scrollAmount, 0), leftMax);
|
||||
scroller.animate({ left: leftTotalAdded }, 250);
|
||||
|
||||
if (leftTotalAdded == 0) {
|
||||
$("#recently-added-page-left").addClass("disabled").blur();
|
||||
} else {
|
||||
$("#recently-added-page-left").removeClass("disabled");
|
||||
}
|
||||
|
||||
if (leftTotalAdded == leftMax) {
|
||||
$("#recently-added-page-right").addClass("disabled").blur();
|
||||
} else {
|
||||
$("#recently-added-page-right").removeClass("disabled");
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
% endif
|
||||
|
@@ -29,14 +29,13 @@ DOCUMENTATION :: END
|
||||
headers = {'movie': ('Movie Libraries', ('Movies', '', '')),
|
||||
'show': ('TV Show Libraries', ('Shows', 'Seasons', 'Episodes')),
|
||||
'artist': ('Music Libraries', ('Artists', 'Albums', 'Tracks')),
|
||||
'photo': ('Photo Libraries', ('Albums', '', 'Photos'))}
|
||||
'photo': ('Photo Libraries', ('Albums', 'Photos', 'Videos'))}
|
||||
%>
|
||||
% for section_type in types:
|
||||
% if section_type in data:
|
||||
<div class="dashboard-stats-instance" id="library-stats-instance-${section_type}" data-section_type="${section_type}">
|
||||
<div class="dashboard-stats-container">
|
||||
<div class="dashboard-stats-background-overlay">
|
||||
<div id="library-stats-background-${section_type}" class="dashboard-stats-background" style="background-image: url(pms_image_proxy?img=/:/resources/${section_type}-fanart.jpg&width=500&height=280&fallback=art);"></div>
|
||||
<div id="library-stats-background-${section_type}" class="dashboard-stats-background" style="background-image: url(pms_image_proxy?img=/:/resources/${section_type}-fanart.jpg&width=500&height=280&opacity=40&background=282828&blur=3&fallback=art);">
|
||||
<div id="library-stats-thumb-${section_type}" class="dashboard-stats-flat svg-icon library-${section_type} hidden-xs"></div>
|
||||
<div class="dashboard-stats-info-container">
|
||||
<div id="library-stats-title-${section_type}" class="dashboard-stats-info-title">
|
||||
|
@@ -3,39 +3,32 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Tautulli - ${title} | ${server_name}</title>
|
||||
<title>Tautulli - ${title}</title>
|
||||
<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/pnotify.custom.min.css" rel="stylesheet" />
|
||||
<link href="${http_root}css/plexpy.css${cache_param}" 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.min.css" rel="stylesheet">
|
||||
|
||||
<!-- Favicons -->
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="${http_root}images/favicon/favicon-32x32.png?v=2.0.0">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="${http_root}images/favicon/favicon-16x16.png?v=2.0.0">
|
||||
<link rel="shortcut icon" href="${http_root}images/favicon/favicon.ico?v=2.0.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">
|
||||
|
||||
<!-- ICONS -->
|
||||
<!-- Android >M39 icon -->
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="${http_root}images/favicon/android-chrome-192x192.png?v=2.0.0">
|
||||
<link rel="manifest" href="${http_root}json/Android-manifest.json?v=2.0.0">
|
||||
<meta name="theme-color" content="#1f1f1f">
|
||||
<!-- Android -->
|
||||
<link rel="manifest" href="${http_root}images/favicon/manifest.json?v=2.0.5">
|
||||
<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.0">
|
||||
<link rel="mask-icon" href="${http_root}images/favicon/safari-pinned-tab.svg?v=2.0.0" color="#1f1f1f">
|
||||
<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">
|
||||
<meta name="apple-mobile-web-app-title" content="Tautulli">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="viewport" content="initial-scale=1">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<!-- IE10 icon -->
|
||||
<!-- Microsoft -->
|
||||
<meta name="application-name" content="Tautulli">
|
||||
<meta name="msapplication-TileColor" content="#1f1f1f">
|
||||
<meta name="msapplication-TileImage" content="${http_root}images/favicon/mstile-144x144.png?v=2.0.0">
|
||||
<meta name="msapplication-config" content="${http_root}xml/IEconfig.xml?v=2.0.0" />
|
||||
<meta name="msapplication-config" content="${http_root}images/favicon/browserconfig.xml?v=2.0.5">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -44,21 +37,19 @@
|
||||
<div class="row">
|
||||
<div class="login-container">
|
||||
<div class="login-logo">
|
||||
<img alt="Tautulli" src="${http_root}images/logo-tautulli.png">
|
||||
<img src="${http_root}images/logo-tautulli-100.png" height="100" alt="PlexPy">
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6 col-sm-offset-3">
|
||||
<form action="${http_root}auth/login" method="post">
|
||||
% if msg:
|
||||
<div class="alert alert-danger" style="text-align: center; padding: 8px;">
|
||||
${msg}
|
||||
<form id="login-form">
|
||||
<div id="incorrect-login" class="alert alert-danger" style="text-align: center; padding: 8px; display: none;">
|
||||
Incorrect username or password.
|
||||
</div>
|
||||
% endif
|
||||
<div class="form-group">
|
||||
<label for="username" class="control-label">
|
||||
Username
|
||||
</label>
|
||||
<input type="text" id="username" name="username" class="form-control" autocorrect="off" autocapitalize="off" value="${username}" autofocus>
|
||||
<input type="text" id="username" name="username" class="form-control" autocorrect="off" autocapitalize="off" autofocus>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password" class="control-label">
|
||||
@@ -72,7 +63,7 @@
|
||||
<input type="checkbox" id="remember_me" name="remember_me" title="for 30 days" value="1" checked="checked" /> Remember me
|
||||
</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-bright login-button"><i class="fa fa-sign-in"></i> Sign In</button>
|
||||
<button id="sign-in" type="submit" class="btn btn-bright login-button"><i class="fa fa-sign-in"></i> Sign In</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -82,5 +73,30 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="${http_root}js/jquery-2.1.4.min.js"></script>
|
||||
<script>
|
||||
$('#login-form').submit(function(event) {
|
||||
event.preventDefault();
|
||||
$('#sign-in').prop('disabled', true).html('<i class="fa fa-refresh fa-spin"></i> Sign In');
|
||||
$.ajax({
|
||||
url: '${http_root}auth/signin',
|
||||
type: 'POST',
|
||||
data: $(this).serialize(),
|
||||
dataType: 'json',
|
||||
statusCode: {
|
||||
200: function() {
|
||||
window.location = "${redirect_uri or http_root}";
|
||||
},
|
||||
401: function() {
|
||||
$('#incorrect-login').show();
|
||||
$('#username').focus();
|
||||
}
|
||||
},
|
||||
complete: function() {
|
||||
$('#sign-in').prop('disabled', false).html('<i class="fa fa-sign-in"></i> Sign In');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@@ -5,7 +5,7 @@
|
||||
|
||||
<%def name="headIncludes()">
|
||||
<link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css">
|
||||
<link rel="stylesheet" href="${http_root}css/plexpy-dataTables.css">
|
||||
<link rel="stylesheet" href="${http_root}css/tautulli-dataTables.css">
|
||||
<style>
|
||||
td {word-break: break-all;}
|
||||
</style>
|
||||
@@ -21,9 +21,9 @@
|
||||
<span><i class="fa fa-list-alt"></i> Logs</span>
|
||||
</div>
|
||||
<div class="button-bar">
|
||||
<div class="btn-group" id="plexpy-log-levels">
|
||||
<div class="btn-group" id="tautulli-log-levels">
|
||||
<label>
|
||||
<select name="plexpy-log-level-filter" id="plexpy-log-level-filter" class="btn" style="color: inherit;">
|
||||
<select name="tautulli-log-level-filter" id="tautulli-log-level-filter" class="btn" style="color: inherit;">
|
||||
<option value="">All log levels</option>
|
||||
<option disabled>────────────</option>
|
||||
<option value="DEBUG">Debug</option>
|
||||
@@ -45,28 +45,30 @@
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<button class="btn btn-dark" id="download-plexpylog"><i class="fa fa-download"></i> Download logs</button>
|
||||
<button class="btn btn-dark" id="download-tautullilog"><i class="fa fa-download"></i> Download logs</button>
|
||||
<button class="btn btn-dark" id="download-plexserverlog" style="display: none;"><i class="fa fa-download"></i> Download logs</button>
|
||||
<button class="btn btn-dark" id="download-plexscannerlog" style="display: none;"><i class="fa fa-download"></i> Download logs</button>
|
||||
<button class="btn btn-dark" id="clear-logs"><i class="fa fa-trash-o"></i> Clear logs</button>
|
||||
<button class="btn btn-dark" id="clear-notify-logs" style="display: none;"><i class="fa fa-trash-o"></i> Clear logs</button>
|
||||
<button class="btn btn-dark" id="clear-newsletter-logs" style="display: none;"><i class="fa fa-trash-o"></i> Clear logs</button>
|
||||
<button class="btn btn-dark" id="clear-login-logs" style="display: none;"><i class="fa fa-trash-o"></i> Clear logs</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class='table-card-back'>
|
||||
<div>
|
||||
<ul id="log_tabs" class="nav nav-pills" role="tablist">
|
||||
<li role="presentation" class="active"><a id="plexpy-logs-btn" href="#tabs-plexpy_log" aria-controls="tabs-plexpy_log" role="tab" data-toggle="tab">Tautulli Logs</a></li>
|
||||
<li role="presentation"><a id="plexpy-api-logs-btn" href="#tabs-plexpy_api_log" aria-controls="tabs-plexpy_api_log" role="tab" data-toggle="tab">Tautulli API Logs</a></li>
|
||||
<li role="presentation" class="active"><a id="tautulli-logs-btn" href="#tabs-tautulli_log" aria-controls="tabs-tautulli_log" role="tab" data-toggle="tab">Tautulli Logs</a></li>
|
||||
<li role="presentation"><a id="tautulli-api-logs-btn" href="#tabs-tautulli_api_log" aria-controls="tabs-tautulli_api_log" role="tab" data-toggle="tab">Tautulli API Logs</a></li>
|
||||
<li role="presentation"><a id="plex-logs-btn" href="#tabs-plex_log" aria-controls="tabs-plex_log" role="tab" data-toggle="tab">Plex Media Server Logs</a></li>
|
||||
<li role="presentation"><a id="plex-scanner-logs-btn" href="#tabs-plex_scanner_log" aria-controls="tabs-plex_scanner_log" role="tab" data-toggle="tab">Plex Media Scanner Logs</a></li>
|
||||
<li role="presentation"><a id="plexpy-websocket-logs-btn" href="#tabs-plex_websocket_log" aria-controls="tabs-plex_websocket_log" role="tab" data-toggle="tab">Plex Websocket Logs</a></li>
|
||||
<li role="presentation"><a id="plex-websocket-logs-btn" href="#tabs-plex_websocket_log" aria-controls="tabs-plex_websocket_log" role="tab" data-toggle="tab">Plex Websocket Logs</a></li>
|
||||
<li role="presentation"><a id="notification-logs-btn" href="#tabs-notification_log" aria-controls="tabs-notification_log" role="tab" data-toggle="tab">Notification Logs</a></li>
|
||||
<li role="presentation"><a id="newsletter-logs-btn" href="#tabs-newsletter_log" aria-controls="tabs-newsletter_log" role="tab" data-toggle="tab">Newsletter Logs</a></li>
|
||||
<li role="presentation"><a id="login-logs-btn" href="#tabs-login_log" aria-controls="tabs-login_log" role="tab" data-toggle="tab">Login Logs</a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="tabs-plexpy_log" data-logfile="plexpy">
|
||||
<table class="display" id="plexpy_log_table" width="100%">
|
||||
<div role="tabpanel" class="tab-pane active" id="tabs-tautulli_log" data-logfile="tautulli">
|
||||
<table class="display" id="tautulli_log_table" width="100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="min-tablet" align="left" id="timestamp">Timestamp</th>
|
||||
@@ -77,8 +79,8 @@
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-plexpy_api_log" data-logfile="plexpy_api">
|
||||
<table class="display" id="plexpy_api_log_table" width="100%">
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-tautulli_api_log" data-logfile="tautulli_api">
|
||||
<table class="display" id="tautulli_api_log_table" width="100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="min-tablet" align="left" id="timestamp">Timestamp</th>
|
||||
@@ -141,6 +143,25 @@
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-newsletter_log">
|
||||
<table class="display" id="newsletter_log_table" width="100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th align="left" id="newsletter_timestamp">Timestamp</th>
|
||||
<th align="left" id="newsletter_newsletter_id">Newsletter ID</th>
|
||||
<th align="left" id="newsletter_agent_name">Agent</th>
|
||||
<th align="left" id="newsletter_notify_action">Action</th>
|
||||
<th align="left" id="newsletter_subject_text">Subject Text</th>
|
||||
<th align="left" id="newsletter_body_text">Body Text</th>
|
||||
<th align="left" id="newsletter_start_date">Start Date</th>
|
||||
<th align="left" id="newsletter_end_date">End Date</th>
|
||||
<th align="left" id="newsletter_uuid">UUID</th>
|
||||
<th align="left" id="newsletter_success"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-login_log">
|
||||
<table class="display login_log_table" id="login_log_table" width="100%">
|
||||
<thead>
|
||||
@@ -191,12 +212,13 @@
|
||||
<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>
|
||||
<script src="${http_root}js/tables/newsletter_logs.js${cache_param}"></script>
|
||||
<script src="${http_root}js/tables/login_logs.js${cache_param}"></script>
|
||||
<script>
|
||||
|
||||
$(document).ready(function() {
|
||||
loadPlexPyLogs('plexpy', selected_log_level);
|
||||
clearSearchButton('plexpy_log_table', log_table);
|
||||
loadtautullilogs('tautulli', selected_log_level);
|
||||
clearSearchButton('tautulli_log_table', log_table);
|
||||
});
|
||||
|
||||
var log_levels = ['DEBUG', 'INFO', 'WARN', 'ERROR'];
|
||||
@@ -227,10 +249,10 @@
|
||||
}
|
||||
|
||||
var selected_log_level = null;
|
||||
function loadPlexPyLogs(logfile, selected_log_level) {
|
||||
function loadtautullilogs(logfile, selected_log_level) {
|
||||
log_table_options.ajax = {
|
||||
url: "get_log",
|
||||
type: 'post',
|
||||
url: 'get_log',
|
||||
type: 'POST',
|
||||
data: function (d) {
|
||||
return {
|
||||
logfile: logfile,
|
||||
@@ -238,10 +260,10 @@
|
||||
log_level: selected_log_level
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
log_table = $('#' + logfile + '_log_table').DataTable(log_table_options);
|
||||
|
||||
$('#plexpy-log-level-filter').on('change', function () {
|
||||
$('#tautulli-log-level-filter').on('change', function () {
|
||||
selected_log_level = $(this).val() || null;
|
||||
log_table.draw();
|
||||
});
|
||||
@@ -249,131 +271,168 @@
|
||||
|
||||
function loadPlexLogs() {
|
||||
plex_log_table_options.ajax = {
|
||||
url: "get_plex_log?log_type=server"
|
||||
}
|
||||
url: 'get_plex_log?log_type=server',
|
||||
type: 'POST'
|
||||
};
|
||||
plex_log_table_options.initComplete = bindLogLevelFilter;
|
||||
plex_log_table = $('#plex_log_table').DataTable(plex_log_table_options);
|
||||
}
|
||||
|
||||
function loadPlexScannerLogs() {
|
||||
plex_log_table_options.ajax = {
|
||||
url: "get_plex_log?log_type=scanner"
|
||||
}
|
||||
url: 'get_plex_log?log_type=scanner',
|
||||
type: 'POST'
|
||||
};
|
||||
plex_log_table_options.initComplete = bindLogLevelFilter;
|
||||
plex_scanner_log_table = $('#plex_scanner_log_table').DataTable(plex_log_table_options);
|
||||
}
|
||||
|
||||
function loadNotificationLogs() {
|
||||
notification_log_table_options.ajax = {
|
||||
url: "get_notification_log",
|
||||
url: 'get_notification_log',
|
||||
type: 'POST',
|
||||
data: function (d) {
|
||||
return {
|
||||
json_data: JSON.stringify(d)
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
notification_log_table = $('#notification_log_table').DataTable(notification_log_table_options);
|
||||
}
|
||||
|
||||
function loadNewsletterLogs() {
|
||||
newsletter_log_table_options.ajax = {
|
||||
url: "get_newsletter_log",
|
||||
data: function (d) {
|
||||
return {
|
||||
json_data: JSON.stringify(d)
|
||||
};
|
||||
}
|
||||
};
|
||||
newsletter_log_table = $('#newsletter_log_table').DataTable(newsletter_log_table_options);
|
||||
}
|
||||
|
||||
function loadLoginLogs() {
|
||||
login_log_table_options.pageLength = 50;
|
||||
login_log_table_options.ajax = {
|
||||
url: "get_user_logins",
|
||||
url: 'get_user_logins',
|
||||
type: 'POST',
|
||||
data: function (d) {
|
||||
return {
|
||||
json_data: JSON.stringify(d)
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
login_log_table = $('#login_log_table').DataTable(login_log_table_options);
|
||||
}
|
||||
|
||||
$("#plexpy-logs-btn").click(function () {
|
||||
$("#plexpy-log-levels").show();
|
||||
$("#tautulli-logs-btn").click(function () {
|
||||
$("#tautulli-log-levels").show();
|
||||
$("#plex-log-levels").hide();
|
||||
$("#clear-logs").show();
|
||||
$("#download-plexpylog").show()
|
||||
$("#download-plexserverlog").hide()
|
||||
$("#download-plexscannerlog").hide()
|
||||
$("#download-tautullilog").show();
|
||||
$("#download-plexserverlog").hide();
|
||||
$("#download-plexscannerlog").hide();
|
||||
$("#clear-notify-logs").hide();
|
||||
$("#clear-newsletter-logs").hide();
|
||||
$("#clear-login-logs").hide();
|
||||
loadPlexPyLogs('plexpy', selected_log_level);
|
||||
clearSearchButton('plexpy_log_table', log_table);
|
||||
loadtautullilogs('tautulli', selected_log_level);
|
||||
clearSearchButton('tautulli_log_table', log_table);
|
||||
});
|
||||
|
||||
$("#plexpy-api-logs-btn").click(function () {
|
||||
$("#plexpy-log-levels").show();
|
||||
$("#tautulli-api-logs-btn").click(function () {
|
||||
$("#tautulli-log-levels").show();
|
||||
$("#plex-log-levels").hide();
|
||||
$("#clear-logs").show();
|
||||
$("#download-plexpylog").show()
|
||||
$("#download-plexserverlog").hide()
|
||||
$("#download-plexscannerlog").hide()
|
||||
$("#download-tautullilog").show();
|
||||
$("#download-plexserverlog").hide();
|
||||
$("#download-plexscannerlog").hide();
|
||||
$("#clear-notify-logs").hide();
|
||||
$("#clear-login-logs").hide();
|
||||
loadPlexPyLogs('plexpy_api', selected_log_level);
|
||||
clearSearchButton('plexpy_api_log_table', log_table);
|
||||
$("#clear-newsletter-logs").hide();
|
||||
$("#clear-login-logs").hide();
|
||||
loadtautullilogs('tautulli_api', selected_log_level);
|
||||
clearSearchButton('tautulli_api_log_table', log_table);
|
||||
});
|
||||
|
||||
$("#plexpy-websocket-logs-btn").click(function () {
|
||||
$("#plexpy-log-levels").show();
|
||||
$("#plex-websocket-logs-btn").click(function () {
|
||||
$("#tautulli-log-levels").show();
|
||||
$("#plex-log-levels").hide();
|
||||
$("#clear-logs").show();
|
||||
$("#download-plexpylog").show()
|
||||
$("#download-plexserverlog").hide()
|
||||
$("#download-plexscannerlog").hide()
|
||||
$("#download-tautullilog").show();
|
||||
$("#download-plexserverlog").hide();
|
||||
$("#download-plexscannerlog").hide();
|
||||
$("#clear-notify-logs").hide();
|
||||
$("#clear-newsletter-logs").hide();
|
||||
$("#clear-login-logs").hide();
|
||||
loadPlexPyLogs('plex_websocket', selected_log_level);
|
||||
loadtautullilogs('plex_websocket', selected_log_level);
|
||||
clearSearchButton('plex_websocket_log_table', log_table);
|
||||
});
|
||||
|
||||
$("#plex-logs-btn").click(function () {
|
||||
$("#plexpy-log-levels").hide();
|
||||
$("#tautulli-log-levels").hide();
|
||||
$("#plex-log-levels").show();
|
||||
$("#clear-logs").hide();
|
||||
$("#download-plexpylog").hide()
|
||||
$("#download-plexserverlog").show()
|
||||
$("#download-plexscannerlog").hide()
|
||||
$("#download-tautullilog").hide();
|
||||
$("#download-plexserverlog").show();
|
||||
$("#download-plexscannerlog").hide();
|
||||
$("#clear-notify-logs").hide();
|
||||
$("#clear-newsletter-logs").hide();
|
||||
$("#clear-login-logs").hide();
|
||||
loadPlexLogs();
|
||||
clearSearchButton('plex_log_table', plex_log_table);
|
||||
});
|
||||
|
||||
$("#plex-scanner-logs-btn").click(function () {
|
||||
$("#plexpy-log-levels").hide();
|
||||
$("#tautulli-log-levels").hide();
|
||||
$("#plex-log-levels").show();
|
||||
$("#clear-logs").hide();
|
||||
$("#download-plexpylog").hide()
|
||||
$("#download-plexserverlog").hide()
|
||||
$("#download-plexscannerlog").show()
|
||||
$("#download-tautullilog").hide();
|
||||
$("#download-plexserverlog").hide();
|
||||
$("#download-plexscannerlog").show();
|
||||
$("#clear-notify-logs").hide();
|
||||
$("#clear-newsletter-logs").hide();
|
||||
$("#clear-login-logs").hide();
|
||||
loadPlexScannerLogs();
|
||||
clearSearchButton('plex_scanner_log_table', plex_scanner_log_table);
|
||||
});
|
||||
|
||||
$("#notification-logs-btn").click(function () {
|
||||
$("#plexpy-log-levels").hide();
|
||||
$("#tautulli-log-levels").hide();
|
||||
$("#plex-log-levels").hide();
|
||||
$("#clear-logs").hide();
|
||||
$("#download-plexpylog").hide()
|
||||
$("#download-plexserverlog").hide()
|
||||
$("#download-plexscannerlog").hide()
|
||||
$("#download-tautullilog").hide();
|
||||
$("#download-plexserverlog").hide();
|
||||
$("#download-plexscannerlog").hide();
|
||||
$("#clear-notify-logs").show();
|
||||
$("#clear-newsletter-logs").hide();
|
||||
$("#clear-login-logs").hide();
|
||||
loadNotificationLogs();
|
||||
clearSearchButton('notification_log_table', notification_log_table);
|
||||
});
|
||||
|
||||
$("#login-logs-btn").click(function () {
|
||||
$("#plexpy-log-levels").hide();
|
||||
$("#newsletter-logs-btn").click(function () {
|
||||
$("#tautulli-log-levels").hide();
|
||||
$("#plex-log-levels").hide();
|
||||
$("#clear-logs").hide();
|
||||
$("#download-plexpylog").hide()
|
||||
$("#download-plexserverlog").hide()
|
||||
$("#download-plexscannerlog").hide()
|
||||
$("#download-tautullilog").hide();
|
||||
$("#download-plexserverlog").hide();
|
||||
$("#download-plexscannerlog").hide();
|
||||
$("#clear-notify-logs").hide();
|
||||
$("#clear-newsletter-logs").show();
|
||||
$("#clear-login-logs").hide();
|
||||
loadNewsletterLogs();
|
||||
clearSearchButton('newsletter_log_table', newsletter_log_table);
|
||||
});
|
||||
|
||||
$("#login-logs-btn").click(function () {
|
||||
$("#tautulli-log-levels").hide();
|
||||
$("#plex-log-levels").hide();
|
||||
$("#clear-logs").hide();
|
||||
$("#download-tautullilog").hide();
|
||||
$("#download-plexserverlog").hide();
|
||||
$("#download-plexscannerlog").hide();
|
||||
$("#clear-notify-logs").hide();
|
||||
$("#clear-newsletter-logs").hide();
|
||||
$("#clear-login-logs").show();
|
||||
loadLoginLogs();
|
||||
clearSearchButton('login_log_table', notification_log_table);
|
||||
@@ -384,9 +443,10 @@
|
||||
});
|
||||
|
||||
$("#clear-logs").click(function () {
|
||||
var logfile = $(".tab-pane.active").data('logfile')
|
||||
var logfile = $(".tab-pane.active").data('logfile');
|
||||
var title = $("#log_tabs li.active a").text();
|
||||
|
||||
$("#confirm-message").text("Are you sure you want to clear the Tautulli logs?");
|
||||
$("#confirm-message").text("Are you sure you want to clear the " + title + "?");
|
||||
$('#confirm-modal').modal();
|
||||
$('#confirm-modal').one('click', '#confirm-button', function () {
|
||||
$.ajax({
|
||||
@@ -396,7 +456,7 @@
|
||||
complete: function (xhr, status) {
|
||||
result = $.parseJSON(xhr.responseText);
|
||||
msg = result.message;
|
||||
if (result.result == 'success') {
|
||||
if (result.result === 'success') {
|
||||
showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 5000)
|
||||
} else {
|
||||
showMsg('<i class="fa fa-times"></i> ' + msg, false, true, 5000, true)
|
||||
@@ -407,7 +467,7 @@
|
||||
});
|
||||
});
|
||||
|
||||
$("#download-plexpylog").click(function () {
|
||||
$("#download-tautullilog").click(function () {
|
||||
var logfile = $(".tab-pane.active").data('logfile');
|
||||
window.location.href = "download_log?logfile=" + logfile;
|
||||
});
|
||||
@@ -421,7 +481,7 @@
|
||||
});
|
||||
|
||||
$("#clear-notify-logs").click(function () {
|
||||
$("#confirm-message").text("Are you sure you want to clear the Tautulli notification logs?");
|
||||
$("#confirm-message").text("Are you sure you want to clear the Tautulli Notification Logs?");
|
||||
$('#confirm-modal').modal();
|
||||
$('#confirm-modal').one('click', '#confirm-button', function () {
|
||||
$.ajax({
|
||||
@@ -430,7 +490,7 @@
|
||||
complete: function (xhr, status) {
|
||||
result = $.parseJSON(xhr.responseText);
|
||||
msg = result.message;
|
||||
if (result.result == 'success') {
|
||||
if (result.result === 'success') {
|
||||
showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 5000)
|
||||
} else {
|
||||
showMsg('<i class="fa fa-times"></i> ' + msg, false, true, 5000, true)
|
||||
@@ -441,8 +501,29 @@
|
||||
});
|
||||
});
|
||||
|
||||
$("#clear-newsletter-logs").click(function () {
|
||||
$("#confirm-message").text("Are you sure you want to clear the Tautulli Newsletter Logs?");
|
||||
$('#confirm-modal').modal();
|
||||
$('#confirm-modal').one('click', '#confirm-button', function () {
|
||||
$.ajax({
|
||||
url: 'delete_newsletter_log',
|
||||
type: 'POST',
|
||||
complete: function (xhr, status) {
|
||||
result = $.parseJSON(xhr.responseText);
|
||||
msg = result.message;
|
||||
if (result.result === 'success') {
|
||||
showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 5000)
|
||||
} else {
|
||||
showMsg('<i class="fa fa-times"></i> ' + msg, false, true, 5000, true)
|
||||
}
|
||||
newsletter_log_table.draw();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$("#clear-login-logs").click(function () {
|
||||
$("#confirm-message").text("Are you sure you want to clear the Tautulli login logs?");
|
||||
$("#confirm-message").text("Are you sure you want to clear the Tautulli Login Logs?");
|
||||
$('#confirm-modal').modal();
|
||||
$('#confirm-modal').one('click', '#confirm-button', function () {
|
||||
$.ajax({
|
||||
@@ -451,7 +532,7 @@
|
||||
complete: function (xhr, status) {
|
||||
result = $.parseJSON(xhr.responseText);
|
||||
msg = result.message;
|
||||
if (result.result == 'success') {
|
||||
if (result.result === 'success') {
|
||||
showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 5000)
|
||||
} else {
|
||||
showMsg('<i class="fa fa-times"></i> ' + msg, false, true, 5000, true)
|
||||
@@ -472,10 +553,10 @@
|
||||
{
|
||||
clearInterval(timer);
|
||||
}
|
||||
if(refreshrate.value != 0)
|
||||
if(refreshrate.value !== 0)
|
||||
{
|
||||
timer = setInterval(function() {
|
||||
if ($("#tabs-plexpy_log").hasClass("active") || $("#tabs-plexpy_api_log").hasClass("active") || $("#tabs-plex_websocket_log").hasClass("active")) {
|
||||
if ($("#tabs-tautulli_log").hasClass("active") || $("#tabs-tautulli_api_log").hasClass("active") || $("#tabs-plex_websocket_log").hasClass("active")) {
|
||||
log_table.ajax.reload();
|
||||
} else if ($("#tabs-plex_log").hasClass("active")) {
|
||||
plex_log_table.ajax.reload();
|
||||
|
@@ -11,12 +11,11 @@ DOCUMENTATION :: END
|
||||
|
||||
<ul class="stacked-configs list-unstyled">
|
||||
% for device in sorted(devices_list, key=lambda k: k['device_name']):
|
||||
<li class="mobile-device" data-id="${device['id']}" data-name="${device['device_name']}">
|
||||
<li class="mobile-device pointer" data-id="${device['id']}" data-name="${device['device_name']}">
|
||||
<span>
|
||||
<!--<span class="toggle-right mobile-device-tooltip edit-mobile-device" data-toggle="tooltip" data-placement="top" title="Edit Device"><i class="fa fa-lg fa-pencil"></i></span>-->
|
||||
<span class="toggle-left"><i class="fa fa-lg fa-mobile"></i></span>
|
||||
<span class="toggle-left"><i class="fa fa-lg fa-fw fa-mobile"></i></span>
|
||||
${device['friendly_name'] or device['device_name']} <span class="friendly_name">(${device['id']})</span>
|
||||
<span class="toggle-right"><i class="fa fa-lg fa-cog"></i></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']}">
|
||||
% if device['last_seen']:
|
||||
<script>
|
||||
@@ -26,14 +25,13 @@ DOCUMENTATION :: END
|
||||
never
|
||||
% endif
|
||||
</span>
|
||||
<!--<span class="toggle-right delete-mobile-device" data-toggle="tooltip" data-placement="top" title="Remove Device"><i class="fa fa-lg fa-times"></i></span>-->
|
||||
</span>
|
||||
</li>
|
||||
% endfor
|
||||
<li class="add-mobile-device" id="register-mobile-device" data-target="#api-qr-modal" data-toggle="modal">
|
||||
<li class="add-mobile-device pointer" id="register-mobile-device" data-target="#api-qr-modal" data-toggle="modal">
|
||||
<span>
|
||||
<span class="toggle-left"><i class="fa fa-lg fa-mobile"></i></span> Register a new device
|
||||
<span class="toggle-right"><i class="fa fa-lg fa-plus"></i></span>
|
||||
<span class="toggle-left"><i class="fa fa-lg fa-fw fa-mobile"></i></span> Register a new device
|
||||
<span class="toggle-right"><i class="fa fa-lg fa-fw fa-plus"></i></span>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -55,7 +53,7 @@ DOCUMENTATION :: END
|
||||
})
|
||||
}
|
||||
return deferred;
|
||||
}
|
||||
};
|
||||
|
||||
function checkQRAddress(url) {
|
||||
var parser = document.createElement('a');
|
||||
@@ -82,7 +80,7 @@ DOCUMENTATION :: END
|
||||
verifiedDevice = false;
|
||||
|
||||
getPlexPyURL().then(function (url) {
|
||||
checkQRAddress(url)
|
||||
checkQRAddress(url);
|
||||
|
||||
$.get('generate_api_key', { device: true }).then(function (token) {
|
||||
$('#api_qr_address').val(url);
|
||||
@@ -120,7 +118,7 @@ DOCUMENTATION :: END
|
||||
|
||||
$('#api_qr_address').change(function () {
|
||||
var url = $(this).val();
|
||||
checkQRAddress(url)
|
||||
checkQRAddress(url);
|
||||
|
||||
$('#api_qr_code').empty().qrcode({
|
||||
text: url + '|' + $('#api_qr_token').val()
|
||||
|
43
data/interfaces/default/newsletter_auth.html
Normal file
@@ -0,0 +1,43 @@
|
||||
<%
|
||||
import urllib
|
||||
%>
|
||||
<!doctype html>
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<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/tautulli.css${cache_param}" rel="stylesheet">
|
||||
<link href="${http_root}css/opensans.min.css" rel="stylesheet">
|
||||
<link href="${http_root}css/font-awesome.min.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="body-container">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="login-container">
|
||||
<div class="newsletter-logo">
|
||||
<img src="${http_root}images/newsletter/newsletter-header.png" height="100" alt="PlexPy">
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6 col-sm-offset-3">
|
||||
<form action="${uri}" method="post" id="newsletter-form">
|
||||
<div class="form-group">
|
||||
<label for="password" class="control-label">
|
||||
Password
|
||||
</label>
|
||||
<input type="password" id="key" name="key" class="form-control" autofocus>
|
||||
</div>
|
||||
<button id="enter" type="submit" class="btn btn-bright login-button"><i class="fa fa-sign-in"></i> Enter</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
792
data/interfaces/default/newsletter_config.html
Normal file
@@ -0,0 +1,792 @@
|
||||
% if newsletter:
|
||||
<%!
|
||||
import json
|
||||
from plexpy import notifiers
|
||||
from plexpy.helpers import anon_url, checked
|
||||
|
||||
all_notifiers = sorted(notifiers.get_notifiers(), key=lambda k: (k['agent_label'].lower(), k['friendly_name'], k['id']))
|
||||
email_notifiers = [n for n in all_notifiers if n['agent_name'] == 'email']
|
||||
email_notifiers = [{'id': 0, 'agent_label': 'New Email Configuration', 'friendly_name': ''}] + email_notifiers
|
||||
other_notifiers = [{'id': 0, 'agent_label': 'Select a Notification Agent', 'friendly_name': ''}] + all_notifiers
|
||||
%>
|
||||
<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" id="newsletter-config-modal-header">${newsletter['agent_label']} Newsletter Settings <small><span class="newsletter_id">(Newsletter ID: ${newsletter['id']})</span></small></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<ul class="nav nav-tabs list-unstyled" role="tablist">
|
||||
<li role="presentation" class="active"><a href="#tabs-newsletter_config" aria-controls="tabs-newsletter_config" role="tab" data-toggle="tab">Configuration</a></li>
|
||||
<li role="presentation"><a href="#tabs-newsletter_saving_sending" aria-controls="tabs-newsletter_saving_sending" role="tab" data-toggle="tab">Saving & Sending</a></li>
|
||||
<li role="presentation"><a href="#tabs-newsletter_text" aria-controls="tabs-newsletter_text" role="tab" data-toggle="tab">Newsletter Text</a></li>
|
||||
<li role="presentation"><a href="#tabs-test_newsletter" aria-controls="tabs-test_newsletter" role="tab" data-toggle="tab">Test Newsletter</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<form action="set_newsletter_config" method="post" class="form" id="set_newsletter_config" data-parsley-validate>
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="tabs-newsletter_config">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="checkbox" style="margin-bottom: 20px;">
|
||||
<label>
|
||||
<input type="checkbox" data-id="active_value" class="checkboxes" value="1" ${checked(newsletter['active'])}> Enable the Newsletter
|
||||
</label>
|
||||
<input type="hidden" id="active_value" name="active" value="${newsletter['active']}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="custom_cron">Schedule</label>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<select class="form-control" id="custom_cron" name="newsletter_config_custom_cron">
|
||||
<option value="0" ${'selected' if newsletter['config']['custom_cron'] == 0 else ''}>Simple</option>
|
||||
<option value="1" ${'selected' if newsletter['config']['custom_cron'] == 1 else ''}>Custom</option>
|
||||
</select>
|
||||
<input type="text" id="cron_value" name="cron" value="${newsletter['cron']}" />
|
||||
<div id="cron-widget"></div>
|
||||
</div>
|
||||
</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>
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="time_frame">Time Frame</label>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="input-group newsletter-time_frame">
|
||||
<span class="input-group-addon form-control btn-dark inactive">Last</span>
|
||||
<input type="number" class="form-control" id="newsletter_config_time_frame" name="newsletter_config_time_frame" value="${newsletter['config']['time_frame']}">
|
||||
<select class="form-control" id="newsletter_config_time_frame_units" name="newsletter_config_time_frame_units">
|
||||
<option value="days" ${'selected' if newsletter['config']['time_frame_units'] == 'days' else ''}>days</option>
|
||||
<option value="hours" ${'selected' if newsletter['config']['time_frame_units'] == 'hours' else ''}>hours</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">Set the time frame to include in the newsletter. Note: Days uses calendar days (i.e. since midnight).</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12 modal-config-section">
|
||||
<input type="hidden" id="newsletter_id" name="newsletter_id" value="${newsletter['id']}" />
|
||||
<input type="hidden" id="agent_id" name="agent_id" value="${newsletter['agent_id']}" />
|
||||
% for item in newsletter['config_options']:
|
||||
% if item['input_type'] == 'help':
|
||||
<div class="form-group">
|
||||
<label>${item['label']}</label>
|
||||
<p class="help-block">${item['description'] | n}</p>
|
||||
</div>
|
||||
% elif item['input_type'] == 'text' or item['input_type'] == 'password':
|
||||
<div class="form-group">
|
||||
<label for="${item['name']}">${item['label']}</label>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<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 ''}>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">${item['description'] | n}</p>
|
||||
</div>
|
||||
% elif item['input_type'] == 'number':
|
||||
<div class="form-group">
|
||||
<label for="${item['name']}">${item['label']}</label>
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<input type="${item['input_type']}" class="form-control" id="${item['name']}" name="${item['name']}" value="${item['value']}" size="30">
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">${item['description'] | n}</p>
|
||||
</div>
|
||||
% elif item['input_type'] == 'button':
|
||||
<div class="form-group">
|
||||
<label for="${item['name']}">${item['label']}</label>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<input type="button" class="btn btn-bright" id="${item['name']}" name="${item['name']}" value="${item['value']}">
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">${item['description'] | n}</p>
|
||||
</div>
|
||||
% elif item['input_type'] == 'checkbox':
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" data-id="${item['name']}" class="checkboxes" value="1" ${checked(item['value'])}> ${item['label']}
|
||||
</label>
|
||||
<p class="help-block">${item['description'] | n}</p>
|
||||
<input type="hidden" id="${item['name']}" name="${item['name']}" value="${item['value']}">
|
||||
</div>
|
||||
% elif item['input_type'] == 'select':
|
||||
<div class="form-group">
|
||||
<label for="${item['name']}">${item['label']}</label>
|
||||
<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()):
|
||||
% if key == item['value']:
|
||||
<option value="${key}" selected>${value}</option>
|
||||
% else:
|
||||
<option value="${key}">${value}</option>
|
||||
% endif
|
||||
% endfor
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">${item['description'] | n}</p>
|
||||
</div>
|
||||
% elif item['input_type'] == 'selectize':
|
||||
<div class="form-group">
|
||||
<label for="${item['name']}">${item['label']}</label>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<select class="form-control" id="${item['name']}" name="${item['name']}">
|
||||
<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():
|
||||
<optgroup label="${section}">
|
||||
% for option in sorted(options, key=lambda x: x['text'].lower()):
|
||||
<option value="${option['value']}">${option['text']}</option>
|
||||
% endfor
|
||||
</optgroup>
|
||||
% endfor
|
||||
% else:
|
||||
<option value="border-all"></option>
|
||||
% for option in sorted(item['select_options'], key=lambda x: x['text'].lower()):
|
||||
<option value="${option['value']}">${option['text']}</option>
|
||||
% endfor
|
||||
% endif
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">${item['description'] | n}</p>
|
||||
</div>
|
||||
% endif
|
||||
% endfor
|
||||
</div>
|
||||
<div class="col-md-12 modal-config-section">
|
||||
<div class="form-group">
|
||||
<label for="id_name">Unique ID Name</label>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<input type="text" class="form-control" id="id_name" name="id_name" value="${newsletter['id_name']}" size="30">
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">Optional: Enter a unique ID name to create a static URL to the last sent scheduled newsletter at <span class="inline-pre">${http_root}newsletter/id/<id_name></span>. Only letters (a-z), numbers (0-9), underscores (_) and hyphens (-) are allowed. Leave blank to disable.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="friendly_name">Description</label>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<input type="text" class="form-control" id="friendly_name" name="friendly_name" value="${newsletter['friendly_name']}" size="30">
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">Optional: Enter a description to help identify this newsletter in the newsletters list.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-newsletter_saving_sending">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<label>Saving</label>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" id="newsletter_config_save_only_checkbox" data-id="newsletter_config_save_only" class="checkboxes" value="1" ${checked(newsletter['config']['save_only'])}> Save HTML File Only
|
||||
</label>
|
||||
<p class="help-block">Enable to save the newsletter HTML file without sending it to any notification agent.</p>
|
||||
<input type="hidden" id="newsletter_config_save_only" name="newsletter_config_save_only" value="${newsletter['config']['save_only']}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="newsletter_config_filename">HTML File Name</label>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<input type="text" class="form-control" id="newsletter_config_filename" name="newsletter_config_filename" value="${newsletter['config']['filename']}" size="30">
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">Optional: Enter the file name to use when saving the newsletter (ending with <span class="inline-pre">.html</span>). You may use any of the <a href="#newsletter-text-sub-modal" data-toggle="modal">newsletter text parameters</a>. Leave blank for default.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12 modal-config-section" id="newsletter_agent_options">
|
||||
<label>Sending</label>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" id="newsletter_config_formatted_checkbox" data-id="newsletter_config_formatted" class="checkboxes" value="1" ${checked(newsletter['config']['formatted'])}> Send Newsletter as an HTML Formatted Email
|
||||
</label>
|
||||
<p class="help-block">Enable to send the newsletter as an HTML formatted Email. Disable to only send a subject and body message to a different notification agent.</p>
|
||||
<input type="hidden" id="newsletter_config_formatted" name="newsletter_config_formatted" value="${newsletter['config']['formatted']}">
|
||||
</div>
|
||||
<div class="form-group" id="email_notifier_select">
|
||||
<label for="newsletter_email_notifier_id">Email Notification Agent</label>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<select class="form-control" id="newsletter_email_notifier_id" name="newsletter_email_notifier_id">
|
||||
% for notifier in email_notifiers:
|
||||
<% selected = 'selected' if notifier['id'] == newsletter['email_config']['notifier_id'] else '' %>
|
||||
% if notifier['friendly_name']:
|
||||
<option value="${notifier['id']}" ${selected}>${notifier['agent_label']} (${notifier['id']} - ${notifier['friendly_name']})</option>
|
||||
% elif notifier['id']:
|
||||
<option value="${notifier['id']}" ${selected}>${notifier['agent_label']} (${notifier['id']})</option>
|
||||
% else:
|
||||
<option value="${notifier['id']}" ${selected}>${notifier['agent_label']}</option>
|
||||
% endif
|
||||
% endfor
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">
|
||||
Select an existing Email notification agent or enter a new configuration below.<br>
|
||||
Note: Make sure HTML support is enabled for the Email notification agent.
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group" id="other_notifier_select">
|
||||
<label for="newsletter_config_notifier_id">Notification Agent</label>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<select class="form-control" id="newsletter_config_notifier_id" name="newsletter_config_notifier_id">
|
||||
% for notifier in other_notifiers:
|
||||
<% selected = 'selected' if notifier['id'] == newsletter['config']['notifier_id'] else '' %>
|
||||
% if notifier['friendly_name']:
|
||||
<option value="${notifier['id']}" ${selected}>${notifier['agent_label']} (${notifier['id']} - ${notifier['friendly_name']})</option>
|
||||
% elif notifier['id']:
|
||||
<option value="${notifier['id']}" ${selected}>${notifier['agent_label']} (${notifier['id']})</option>
|
||||
% else:
|
||||
<option value="${notifier['id']}" ${selected}>${notifier['agent_label']}</option>
|
||||
% endif
|
||||
% endfor
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">
|
||||
Select an existing notification agent where the subject and body text will be sent.<br>
|
||||
Note: Self-hosted newsletters must be enabled under <a data-tab-destination="tabs-notifications" data-dismiss="modal" data-target="#newsletter_self_hosted">Newsletters</a> to include a link to the newsletter.
|
||||
</p>
|
||||
</div>
|
||||
<div id="newsletter-email-config">
|
||||
% for item in newsletter['email_config_options']:
|
||||
% if item['input_type'] == 'help':
|
||||
<div class="form-group">
|
||||
<label>${item['label']}</label>
|
||||
<p class="help-block">${item['description'] | n}</p>
|
||||
</div>
|
||||
% elif item['input_type'] == 'text' or item['input_type'] == 'password':
|
||||
<div class="form-group">
|
||||
<label for="${item['name']}">${item['label']}</label>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<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 ''}>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">${item['description'] | n}</p>
|
||||
</div>
|
||||
% elif item['input_type'] == 'number':
|
||||
<div class="form-group">
|
||||
<label for="${item['name']}">${item['label']}</label>
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<input type="${item['input_type']}" class="form-control" id="${item['name']}" name="${item['name']}" value="${item['value']}" size="30">
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">${item['description'] | n}</p>
|
||||
</div>
|
||||
% elif item['input_type'] == 'button':
|
||||
<div class="form-group">
|
||||
<label for="${item['name']}">${item['label']}</label>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<input type="button" class="btn btn-bright" id="${item['name']}" name="${item['name']}" value="${item['value']}">
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">${item['description'] | n}</p>
|
||||
</div>
|
||||
% elif item['input_type'] == 'checkbox' and item['name'] != 'newsletter_email_html_support':
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" data-id="${item['name']}" class="checkboxes" value="1" ${checked(item['value'])}> ${item['label']}
|
||||
</label>
|
||||
<p class="help-block">${item['description'] | n}</p>
|
||||
<input type="hidden" id="${item['name']}" name="${item['name']}" value="${item['value']}">
|
||||
</div>
|
||||
% elif item['input_type'] == 'select':
|
||||
<div class="form-group">
|
||||
<label for="${item['name']}">${item['label']}</label>
|
||||
<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()):
|
||||
% if key == item['value']:
|
||||
<option value="${key}" selected>${value}</option>
|
||||
% else:
|
||||
<option value="${key}">${value}</option>
|
||||
% endif
|
||||
% endfor
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">${item['description'] | n}</p>
|
||||
</div>
|
||||
% elif item['input_type'] == 'selectize':
|
||||
<div class="form-group">
|
||||
<label for="${item['name']}">${item['label']}</label>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<select class="form-control" id="${item['name']}" name="${item['name']}">
|
||||
<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():
|
||||
<optgroup label="${section}">
|
||||
% for option in sorted(options, key=lambda x: x['text'].lower()):
|
||||
<option value="${option['value']}">${option['text']}</option>
|
||||
% endfor
|
||||
</optgroup>
|
||||
% endfor
|
||||
% else:
|
||||
<option value="border-all"></option>
|
||||
% for option in sorted(item['select_options'], key=lambda x: x['text'].lower()):
|
||||
<option value="${option['value']}">${option['text']}</option>
|
||||
% endfor
|
||||
% endif
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">${item['description'] | n}</p>
|
||||
</div>
|
||||
% endif
|
||||
% endfor
|
||||
<input type="hidden" id="newsletter_email_html_support" name="newsletter_email_html_support" value="1">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-newsletter_text">
|
||||
<label>Newsletter Text</label>
|
||||
<p class="help-block">
|
||||
Set the custom formatted text for each type of notification.
|
||||
<a href="#newsletter-text-sub-modal" data-toggle="modal">Click here</a> for a list of available parameters which can be used.
|
||||
</p>
|
||||
<p class="help-block">
|
||||
You can also add text modifiers to change the case or slice parameters with a list of items.
|
||||
<a href="#notify-text-modifiers-modal" data-toggle="modal">Click here</a> to view usage information.
|
||||
</p>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<label for="subject">Subject</label>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<input type="text" class="form-control" id="subject" name="subject" value="${newsletter['subject']}" size="30">
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">
|
||||
Enter a custom subject line for the newsletter. Leave blank for default.
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group" id="newsletter_body">
|
||||
<label for="body">Body</label>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<textarea class="form-control" id="body" name="body" data-autoresize>${newsletter['body']}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">
|
||||
Enter a custom body line for the newsletter notification. Leave blank for default.
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="message">Message</label>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<textarea class="form-control" id="message" name="message" data-autoresize>${newsletter['message']}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">
|
||||
Enter a custom message to include on the newsletter.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-test_newsletter">
|
||||
<label>Preview Newsletter</label>
|
||||
<p class="help-block">
|
||||
Preview the ${newsletter['agent_label']} newsletter.
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<input type="button" class="btn btn-bright" id="preview_newsletter" name="preview_newsletter" value="Preview ${newsletter['agent_label']} Newsletter">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<label>Test Newsletter</label>
|
||||
<p class="help-block">
|
||||
Test if the ${newsletter['agent_label']} newsletter is working. Check the <a href="logs">logs</a> for troubleshooting.
|
||||
</p>
|
||||
<p class="help-block">
|
||||
Warning: This will send an actual newsletter to your notification agent!
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<input type="button" class="btn btn-bright" id="test_newsletter" name="test_newsletter" value="Test ${newsletter['agent_label']} Newsletter">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<input type="button" id="delete-newsletter-item" class="btn btn-danger btn-edit" style="float:left;" value="Delete">
|
||||
<input type="button" id="duplicate-newsletter-item" class="btn btn-dark btn-edit" style="float:left;" value="Duplicate">
|
||||
<input type="button" id="save-newsletter-item" class="btn btn-bright" value="Save">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="${http_root}js/jquery-cron-min.js"></script>
|
||||
<script>
|
||||
|
||||
$('#newsletter-config-modal').unbind('hidden.bs.modal');
|
||||
|
||||
var cron_widget = $('#cron-widget').cron({
|
||||
initial: '0 0 * * 0',
|
||||
classes: 'form-control cron-select',
|
||||
onChange: function() {
|
||||
$("#cron_value").val($(this).cron('value'));
|
||||
}
|
||||
});
|
||||
|
||||
if (${newsletter['config']['custom_cron']}) {
|
||||
$('#cron_value').val('${newsletter['cron']}');
|
||||
} else {
|
||||
try {
|
||||
cron_widget.cron('value', '${newsletter['cron']}');
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function toggleCustomCron() {
|
||||
if ($('#custom_cron').val() === '1'){
|
||||
$('#cron-widget').hide();
|
||||
$('#cron_value').show();
|
||||
$('#simple_cron_message').hide();
|
||||
$('#custom_cron_message').show();
|
||||
} else {
|
||||
$('#cron-widget').show();
|
||||
$('#cron_value').hide();
|
||||
$('#simple_cron_message').show();
|
||||
$('#custom_cron_message').hide();
|
||||
}
|
||||
}
|
||||
toggleCustomCron();
|
||||
|
||||
$('#custom_cron').change(function () {
|
||||
toggleCustomCron();
|
||||
});
|
||||
|
||||
function validateFilename() {
|
||||
var filename = $('#newsletter_config_filename').val();
|
||||
if (filename !== '' && !(filename.endsWith('.html'))) {
|
||||
showMsg('<i class="fa fa-times"></i> Failed to save newsletter. Invalid file name.', false, true, 5000, true);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function validateIDName() {
|
||||
var id_name = $('#id_name').val();
|
||||
if (/^[a-zA-Z0-9_-]*$/.test(id_name)) {
|
||||
return true;
|
||||
} else {
|
||||
showMsg('<i class="fa fa-times"></i> Failed to save newsletter. Invalid unique ID name.', false, true, 5000, true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var $incl_libraries = $('#newsletter_config_incl_libraries').selectize({
|
||||
plugins: ['remove_button'],
|
||||
maxItems: null,
|
||||
render: {
|
||||
option: function(item) {
|
||||
if (item.value.endsWith('-all')) {
|
||||
return '<div class="' + item.value + '">' + item.text + '</div>'
|
||||
}
|
||||
return '<div>' + item.text + '</div>';
|
||||
}
|
||||
},
|
||||
onItemAdd: function(value) {
|
||||
if (value === 'select-all') {
|
||||
var all_keys = $.map(this.options, function(option){
|
||||
return option.value.endsWith('-all') ? null : option.value;
|
||||
});
|
||||
this.setValue(all_keys);
|
||||
} else if (value === 'remove-all') {
|
||||
this.clear();
|
||||
this.refreshOptions();
|
||||
this.positionDropdown();
|
||||
}
|
||||
}
|
||||
});
|
||||
var incl_libraries = $incl_libraries[0].selectize;
|
||||
incl_libraries.setValue(${json.dumps(next((c['value'] for c in newsletter['config_options'] if c['name'] == 'newsletter_config_incl_libraries'), [])) | n});
|
||||
|
||||
initConfigCheckbox('#newsletter_config_save_only_checkbox', '#newsletter_agent_options', true);
|
||||
|
||||
function toggleEmailSelect () {
|
||||
if ($('#newsletter_config_formatted_checkbox').is(':checked')) {
|
||||
$('#newsletter_body').hide();
|
||||
$('#email_notifier_select').show();
|
||||
$('#other_notifier_select').hide();
|
||||
toggleNewEmailConfig();
|
||||
} else {
|
||||
$('#newsletter_body').show();
|
||||
$('#email_notifier_select').hide();
|
||||
$('#other_notifier_select').show();
|
||||
$('#newsletter-email-config').hide();
|
||||
}
|
||||
}
|
||||
toggleEmailSelect();
|
||||
|
||||
$('#newsletter_config_formatted_checkbox').change(function () {
|
||||
toggleEmailSelect();
|
||||
});
|
||||
|
||||
function toggleNewEmailConfig () {
|
||||
if ($('#newsletter_config_formatted_checkbox').is(':checked') && $('#newsletter_email_notifier_id').val() === '0') {
|
||||
$('#newsletter-email-config').show();
|
||||
} else {
|
||||
$('#newsletter-email-config').hide();
|
||||
}
|
||||
}
|
||||
toggleNewEmailConfig();
|
||||
|
||||
$('#newsletter_email_notifier_id').change(function () {
|
||||
toggleNewEmailConfig();
|
||||
});
|
||||
|
||||
var REGEX_EMAIL = '([a-z0-9!#$%&\'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&\'*+/=?^_`{|}~-]+)*@' +
|
||||
'(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)';
|
||||
var $email_selectors = $('#newsletter_email_to, #newsletter_email_cc, #newsletter_email_bcc').selectize({
|
||||
plugins: ['remove_button'],
|
||||
maxItems: null,
|
||||
render: {
|
||||
item: function(item, escape) {
|
||||
return '<div>' +
|
||||
(item.text ? '<span class="item-text">' + escape(item.text) + '</span>' : '') +
|
||||
(item.value ? '<span class="item-value">' + escape(item.value) + '</span>' : '') +
|
||||
'</div>';
|
||||
},
|
||||
option: function(item, escape) {
|
||||
var label = item.text || item.value;
|
||||
var caption = item.text ? item.value : null;
|
||||
if (item.value.endsWith('-all')) {
|
||||
return '<div class="' + item.value + '">' + escape(label) + '</div>'
|
||||
}
|
||||
return '<div>' +
|
||||
escape(label) +
|
||||
(caption ? '<span class="caption">' + escape(caption) + '</span>' : '') +
|
||||
'</div>';
|
||||
}
|
||||
},
|
||||
onItemAdd: function(value) {
|
||||
if (value === 'select-all') {
|
||||
var all_keys = $.map(this.options, function(option){
|
||||
return option.value.endsWith('-all') ? null : option.value;
|
||||
});
|
||||
this.setValue(all_keys);
|
||||
} else if (value === 'remove-all') {
|
||||
this.clear();
|
||||
this.refreshOptions();
|
||||
this.positionDropdown();
|
||||
}
|
||||
},
|
||||
createFilter: function(input) {
|
||||
var match, regex;
|
||||
|
||||
// email@address.com
|
||||
regex = new RegExp('^' + REGEX_EMAIL + '$', 'i');
|
||||
match = input.match(regex);
|
||||
if (match) return !this.options.hasOwnProperty(match[0]);
|
||||
|
||||
// user <email@address.com>
|
||||
regex = new RegExp('^([^<]*)\<' + REGEX_EMAIL + '\>$', 'i');
|
||||
match = input.match(regex);
|
||||
if (match) return !this.options.hasOwnProperty(match[2]);
|
||||
|
||||
return false;
|
||||
},
|
||||
create: function(input) {
|
||||
if ((new RegExp('^' + REGEX_EMAIL + '$', 'i')).test(input)) {
|
||||
return {value: input};
|
||||
}
|
||||
var match = input.match(new RegExp('^([^<]*)\<' + REGEX_EMAIL + '\>$', 'i'));
|
||||
if (match) {
|
||||
return {
|
||||
value : match[2],
|
||||
text : $.trim(match[1])
|
||||
};
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
var email_to = $email_selectors[0].selectize;
|
||||
var email_cc = $email_selectors[1].selectize;
|
||||
var email_bcc = $email_selectors[2].selectize;
|
||||
email_to.setValue(${json.dumps(next((c['value'] for c in newsletter['email_config_options'] if c['name'] == 'newsletter_email_to'), [])) | n});
|
||||
email_cc.setValue(${json.dumps(next((c['value'] for c in newsletter['email_config_options'] if c['name'] == 'newsletter_email_cc'), [])) | n});
|
||||
email_bcc.setValue(${json.dumps(next((c['value'] for c in newsletter['email_config_options'] if c['name'] == 'newsletter_email_bcc'), [])) | n});
|
||||
|
||||
function reloadModal() {
|
||||
$.ajax({
|
||||
url: 'get_newsletter_config_modal',
|
||||
data: { newsletter_id: '${newsletter["id"]}' },
|
||||
cache: false,
|
||||
async: true,
|
||||
complete: function (xhr, status) {
|
||||
$('#newsletter-config-modal').html(xhr.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function saveCallback(jqXHR) {
|
||||
if (jqXHR) {
|
||||
var result = $.parseJSON(jqXHR.responseText);
|
||||
var msg = result.message;
|
||||
if (result.result == 'success') {
|
||||
showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 5000)
|
||||
} else {
|
||||
showMsg('<i class="fa fa-times"></i> ' + msg, false, true, 5000, true)
|
||||
}
|
||||
}
|
||||
|
||||
getNewslettersTable();
|
||||
}
|
||||
|
||||
function deleteCallback() {
|
||||
$('#newsletter-config-modal').modal('hide');
|
||||
getNewslettersTable();
|
||||
}
|
||||
|
||||
function duplicateCallback(result) {
|
||||
// Set new newsletter id
|
||||
$('#newsletter_id').val(result.newsletter_id);
|
||||
// Clear friendly name
|
||||
$('#friendly_name').val("");
|
||||
|
||||
saveNewsletter();
|
||||
|
||||
$('#newsletter-config-modal').on('hidden.bs.modal', function () {
|
||||
loadNewsletterConfig(result.newsletter_id);
|
||||
});
|
||||
$('#newsletter-config-modal').modal('hide');
|
||||
}
|
||||
|
||||
function saveNewsletter() {
|
||||
// Trim all text inputs before saving
|
||||
$('input[type=text]').val(function(_, value) {
|
||||
return $.trim(value);
|
||||
});
|
||||
// Make sure simple cron value is set
|
||||
if ($('#custom_cron').val() === '0'){
|
||||
$("#cron_value").val(cron_widget.cron('value'));
|
||||
}
|
||||
if (validateFilename() && validateIDName()){
|
||||
doAjaxCall('set_newsletter_config', $(this), 'tabs', true, true, saveCallback);
|
||||
}
|
||||
}
|
||||
|
||||
$('#delete-newsletter-item').click(function () {
|
||||
var msg = 'Are you sure you want to delete this <strong>${newsletter["agent_label"]}</strong> newsletter?';
|
||||
var url = 'delete_newsletter';
|
||||
confirmAjaxCall(url, msg, { newsletter_id: '${newsletter["id"]}' }, null, deleteCallback);
|
||||
});
|
||||
|
||||
$('#duplicate-newsletter-item').click(function() {
|
||||
var msg = 'Are you sure you want to duplicate this <strong>${newsletter["agent_label"]}</strong> newsletter?';
|
||||
var url = 'add_newsletter_config';
|
||||
confirmAjaxCall(url, msg, { agent_id: '${newsletter["agent_id"]}' }, null, duplicateCallback);
|
||||
});
|
||||
|
||||
$('#save-newsletter-item').click(function () {
|
||||
saveNewsletter();
|
||||
});
|
||||
|
||||
$('#preview_newsletter').click(function () {
|
||||
doAjaxCall('set_newsletter_config', $(this), 'tabs', true, false, previewNewsletter);
|
||||
});
|
||||
|
||||
$('#test_newsletter').click(function () {
|
||||
doAjaxCall('set_newsletter_config', $(this), 'tabs', true, false, sendTestNewsletter);
|
||||
});
|
||||
|
||||
function previewNewsletter() {
|
||||
showMsg('<i class="fa fa-check"></i> Check pop-up blocker if no response.', false, true, 2000);
|
||||
window.open('newsletter_preview?newsletter_id=' + $('#newsletter_id').val());
|
||||
}
|
||||
|
||||
function sendTestNewsletter() {
|
||||
showMsg('<i class="fa fa-refresh fa-spin"></i> Sending Newsletter', false);
|
||||
$.ajax({
|
||||
url: 'send_newsletter',
|
||||
data: {
|
||||
newsletter_id: $('#newsletter_id').val(),
|
||||
notify_action: 'test'
|
||||
},
|
||||
cache: false,
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$("${', '.join(['#' + c['name'] for c in newsletter['config_options'] if c.get('refresh')])}").on('change', function () {
|
||||
// Reload modal to update certain fields
|
||||
doAjaxCall('set_newsletter_config', $(this), 'tabs', true, false, reloadModal);
|
||||
return false;
|
||||
});
|
||||
|
||||
// Never send checkbox values directly, always substitute value in hidden input.
|
||||
$('.checkboxes').click(function () {
|
||||
var configToggle = $(this).data('id');
|
||||
if ($(this).is(':checked')) {
|
||||
$('#'+configToggle).val(1);
|
||||
} else {
|
||||
$('#'+configToggle).val(0);
|
||||
}
|
||||
});
|
||||
|
||||
// auto resizing textarea for custom notification message body
|
||||
$('textarea[data-autoresize]').each(function () {
|
||||
var offset = this.offsetHeight - this.clientHeight;
|
||||
var resizeTextarea = function (el) {
|
||||
$(el).css('height', 'auto').css('height', el.scrollHeight + offset);
|
||||
};
|
||||
$(this).on('focus keyup input', function () { resizeTextarea(this); }).removeAttr('data-autoresize');
|
||||
});
|
||||
</script>
|
||||
% else:
|
||||
<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" id="newsletter-config-modal-header">Error</h4>
|
||||
</div>
|
||||
<div class="modal-body" style="text-align: center">
|
||||
<strong>
|
||||
<i class="fa fa-exclamation-circle"></i> Failed to retrieve newsletter configuration. Check the <a href="logs">logs</a> for more info.
|
||||
</strong>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
48
data/interfaces/default/newsletter_preview.html
Normal file
@@ -0,0 +1,48 @@
|
||||
<%
|
||||
import urllib
|
||||
%>
|
||||
<!doctype html>
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Tautulli - ${title} | ${server_name}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="${http_root}css/tautulli.css${cache_param}" rel="stylesheet">
|
||||
<style>
|
||||
* {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="loader" class="newsletter-loader-container">
|
||||
<div class="newsletter-loader-message">
|
||||
<div class="newsletter-loader"></div>
|
||||
<br>
|
||||
Generating Newsletter
|
||||
<br>
|
||||
Please wait, this may take a few minutes...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="${http_root}js/jquery-2.1.4.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
var frame = $('<iframe></iframe>', {
|
||||
src: 'real_newsletter?${urllib.urlencode(kwargs) | n}',
|
||||
frameborder: '0',
|
||||
style: 'display: none; height: 100vh; width: 100vw;'
|
||||
});
|
||||
frame.on('load', function (e) {
|
||||
$(e.target).fadeIn();
|
||||
$('#loader').fadeOut();
|
||||
});
|
||||
$('body').append(frame);
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
51
data/interfaces/default/newsletters_table.html
Normal file
@@ -0,0 +1,51 @@
|
||||
<%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: newsletters_table.html
|
||||
Version: 0.1
|
||||
|
||||
DOCUMENTATION :: END
|
||||
</%doc>
|
||||
|
||||
<% from plexpy.newsletter_handler import NEWSLETTER_SCHED %>
|
||||
<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']}">
|
||||
<span>
|
||||
<span class="toggle-left trigger-tooltip ${'active' if newsletter['active'] else ''}" data-toggle="tooltip" data-placement="top" title="Newsletter ${'active' if newsletter['active'] else 'inactive'}"><i class="fa fa-lg fa-fw fa-newspaper-o"></i></span>
|
||||
% if newsletter['friendly_name']:
|
||||
${newsletter['agent_label']} <span class="friendly_name">(${newsletter['id']} - ${newsletter['friendly_name']})</span>
|
||||
% else:
|
||||
${newsletter['agent_label']} <span class="friendly_name">(${newsletter['id']})</span>
|
||||
% 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'])) %>
|
||||
<script>
|
||||
$("#newsletter-next_run-${newsletter['id']}").text(moment("${job.next_run_time}", "YYYY-MM-DD HH:mm:ssZ").fromNow())
|
||||
</script>
|
||||
% endif
|
||||
</span>
|
||||
</span>
|
||||
</li>
|
||||
% endfor
|
||||
<li class="add-newsletter-agent pointer" id="add-newsletter-agent" data-target="#add-newsletter-modal" data-toggle="modal">
|
||||
<span>
|
||||
<span class="toggle-left"><i class="fa fa-lg fa-fw fa-newspaper-o"></i></span> Add a new newsletter agent
|
||||
<span class="toggle-right"><i class="fa fa-lg fa-fw fa-plus"></i></span>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<script>
|
||||
// Load newsletter config modal
|
||||
$(".newsletter-agent").click(function () {
|
||||
var newsletter_id = $(this).data('id');
|
||||
loadNewsletterConfig(newsletter_id);
|
||||
});
|
||||
|
||||
$('.trigger-tooltip').tooltip();
|
||||
</script>
|
@@ -1,10 +1,13 @@
|
||||
<%!
|
||||
from plexpy import helpers, notifiers
|
||||
available_notification_actions = notifiers.available_notification_actions()
|
||||
%>
|
||||
% if notifier:
|
||||
<link href="${http_root}css/selectize.bootstrap3.css" rel="stylesheet" />
|
||||
<link href="${http_root}css/selectize.min.css" rel="stylesheet" />
|
||||
<%!
|
||||
import json
|
||||
from plexpy import notifiers, users
|
||||
from plexpy.helpers import checked
|
||||
available_notification_actions = notifiers.available_notification_actions()
|
||||
|
||||
user_emails = [{'user': u['friendly_name'] or u['username'], 'email': u['email']} for u in users.Users().get_users() if u['email']]
|
||||
sorted(user_emails, key=lambda u: u['user'])
|
||||
%>
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
@@ -15,7 +18,7 @@
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<ul class="nav nav-tabs list-unstyled" role="tablist">
|
||||
<li role="presentation" class="active"><a href="#tabs-config" aria-controls="tabs-config" role="tab" data-toggle="tab">Configuration</a></li>
|
||||
<li role="presentation" class="active"><a href="#tabs-notifier_config" aria-controls="tabs-notifier_config" role="tab" data-toggle="tab">Configuration</a></li>
|
||||
<li role="presentation"><a href="#tabs-notify_triggers" aria-controls="tabs-notify_triggers" role="tab" data-toggle="tab">Triggers</a></li>
|
||||
<li role="presentation"><a href="#tabs-notify_conditions" aria-controls="tabs-notify_conditions" role="tab" data-toggle="tab">Conditions</a></li>
|
||||
<li role="presentation"><a href="#tabs-notify_text" aria-controls="tabs-notify_text" role="tab" data-toggle="tab">${'Arguments' if notifier['agent_name'] == 'scripts' else 'Text'}</a></li>
|
||||
@@ -24,7 +27,7 @@
|
||||
</div>
|
||||
<form action="set_notifier_config" method="post" class="form" id="set_notifier_config" data-parsley-validate>
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="tabs-config">
|
||||
<div role="tabpanel" class="tab-pane active" id="tabs-notifier_config">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<input type="hidden" id="notifier_id" name="notifier_id" value="${notifier['id']}" />
|
||||
@@ -39,11 +42,8 @@
|
||||
<div class="form-group">
|
||||
<label for="${item['name']}">${item['label']}</label>
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="col-md-12">
|
||||
<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 ''}>
|
||||
% if item['name'] == 'osx_notify_app':
|
||||
<a href="javascript:void(0)" id="osxnotifyregister">Register</a>
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">${item['description'] | n}</p>
|
||||
@@ -62,7 +62,7 @@
|
||||
<div class="form-group">
|
||||
<label for="${item['name']}">${item['label']}</label>
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="col-md-12">
|
||||
<input type="button" class="btn btn-bright" id="${item['name']}" name="${item['name']}" value="${item['value']}">
|
||||
</div>
|
||||
</div>
|
||||
@@ -71,7 +71,7 @@
|
||||
% elif item['input_type'] == 'checkbox':
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" data-id="${item['name']}" class="checkboxes" value="1" ${helpers.checked(item['value'])}> ${item['label']}
|
||||
<input type="checkbox" data-id="${item['name']}" class="checkboxes" value="1" ${checked(item['value'])}> ${item['label']}
|
||||
</label>
|
||||
<p class="help-block">${item['description'] | n}</p>
|
||||
<input type="hidden" id="${item['name']}" name="${item['name']}" value="${item['value']}">
|
||||
@@ -80,7 +80,7 @@
|
||||
<div class="form-group">
|
||||
<label for="${item['name']}">${item['label']}</label>
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="col-md-12">
|
||||
<select class="form-control" id="${item['name']}" name="${item['name']}">
|
||||
% for key, value in sorted(item['select_options'].iteritems()):
|
||||
% if key == item['value']:
|
||||
@@ -94,14 +94,41 @@
|
||||
</div>
|
||||
<p class="help-block">${item['description'] | n}</p>
|
||||
</div>
|
||||
% elif item['input_type'] == 'selectize':
|
||||
<div class="form-group">
|
||||
<label for="${item['name']}">${item['label']}</label>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<select class="form-control" id="${item['name']}" name="${item['name']}">
|
||||
<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():
|
||||
<optgroup label="${section}">
|
||||
% for option in sorted(options, key=lambda x: x['text'].lower()):
|
||||
<option value="${option['value']}">${option['text']}</option>
|
||||
% endfor
|
||||
</optgroup>
|
||||
% endfor
|
||||
% else:
|
||||
<option value="border-all"></option>
|
||||
% for option in sorted(item['select_options'], key=lambda x: x['text'].lower()):
|
||||
<option value="${option['value']}">${option['text']}</option>
|
||||
% endfor
|
||||
% endif
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">${item['description'] | n}</p>
|
||||
</div>
|
||||
% endif
|
||||
% endfor
|
||||
</div>
|
||||
<div class="col-md-12" style="margin-top: 10px; padding-top: 10px; border-top: 1px solid #444;">
|
||||
<div class="col-md-12 modal-config-section">
|
||||
<div class="form-group">
|
||||
<label for="friendly_name">Description</label>
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="col-md-12">
|
||||
<input type="text" class="form-control" id="friendly_name" name="friendly_name" value="${notifier['friendly_name']}" size="30">
|
||||
</div>
|
||||
</div>
|
||||
@@ -120,7 +147,7 @@
|
||||
% for action in available_notification_actions:
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" data-id="${action['name']}" class="checkboxes" value="1" ${helpers.checked(notifier['actions'][action['name']])}> Notify on ${action['label']}
|
||||
<input type="checkbox" data-id="${action['name']}" class="checkboxes" value="1" ${checked(notifier['actions'][action['name']])}> ${action['label']}
|
||||
</label>
|
||||
<p class="help-block">${action['description'] | n}</p>
|
||||
<input type="hidden" id="${action['name']}" name="${action['name']}" value="${notifier['actions'][action['name']]}">
|
||||
@@ -132,20 +159,19 @@
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-notify_conditions">
|
||||
<label>Notification Conditions</label>
|
||||
<p class="help-block">
|
||||
Add custom notification conditions.
|
||||
Add custom conditions to only <em>allow certain notifications</em>. By default, all notifications will be sent if there are no conditions.
|
||||
<a href="#notify-text-sub-modal" data-toggle="modal">Click here</a> for a description of all the parameters.
|
||||
</p>
|
||||
<p class="help-block">
|
||||
Note: Conditions are checked after the notification trigger and the notification will only be sent if the condition logic is satisfied.
|
||||
</p>
|
||||
<div id="condition-widget"></div>
|
||||
<input type="hidden" name="custom_conditions" id="custom_conditions" />
|
||||
<input type="hidden" id="custom_conditions" name="custom_conditions" />
|
||||
|
||||
<div class="form-group">
|
||||
<label for="custom_condition_logic">Condition Logic</label>
|
||||
<input type="text" class="form-control" name="custom_conditions_logic" id="custom_conditions_logic" value="${notifier['custom_conditions_logic']}" required />
|
||||
<label for="custom_conditions_logic">Condition Logic</label>
|
||||
<input type="text" class="form-control" name="custom_conditions_logic" id="custom_conditions_logic" value="${notifier['custom_conditions_logic']}" />
|
||||
<div id="custom_conditions_logic_error" class="alert alert-danger" role="alert" style="padding-top: 5px; padding-bottom: 5px; margin: 0; display: none;"><i class="fa fa-exclamation-triangle" style="color: #a94442;"></i> <span></span></div>
|
||||
<p class="help-block">
|
||||
Enter the logic to use when evaluating the conditions (e.g. <span class="inline-pre">{1} and ({2} or {3})</span>).
|
||||
Optional: Enter custom logic to use when evaluating the conditions (e.g. <span class="inline-pre">{1} and ({2} or {3})</span>).
|
||||
Leave blank for implicit <span class="inline-pre">and</span> between all conditions.
|
||||
</p>
|
||||
<p class="help-block">
|
||||
Note: Only the keywords <span class="inline-pre">and</span>/<span class="inline-pre">or</span> and brackets <span class="inline-pre">()</span> are supported.
|
||||
@@ -186,7 +212,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="col-md-12">
|
||||
<input type="button" class="btn btn-bright notifier-text-preview" data-action="${action['name']}" value="Preview Arguments">
|
||||
</div>
|
||||
</div>
|
||||
@@ -213,7 +239,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="col-md-12">
|
||||
<input type="button" class="btn btn-bright notifier-text-preview" data-action="${action['name']}" value="Preview Text">
|
||||
</div>
|
||||
</div>
|
||||
@@ -279,7 +305,7 @@
|
||||
% endif
|
||||
<div class="form-group">
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="col-md-12">
|
||||
<input type="button" class="btn btn-bright" id="test_notifier" name="test_notifier" value="Test ${notifier['agent_label']}">
|
||||
</div>
|
||||
</div>
|
||||
@@ -303,16 +329,16 @@
|
||||
$('#notifier-config-modal').unbind('hidden.bs.modal');
|
||||
|
||||
// Need this for setting conditions since conditions contain the character "
|
||||
$('#custom_conditions').val('${notifier['custom_conditions'] | n}')
|
||||
$('#custom_conditions').val(JSON.stringify(${json.dumps(notifier["custom_conditions"]) | n}));
|
||||
|
||||
$('#condition-widget').filterer({
|
||||
parameters: ${parameters | n},
|
||||
conditions: ${notifier["custom_conditions"] | n},
|
||||
parameters: ${json.dumps(parameters) | n},
|
||||
conditions: ${json.dumps(notifier["custom_conditions"]) | n},
|
||||
updateConditions: function(newConditions){
|
||||
$('#custom_conditions').val(JSON.stringify(newConditions));
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
function reloadModal() {
|
||||
$.ajax({
|
||||
url: 'get_notifier_config_modal',
|
||||
@@ -329,7 +355,7 @@
|
||||
if (jqXHR) {
|
||||
var result = $.parseJSON(jqXHR.responseText);
|
||||
var msg = result.message;
|
||||
if (result.result == 'success') {
|
||||
if (result.result === 'success') {
|
||||
showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 5000)
|
||||
} else {
|
||||
showMsg('<i class="fa fa-times"></i> ' + msg, false, true, 5000, true)
|
||||
@@ -365,8 +391,10 @@
|
||||
$('input[type=text]').val(function(_, value) {
|
||||
return $.trim(value);
|
||||
});
|
||||
// Reload modal to update certain fields
|
||||
doAjaxCall('set_notifier_config', $(this), 'tabs', true, true, saveCallback);
|
||||
if (validateLogic()) {
|
||||
// Reload modal to update certain fields
|
||||
doAjaxCall('set_notifier_config', $(this), 'tabs', true, true, saveCallback);
|
||||
}
|
||||
}
|
||||
|
||||
$('#delete-notifier-item').click(function () {
|
||||
@@ -378,7 +406,7 @@
|
||||
$('#duplicate-notifier-item').click(function() {
|
||||
var msg = 'Are you sure you want to duplicate this <strong>${notifier["agent_label"]}</strong> notification agent?';
|
||||
var url = 'add_notifier_config';
|
||||
confirmAjaxCall(url, msg, { agent_id: "${notifier['agent_id']}" }, null, duplicateCallback);
|
||||
confirmAjaxCall(url, msg, { agent_id: '${notifier["agent_id"]}' }, null, duplicateCallback);
|
||||
});
|
||||
|
||||
$('#save-notifier-item').click(function () {
|
||||
@@ -386,34 +414,49 @@
|
||||
});
|
||||
|
||||
% if notifier['agent_name'] == 'facebook':
|
||||
if (location.protocol !== 'https:') {
|
||||
$('#tabs-config .form-group:first').prepend(
|
||||
'<div class="form-group">' +
|
||||
'<label>Warning</label>' +
|
||||
'<p class="help-block" style="color: #eb8600;">Facebook requires HTTPS for authorization. ' +
|
||||
'Please enable HTTPS for Tautulli under <a data-tab-destination="tabs-web_interface" data-dismiss="modal" data-target="#enable_https">Web Interface</a>.</p>' +
|
||||
'</div>'
|
||||
);
|
||||
$('#facebook_redirect_uri').val('HTTPS not enabled');
|
||||
|
||||
} else {
|
||||
$('#facebook_redirect_uri').val(location.href.split('/settings')[0] + '/facebook_redirect');
|
||||
}
|
||||
|
||||
function disableFacebookRequest() {
|
||||
if ($('#facebook_app_id').val() != '' && $('#facebook_app_secret').val() != '') { $('#facebook_facebookStep1').prop('disabled', false); }
|
||||
else { $('#facebook_facebookStep1').prop('disabled', true); }
|
||||
if ($('#facebook_app_id').val() !== '' && $('#facebook_app_secret').val() !== '') { $('#facebook_facebook_auth').prop('disabled', false); }
|
||||
else { $('#facebook_facebook_auth').prop('disabled', true); }
|
||||
}
|
||||
disableFacebookRequest();
|
||||
$('#facebook_app_id, #facebook_app_secret').on('change', function () {
|
||||
disableFacebookRequest();
|
||||
});
|
||||
|
||||
$('#facebook_facebookStep1').click(function () {
|
||||
$('#facebook_facebook_auth').click(function () {
|
||||
// Remove trailing '/' from Facebook redirect URI
|
||||
if ($('#facebook_redirect_uri') && $('#facebook_redirect_uri').val().endsWith('/')) {
|
||||
$('#facebook_redirect_uri').val($('#facebook_redirect_uri').val().slice(0, -1));
|
||||
}
|
||||
|
||||
var facebook_token;
|
||||
$.ajax({
|
||||
url: 'facebookStep1',
|
||||
url: 'facebook_auth',
|
||||
data: {
|
||||
app_id: $('#facebook_app_id').val(),
|
||||
app_secret: $('#facebook_app_secret').val(),
|
||||
redirect_uri: $('#facebook_redirect_uri').val(),
|
||||
redirect_uri: $('#facebook_redirect_uri').val()
|
||||
},
|
||||
cache: false,
|
||||
async: true,
|
||||
complete: function (xhr, status) {
|
||||
var result = $.parseJSON(xhr.responseText);
|
||||
var msg = result.msg;
|
||||
if (result.result == 'success') {
|
||||
if (result.result === 'success') {
|
||||
showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 5000);
|
||||
window.open(result.url);
|
||||
|
||||
@@ -452,20 +495,231 @@
|
||||
|
||||
$('#notifier-config-modal').on('hidden.bs.modal', function () {
|
||||
facebook_token = false;
|
||||
})
|
||||
});
|
||||
|
||||
% elif notifier['agent_name'] == 'browser':
|
||||
$('#browser_allow_browser').click(function () {
|
||||
PNotify.desktop.permission();
|
||||
})
|
||||
});
|
||||
|
||||
% elif notifier['agent_name'] == 'osx':
|
||||
$('#osxnotifyregister').click(function () {
|
||||
$('#osx_notify_register').click(function () {
|
||||
var osx_notify_app = $('#osx_notify_app').val();
|
||||
$.get('osxnotifyregister', { 'app': osx_notify_app }, function (data) { showMsg('<i class="fa fa-check"></i> ' + data, false, true, 3000); });
|
||||
})
|
||||
});
|
||||
|
||||
% elif notifier['agent_name'] == 'email':
|
||||
var REGEX_EMAIL = '([a-z0-9!#$%&\'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&\'*+/=?^_`{|}~-]+)*@' +
|
||||
'(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)';
|
||||
var $email_selectors = $('#email_to, #email_cc, #email_bcc').selectize({
|
||||
plugins: ['remove_button'],
|
||||
maxItems: null,
|
||||
render: {
|
||||
item: function(item, escape) {
|
||||
return '<div>' +
|
||||
(item.text ? '<span class="item-text">' + escape(item.text) + '</span>' : '') +
|
||||
(item.value ? '<span class="item-value">' + escape(item.value) + '</span>' : '') +
|
||||
'</div>';
|
||||
},
|
||||
option: function(item, escape) {
|
||||
var label = item.text || item.value;
|
||||
var caption = item.text ? item.value : null;
|
||||
if (item.value.endsWith('-all')) {
|
||||
return '<div class="' + item.value + '">' + escape(label) + '</div>'
|
||||
}
|
||||
return '<div>' +
|
||||
escape(label) +
|
||||
(caption ? '<span class="caption">' + escape(caption) + '</span>' : '') +
|
||||
'</div>';
|
||||
}
|
||||
},
|
||||
onItemAdd: function(value) {
|
||||
if (value === 'select-all') {
|
||||
var all_keys = $.map(this.options, function(option){
|
||||
return option.value.endsWith('-all') ? null : option.value;
|
||||
});
|
||||
this.setValue(all_keys);
|
||||
} else if (value === 'remove-all') {
|
||||
this.clear();
|
||||
this.refreshOptions();
|
||||
this.positionDropdown();
|
||||
}
|
||||
},
|
||||
createFilter: function(input) {
|
||||
var match, regex;
|
||||
|
||||
// email@address.com
|
||||
regex = new RegExp('^' + REGEX_EMAIL + '$', 'i');
|
||||
match = input.match(regex);
|
||||
if (match) return !this.options.hasOwnProperty(match[0]);
|
||||
|
||||
// user <email@address.com>
|
||||
regex = new RegExp('^([^<]*)\<' + REGEX_EMAIL + '\>$', 'i');
|
||||
match = input.match(regex);
|
||||
if (match) return !this.options.hasOwnProperty(match[2]);
|
||||
|
||||
return false;
|
||||
},
|
||||
create: function(input) {
|
||||
if ((new RegExp('^' + REGEX_EMAIL + '$', 'i')).test(input)) {
|
||||
return {value: input};
|
||||
}
|
||||
var match = input.match(new RegExp('^([^<]*)\<' + REGEX_EMAIL + '\>$', 'i'));
|
||||
if (match) {
|
||||
return {
|
||||
value : match[2],
|
||||
text : $.trim(match[1])
|
||||
};
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
var email_to = $email_selectors[0].selectize;
|
||||
var email_cc = $email_selectors[1].selectize;
|
||||
var email_bcc = $email_selectors[2].selectize;
|
||||
email_to.setValue(${json.dumps(next((c['value'] for c in notifier['config_options'] if c['name'] == 'email_to'), [])) | n});
|
||||
email_cc.setValue(${json.dumps(next((c['value'] for c in notifier['config_options'] if c['name'] == 'email_cc'), [])) | n});
|
||||
email_bcc.setValue(${json.dumps(next((c['value'] for c in notifier['config_options'] if c['name'] == 'email_bcc'), [])) | n});
|
||||
|
||||
% elif notifier['agent_name'] == 'join':
|
||||
var $join_device_names = $('#join_device_names').selectize({
|
||||
plugins: ['remove_button'],
|
||||
maxItems: null,
|
||||
create: true
|
||||
});
|
||||
var join_device_names = $join_device_names[0].selectize;
|
||||
join_device_names.setValue(${json.dumps(next((c['value'] for c in notifier['config_options'] if c['name'] == 'join_device_names'), [])) | n});
|
||||
|
||||
% elif notifier['agent_name'] == 'zapier':
|
||||
$('#zapier_test_hook').click(function () {
|
||||
$.get('zapier_test_hook', { 'zapier_hook': $('#zapier_hook').val() }, function (data) {
|
||||
if (data.result === 'success') {
|
||||
showMsg('<i class="fa fa-check"></i> ' + data.msg, false, true, 5000);
|
||||
} else {
|
||||
showMsg('<i class="fa fa-times"></i> ' + data.msg, false, true, 5000, true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
% elif notifier['agent_name'] == 'pushover':
|
||||
function pushoverPriority() {
|
||||
if ($('#pushover_priority').val() == '2') {
|
||||
$('#pushover_retry').closest('.form-group').show();
|
||||
$('#pushover_expire').closest('.form-group').show();
|
||||
} else {
|
||||
$('#pushover_retry').closest('.form-group').hide();
|
||||
$('#pushover_expire').closest('.form-group').hide();
|
||||
}
|
||||
}
|
||||
|
||||
pushoverPriority();
|
||||
$('#pushover_priority').change( function () {
|
||||
pushoverPriority();
|
||||
});
|
||||
|
||||
% endif
|
||||
|
||||
function validateLogic() {
|
||||
const valid_tokens = /(\(|\)|and|or)/g;
|
||||
const conditions_pattern = /{\d+}/g;
|
||||
|
||||
var custom_conditions = $.parseJSON($('#custom_conditions').val());
|
||||
var custom_conditions_logic = $('#custom_conditions_logic').val();
|
||||
var num_cond = custom_conditions.length;
|
||||
|
||||
var tokens = $.map(custom_conditions_logic.toLowerCase().split(valid_tokens), $.trim).filter(String);
|
||||
|
||||
var stack = [[]];
|
||||
var temp;
|
||||
|
||||
var cond_next = true;
|
||||
var bool_next = false;
|
||||
var open_bracket_next = true;
|
||||
var close_bracket_next = false;
|
||||
var nest_and = 0;
|
||||
var nest_nest_and = 0;
|
||||
|
||||
try {
|
||||
$.each(tokens, function(i, x) {
|
||||
if (open_bracket_next && x === '(') {
|
||||
stack[stack.length-1].push([]);
|
||||
temp = stack[stack.length-1];
|
||||
stack.push(temp[temp.length-1]);
|
||||
cond_next = true;
|
||||
bool_next = false;
|
||||
open_bracket_next = true;
|
||||
close_bracket_next = false;
|
||||
if (nest_and) {
|
||||
nest_nest_and += 1
|
||||
}
|
||||
} else if (close_bracket_next && x === ')') {
|
||||
stack.pop();
|
||||
if (stack.length === 0) {
|
||||
throw 'opening bracket is missing';
|
||||
}
|
||||
cond_next = false;
|
||||
bool_next = true;
|
||||
open_bracket_next = false;
|
||||
close_bracket_next = true;
|
||||
if (nest_and > 0 && nest_nest_and > 0 && nest_and === nest_nest_and) {
|
||||
stack.pop();
|
||||
nest_and -= 1;
|
||||
nest_nest_and -= 1;
|
||||
}
|
||||
} else if (cond_next && x.match(conditions_pattern)) {
|
||||
if (isNaN(x.slice(1, -1))) {
|
||||
throw 'invalid condition logic'
|
||||
} else {
|
||||
var num = parseInt(x.slice(1, -1));
|
||||
}
|
||||
if (!(0 < num && num <= num_cond)) {
|
||||
throw 'invalid condition number in condition logic'
|
||||
}
|
||||
stack[stack.length-1].push(num);
|
||||
cond_next = false;
|
||||
bool_next = true;
|
||||
open_bracket_next = false;
|
||||
close_bracket_next = true;
|
||||
if (nest_and > nest_nest_and) {
|
||||
stack.pop();
|
||||
nest_and -= 1;
|
||||
}
|
||||
} else if (bool_next && x === 'and' && i < tokens.length-1) {
|
||||
stack[stack.length-1].push([]);
|
||||
temp = stack[stack.length-1];
|
||||
stack.push(temp[temp.length-1]);
|
||||
temp = stack[stack.length-2];
|
||||
stack[stack.length-1].push(temp.splice(0, temp.length-2) + temp.splice(temp.length-2, temp.length-1));
|
||||
stack[stack.length-1].push(x);
|
||||
cond_next = true;
|
||||
bool_next = false;
|
||||
open_bracket_next = true;
|
||||
close_bracket_next = false;
|
||||
nest_and += 1;
|
||||
} else if (bool_next && x === 'or' && i < tokens.length-1) {
|
||||
stack[stack.length-1].push(x);
|
||||
cond_next = true;
|
||||
bool_next = false;
|
||||
open_bracket_next = true;
|
||||
close_bracket_next = false;
|
||||
} else {
|
||||
throw 'invalid condition logic';
|
||||
}
|
||||
});
|
||||
|
||||
if (stack.length > 1) {
|
||||
throw 'closing bracket is missing';
|
||||
}
|
||||
|
||||
$('#custom_conditions_logic_error').hide();
|
||||
return true;
|
||||
} catch (e) {
|
||||
$('#custom_conditions_logic_error span').text(e);
|
||||
$('#custom_conditions_logic_error').show();
|
||||
showMsg('<i class="fa fa-times"></i> Failed to save notifier. Invalid condition logic.', false, true, 5000, true);
|
||||
}
|
||||
}
|
||||
|
||||
$('.notifier-text-preview').click(function () {
|
||||
var action = $(this).data('action');
|
||||
var subject = $('#' + action + '_subject').val();
|
||||
@@ -493,11 +747,12 @@
|
||||
});
|
||||
|
||||
function sendTestNotification() {
|
||||
if ('${notifier["agent_name"]}' != 'browser') {
|
||||
showMsg('<i class="fa fa-refresh fa-spin"></i> Sending Notification', false);
|
||||
if ('${notifier["agent_name"]}' !== 'browser') {
|
||||
$.ajax({
|
||||
url: 'send_notification',
|
||||
data: {
|
||||
notifier_id: '${notifier["id"]}',
|
||||
notifier_id: $('#notifier_id').val(),
|
||||
subject: $('#test_subject').val(),
|
||||
body: $('#test_body').val(),
|
||||
script: $('#test_script').val(),
|
||||
@@ -506,18 +761,16 @@
|
||||
},
|
||||
cache: false,
|
||||
async: true,
|
||||
complete: function (xhr, status) {
|
||||
if (xhr.responseText.indexOf('sent') > -1) {
|
||||
msg = '<i class="fa fa-check"></i> ' + xhr.responseText;
|
||||
showMsg(msg, false, true, 2000);
|
||||
success: function (data) {
|
||||
if (data.result === 'success') {
|
||||
showMsg('<i class="fa fa-check"></i> ' + data.message, false, true, 5000);
|
||||
} else {
|
||||
msg = '<i class="fa fa-times"></i> ' + xhr.responseText;
|
||||
showMsg(msg, false, true, 2000, true);
|
||||
showMsg('<i class="fa fa-exclamation-circle"></i> ' + data.message, false, true, 5000, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if ($('#browser_auto_hide_delay').val() == "0") {
|
||||
if ($('#browser_auto_hide_delay').val() === "0") {
|
||||
PNotify.prototype.options.hide = false;
|
||||
} else {
|
||||
PNotify.prototype.options.hide = true;
|
||||
@@ -564,10 +817,10 @@
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
|
||||
<h4 class="modal-title" id="notifier-config-modal-header">Error</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<center><strong>
|
||||
<div class="modal-body" style="text-align: center">
|
||||
<strong>
|
||||
<i class="fa fa-exclamation-circle"></i> Failed to retrieve notifier configuration. Check the <a href="logs">logs</a> for more info.
|
||||
</strong></center>
|
||||
</strong>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
</div>
|
||||
|
@@ -10,23 +10,23 @@ DOCUMENTATION :: END
|
||||
</%doc>
|
||||
|
||||
<ul class="stacked-configs list-unstyled">
|
||||
% for notifier in sorted(notifiers_list, key=lambda k: (k['agent_label'], k['friendly_name'], k['id'])):
|
||||
<li class="notification-agent" data-id="${notifier['id']}">
|
||||
% for notifier in sorted(notifiers_list, key=lambda k: (k['agent_label'].lower(), k['friendly_name'], k['id'])):
|
||||
<li class="notification-agent pointer" data-id="${notifier['id']}">
|
||||
<span>
|
||||
<span class="toggle-left trigger-tooltip ${'active' if notifier['active'] else ''}" data-toggle="tooltip" data-placement="top" title="Triggers ${'active' if notifier['active'] else 'inactive'}"><i class="fa fa-lg fa-bell"></i></span>
|
||||
<span class="toggle-left trigger-tooltip ${'active' if notifier['active'] else ''}" data-toggle="tooltip" data-placement="top" title="Triggers ${'active' if notifier['active'] else 'inactive'}"><i class="fa fa-lg fa-fw fa-bell"></i></span>
|
||||
% if notifier['friendly_name']:
|
||||
${notifier['agent_label']} <span class="friendly_name">(${notifier['id']} - ${notifier['friendly_name']})</span>
|
||||
% else:
|
||||
${notifier['agent_label']} <span class="friendly_name">(${notifier['id']})</span>
|
||||
% endif
|
||||
<span class="toggle-right"><i class="fa fa-lg fa-cog"></i></span>
|
||||
<span class="toggle-right"><i class="fa fa-lg fa-fw fa-cog"></i></span>
|
||||
</span>
|
||||
</li>
|
||||
% endfor
|
||||
<li class="add-notification-agent" id="add-notification-agent" data-target="#add-notifier-modal" data-toggle="modal">
|
||||
<li class="add-notification-agent pointer" id="add-notification-agent" data-target="#add-notifier-modal" data-toggle="modal">
|
||||
<span>
|
||||
<span class="toggle-left"><i class="fa fa-lg fa-bell"></i></span> Add a new notification agent
|
||||
<span class="toggle-right"><i class="fa fa-lg fa-plus"></i></span>
|
||||
<span class="toggle-left"><i class="fa fa-lg fa-fw fa-bell"></i></span> Add a new notification agent
|
||||
<span class="toggle-right"><i class="fa fa-lg fa-fw fa-plus"></i></span>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
|