Compare commits
1361 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f335ffa8d5 | ||
![]() |
793665d62a | ||
![]() |
7da5730c73 | ||
![]() |
1f587ed698 | ||
![]() |
1032fdfe7a | ||
![]() |
818e7723ff | ||
![]() |
a69008e179 | ||
![]() |
91c647f9ae | ||
![]() |
36b80aa6d3 | ||
![]() |
c35fcc727c | ||
![]() |
749e1fcebe | ||
![]() |
084732706d | ||
![]() |
2aff7713cd | ||
![]() |
683a782723 | ||
![]() |
5108e1bb09 | ||
![]() |
d8298a12eb | ||
![]() |
042b48c1fd | ||
![]() |
8fac54aa71 | ||
![]() |
244008d539 | ||
![]() |
502b807e45 | ||
![]() |
35914b9a48 | ||
![]() |
24ac34d5e2 | ||
![]() |
a5807f21b4 | ||
![]() |
e3b71a729e | ||
![]() |
ebb287e1ee | ||
![]() |
bd3497b2bf | ||
![]() |
034f3ee308 | ||
![]() |
a946879fc1 | ||
![]() |
9f964b5a87 | ||
![]() |
ed0b41cd19 | ||
![]() |
dc87591992 | ||
![]() |
d05e80e573 | ||
![]() |
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 | ||
![]() |
38ad9c9893 | ||
![]() |
fb75b9566f | ||
![]() |
80a6118bf9 | ||
![]() |
6b2bc0108b | ||
![]() |
bd07cf5a04 | ||
![]() |
f07ad960dc | ||
![]() |
824e975598 | ||
![]() |
4f44c64e4a | ||
![]() |
a449670667 | ||
![]() |
7cd38581a6 | ||
![]() |
507ecee2e3 | ||
![]() |
95a6c62dd9 | ||
![]() |
b8c1fef700 | ||
![]() |
7a8301a8dc | ||
![]() |
19f029cff0 | ||
![]() |
55bdde808b | ||
![]() |
abab2f3f6b | ||
![]() |
c586fe1d07 | ||
![]() |
1b0c32f4e7 | ||
![]() |
e667f4487f | ||
![]() |
507be2e6e3 | ||
![]() |
5ff3c433b2 | ||
![]() |
7c825f72aa | ||
![]() |
36e604dc91 | ||
![]() |
b6c5068bc4 | ||
![]() |
af78f98106 | ||
![]() |
b209c29749 | ||
![]() |
e28965f8b3 | ||
![]() |
117c228672 | ||
![]() |
359273e26e | ||
![]() |
b22ae58f79 | ||
![]() |
07d8fbb7f4 | ||
![]() |
9edce2d387 | ||
![]() |
1787fff8c3 | ||
![]() |
1b5e5f37d0 | ||
![]() |
b76143116c | ||
![]() |
20e3eebd6a | ||
![]() |
f42f1182f2 | ||
![]() |
7e7609743a | ||
![]() |
a00f36f83b | ||
![]() |
e353d0518c | ||
![]() |
5efe2be7b2 | ||
![]() |
53d0bef260 | ||
![]() |
ecd0a199f1 | ||
![]() |
019787b32d | ||
![]() |
1ca1f9975c | ||
![]() |
2d76991a86 | ||
![]() |
2fee437010 | ||
![]() |
4e7c91617b | ||
![]() |
86e1e99b33 | ||
![]() |
595af9e70b | ||
![]() |
d23c4ef319 | ||
![]() |
1d9d16b69b | ||
![]() |
a6ee70b7ec | ||
![]() |
7921681fad | ||
![]() |
256ddde07d | ||
![]() |
e606bd512f | ||
![]() |
ffb38ef599 | ||
![]() |
f9e7c2bdb6 | ||
![]() |
76cc61e0c2 | ||
![]() |
9ba6b230e6 | ||
![]() |
31f03e0114 | ||
![]() |
7e7693f206 | ||
![]() |
6cae12d467 | ||
![]() |
1235f73332 | ||
![]() |
75dfb82a88 | ||
![]() |
a2eeda64df | ||
![]() |
718049b9f3 | ||
![]() |
fdff9d7454 | ||
![]() |
976418de0f | ||
![]() |
e51e9493b1 | ||
![]() |
8535fa4e0a | ||
![]() |
16b05a47c0 | ||
![]() |
ff7fee6abf | ||
![]() |
7f2e740bc0 | ||
![]() |
3df31bb01b | ||
![]() |
c6a36cb4cf | ||
![]() |
2873d8bba3 | ||
![]() |
d77696be59 | ||
![]() |
1bd2f332d9 | ||
![]() |
e4a2616ffa | ||
![]() |
99f93a8d50 | ||
![]() |
e80d88c9ff | ||
![]() |
8d2c9a7764 | ||
![]() |
de589f59a1 | ||
![]() |
627f552976 | ||
![]() |
67bac92849 | ||
![]() |
e1dc299cba | ||
![]() |
3f668ae5c6 | ||
![]() |
0d54e9f2d6 | ||
![]() |
ef6ef868e0 | ||
![]() |
5d8da23c3f | ||
![]() |
43f285951d | ||
![]() |
7714ecc7dd | ||
![]() |
6a39201961 | ||
![]() |
f0ca7385b9 | ||
![]() |
567bba0c44 | ||
![]() |
7559c0c6f8 | ||
![]() |
58bcd068f2 | ||
![]() |
7047829943 | ||
![]() |
b6a634ea6f | ||
![]() |
15a871889f | ||
![]() |
9d2cfaa3f5 | ||
![]() |
8625d3f127 | ||
![]() |
f07a7fa2cf | ||
![]() |
c6a4c4d6b3 | ||
![]() |
df55f70991 | ||
![]() |
37951cd69b | ||
![]() |
cfd3099626 | ||
![]() |
3db6c98c27 | ||
![]() |
3c04b04b98 | ||
![]() |
d207d4a508 | ||
![]() |
df4e466fdf | ||
![]() |
5417747473 | ||
![]() |
cf6847d777 | ||
![]() |
464fa1f8a3 | ||
![]() |
665a6435ef | ||
![]() |
2d64ba4a0e | ||
![]() |
a6fd7b581f | ||
![]() |
58712a2d05 | ||
![]() |
39da58d3bc | ||
![]() |
daec864f50 | ||
![]() |
54cd860c13 | ||
![]() |
fe210646c3 | ||
![]() |
006c778dca | ||
![]() |
bd636b756b | ||
![]() |
d21b74f231 | ||
![]() |
4354f72578 | ||
![]() |
f5ca522e6c | ||
![]() |
3c1d0d3128 | ||
![]() |
5d2f1d7014 | ||
![]() |
a17c3f8138 | ||
![]() |
45f002a797 | ||
![]() |
4a16ee6865 | ||
![]() |
76e9e24c41 | ||
![]() |
92a3589588 | ||
![]() |
1e10ddec99 | ||
![]() |
ad827e62f6 | ||
![]() |
6dd7e8445a | ||
![]() |
9364e5ab91 | ||
![]() |
8a556a50ab | ||
![]() |
532ff59dfe | ||
![]() |
9a48383eb4 | ||
![]() |
07524242c0 | ||
![]() |
3b0e4bf254 | ||
![]() |
da3abf11a2 | ||
![]() |
aee752e04e | ||
![]() |
b4fa6befdf | ||
![]() |
cd007ee0eb | ||
![]() |
bbe309e7bd | ||
![]() |
f414d7aa16 | ||
![]() |
be2989ead1 | ||
![]() |
1c8e581cf1 | ||
![]() |
a81dfe83a9 | ||
![]() |
4b9b8d61f2 | ||
![]() |
4fa5fb909c | ||
![]() |
2d7fb82dd6 | ||
![]() |
48152c9ba3 | ||
![]() |
a5771dccf5 | ||
![]() |
581b8375b9 | ||
![]() |
ec685407bb | ||
![]() |
be9a1dcf06 | ||
![]() |
1f7e8b4d9a | ||
![]() |
9058c5accb | ||
![]() |
a0799e8197 | ||
![]() |
21309ba280 | ||
![]() |
b66018a5c3 | ||
![]() |
f0e14b6de5 | ||
![]() |
845c3d419a | ||
![]() |
00e281dfb7 | ||
![]() |
b3da08ce74 | ||
![]() |
50753db4ff | ||
![]() |
862c9cea87 | ||
![]() |
427f24dffe | ||
![]() |
68072d8340 | ||
![]() |
1ac4d43db2 | ||
![]() |
e0b78adfee | ||
![]() |
0c481fc005 | ||
![]() |
9f6ee3e761 | ||
![]() |
81dca01302 | ||
![]() |
0c62a83145 | ||
![]() |
c780b9fd65 | ||
![]() |
0e180cb242 | ||
![]() |
41101921ed | ||
![]() |
80ee8f9617 | ||
![]() |
1f55b5457e | ||
![]() |
b3fe6145e2 | ||
![]() |
527af7063d | ||
![]() |
25455e8194 | ||
![]() |
c27c1379d0 | ||
![]() |
42a7ae36c2 | ||
![]() |
ce846d65cb | ||
![]() |
dc25ef857f | ||
![]() |
6f0c650a72 | ||
![]() |
8348424758 | ||
![]() |
9701474662 | ||
![]() |
e284a17034 | ||
![]() |
abf55df26d | ||
![]() |
6488a7436a | ||
![]() |
cceff2e8dd | ||
![]() |
161e6f70d0 | ||
![]() |
d4e5a750e0 | ||
![]() |
b1264bedd8 | ||
![]() |
5323ae2943 | ||
![]() |
af77e51307 | ||
![]() |
b4e8689e92 | ||
![]() |
8ec30a77ff | ||
![]() |
29c7853380 | ||
![]() |
cd417aaf44 | ||
![]() |
428a5cc0ff | ||
![]() |
d128d7c8e6 | ||
![]() |
8027199bd5 | ||
![]() |
099a887cc7 | ||
![]() |
e17c551555 | ||
![]() |
f7166f37f5 | ||
![]() |
6bc12e17fd | ||
![]() |
ea41d06023 | ||
![]() |
5147baab05 | ||
![]() |
ae5777e612 | ||
![]() |
1b5a7d78d4 | ||
![]() |
7ba2b7828c | ||
![]() |
1eac1002b2 | ||
![]() |
f41280f5b9 | ||
![]() |
8653b5b928 | ||
![]() |
5b5ebadfea | ||
![]() |
12c94ee79e | ||
![]() |
d536a10d20 | ||
![]() |
2db3b093b9 | ||
![]() |
8aa94cc32d | ||
![]() |
2e83b42ba9 | ||
![]() |
42b1a0d3de | ||
![]() |
9d49a2bd11 | ||
![]() |
f3349c64a9 | ||
![]() |
565dea5ecf | ||
![]() |
2ebe849b8e | ||
![]() |
1c50e615cf | ||
![]() |
ed2d34e979 | ||
![]() |
c404016700 | ||
![]() |
14b0353ba4 | ||
![]() |
fbe136a350 | ||
![]() |
4fcfea943e | ||
![]() |
279d27d081 | ||
![]() |
84bfe99017 | ||
![]() |
7c0c7eea30 | ||
![]() |
77b0f69b0c | ||
![]() |
e71e588a91 | ||
![]() |
beb8df9dda | ||
![]() |
014cac7789 | ||
![]() |
41caaa7730 | ||
![]() |
997e2cdfd8 | ||
![]() |
a939d1bfd8 | ||
![]() |
59f12c71a9 | ||
![]() |
0528ce983f | ||
![]() |
f2f28f48cb | ||
![]() |
006e7c214d | ||
![]() |
fc37a8afa3 | ||
![]() |
c2243320ec | ||
![]() |
f5969f271a | ||
![]() |
c7063b5973 | ||
![]() |
23e0379beb | ||
![]() |
08619244f0 | ||
![]() |
5029b19d37 | ||
![]() |
4bdf520bce | ||
![]() |
0a493b9349 | ||
![]() |
a612de52f9 | ||
![]() |
a85d4a678d | ||
![]() |
08fdcac240 | ||
![]() |
40060255ee | ||
![]() |
d2da193978 | ||
![]() |
441c56854d | ||
![]() |
e6023a4702 | ||
![]() |
d031d74bc6 | ||
![]() |
2952d1360a | ||
![]() |
301ef85e89 | ||
![]() |
ddf671abd1 | ||
![]() |
f60c978d80 | ||
![]() |
42a895b095 | ||
![]() |
c405f04e9c | ||
![]() |
bc5ce52236 | ||
![]() |
896922de4f | ||
![]() |
203a6d47b4 | ||
![]() |
8e13e2deb2 | ||
![]() |
d18c2ffddb | ||
![]() |
50969c24cc | ||
![]() |
f3070763fa | ||
![]() |
ca093875b4 | ||
![]() |
f77e061ff1 | ||
![]() |
0df1bd137d | ||
![]() |
651125ef2c | ||
![]() |
a5eb0e7faa | ||
![]() |
3513f7fe2c | ||
![]() |
d845b3f7b0 | ||
![]() |
9318c4742d | ||
![]() |
e85cdd5609 | ||
![]() |
83f80b9288 | ||
![]() |
76fe771d8c | ||
![]() |
c6ad09fe8f | ||
![]() |
a3014638c8 | ||
![]() |
8b15c63b0d | ||
![]() |
589adf4df9 | ||
![]() |
b07d85f233 | ||
![]() |
f0e5855a8e | ||
![]() |
5997aa5cd9 | ||
![]() |
8a2696adf7 | ||
![]() |
fd1bd7f215 | ||
![]() |
74876ea3c9 | ||
![]() |
f6fc142c47 | ||
![]() |
78fd83746e | ||
![]() |
2794a4b550 | ||
![]() |
7244b15821 | ||
![]() |
45a1c119ac | ||
![]() |
82b42f92a9 | ||
![]() |
c4fc831968 | ||
![]() |
83c1ca195f | ||
![]() |
3166b91cb7 | ||
![]() |
914e3a3988 | ||
![]() |
cb2a38addc | ||
![]() |
9fee0d711a | ||
![]() |
989ebcd3b7 | ||
![]() |
a46b24213b | ||
![]() |
873f857c82 | ||
![]() |
b9a22461c1 | ||
![]() |
c01731ebe9 | ||
![]() |
89b837393d | ||
![]() |
cea851d38e | ||
![]() |
992f0baf93 | ||
![]() |
e272a0eecc | ||
![]() |
0c95297659 | ||
![]() |
628cef11fa | ||
![]() |
2c5d131019 | ||
![]() |
85a02771a7 | ||
![]() |
67e5c7de2c | ||
![]() |
00ff9c5736 | ||
![]() |
ca472ff597 | ||
![]() |
d875f21647 | ||
![]() |
403ed7f63f | ||
![]() |
3e67177f73 | ||
![]() |
e49517ef05 | ||
![]() |
f44121e2f9 | ||
![]() |
de6215a933 | ||
![]() |
876ff65694 | ||
![]() |
1c142ef3f4 | ||
![]() |
bfe5209c05 | ||
![]() |
83ac03578d | ||
![]() |
e34472084c | ||
![]() |
4dce0ec015 | ||
![]() |
304c2429bb | ||
![]() |
957c7aeab9 | ||
![]() |
6a0b0327c3 | ||
![]() |
07a1136839 | ||
![]() |
70f006d06d | ||
![]() |
651f4ebf5f | ||
![]() |
fc7b396e45 | ||
![]() |
eac31d10f1 | ||
![]() |
270d4f510a | ||
![]() |
285599cf90 | ||
![]() |
01ec812e1c | ||
![]() |
9cf51d403a | ||
![]() |
dfac33723a | ||
![]() |
655f1c249e | ||
![]() |
e31e531f9c | ||
![]() |
d7cef5add7 | ||
![]() |
3742f33d08 | ||
![]() |
82ac33dd75 | ||
![]() |
03eec610fa | ||
![]() |
66191d2ff9 | ||
![]() |
9e64463e70 | ||
![]() |
ebf014406d | ||
![]() |
8c7c0101cd | ||
![]() |
4d6179dfdd | ||
![]() |
21f6a076cb | ||
![]() |
bb938d4482 | ||
![]() |
411e798f8e | ||
![]() |
0c5fa37d8b | ||
![]() |
fd4a2bdb4d | ||
![]() |
38d2345cd2 | ||
![]() |
cc45ad0b63 | ||
![]() |
0ab9cdf97c | ||
![]() |
580595e5fa | ||
![]() |
ca573ce73a | ||
![]() |
c75099decc | ||
![]() |
d874697eef | ||
![]() |
cb5252b8d4 | ||
![]() |
5f28ead6e9 | ||
![]() |
bf99c0e290 | ||
![]() |
5aaa014207 | ||
![]() |
a13f84fbf6 | ||
![]() |
1618921a57 | ||
![]() |
864063676f | ||
![]() |
17649bf36a | ||
![]() |
8f22b118be | ||
![]() |
6f97036446 | ||
![]() |
ffcde69352 | ||
![]() |
1520062878 | ||
![]() |
228777963f | ||
![]() |
08a9b59ca4 | ||
![]() |
7da1edfcc5 | ||
![]() |
f63b5514f3 | ||
![]() |
486ddb55b2 | ||
![]() |
6720b44f43 | ||
![]() |
0c7db9c1ee | ||
![]() |
7df455309e | ||
![]() |
0a4b3f02db | ||
![]() |
b8fc6e6b0f | ||
![]() |
39b8f49894 | ||
![]() |
c3b5621dda | ||
![]() |
5d58d9f5d4 | ||
![]() |
52a6f639b0 | ||
![]() |
cfcd32a10b | ||
![]() |
9e8d55e0f3 | ||
![]() |
ed6a42f747 | ||
![]() |
ffdd9c9cbf | ||
![]() |
f45bd49421 | ||
![]() |
e9cfc59400 | ||
![]() |
b98faa0671 | ||
![]() |
bfb6aee908 | ||
![]() |
5b6f876ddc | ||
![]() |
6737f45948 | ||
![]() |
1b42f95643 | ||
![]() |
b3f43f956e | ||
![]() |
abe75c9744 | ||
![]() |
1b1e4639fd | ||
![]() |
6d0327f662 | ||
![]() |
19e379f084 | ||
![]() |
2f291b167e | ||
![]() |
d47daba8c1 | ||
![]() |
27183483a9 | ||
![]() |
31b6ff003a | ||
![]() |
0e58369873 | ||
![]() |
94ddf041aa | ||
![]() |
bca9da3964 | ||
![]() |
1ca1b5aefa | ||
![]() |
9eec985fb9 | ||
![]() |
d1551bd8c7 | ||
![]() |
91716527a4 | ||
![]() |
83c304290b | ||
![]() |
5ee986593c | ||
![]() |
7187aff1a6 | ||
![]() |
44f39e7fea | ||
![]() |
f42d7b0da0 | ||
![]() |
50941055fb | ||
![]() |
a361296930 | ||
![]() |
61507c03a0 | ||
![]() |
8595fb97ce | ||
![]() |
7a37408459 | ||
![]() |
303ccce8b4 | ||
![]() |
d8cc76c7d7 | ||
![]() |
fd35295093 | ||
![]() |
72764c614e | ||
![]() |
b8106186fb | ||
![]() |
eb3c189ab6 | ||
![]() |
16c7d27508 | ||
![]() |
be50ecd033 | ||
![]() |
4170ed55a6 | ||
![]() |
31c9ecaf44 | ||
![]() |
e1bd5ed49e | ||
![]() |
d97cacff14 | ||
![]() |
8ed2f0eafa | ||
![]() |
438e525319 | ||
![]() |
2a2237f542 | ||
![]() |
06db6826ed | ||
![]() |
08a8b5fee0 | ||
![]() |
82f4c99025 | ||
![]() |
7b2a7aff9f | ||
![]() |
1206d13978 | ||
![]() |
8bdf1af021 | ||
![]() |
f0595b8b0a | ||
![]() |
258ec197d7 | ||
![]() |
7d3711bf5a | ||
![]() |
8925a0dc78 | ||
![]() |
38ccd26b96 | ||
![]() |
7e8fddc50c | ||
![]() |
bc1452d1d0 | ||
![]() |
d285107cfb | ||
![]() |
354700fcbb | ||
![]() |
ef85fba2e5 | ||
![]() |
960b601384 | ||
![]() |
21d9091b43 | ||
![]() |
e881c32797 | ||
![]() |
57f1af05f5 | ||
![]() |
254f41a2cc | ||
![]() |
59ce3404c9 | ||
![]() |
11c7342299 | ||
![]() |
8b0959aa69 | ||
![]() |
9cd8ed12b9 | ||
![]() |
78b10d7ab5 | ||
![]() |
a322ec2b23 | ||
![]() |
1f23654735 | ||
![]() |
e1112b95c7 | ||
![]() |
4e043109bf | ||
![]() |
58e670443d | ||
![]() |
86a9230da8 | ||
![]() |
86f84766c1 | ||
![]() |
fdc7078e5c | ||
![]() |
c649ebfcc0 | ||
![]() |
6aa0d4cd0b | ||
![]() |
1a2e205c1f | ||
![]() |
5dd04cb8ab | ||
![]() |
62d05e5e08 | ||
![]() |
1c087ec856 | ||
![]() |
010c12da67 | ||
![]() |
9bdac38561 | ||
![]() |
790ca9c90a | ||
![]() |
58f72d2d9c | ||
![]() |
285e6513ed | ||
![]() |
412bc8cf2d | ||
![]() |
45cd8b8a00 | ||
![]() |
ae2227959e | ||
![]() |
b50c92f919 | ||
![]() |
93a1d9c164 | ||
![]() |
0b10e68c60 | ||
![]() |
73ac4076ac | ||
![]() |
5968b82a0b | ||
![]() |
ce1d2a0fd9 | ||
![]() |
de3f813b46 | ||
![]() |
4797b1a3b7 | ||
![]() |
3e996d284d | ||
![]() |
420c5a0836 | ||
![]() |
c6b953055a | ||
![]() |
1cd0c112a6 | ||
![]() |
492d28ea37 | ||
![]() |
4eb7e03b67 | ||
![]() |
e029f329eb | ||
![]() |
47de9a752c | ||
![]() |
51c9aa2887 | ||
![]() |
82499a53d4 | ||
![]() |
df15302f2c | ||
![]() |
039b51262d | ||
![]() |
465add46d4 | ||
![]() |
bce965b402 | ||
![]() |
95ce293169 | ||
![]() |
5d604c2cad | ||
![]() |
ed2d3ca277 | ||
![]() |
0478f40d02 | ||
![]() |
a4be73da3b | ||
![]() |
762192518f | ||
![]() |
fa51df192d | ||
![]() |
1a5cc02097 | ||
![]() |
a07f54ca33 | ||
![]() |
6a8cbe92a9 | ||
![]() |
16d9376ec9 | ||
![]() |
4356f5c72a | ||
![]() |
076dc94292 | ||
![]() |
fbc527010a | ||
![]() |
5b4a22276d | ||
![]() |
b55a563fce | ||
![]() |
2a701a6dfe | ||
![]() |
8931fb4758 | ||
![]() |
2124165319 | ||
![]() |
0d701129a0 | ||
![]() |
ebd8625e1e | ||
![]() |
b68ca67386 | ||
![]() |
17a7019c60 | ||
![]() |
54af92251c | ||
![]() |
d9edeb747d | ||
![]() |
b69b722a37 | ||
![]() |
669c23ea09 | ||
![]() |
2b3ba8e7fa | ||
![]() |
9a761e7d30 | ||
![]() |
9d00e052f0 | ||
![]() |
7c159e97de | ||
![]() |
ba8e4ff33c | ||
![]() |
9b067a437c | ||
![]() |
aba39d06bf | ||
![]() |
469d22a833 | ||
![]() |
43bd49ce5b | ||
![]() |
79dc190ccc | ||
![]() |
495659e9cd | ||
![]() |
2fec2c9e4c | ||
![]() |
9cba66634d | ||
![]() |
b2f63bf231 | ||
![]() |
9c9ef22730 | ||
![]() |
5d84ec3be2 | ||
![]() |
2150961d27 | ||
![]() |
53bca5a3d3 | ||
![]() |
cd3938eb33 | ||
![]() |
eb0b88bfcf | ||
![]() |
b9bbf8bbca | ||
![]() |
65b3d0c0de | ||
![]() |
93b8f32f68 | ||
![]() |
28bb164e8e | ||
![]() |
4911cc76a3 | ||
![]() |
26ac539bc4 | ||
![]() |
75ae6b16a4 | ||
![]() |
2835b1d28f | ||
![]() |
748aad16d7 | ||
![]() |
2c2fbb8583 | ||
![]() |
20edcbf7fa | ||
![]() |
db81dc39ba | ||
![]() |
c3b0aef1ef | ||
![]() |
50e29efdfe | ||
![]() |
285e41bc88 | ||
![]() |
ea9d0fc449 | ||
![]() |
9cdd2eef81 | ||
![]() |
2f8833236a | ||
![]() |
2b680eeb6d | ||
![]() |
809f120db0 | ||
![]() |
6d9ef8bbc3 | ||
![]() |
a26d6ec6bb | ||
![]() |
2d26ced3fc | ||
![]() |
d74cd4bf24 | ||
![]() |
f040d897a7 | ||
![]() |
ed2f87f57b | ||
![]() |
9b9e31f54c | ||
![]() |
b3cfcf660e | ||
![]() |
e5bcd1f94e | ||
![]() |
2b6fa769f7 | ||
![]() |
3ccc82f343 | ||
![]() |
f4273cafb6 | ||
![]() |
59d63f61d9 | ||
![]() |
9d9103a83b | ||
![]() |
0b085b6d03 | ||
![]() |
f77538f179 | ||
![]() |
f7810f7f95 | ||
![]() |
4d28e4603f | ||
![]() |
8b787e4ae0 | ||
![]() |
f5ba168172 | ||
![]() |
1df6dadbdd | ||
![]() |
3dc29144a3 | ||
![]() |
951167ce17 | ||
![]() |
906e4055d8 | ||
![]() |
2f5526388a | ||
![]() |
82341642f4 | ||
![]() |
c96b1eb09d | ||
![]() |
f5bfa67c69 | ||
![]() |
47797ffcd4 | ||
![]() |
a73053e380 | ||
![]() |
bc042fead7 | ||
![]() |
ed6779e937 | ||
![]() |
ee7ca68f87 | ||
![]() |
32693b6378 | ||
![]() |
984e5588c8 | ||
![]() |
a42a1af867 | ||
![]() |
03de680915 | ||
![]() |
8c6e142314 | ||
![]() |
b12bde4f79 | ||
![]() |
1120aa3841 | ||
![]() |
652ca73126 | ||
![]() |
8706e72f6a | ||
![]() |
319d521773 | ||
![]() |
d9474cdcc5 | ||
![]() |
e49a34177a | ||
![]() |
67d203e011 | ||
![]() |
0d38b3de16 | ||
![]() |
38116a14f3 | ||
![]() |
b28f0b65f0 | ||
![]() |
13ab4a9363 | ||
![]() |
7cb7783a34 | ||
![]() |
d1a13dad38 | ||
![]() |
b4e06dea99 | ||
![]() |
0f92dc0fdf | ||
![]() |
6a58895d37 | ||
![]() |
1709a2b7df | ||
![]() |
febb3da0c1 | ||
![]() |
552a428985 | ||
![]() |
38e04bd42a | ||
![]() |
8f0ba5ba4f | ||
![]() |
c67aedceb1 | ||
![]() |
b3a7fbd9b5 | ||
![]() |
29522428de | ||
![]() |
77bd52b2ae | ||
![]() |
d8112e7628 | ||
![]() |
ffa208e73f | ||
![]() |
61ead15c38 | ||
![]() |
407e2ae481 | ||
![]() |
5fb16edf43 | ||
![]() |
8eb5c475bb | ||
![]() |
84090310f7 | ||
![]() |
fc98e2f052 | ||
![]() |
bedcfa9520 | ||
![]() |
bb152b590b | ||
![]() |
3623732cf7 | ||
![]() |
05ba89f164 | ||
![]() |
cb5053476d | ||
![]() |
cee656a053 | ||
![]() |
cfc7d529e1 | ||
![]() |
a93dc68e6c | ||
![]() |
2d91cfd3db | ||
![]() |
36e81f44cb | ||
![]() |
cb0e65337f | ||
![]() |
1c627f4649 | ||
![]() |
16cbfed20b | ||
![]() |
f6a3bc57e2 | ||
![]() |
594443d1dc | ||
![]() |
c3378e1653 | ||
![]() |
bc57dd650c | ||
![]() |
311a8c6fa3 | ||
![]() |
bdb43c0e9e | ||
![]() |
8033b47596 | ||
![]() |
9d5052cc68 | ||
![]() |
f4c9dc8a5f | ||
![]() |
a3f0a78df0 | ||
![]() |
b70363e005 | ||
![]() |
65eab801e8 | ||
![]() |
9e764248d3 | ||
![]() |
a660a1c44b | ||
![]() |
33458c1bdb | ||
![]() |
e5530182cd | ||
![]() |
9ecabc3faf | ||
![]() |
8b58f6b861 | ||
![]() |
9e41bf529d | ||
![]() |
36398fe958 | ||
![]() |
69cfbea5f3 | ||
![]() |
1e1e3beca6 | ||
![]() |
a726154b1d | ||
![]() |
c2ccb51ef5 | ||
![]() |
a1d7062b1f | ||
![]() |
2e1e3f8409 | ||
![]() |
885604ef06 | ||
![]() |
cb8a5504f6 | ||
![]() |
325fa19e46 | ||
![]() |
06b684c899 | ||
![]() |
363d1b07ca | ||
![]() |
663b9a610a | ||
![]() |
f598d5046e | ||
![]() |
ae381f7762 | ||
![]() |
acc18b8d68 | ||
![]() |
6f33d29a51 | ||
![]() |
be82e64add | ||
![]() |
3ee000ed7d | ||
![]() |
0f338edacd | ||
![]() |
085d937946 | ||
![]() |
0cbc4b9546 | ||
![]() |
9e1c4b1a88 | ||
![]() |
b47f542df7 | ||
![]() |
baed101ef1 | ||
![]() |
c9ee6e3af9 | ||
![]() |
8ed7688277 | ||
![]() |
27716d080f | ||
![]() |
4311d12603 | ||
![]() |
6aa786698e | ||
![]() |
b0eb98c667 | ||
![]() |
955b69a9bf | ||
![]() |
5f5bfa864d | ||
![]() |
6ec6c69dba | ||
![]() |
19f3286a82 | ||
![]() |
36a3cae9c2 | ||
![]() |
86215c34be | ||
![]() |
4ad421d4d0 | ||
![]() |
e79f6d5617 | ||
![]() |
baf44a97b4 | ||
![]() |
c14053a199 | ||
![]() |
1e5153d69e | ||
![]() |
fed38bd046 | ||
![]() |
89d298ea65 | ||
![]() |
cd35fa1802 | ||
![]() |
150453bff3 | ||
![]() |
49833b3c51 | ||
![]() |
4a0f0238b0 | ||
![]() |
83b97111a0 | ||
![]() |
43bbf32098 | ||
![]() |
a70817f421 | ||
![]() |
9ae441b75a | ||
![]() |
21fcbd85d8 | ||
![]() |
e1b61214b7 | ||
![]() |
2d10b0748c | ||
![]() |
f4e719749a | ||
![]() |
600bca7e8b | ||
![]() |
a3a62b1d94 | ||
![]() |
dbe783d31d | ||
![]() |
f0d8492b66 | ||
![]() |
7587eb9ac2 | ||
![]() |
fe2fdafbb1 | ||
![]() |
e50c77d8c6 | ||
![]() |
81f9f52353 | ||
![]() |
6e5b02d326 | ||
![]() |
d09c7b13b3 | ||
![]() |
ff532a5c6c | ||
![]() |
698275633f | ||
![]() |
c5dff312e1 | ||
![]() |
972412e712 | ||
![]() |
453c46df00 | ||
![]() |
f001e19728 | ||
![]() |
3da8cc1e7f | ||
![]() |
68d124ff04 | ||
![]() |
4921458782 | ||
![]() |
86aa21a8bb | ||
![]() |
65de742f96 | ||
![]() |
4043398e01 | ||
![]() |
7be651f5cf | ||
![]() |
5ddd4d045e | ||
![]() |
1cc7e8725d | ||
![]() |
6fe115fd0d | ||
![]() |
d58dfec5ea | ||
![]() |
3e6f5ac70e | ||
![]() |
a1821fabf9 | ||
![]() |
b6461f4f9e | ||
![]() |
0781018e4e | ||
![]() |
2f8e768c5c | ||
![]() |
1622b0fa29 | ||
![]() |
e147ce9039 | ||
![]() |
2aa059a170 | ||
![]() |
ae60b21375 | ||
![]() |
03faebe776 | ||
![]() |
f66afc4cae | ||
![]() |
b327413bfa | ||
![]() |
54776e2712 | ||
![]() |
4b8d4488d7 | ||
![]() |
a2a1b66fc3 | ||
![]() |
d2bdb597f6 | ||
![]() |
85a7819469 | ||
![]() |
5689dfd3e3 | ||
![]() |
3c76ead1ab | ||
![]() |
be7fbdf5d8 | ||
![]() |
fc21f043ae | ||
![]() |
11659df89d | ||
![]() |
2bfa770f60 | ||
![]() |
4679121115 | ||
![]() |
3ecc90d21a | ||
![]() |
fd587fe108 | ||
![]() |
0f851ec2a3 | ||
![]() |
4d057a1c5e | ||
![]() |
c8b13ff5e1 | ||
![]() |
545dd08535 | ||
![]() |
c0a5a8d775 | ||
![]() |
d462ebe8e5 | ||
![]() |
5d7ba8cf14 | ||
![]() |
af9786f149 | ||
![]() |
6b990ee78a | ||
![]() |
f87102ccc7 | ||
![]() |
63398089cd | ||
![]() |
89694b5069 | ||
![]() |
4f8a5211f8 | ||
![]() |
b45df26fdc | ||
![]() |
c0b0181475 | ||
![]() |
62600a450a | ||
![]() |
4be41336b3 | ||
![]() |
3abea4ad3c | ||
![]() |
b2304992e5 | ||
![]() |
f9825410dc | ||
![]() |
24205dc86e | ||
![]() |
9fcd0da83d | ||
![]() |
e99bc73e46 | ||
![]() |
d8ad9adabd | ||
![]() |
11aa7d0140 | ||
![]() |
6f97173b00 | ||
![]() |
54a7367fb6 | ||
![]() |
51a12099e4 | ||
![]() |
00c0c96b1b | ||
![]() |
0aa2537d1e | ||
![]() |
14489e1db9 | ||
![]() |
bff4eaba56 | ||
![]() |
4a5d2f8502 | ||
![]() |
72af2fa281 | ||
![]() |
cf70b3e989 | ||
![]() |
7ebd74a54a | ||
![]() |
9a650b5cd6 | ||
![]() |
ffa1331802 | ||
![]() |
dacb4ea7d6 | ||
![]() |
ae5889dbac | ||
![]() |
7b169e9439 | ||
![]() |
541d2904d3 | ||
![]() |
2080bbcbca | ||
![]() |
f4c38008a1 | ||
![]() |
2d38b15f1b | ||
![]() |
13257b9f86 | ||
![]() |
22f106b357 | ||
![]() |
352b5aadba | ||
![]() |
f86c9ea947 | ||
![]() |
2f5f0ba1e1 | ||
![]() |
6a72923182 | ||
![]() |
d62f7b2a5f | ||
![]() |
8ca6255ff3 | ||
![]() |
d3b3afd593 | ||
![]() |
57cb5ff6cf | ||
![]() |
39f3da6cde | ||
![]() |
5a236c9357 | ||
![]() |
4b0eab57a8 | ||
![]() |
74a232630a | ||
![]() |
71d023ab77 | ||
![]() |
786a374233 | ||
![]() |
41899872cd | ||
![]() |
076659db52 | ||
![]() |
8f665622d6 | ||
![]() |
5cc6e0b172 | ||
![]() |
bff22900cb | ||
![]() |
5e79c9fd62 | ||
![]() |
92f55c254c | ||
![]() |
39034e38f6 | ||
![]() |
3c7b9558fe | ||
![]() |
c8f7f40b46 | ||
![]() |
2a764cf190 | ||
![]() |
ba6ef4d629 | ||
![]() |
67d3505733 | ||
![]() |
252145cf58 | ||
![]() |
dbc62542ef | ||
![]() |
005829ab72 | ||
![]() |
448c8b0e8a | ||
![]() |
ff0e724ee5 | ||
![]() |
568e4a5ee8 | ||
![]() |
fbf4a524c1 | ||
![]() |
29db2e958f | ||
![]() |
cc7bcbf9d5 | ||
![]() |
98d4484e6c | ||
![]() |
0b126278f9 | ||
![]() |
cc919415bb | ||
![]() |
a3f398390c | ||
![]() |
5ae89368f1 | ||
![]() |
c8f1cb0a0a | ||
![]() |
f783b08b78 | ||
![]() |
85e0c6d3cd | ||
![]() |
2259a96058 | ||
![]() |
afed5841e7 | ||
![]() |
52361cd505 | ||
![]() |
b743cca7bc | ||
![]() |
1d01d0bff1 | ||
![]() |
c45a488962 | ||
![]() |
6731c44541 | ||
![]() |
b04ed83963 | ||
![]() |
c35b79e642 | ||
![]() |
ed8c7c1052 | ||
![]() |
498a074222 | ||
![]() |
3fe6db4d42 | ||
![]() |
6e5cd82dfb | ||
![]() |
cbf3488de9 | ||
![]() |
c72314fb71 | ||
![]() |
c4af6feb92 | ||
![]() |
08537c1d69 | ||
![]() |
425da82f5f | ||
![]() |
2cfbf7c39a | ||
![]() |
d4eed9f8fd | ||
![]() |
fe10170826 | ||
![]() |
8dc3b0b250 | ||
![]() |
75da1220af | ||
![]() |
37a2c3c631 | ||
![]() |
5c5722714d | ||
![]() |
2ba529f9e3 | ||
![]() |
fd760ff015 | ||
![]() |
daab1d917b | ||
![]() |
4c3a63a7e1 | ||
![]() |
7cc58b84da | ||
![]() |
2bac4ac1a7 | ||
![]() |
c5b2b86786 | ||
![]() |
5652a2b6c2 | ||
![]() |
bd19f543a2 | ||
![]() |
cc1e888227 | ||
![]() |
d73e379dcf | ||
![]() |
0569abd00d | ||
![]() |
7f5d9bec87 | ||
![]() |
b39e7bbb6d | ||
![]() |
70270a8e3b | ||
![]() |
efdc050a28 | ||
![]() |
e8a65df7f0 | ||
![]() |
59628a72fb | ||
![]() |
a4d6c6c0d8 | ||
![]() |
bea82c6640 | ||
![]() |
1ba3bdfbda | ||
![]() |
98b4000bc0 | ||
![]() |
14f6824931 | ||
![]() |
795d7d0a93 | ||
![]() |
673fa2b556 | ||
![]() |
0e2504fc78 | ||
![]() |
2afca9f2b4 | ||
![]() |
464d2a541d | ||
![]() |
fa8c5e0982 | ||
![]() |
5e15884d8f | ||
![]() |
b5e9ff3b4e | ||
![]() |
fed7d4cc34 | ||
![]() |
d7ab066ff8 | ||
![]() |
4100917016 | ||
![]() |
5d2c1ffb88 | ||
![]() |
ddb0f198a9 | ||
![]() |
13438e3e25 | ||
![]() |
05a410b327 | ||
![]() |
23fa64d289 | ||
![]() |
1920c9b7e3 | ||
![]() |
eedd0d9c07 | ||
![]() |
9ef389d335 | ||
![]() |
6542997520 | ||
![]() |
a58b2e2038 | ||
![]() |
6860e348dc | ||
![]() |
5e094e7597 | ||
![]() |
2f2cb8386b | ||
![]() |
965fd170bd | ||
![]() |
2610d29b60 | ||
![]() |
064131c842 | ||
![]() |
00b6bf8394 | ||
![]() |
2a885d709d | ||
![]() |
5bed46c0aa | ||
![]() |
8b27c7e01a | ||
![]() |
177902a286 | ||
![]() |
48b0f7dc27 | ||
![]() |
d5f4a1a48a | ||
![]() |
de9f60aa7f | ||
![]() |
c93b65b299 | ||
![]() |
3c6a6cdc5b | ||
![]() |
b669f3d715 | ||
![]() |
f663fac220 | ||
![]() |
bc42e79bb5 | ||
![]() |
ca29333cd0 | ||
![]() |
f9f478e100 | ||
![]() |
0873beaed2 |
10
.gitignore
vendored
@@ -12,16 +12,18 @@
|
||||
*.db*
|
||||
*.db-journal
|
||||
*.ini
|
||||
release.lock
|
||||
version.lock
|
||||
logs/*
|
||||
cache/*
|
||||
*.mmdb
|
||||
|
||||
# HTTPS Cert/Key #
|
||||
##################
|
||||
*.crt
|
||||
*.key
|
||||
*.csr
|
||||
*.pem
|
||||
/*.crt
|
||||
/*.key
|
||||
/*.csr
|
||||
/*.pem
|
||||
|
||||
# Mergetool
|
||||
*.orgin
|
||||
|
890
CHANGELOG.md
@@ -1,5 +1,833 @@
|
||||
# Changelog
|
||||
|
||||
## 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:
|
||||
* Fix: Missing video_height database column.
|
||||
* Notifications:
|
||||
* Fix: Join API key.
|
||||
* Change: Temporarily disable broken browser notifications.
|
||||
* UI:
|
||||
* Fix: Incorrect fallback image for music watch statistics.
|
||||
|
||||
|
||||
## v2.0.0-beta (2017-12-18)
|
||||
|
||||
* Monitoring:
|
||||
* New: More detailed stream info including subtitles, bitrates, bandwidth, and quality profiles.
|
||||
* New: Terminate sessions from the current activity (Plex Pass only).
|
||||
* Change: Monitoring uses websockets only now.
|
||||
* Notifications:
|
||||
* New: Completely new notification system.
|
||||
* Allow adding multiple of the same notification agent and/or duplicating existing notification agents.
|
||||
* Each notification agent has it's own notification triggers and notification text.
|
||||
* Notification agents are stored in the database instead of the config file. Some notification configurations may have been lost in the transfer. Sorry.
|
||||
* New: Discord notification agent.
|
||||
* New: GroupMe notification agent.
|
||||
* New: MQTT notification agent.
|
||||
* New: More customizable info cards for Discord, Facebook, Hipchat, and Slack.
|
||||
* New: Script notifications are configured individually per script with separate arguments for each notification action.
|
||||
* New: Icon and duration options for Plex Home Theater and XBMC notifications.
|
||||
* New: Notification for Tautulli updates.
|
||||
* New: Added <show>, <season>, <artist>, and <album> notification exclusion tags.
|
||||
* <tv> is renamed to <episode>, and <music> is renamed to <track>
|
||||
* New: Preview notification text in the notifier settings.
|
||||
* New: Properly group recently added notifications when adding a batch of media.
|
||||
* The {season_num}, {episode_num}, and {track_num} parameters will be substituted with the range (e.g. 06-10)
|
||||
* New: Option to group recently added notifications by show/artist or season/album.
|
||||
* New: More detailed media info (video, audio, subtitle, file, etc.) notification options available.
|
||||
* New: Added notification text modifiers to change case and slice lists.
|
||||
* New: Custom notification conditions using parameters to filter notifications.
|
||||
* New: Button to trigger manual recently added notifications from the info pages.
|
||||
* New: Lookup TVMaze and TheMovieDatabase links.
|
||||
* Remove: The shared Imgur client ID has been removed. Please enter your own client ID in the settings to continue uploading posters.
|
||||
* Change: Notifications with a blank subject or body will no longer be sent at all.
|
||||
* Change: Line breaks inserted automatically in Email notification text.
|
||||
* Change: Notifications for season/episodes now use the season poster and album/track now use the album art.
|
||||
* Change: The {action} parameter is no longer capitalized.
|
||||
* Change: Notification success or failure added to notification logs.
|
||||
* API:
|
||||
* New: Added check for Plex Media Server updates with the Tautulli API.
|
||||
* New: Added show/artist and episode/track titles to the "get_history" API command.
|
||||
* New: Added manual trigger for recently added notifications.
|
||||
* Remove: Defunct API v1.
|
||||
* Change: The "notify" API command now requires a notifier_id instead of an agent_id. The notifier ID can be found in the settings for each notification agent.
|
||||
* Change: The returned json for the "get_metadata" API command is no longer nested under the "metadata" key.
|
||||
* UI:
|
||||
* New: Updated current activity, watch statistics, and library statistics cards on the home page.
|
||||
* New: Toggle stats and recently added categories directly on the homepage.
|
||||
* New: Ability to delete synced items from the Synced Items page.
|
||||
* New: Updated platform icons to a uniform style.
|
||||
* Remove: Setting for number of top items for watch statistic cards.
|
||||
* Change: Separate API and websocket logs.
|
||||
* Android Tautulli Remote App (beta):
|
||||
* New: Download the Tautulli Remote app on Google Play!
|
||||
* Link the app using a QR code in the Tautulli settings.
|
||||
* New: Push notifications directly to the Tautulli Remote app.
|
||||
* Other:
|
||||
* New: Option to update Tautulli automatically when an update is available.
|
||||
* New: Option to switch the tracking git remote and branch.
|
||||
* New: Option to change the path to your git environment variable.
|
||||
* New: Option to use a HTTPS certificate chain.
|
||||
* New: Option to override the Plex Web URL for click-through links.
|
||||
* New: Separate watched percentage for movies, episodes, and tracks.
|
||||
* New: Show changelog after updating Tautulli.
|
||||
* New: Support for IPv6 geolocation lookup.
|
||||
* New: Download the Tautulli configuration file or database from the settings.
|
||||
* New: Log failed Tautulli login attempts.
|
||||
* Fix: Modal popups not working on mobile Safari.
|
||||
* Fix: Prevent password managers from autofilling the password in the settings.
|
||||
* Fix: Unable to search with special characters.
|
||||
* Remove: Some unused options have been removed from the settings page.
|
||||
* Change: The database schema has been changed, and reverting back to PlexPy v1 will not work.
|
||||
* Change: The dev branch has been depreciated. A master/beta/nightly system is used instead.
|
||||
|
||||
|
||||
## v1.4.25 (2017-10-02)
|
||||
|
||||
* Fix: Tab instead of spaces preventing startup.
|
||||
|
||||
|
||||
## v1.4.24 (2017-10-01)
|
||||
|
||||
* Fix: New Plex Web urls. (Thanks @Joshua1337)
|
||||
* Fix: Fallback to the product name if the player title is blank.
|
||||
* New: Added no forking option to startup arguments. (Thanks @Vashypooh)
|
||||
|
||||
|
||||
## v1.4.23 (2017-09-30)
|
||||
|
||||
* Fix: Playstation 4 platform name.
|
||||
* Fix: PlexWatch and Plexivity import.
|
||||
* Fix: Pushbullet authorization header.
|
||||
|
||||
|
||||
## v1.4.22 (2017-08-19)
|
||||
|
||||
* Fix: Cleaning up of old config backups.
|
||||
* Fix: Temporary fix for incorrect source media info.
|
||||
|
||||
|
||||
## v1.4.21 (2017-07-01)
|
||||
|
||||
* New: Updated donation methods.
|
||||
|
||||
|
||||
## v1.4.20 (2017-06-24)
|
||||
|
||||
* New: Added platform image for the PlexTogether player.
|
||||
* Fix: Corrected math used to calculate human duration. (Thanks @senepa)
|
||||
* Fix: Sorting of 4k in media info tables.
|
||||
* Fix: Update file sizes when refreshing media info tables.
|
||||
* Fix: Support a custom port for Mattermost (Slack) notifications.
|
||||
|
||||
|
||||
## v1.4.19 (2017-05-31)
|
||||
|
||||
* Fix: Video resolution not showing up for transcoded streams on PMS 1.7.x.
|
||||
|
||||
|
||||
## v1.4.18 (2017-04-22)
|
||||
|
||||
* New: Added some new Arnold quotes. (Thanks @senepa)
|
||||
* Fix: Text wrapping in datatable footers.
|
||||
* Fix: API command get_apikey. (Thanks @Hellowlol)
|
||||
|
||||
|
||||
## v1.4.17 (2017-03-04)
|
||||
|
||||
* New: Configurable month range for the Plays by month graph. (Thanks @Pbaboe)
|
||||
* New: Option to chanage the week to start on Monday for the the Plays by day of week graph. (Thanks @Pbaboe)
|
||||
* Fix: Invalid iOS icon file paths. (Thanks @demonbane)
|
||||
* Fix: Plex Web 3.0 URLs on info pages and notifications.
|
||||
* Fix: Update bitcoin donation link to Coinbase.
|
||||
* Fix: Update init scripts. (Thanks @ampsonic)
|
||||
|
||||
|
||||
## v1.4.16 (2016-11-25)
|
||||
|
||||
* Fix: Websocket for new json response on PMS 1.3.0.
|
||||
* Fix: Update stream and transcoder tooltip percent.
|
||||
* Fix: Typo in the edit user modal.
|
||||
|
||||
|
||||
## v1.4.15 (2016-11-11)
|
||||
|
||||
* New: Add stream and transcoder progress percent to the current activity tooltip.
|
||||
* Fix: Refreshing of images in the cache when authentication is disabled.
|
||||
* Fix: Plex.tv authentication with special characters in the username or password.
|
||||
* Fix: Line breaks in the info page summaries.
|
||||
* Fix: Redirect to the proper http root when restarting.
|
||||
* Fix: API result type and responses showing incorrectly. (Thanks @Hellowlol)
|
||||
* Change: Use https URL for app.plex.tv.
|
||||
* Change: Show API traceback errors in the browser with debugging enabled. (Thanks @Hellowlol)
|
||||
* Change: Increase table width on mobile devices and max width set to 1750px. (Thanks @XusBadia)
|
||||
|
||||
|
||||
## v1.4.14 (2016-10-12)
|
||||
|
||||
* Fix: History logging locking up if media is removed from Plex before PlexPy can save the session.
|
||||
* Fix: Unable to save API key in the settings.
|
||||
* Fix: Some typos in the settings. (Thanks @Leafar3456)
|
||||
* Change: Disable script timeout by setting timeout to 0 seconds.
|
||||
|
||||
|
||||
## v1.4.13 (2016-10-08)
|
||||
|
||||
* New: Option to set the number of days to keep PlexPy backups.
|
||||
* New: Option to add a supplementary url to Pushover notifications.
|
||||
* New: Option to set a timeout duration for script notifications.
|
||||
* New: Added flush temporary sessions button to extra settings for emergency use.
|
||||
* New: Added pms_image_proxy to the API.
|
||||
* Fix: Insanely long play durations being recorded when connection to the Plex server is lost.
|
||||
* Fix: Script notification output not being sent to the logger.
|
||||
* Fix: New libraries not being added to homepage automatically.
|
||||
* Fix: Success message shown incorrectly when sending a test notification.
|
||||
* Fix: PlexPy log level filter not working.
|
||||
* Fix: Admin username not shown in login logs.
|
||||
* Fix: FeatHub link in readme document.
|
||||
* Change: Posters disabled by default for all notification agents.
|
||||
* Change: Disable manual changing of the PlexPy API key.
|
||||
* Change: Force refresh the Plex.tv token when fetching a new token.
|
||||
* Change: Script notifications run in a new thread with the timeout setting.
|
||||
* Change: Watched percent moved to general settings.
|
||||
* Change: Use human readable file sizes to the media info tables. (Thanks @logaritmisk)
|
||||
* Change: Update pytz library.
|
||||
|
||||
|
||||
## v1.4.12 (2016-09-18)
|
||||
|
||||
* Fix: PMS update check not working for MacOSX.
|
||||
* Fix: Square covers for music stats on homepage.
|
||||
* Fix: Card width on the homepage for iPhone 6/7 Plus. (Thanks @XusBadia)
|
||||
* Fix: Check for running PID when starting PlexPy. (Thanks @spolyack)
|
||||
* Fix: FreeBSD service script not stopping PlexPy properly.
|
||||
* Fix: Some web UI cleanup.
|
||||
* Change: GitHub repostitory moved.
|
||||
|
||||
|
||||
## v1.4.11 (2016-09-02)
|
||||
|
||||
* Fix: PlexWatch and Plexivity import errors.
|
||||
* Fix: Searching in history datatables.
|
||||
* Fix: Notifications not sending for Local user.
|
||||
|
||||
|
||||
## v1.4.10 (2016-08-15)
|
||||
|
||||
* Fix: Missing python ipaddress module preventing PlexPy from starting.
|
||||
|
||||
|
||||
## v1.4.9 (2016-08-14)
|
||||
|
||||
* New: Option to include current activity in the history tables.
|
||||
* New: ISP lookup info in the IP address modal.
|
||||
* New: Option to disable web page previews for Telegram notifications.
|
||||
* Fix: Send correct JSON header for Slack/Mattermost notifications.
|
||||
* Fix: Twitter and Facebook test notifications incorrectly showing as "failed".
|
||||
* Fix: Current activity progress bars extending past 100%.
|
||||
* Fix: Typo in the setup wizard. (Thanks @wopian)
|
||||
* Fix: Update PMS server version before checking for a new update.
|
||||
* Change: Compare distro and build when checking for server updates.
|
||||
* Change: Nicer y-axis intervals when viewing "Play Duration" graphs.
|
||||
|
||||
|
||||
## v1.4.8 (2016-07-16)
|
||||
|
||||
* New: Setting to specify PlexPy backup interval.
|
||||
* Fix: User Concurrent Streams Notifications by IP Address checkbox not working.
|
||||
* Fix: Substitute {update_version} in fallback PMS update notification text.
|
||||
* Fix: Check version for automatic IP logging setting.
|
||||
* Fix: Use library refresh interval.
|
||||
|
||||
|
||||
## v1.4.7 (2016-07-14)
|
||||
|
||||
* New: Use MaxMind GeoLite2 for IP address lookup.
|
||||
* Note: The GeoLite2 database must be installed from the settings page.
|
||||
* New: Check for Plex updates using plex.tv downloads instead of the server API.
|
||||
* Note: Check for Plex updates has been disabled and must be re-enabled in the settings.
|
||||
* New: More notification options for Plex updates.
|
||||
* New: Notifications for concurrent streams by a single user.
|
||||
* New: Notifications for user streaming from a new device.
|
||||
* New: HipChat notification agent. (Thanks @aboron)
|
||||
* Fix: Username showing as blank when friendly name is blank.
|
||||
* Fix: Direct stream count wrong in the current activity header.
|
||||
* Fix: Current activity reporting direct stream when reducing the stream quality switches to transcoding.
|
||||
* Fix: Apostophe in an Arnold quote causing the shutdown/restart page to crash.
|
||||
* Fix: Disable refreshing posters in guest mode.
|
||||
* Fix: PlexWatch/Plexivity import unable to select the "grouped" database table.
|
||||
* Change: Updated Facebook notification instructions.
|
||||
* Change: Subject line optional for Join notifications.
|
||||
* Change: Line break between subject and body text instead of a colon for Facebook, Slack, Twitter, and Telegram.
|
||||
* Change: Allow Mattermost notifications using the Slack config.
|
||||
* Change: Better formatting for Slack poster notifications.
|
||||
* Change: Telegram only notifies once instead of twice when posters are enabled.
|
||||
* Change: Host Open Sans font locally instead of querying Google Fonts.
|
||||
|
||||
|
||||
## v1.4.6 (2016-06-11)
|
||||
|
||||
* New: Added User and Library statistics to the API.
|
||||
* New: Ability to refresh individual poster images without clearing the entire cache. (Thanks @Hellowlol)
|
||||
* New: Added {added_date}, {updated_date}, and {last_viewed_date} to metadata notification options.
|
||||
* New: Log level filter for Plex logs. (Thanks @sanderploegsma)
|
||||
* New: Log level filter for PlexPy logs.
|
||||
* New: Button to download Plex logs directly from the web interface.
|
||||
* New: Advanced setting in the config file to change the number of Plex log lines retrieved.
|
||||
* Fix: FreeBSD and FreeNAS init scripts to reflect the path in the installation guide. (Thanks @nortron)
|
||||
* Fix: Monitoring crashing when failed to retrieve current activity.
|
||||
|
||||
|
||||
## v1.4.5 (2016-05-25)
|
||||
|
||||
* Fix: PlexPy unable to start if failed to get shared libraries for a user.
|
||||
* Fix: Matching port number when retrieving the PMS url.
|
||||
* Fix: Extract mapped IPv4 address in Plexivity import.
|
||||
* Change: Revert back to internal url when retrieving PMS images.
|
||||
|
||||
|
||||
## v1.4.4 (2016-05-24)
|
||||
|
||||
* Fix: Image queries crashing the PMS when playing clips from channels.
|
||||
* Fix: Plexivity import if IP address is missing.
|
||||
* Fix: Tooltips shown behind the datatable headers.
|
||||
* Fix: Current activity instances rendered in a random order causing them to jump around.
|
||||
|
||||
|
||||
## v1.4.3 (2016-05-22)
|
||||
|
||||
* Fix: PlexPy not starting without any authentication method.
|
||||
|
||||
|
||||
## v1.4.2 (2016-05-22)
|
||||
|
||||
* New: Option to use HTTP basic authentication instead of the HTML login form.
|
||||
* Fix: Unable to save settings when enabling the HTTP proxy setting.
|
||||
* Change: Match the PMS port when retrieving the PMS url.
|
||||
|
||||
|
||||
## v1.4.1 (2016-05-20)
|
||||
|
||||
* New: HTTP Proxy checkbox in the settings. Enable this if using an SSL enabled reverse proxy in front of PlexPy.
|
||||
* Fix: Check for blank username/password on login.
|
||||
* Fix: Persist current activity artwork blur across refreshes when transcoding details are visible.
|
||||
* Fix: Send notifications to multiple XBMC/Plex Home Theater devices.
|
||||
* Fix: Reset PMS identifier when clicking verify server button in settings.
|
||||
* Fix: Crash when trying to group current activity session in database.
|
||||
* Fix: Check current activity returns sessions when refreshing.
|
||||
* Fix: Logs sorted out of order.
|
||||
* Fix: Resolution reported incorrectly in the stream info modal.
|
||||
* Fix: PlexPy crashing when hashing password in the config file.
|
||||
* Fix: CherryPy doubling the port number when accessing PlexPy locally with http_proxy enabled.
|
||||
* Change: Sort by most recent for ties in watch statistics.
|
||||
* Change: Refresh Join devices when changing the API key.
|
||||
* Change: Format the Join device IDs.
|
||||
* Change: Join notifications now sent with Python Requests module.
|
||||
* Change: Add paging for recently added in the API.
|
||||
|
||||
|
||||
## v1.4.0 (2016-05-15)
|
||||
|
||||
* New: An HTML form login page with sessions support.
|
||||
* New: Guest access control for shared users using Plex.tv authentication.
|
||||
* Enable the option in the settings and toggle guest access per user from Users > Edit mode.
|
||||
* Guests can only view their own user data. Other user info is removed/masked.
|
||||
* Guests can only view media from libraries that are shared with them (content rating and label filters are respected). Other libraries are removed/masked.
|
||||
* All settings and admin controls are restricted from guests.
|
||||
* All current activity on the server is shown, but with masked user/metadata info.
|
||||
* New: Login logs table on the User and Logs pages.
|
||||
* New: Filter the history table by user.
|
||||
* New: Filter the graphs by user. (Thanks @Otger)
|
||||
* New: Option to hash the admin passowrd in the config file.
|
||||
* New: Options to enable/disable/rearrange each section on the homepage
|
||||
* New: Toggle media types for recently added items on the homepage.
|
||||
* New: Option to enter an Imgur API client ID for uploading posters.
|
||||
* Note: The shared Imgur client id will be removed in a future PlexPy update! Please enter your own client id in the settings to continue uploading posters!
|
||||
* New: HTML support for Email.
|
||||
* New: Posters and HTML support for Telegram.
|
||||
* New: Poster support for Slack.
|
||||
* New: Poster support for Twitter.
|
||||
* New: Re-added Plex Home Theater notification agent.
|
||||
* New: Browser notification agent (experimental).
|
||||
* New: Added {plex_url} as a notification option.
|
||||
* New: Added transcode decision to the activity header.
|
||||
* New: Documentation for APIv2 (see API.md for details).
|
||||
* New: Import a Plexivity database into PlexPy.
|
||||
* New: Prettier fallback image for art/episodes.
|
||||
* New: Prettier confirm modal dialogues.
|
||||
* New: Cache images to reduce Plex API calls. This can be disabled in the under Settings > Extra Settings. (Thanks @Hellowlol)
|
||||
* New: Scheduled backups of the config file.
|
||||
* New: Button to clear the PlexPy cache/images in the settings.
|
||||
* New: Button to manually backup the PlexPy database/config in the settings.
|
||||
* New: Button to clear the PlexPy logs in the settings.
|
||||
* New: Button to download PlexPy log file on the Logs tab.
|
||||
* New: Advanced setting in config file to change the Plex API timeout value.
|
||||
* Fix: Mixed content HTTP request in settings (for reverse proxies with SSL).
|
||||
* Fix: Rename recently "watched" music to "played".
|
||||
* Change: Current activity details now persists across refreshes.
|
||||
* Change: Smoother transitions between preview thumbnails in current activity.
|
||||
* Change: Datatables now display all columns and scroll horizontally on smaller screens.
|
||||
* Change: Ability to change the base URL for reverse proxies in the web interface.
|
||||
* Change: Added a "Verify Server" button in the settings.
|
||||
* Change: Added request status code in the logs for notifer errors.
|
||||
* Change: Remove in-memory logs and read lines from log file instead. (Thanks @Hellowlol)
|
||||
* Change: Limit number of failed attempts to write sessions to history. Default is 5 attempts.
|
||||
* Change: A bunch of UI updates.
|
||||
* Change: A bunch of backend code cleanup.
|
||||
* Removed: All unused Python packages.
|
||||
|
||||
|
||||
## v1.3.16 (2016-05-01)
|
||||
|
||||
* Fix: Viewing photos crashing PlexPy.
|
||||
* Fix: Persist Users > Edit mode on datatable page change.
|
||||
* Fix: PMS update notifications broken.
|
||||
* Change: Cache notifications poster with thread ID to avoid overwritting images.
|
||||
|
||||
|
||||
## v1.3.15 (2016-04-18)
|
||||
|
||||
* Fix: Slack notifications failing when using and icon URL.
|
||||
* Fix: 127.0.0.1 showing as an external IP address on the history tables.
|
||||
* Fix: Regression file sizes not shown in the media info table footer.
|
||||
* Fix: Retrieving proper PMS URL when multiple connections are published to plex.tv.
|
||||
* Fix: Some typos in the logger.
|
||||
* Fix: Some other typos in the WebUI. (Thanks @xtjoeytx)
|
||||
* Change: Optimized mobile web app icons and spash screens. (Thanks @alotufo)
|
||||
|
||||
|
||||
## v1.3.14 (2016-03-29)
|
||||
|
||||
* Fix: Regression for missing notify_action for script notifications.
|
||||
* Fix: Typo for home stats cards in the settings.
|
||||
|
||||
|
||||
## v1.3.13 (2016-03-27)
|
||||
|
||||
* Fix: Only mask strings longer than 5 characters in logs.
|
||||
|
||||
|
||||
## v1.3.12 (2016-03-27)
|
||||
|
||||
* Fix: "Check GitHub for updates" not rescheduling when toggling setting.
|
||||
* Fix: Bug where notifications would fail if metadata is not found.
|
||||
* Fix: Bug where notifications would fail if unable to upload poster to Imgur.
|
||||
* Fix: PlexPy will now start properly for different Python environment variables.
|
||||
* New: Feature requests moved to FeatHub.
|
||||
* New: Ability to specify a GitHub API token for updates (optional).
|
||||
* New: Mask out sensitive information from the logs.
|
||||
* New: New and updated Arnold quotes. (Thanks @Vilsol & @Chrisophogus)
|
||||
* New: "First" and "Last" page buttons to datatables.
|
||||
* New: Access log file from the "Help & Info" page.
|
||||
* New: CherryPy environment options (for development). (Thanks @codedecay)
|
||||
* New: PlexPy development environment (for development only).
|
||||
* Change: Facebook posts with a posters now include a summary.
|
||||
* Change: Facebook posts now use a default poster if the poster is not found or unable to upload to Imgur.
|
||||
* Change: IFTTT events can be fromatted with the {action} name.
|
||||
* Change: Logs now use ISO date format to avoid locale encoding errors. (Thanks @alshain)
|
||||
* Remove: Non-functioning Plex notification agent.
|
||||
|
||||
|
||||
## v1.3.11 (2016-03-15)
|
||||
|
||||
* Fix: Typo preventing history logging for websockets.
|
||||
|
||||
|
||||
## v1.3.10 (2016-03-12)
|
||||
|
||||
* Fix: Actually allow HTML tags for Pushover.
|
||||
* Fix: PlexPy not restarting on Windows if there is a space in the folder path.
|
||||
* Fix: Reconnect websocket when changing PMS SSL setting.
|
||||
* Fix: Datatables not loading when view_offset or duration is blank.
|
||||
* Fix: Bug when checking the PMS version in the settings.
|
||||
* Fix: Auto-refreshing of log tables.
|
||||
* Fix: Logging of IPv6 addresses. (PMS version >0.9.14 only.)
|
||||
* Fix: Hide days selection from the Play Totals graph page.
|
||||
* Fix: PlexPy overwriting user's own SSL certificate/key.
|
||||
* Fix: Multiple watched notifications when using websocket.
|
||||
* Fix: Some missing python library imports.
|
||||
* Fix: Some typos in settings and PlexWatch importer.
|
||||
* New: Ability to get notified of PMS updates.
|
||||
* New: Ability to disable the link to Plex Web with Facebook notifications and use IMDB, TVDB, TMDb, or Last.fm instead.
|
||||
* New: Ability to reset Imgur poster url from the info page if the poster is changed.
|
||||
* New: Tooltips on the current activity progress bars.
|
||||
* New: Side scrolling of Recently Added/Recently Played items.
|
||||
* New: Document all date/time format options.
|
||||
* New: Button to clear notification logs.
|
||||
* New: Customizable backup, cache, and log directories.
|
||||
* Change: Retry writing sessions to history if it fails, so sessions don't get lost. (Activity pinger only, not availble for websocket.)
|
||||
* Change: Save any unknown sessions to the "Local" user.
|
||||
* Change: History table modal is filtered depending on which graph series is clicked.
|
||||
* Change: Revert back to saving the state of datatables (search, sorting, entries per page, etc.).
|
||||
* Change: Newlines are not longer stripped from notification text which allows for finer control of how notifications look.
|
||||
* Change: Updated FreeNAS/FreeBSD init scripts. (Must have updated jails.) (Thanks @chiviak)
|
||||
|
||||
|
||||
## v1.3.9 (2016-02-21)
|
||||
|
||||
* Fix: Recently added notification not sent to all notification agents.
|
||||
* New: Pushover HTML support. (Thanks @elseym)
|
||||
|
||||
|
||||
## v1.3.8 (2016-02-21)
|
||||
|
||||
* Fix: Regression unable to clear HTTP password.
|
||||
* Fix: Remove media tags from script arguments for server notifications.
|
||||
* Fix: Encode poster titles to UTF-8 for Imgur upload.
|
||||
* Fix: Allow notifications to send without poster if Imgur upload fails.
|
||||
* New: Notification Logs table in the Logs tab.
|
||||
* New: Toggle in settings to enable posters in notifications. (Disabled by default.)
|
||||
* Change: Save Imgur poster URL to database so upload is not needed every time.
|
||||
* Change: Notify log in database to log each event as a separate entry.
|
||||
* Change: Monitor remote access is unchecked if remote access is disabled on server.
|
||||
|
||||
|
||||
## v1.3.7 (2016-02-20)
|
||||
|
||||
* Fix: Verifying server with SSL enabled.
|
||||
@@ -7,18 +835,18 @@
|
||||
* Fix: Video metadata flags showing up for track info.
|
||||
* Fix: Custom library icons not applied to Library Statistics.
|
||||
* Fix: Typos in the Web UI.
|
||||
* Add: ETA to Current Activity overlay.
|
||||
* Add: Total duration to Libraries and Users tables.
|
||||
* Add: {machine_id} to notification options.
|
||||
* Add: IMDB, TVDB, TMDb, Last.fm, and Trackt IDs/URLs to notification options.
|
||||
* Add: {poster_url} to notification options using Imgur.
|
||||
* Add: Poster and link for Facebook notifications.
|
||||
* Add: Log javascript errors from the Web UI.
|
||||
* Add: Configuration and Scheduler info to the settings page.
|
||||
* Add: Schedule background task to backup the PlexPy database.
|
||||
* Add: URL anonymizer for external links.
|
||||
* Add: Plex Media Scanner log file to Log viewer.
|
||||
* Add: API v2 (sill very experimental) (Thanks @Hellowlol)
|
||||
* New: ETA to Current Activity overlay.
|
||||
* New: Total duration to Libraries and Users tables.
|
||||
* New: {machine_id} to notification options.
|
||||
* New: IMDB, TVDB, TMDb, Last.fm, and Trackt IDs/URLs to notification options.
|
||||
* New: {poster_url} to notification options using Imgur.
|
||||
* New: Poster and link for Facebook notifications.
|
||||
* New: Log javascript errors from the Web UI.
|
||||
* New: Configuration and Scheduler info to the settings page.
|
||||
* New: Schedule background task to backup the PlexPy database.
|
||||
* New: URL anonymizer for external links.
|
||||
* New: Plex Media Scanner log file to Log viewer.
|
||||
* New: API v2 (sill very experimental). (Thanks @Hellowlol)
|
||||
* Change: Allow secure websocket connections.
|
||||
* Change: History grouping now accounts for the view offset.
|
||||
* Change: Subject line can be toggled off for Facebook, Slack, Telegram, and Twitter.
|
||||
@@ -41,9 +869,9 @@
|
||||
* Fix: Unable to expand media info table when missing "Added At" date.
|
||||
* Fix: Server verification for unpublished servers.
|
||||
* Fix: Updating PMS identifier for server change.
|
||||
* Add: {stream_time}, {remaining_time}, and {progress_time} to notification options.
|
||||
* Add: Powershell script support. (Thanks @Hellowlol)
|
||||
* Add: Method to delete duplicate libraries.
|
||||
* New: {stream_time}, {remaining_time}, and {progress_time} to notification options.
|
||||
* New: Powershell script support. (Thanks @Hellowlol)
|
||||
* New: Method to delete duplicate libraries.
|
||||
* Change: Daemonize before running start up tasks.
|
||||
|
||||
|
||||
@@ -55,7 +883,7 @@
|
||||
* Fix: Libraries and Users lists not refreshing.
|
||||
* Fix: Server verification in settings.
|
||||
* Fix: Empty libraries not added to database.
|
||||
* Add: Unique identifiers to notification options.
|
||||
* New: Unique identifiers to notification options.
|
||||
* Remove: Requirement of media type toggles for recently added notifications.
|
||||
* Remove: Built in Twitter key and secret.
|
||||
* Change: Unnecessary quoting of script arguments.
|
||||
@@ -88,21 +916,21 @@
|
||||
|
||||
## v1.3.0 (2016-01-23)
|
||||
|
||||
* Add: Brand new Libraries section.
|
||||
* Add: Lots of new library statistics.
|
||||
* Add: Media info table for libraries.
|
||||
* Add: Web app for Android and iOS. (Thanks @zobe123)
|
||||
* Add: Slack notification agent. (Thanks @richipargo)
|
||||
* Add: Facebook notification agent.
|
||||
* Add: Custom script notification agent. (Thanks @Hellowlol)
|
||||
* Add: Custom "From Name" to email notification agent.
|
||||
* Add: Ability to test notifications / send custom one-off notifications.
|
||||
* Add: 'datestamp' and 'timestamp' notification options.
|
||||
* Add: More concurrent stream statistics.
|
||||
* Add: Media info flags on the info pages.
|
||||
* Add: Ability to fix broken metadata if the item has been moved in Plex.
|
||||
* Add: Ability to rearrange the homepage statistics cards.
|
||||
* Add: CentOS startup script (Thanks @PHoSawyer)
|
||||
* New: Brand new Libraries section.
|
||||
* New: Lots of new library statistics.
|
||||
* New: Media info table for libraries.
|
||||
* New: Web app for Android and iOS. (Thanks @zobe123)
|
||||
* New: Slack notification agent. (Thanks @richipargo)
|
||||
* New: Facebook notification agent.
|
||||
* New: Custom script notification agent. (Thanks @Hellowlol)
|
||||
* New: Custom "From Name" to email notification agent.
|
||||
* New: Ability to test notifications / send custom one-off notifications.
|
||||
* New: 'datestamp' and 'timestamp' notification options.
|
||||
* New: More concurrent stream statistics.
|
||||
* New: Media info flags on the info pages.
|
||||
* New: Ability to fix broken metadata if the item has been moved in Plex.
|
||||
* New: Ability to rearrange the homepage statistics cards.
|
||||
* New: CentOS startup script (Thanks @PHoSawyer)
|
||||
* Fix: Server name blank after first run wizard.
|
||||
* Fix: Incorrect duration for grouped home stats.
|
||||
* Fix: Allow SSL when verifying server in settings.
|
||||
|
@@ -1,15 +1,7 @@
|
||||
# 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, improvements or feature requests. 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.
|
||||
|
||||
* Use the search function. Chances are that your problem is already discussed. Do not append to (closed) issues if your problem does not fit the discussion.
|
||||
* Visit the [Troubleshooting](../../wiki/TroubleShooting) wiki first.
|
||||
* Use [proper formatting](https://help.github.com/articles/github-flavored-markdown/). Paste your logs in code blocks.
|
||||
* Close your issue if you resolved it.
|
||||
# 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.
|
||||
@@ -17,12 +9,12 @@ All pull requests should be based on the `dev` branch, to minimize cross merges.
|
||||
### 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 different platforms, including Network Attached Storage devices such as Synology.
|
||||
|
||||
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
|
||||
@@ -38,12 +30,12 @@ Although PlexPy did not adapt a code convention in the past, we try to follow th
|
||||
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.
|
||||
Tautulli 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 targetted. There is no specific mobile version of Tautulli yet.
|
||||
|
||||
#### Conventions
|
||||
* 4 space indentation
|
||||
|
41
ISSUE_TEMPLATE.md
Normal file
@@ -0,0 +1,41 @@
|
||||
<!---
|
||||
Reporting Issues:
|
||||
* To ensure that a developer has enough information to work with please include all of the information below.
|
||||
Please provide as much detail as possible. Screenshots can be very useful to see the problem.
|
||||
* Use proper markdown syntax to structure your post (i.e. code/log in code blocks).
|
||||
See: https://help.github.com/articles/basic-writing-and-formatting-syntax/
|
||||
* Include a link to your **FULL** log file that has the error(not just a few lines!).
|
||||
Please use [Gist](http://gist.github.com) or [Pastebin](http://pastebin.com/).
|
||||
|
||||
Feature Requests:
|
||||
* Feature requests are handled on FeatHub: http://feathub.com/Tautulli/Tautulli
|
||||
* Do not post them on the GitHub issues tracker.
|
||||
-->
|
||||
|
||||
**Version:**
|
||||
|
||||
**Branch:**
|
||||
|
||||
**Commit hash:**
|
||||
|
||||
**Operating system:**
|
||||
|
||||
**Python version:**
|
||||
|
||||
**What you did?**
|
||||
|
||||
**What happened?**
|
||||
|
||||
**What you expected?**
|
||||
|
||||
**How can we reproduce your issue?**
|
||||
<!-- Provide a detailed step-by-step. -->
|
||||
|
||||
**What are your (relevant) settings?**
|
||||
|
||||
**Link to logs:**
|
||||
|
||||
|
||||
<!--
|
||||
Close your issue when it's solved! If you found the solution yourself please comment so that others benefit from it.
|
||||
-->
|
228
PlexPy.py
@@ -1,232 +1,28 @@
|
||||
#!/usr/bin/env python
|
||||
#!/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 PlexPy.
|
||||
# This file is part of Tautulli.
|
||||
#
|
||||
# PlexPy is free software: you can redistribute it and/or modify
|
||||
# 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.
|
||||
#
|
||||
# PlexPy is distributed in the hope that it will be useful,
|
||||
# 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 PlexPy. If not, see <http://www.gnu.org/licenses/>.
|
||||
# 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/'))
|
||||
|
||||
from plexpy import webstart, logger, web_socket
|
||||
|
||||
import locale
|
||||
import time
|
||||
import signal
|
||||
import argparse
|
||||
import plexpy
|
||||
|
||||
# Register signals, such as CTRL + C
|
||||
signal.signal(signal.SIGINT, plexpy.sig_handler)
|
||||
signal.signal(signal.SIGTERM, plexpy.sig_handler)
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
PlexPy application entry point. Parses arguments, setups encoding and
|
||||
initializes the application.
|
||||
"""
|
||||
|
||||
# Fixed paths to PlexPy
|
||||
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 PlexPy to run on a specified port')
|
||||
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)')
|
||||
|
||||
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.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.pidfile:
|
||||
plexpy.PIDFILE = str(args.pidfile)
|
||||
|
||||
# If the pidfile already exists, plexpy may still be running, so
|
||||
# exit
|
||||
if os.path.exists(plexpy.PIDFILE):
|
||||
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.ini')
|
||||
|
||||
# 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, 'plexpy.db')
|
||||
|
||||
if plexpy.DAEMON:
|
||||
plexpy.daemonize()
|
||||
|
||||
# Read config and start logging
|
||||
plexpy.initialize(config_file)
|
||||
|
||||
# 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_proxy': plexpy.CONFIG.HTTP_PROXY,
|
||||
'enable_https': plexpy.CONFIG.ENABLE_HTTPS,
|
||||
'https_cert': plexpy.CONFIG.HTTPS_CERT,
|
||||
'https_key': plexpy.CONFIG.HTTPS_KEY,
|
||||
'http_username': plexpy.CONFIG.HTTP_USERNAME,
|
||||
'http_password': plexpy.CONFIG.HTTP_PASSWORD,
|
||||
}
|
||||
webstart.initialize(web_config)
|
||||
|
||||
# Start the background threads
|
||||
plexpy.start()
|
||||
|
||||
# Open connection for websocket
|
||||
if plexpy.CONFIG.MONITORING_USE_WEBSOCKET:
|
||||
try:
|
||||
web_socket.start_thread()
|
||||
except:
|
||||
logger.warn(u"Websocket :: Unable to open connection.")
|
||||
# Fallback to polling
|
||||
plexpy.POLLING_FAILOVER = True
|
||||
plexpy.initialize_scheduler()
|
||||
|
||||
# Open webbrowser
|
||||
if plexpy.CONFIG.LAUNCH_BROWSER and not args.nolaunch:
|
||||
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)
|
||||
else:
|
||||
plexpy.shutdown(restart=True, update=True)
|
||||
|
||||
plexpy.SIGNAL = None
|
||||
|
||||
# Call main()
|
||||
# Call main() from Tautulli.py
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
63
README.md
@@ -1,14 +1,15 @@
|
||||
# PlexPy
|
||||
# Tautulli
|
||||
|
||||
[](https://gitter.im/drzoidberg33/plexpy?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://discord.gg/tQcWEUp)
|
||||
[](https://www.reddit.com/r/Tautulli/)
|
||||
[](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 (www.plex.tv).
|
||||
A python based web application for monitoring, analytics and notifications for [Plex Media Server](https://plex.tv).
|
||||
|
||||
This project is based on code from [Headphones](https://github.com/rembo10/headphones) and [PlexWatchWeb](https://github.com/ecleese/plexWatchWeb).
|
||||
|
||||
* PlexPy [forum thread](https://forums.plex.tv/discussion/169591/plexpy-another-plex-monitoring-program)
|
||||
|
||||
## Features
|
||||
|
||||
* Responsive web design viewable on desktop, tablet and mobile web browsers.
|
||||
* Themed to complement Plex/Web.
|
||||
* Easy configuration setup (no separate web server required).
|
||||
@@ -24,52 +25,24 @@ This project is based on code from [Headphones](https://github.com/rembo10/headp
|
||||
* Full sync list data on all users syncing items from your library.
|
||||
* And many more!!
|
||||
|
||||
## Preview
|
||||
|
||||
* [Full preview gallery available on our website](http://tautulli.com)
|
||||
|
||||

|
||||
|
||||
## Installation and Support
|
||||
|
||||
* [Installation Guides](https://github.com/drzoidberg33/plexpy/wiki/Installation) shows you how to install PlexPy.
|
||||
* [FAQs](https://github.com/drzoidberg33/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/drzoidberg33/plexpy/wiki) for
|
||||
[ [Installation] ](https://github.com/drzoidberg33/plexpy/wiki/Installation) and
|
||||
[ [FAQs] ](https://github.com/drzoidberg33/plexpy/wiki/Frequently-Asked-Questions-(FAQ)).
|
||||
- For basic questions try asking on [Gitter](https://gitter.im/drzoidberg33/plexpy) 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/drzoidberg33/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:
|
||||
- [ ] Branch
|
||||
- [ ] Version/Commit hash
|
||||
- [ ] Your operating system and 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
|
||||
|
||||
1. Search for similar existing 'issues', feature requests can be recognized by the blue `enhancement` label.
|
||||
2. If a similar request exists, post a comment (+1, or add a new idea to the existing request).
|
||||
3. If no similar requests exist, you can create a new one.
|
||||
4. Provide a clear title to easily identify the feature request.
|
||||
5. Tag your feature request with `[Feature Request]` so it can be identified easily.
|
||||
* Please see the [Issues Repository](https://github.com/Tautulli/Tautulli-Issues).
|
||||
|
||||
## License
|
||||
|
||||
This is free software under the GPL v3 open source license. Feel free to do with it what you wish, but any modification must be open sourced. A copy of the license is included.
|
||||
|
||||
This software includes Highsoft software libraries which you may freely distribute for non-commercial use. Commerical users must licence this software, for more information visit https://shop.highsoft.com/faq/non-commercial#non-commercial-redistribution.
|
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,11 +2,18 @@
|
||||
<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">Import PlexWatch Database</h4>
|
||||
<h4 class="modal-title">Import ${app} Database</h4>
|
||||
</div>
|
||||
<div class="modal-body" id="modal-text">
|
||||
<p class="help-block">
|
||||
Please ensure your PlexWatch database is at version 0.3.2 or higher.
|
||||
<%
|
||||
v = ''
|
||||
if app == 'PlexWatch':
|
||||
v = '0.3.2'
|
||||
elif app == 'Plexivity':
|
||||
v = '0.9.8'
|
||||
%>
|
||||
<strong>Please ensure your ${app} database is at version ${v} or higher.</strong>
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<label for="db_location">Database Location</label>
|
||||
@@ -15,7 +22,7 @@
|
||||
<input type="text" class="form-control" id="db_location" name="db_location" value="" required>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">Enter the path and file name for the PlexWatch database you wish to import.</p>
|
||||
<p class="help-block">Enter the path and file name for the ${app} database you wish to import.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="table_name">Table Name</label>
|
||||
@@ -23,7 +30,7 @@
|
||||
<div class="col-xs-4">
|
||||
<select id="table_name" class="form-control" name="table_name">
|
||||
<option value="processed">processed</option>
|
||||
<option value="processed">grouped</option>
|
||||
<option value="grouped">grouped</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -41,7 +48,7 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div>
|
||||
<span id="status-message"></span>
|
||||
<span id="status-message" style="padding-right: 25px;"></span>
|
||||
<input type="button" id="import_db" class="btn btn-bright" value="Import">
|
||||
</div>
|
||||
</div>
|
||||
@@ -54,8 +61,13 @@
|
||||
var table_name = $("#table_name").val();
|
||||
var import_ignore_interval = $("#import_ignore_interval").val();
|
||||
$.ajax({
|
||||
url: 'get_plexwatch_export_data',
|
||||
data: {database_path: database_path, table_name:table_name, import_ignore_interval:import_ignore_interval},
|
||||
url: 'import_database',
|
||||
data: {
|
||||
app: "${app}",
|
||||
database_path: database_path,
|
||||
table_name: table_name,
|
||||
import_ignore_interval: import_ignore_interval
|
||||
},
|
||||
cache: false,
|
||||
async: true,
|
||||
success: function(data) {
|
@@ -1,151 +1,68 @@
|
||||
<%
|
||||
import plexpy
|
||||
from plexpy import version
|
||||
import plexpy
|
||||
from plexpy import version
|
||||
from plexpy.helpers import anon_url
|
||||
from plexpy.notifiers import BROWSER_NOTIFIERS
|
||||
%>
|
||||
<!doctype html>
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>PlexPy - ${title} | ${server_name}</title>
|
||||
<title>Tautulli - ${title} | ${server_name}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<link href="interfaces/default/css/bootstrap3/bootstrap.css" rel="stylesheet">
|
||||
<link href="interfaces/default/css/plexpy.css" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet" type="text/css">
|
||||
<link href="interfaces/default/css/font-awesome.min.css" rel="stylesheet">
|
||||
<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/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()}
|
||||
|
||||
<link rel="icon" type="image/x-icon" href="interfaces/default/images/favicon.ico"/>
|
||||
<link rel="shortcut icon" href="interfaces/default/images/favicon.png">
|
||||
|
||||
<!-- Allow web app to be run in full-screen mode. -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<!-- Configure the status bar. -->
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<!-- Set the viewport. -->
|
||||
<meta name="viewport" content="initial-scale=1">
|
||||
<!-- Disable automatic phone number detection. -->
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<!-- Favicons -->
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="${http_root}images/favicon/favicon-32x32.png?v=2.0.5">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="${http_root}images/favicon/favicon-16x16.png?v=2.0.5">
|
||||
<link rel="shortcut icon" href="${http_root}images/favicon/favicon.ico?v=2.0.5">
|
||||
|
||||
<!-- ICONS -->
|
||||
<!-- IE10 icon -->
|
||||
<meta name="application-name" content="PlexPy" />
|
||||
<meta name="msapplication-config" content="interfaces/default/xml/IEconfig.xml"/>
|
||||
<!-- Android >M39 icon -->
|
||||
<link rel="manifest" href="interfaces/default/json/Android-manifest.json">
|
||||
<!-- iPad retina icon -->
|
||||
<link href="interfaces/default/images/res/ios/icon-76@2x.png" sizes="152x152" rel="apple-touch-icon-precomposed">
|
||||
<!-- iPad retina icon (iOS < 7) -->
|
||||
<link href="interfaces/default/images/res/ios/icon-72@2x.png" sizes="144x144" rel="apple-touch-icon-precomposed">
|
||||
<!-- iPad non-retina icon -->
|
||||
<link href="interfaces/default/images/res/ios/icon-76.png" sizes="76x76" rel="apple-touch-icon-precomposed">
|
||||
<!-- iPad non-retina icon (iOS < 7) -->
|
||||
<link href="interfaces/default/images/res/ios/icon-72.png" sizes="72x72" rel="apple-touch-icon-precomposed">
|
||||
<!-- iPhone 6 Plus icon -->
|
||||
<link href="interfaces/default/images/res/ios/icon-60@2x.png" sizes="120x120" rel="apple-touch-icon-precomposed">
|
||||
<!-- iPhone retina icon (iOS < 7) -->
|
||||
<link href="interfaces/default/images/res/ios/icon@2x.png" sizes="114x114" rel="apple-touch-icon-precomposed">
|
||||
<!-- iPhone non-retina icon (iOS < 7) -->
|
||||
<link href="interfaces/default/images/res/ios/icon.png" sizes="57x57" rel="apple-touch-icon-precomposed">
|
||||
<!-- iPhone / iPod Touch -->
|
||||
<link href="interfaces/default/images/res/ios/icon-60@3x.png" sizes="180x180" rel="apple-touch-icon-precomposed">
|
||||
<link href="interfaces/default/images/res/ios/icon-60.png" sizes="60x60" rel="apple-touch-icon-precomposed">
|
||||
<!-- Spotlight Icon -->
|
||||
<link href="interfaces/default/images/res/ios/icon-40.png" sizes="40x40" rel="apple-touch-icon-precomposed">
|
||||
<link href="interfaces/default/images/res/ios/icon-40@2x.png" sizes="80x80" rel="apple-touch-icon-precomposed">
|
||||
<!-- iPhone Spotlight and Settings Icon -->
|
||||
<link href="interfaces/default/images/res/ios/icon-small.png" sizes="29x29" rel="apple-touch-icon-precomposed">
|
||||
<link href="interfaces/default/images/res/ios/icon-small@2x.png" sizes="58x58" rel="apple-touch-icon-precomposed">
|
||||
<!-- iPad Spotlight and Settings Icon -->
|
||||
<link href="interfaces/default/images/res/ios/icon-50.png" sizes="50x50" rel="apple-touch-icon-precomposed">
|
||||
<link href="interfaces/default/images/res/ios/icon-50@2x.png" sizes="100x100" rel="apple-touch-icon-precomposed">
|
||||
|
||||
<!-- STARTUP IMAGES -->
|
||||
<!-- iPad retina portrait startup image -->
|
||||
<link href="interfaces/default/images/res/screen/ios/Default-Portrait@2x~ipad.png"
|
||||
media="(device-width: 768px) and (device-height: 1024px)
|
||||
and (-webkit-device-pixel-ratio: 2)
|
||||
and (orientation: portrait)"
|
||||
rel="apple-touch-startup-image">
|
||||
|
||||
<!-- iPad retina landscape startup image -->
|
||||
<link href="interfaces/default/images/res/screen/ios/Default-Landscape@2x~ipad.png"
|
||||
media="(device-width: 768px) and (device-height: 1024px)
|
||||
and (-webkit-device-pixel-ratio: 2)
|
||||
and (orientation: landscape)"
|
||||
rel="apple-touch-startup-image">
|
||||
|
||||
<!-- iPad non-retina portrait startup image -->
|
||||
<link href="interfaces/default/images/res/screen/ios/Default-Portrait~ipad.png"
|
||||
media="(device-width: 768px) and (device-height: 1024px)
|
||||
and (-webkit-device-pixel-ratio: 1)
|
||||
and (orientation: portrait)"
|
||||
rel="apple-touch-startup-image">
|
||||
|
||||
<!-- iPad non-retina landscape startup image -->
|
||||
<link href="interfaces/default/images/res/screen/ios/Default-Landscape~ipad.png"
|
||||
media="(device-width: 768px) and (device-height: 1024px)
|
||||
and (-webkit-device-pixel-ratio: 1)
|
||||
and (orientation: landscape)"
|
||||
rel="apple-touch-startup-image">
|
||||
|
||||
<!-- iPhone 6 Plus portrait startup image -->
|
||||
<link href="interfaces/default/images/res/screen/ios/Default-736h.png"
|
||||
media="(device-width: 414px) and (device-height: 736px)
|
||||
and (-webkit-device-pixel-ratio: 3)
|
||||
and (orientation: portrait)"
|
||||
rel="apple-touch-startup-image">
|
||||
|
||||
<!-- iPhone 6 Plus landscape startup image -->
|
||||
<link href="interfaces/default/images/res/screen/ios/Default-Landscape-736h.png"
|
||||
media="(device-width: 414px) and (device-height: 736px)
|
||||
and (-webkit-device-pixel-ratio: 3)
|
||||
and (orientation: landscape)"
|
||||
rel="apple-touch-startup-image">
|
||||
|
||||
<!-- iPhone 6 startup image -->
|
||||
<link href="interfaces/default/images/res/screen/ios/Default-667h.png"
|
||||
media="(device-width: 375px) and (device-height: 667px)
|
||||
and (-webkit-device-pixel-ratio: 2)"
|
||||
rel="apple-touch-startup-image">
|
||||
|
||||
<!-- iPhone 5 startup image -->
|
||||
<link href="interfaces/default/images/res/screen/ios/Default-568h@2x~iphone5.jpg"
|
||||
media="(device-width: 320px) and (device-height: 568px)
|
||||
and (-webkit-device-pixel-ratio: 2)"
|
||||
rel="apple-touch-startup-image">
|
||||
|
||||
<!-- iPhone < 5 retina startup image -->
|
||||
<link href="interfaces/default/images/res/screen/ios/Default@2x~iphone.png"
|
||||
media="(device-width: 320px) and (device-height: 480px)
|
||||
and (-webkit-device-pixel-ratio: 2)"
|
||||
rel="apple-touch-startup-image">
|
||||
|
||||
<!-- iPhone < 5 non-retina startup image -->
|
||||
<link href="interfaces/default/images/res/screen/ios/Default~iphone.png"
|
||||
media="(device-width: 320px) and (device-height: 480px)
|
||||
and (-webkit-device-pixel-ratio: 1)"
|
||||
rel="apple-touch-startup-image">
|
||||
|
||||
<!-- 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.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">
|
||||
<!-- Microsoft -->
|
||||
<meta name="application-name" content="Tautulli">
|
||||
<meta name="msapplication-config" content="${http_root}images/favicon/browserconfig.xml?v=2.0.5">
|
||||
</head>
|
||||
|
||||
<body class="content">
|
||||
<div class="container">
|
||||
<div id="ajaxMsg" class="ajaxMsg"></div>
|
||||
% 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 PlexPy. <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="https://github.com/${plexpy.CONFIG.GIT_USER}/plexpy/compare/${plexpy.CURRENT_VERSION}...${plexpy.LATEST_VERSION}" target="_blank">
|
||||
newer version</a> is available. You're ${plexpy.COMMITS_BEHIND} commits behind. <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">
|
||||
<div class="container-fluid">
|
||||
@@ -156,8 +73,8 @@ from plexpy import version
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="home">
|
||||
<img alt="PlexPy" src="interfaces/default/images/logo-plexpy@2x.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">
|
||||
@@ -166,7 +83,7 @@ from plexpy import version
|
||||
<form action="search" method="post" class="form" id="search_form">
|
||||
<div class="input-group">
|
||||
<span class="input-textbox">
|
||||
<input type="text" class="form-control" name="query" id="query" aria-label="Search" placeholder="Search..."/>
|
||||
<input type="text" class="form-control" name="query" id="query" aria-label="Search" placeholder="Search Plex library..."/>
|
||||
</span>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-dark btn-inactive" type="submit" id="search_button"><i class="fa fa-search"></i></button>
|
||||
@@ -175,9 +92,9 @@ from plexpy import version
|
||||
</form>
|
||||
</li>
|
||||
% if title == "Home":
|
||||
<li class="active"><a href="home"><i class="fa fa-lg fa-home"></i></a></li>
|
||||
<li class="active"><a href="home"><i class="fa fa-lg fa-home"></i></a></li>
|
||||
% else:
|
||||
<li><a href="home"><i class="fa fa-lg fa-home"></i></a></li>
|
||||
<li><a href="home"><i class="fa fa-lg fa-home"></i></a></li>
|
||||
% endif
|
||||
% if title == "Libraries" or title == "Library" or title == "Info":
|
||||
<li class="active"><a href="libraries">Libraries</a></li>
|
||||
@@ -185,9 +102,9 @@ from plexpy import version
|
||||
<li><a href="libraries">Libraries</a></li>
|
||||
% endif
|
||||
% if title == "Users" or title == "User":
|
||||
<li class="active"><a href="users">Users</a></li>
|
||||
<li class="active"><a href="users">Users</a></li>
|
||||
% else:
|
||||
<li><a href="users">Users</a></li>
|
||||
<li><a href="users">Users</a></li>
|
||||
% endif
|
||||
% if title == "History":
|
||||
<li class="active"><a href="history">History</a></li>
|
||||
@@ -195,25 +112,46 @@ from plexpy import version
|
||||
<li><a href="history">History</a></li>
|
||||
% endif
|
||||
% if title == "Graphs":
|
||||
<li class="active"><a href="graphs">Graphs</a></li>
|
||||
<li class="active"><a href="graphs">Graphs</a></li>
|
||||
% else:
|
||||
<li><a href="graphs">Graphs</a></li>
|
||||
<li><a href="graphs">Graphs</a></li>
|
||||
% endif
|
||||
% if title == "Synced Items":
|
||||
<li class="active"><a href="sync">Synced Items</a></li>
|
||||
<li class="active"><a href="sync">Synced Items</a></li>
|
||||
% else:
|
||||
<li><a href="sync">Synced Items</a></li>
|
||||
% endif
|
||||
% if title == "Log":
|
||||
<li class="active"><a href="logs">Logs</a></li>
|
||||
% else:
|
||||
<li><a href="logs">Logs</a></li>
|
||||
<li><a href="sync">Synced Items</a></li>
|
||||
% endif
|
||||
% if title == "Settings":
|
||||
<li class="active"><a href="settings">Settings</a></li>
|
||||
<li class="dropdown active">
|
||||
% else:
|
||||
<li><a href="settings">Settings</a></li>
|
||||
<li class="dropdown">
|
||||
% endif
|
||||
<% href = 'settings' if _session['user_group'] == 'admin' else '#' %>
|
||||
<a href="#" class="dropdown-toggle" aria-haspopup="true" data-toggle="dropdown" data-hover="dropdown" data-href="${href}"><i class="fa fa-lg fa-cogs"></i> <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu" id="settings-dropdown-menu">
|
||||
% if _session['user_group'] == 'admin':
|
||||
<li><a href="settings"><i class="fa fa-fw fa-cogs"></i> Settings</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li><a href="logs"><i class="fa fa-fw fa-list-alt"></i> View Logs</a></li>
|
||||
<li><a href="${anon_url('https://github.com/%s/%s-Wiki/wiki/Frequently-Asked-Questions' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}" target="_blank"><i class="fa fa-fw fa-question-circle"></i> FAQ</a></li>
|
||||
<li><a href="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>
|
||||
<li role="separator" class="divider"></li>
|
||||
% if plexpy.CONFIG.CHECK_GITHUB:
|
||||
<li><a href="#" id="nav-update"><i class="fa fa-fw fa-arrow-circle-up"></i> Check for Updates</a></li>
|
||||
% endif
|
||||
<li><a href="#" id="nav-restart"><i class="fa fa-fw fa-refresh"></i> Restart</a></li>
|
||||
<li><a href="#" id="nav-shutdown"><i class="fa fa-fw fa-power-off"></i> Shutdown</a></li>
|
||||
% else:
|
||||
<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['exp']:
|
||||
<li><a href="${http_root}auth/logout"><i class="fa fa-fw fa-sign-out"></i> Sign Out</a></li>
|
||||
% endif
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -221,25 +159,227 @@ from plexpy import version
|
||||
</div>
|
||||
|
||||
${next.headerIncludes()}
|
||||
|
||||
<div class="body-container">
|
||||
${next.body()}
|
||||
</div>
|
||||
|
||||
<script src="interfaces/default/js/jquery-2.1.4.min.js"></script>
|
||||
<script src="interfaces/default/js/bootstrap3/bootstrap.min.js"></script>
|
||||
<script src="interfaces/default/js/script.js"></script>
|
||||
${next.modalIncludes()}
|
||||
|
||||
% if _session['user_group'] != 'admin':
|
||||
<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 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>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="col-md-6" style="margin: auto;">
|
||||
<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">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password" class="control-label">
|
||||
Password
|
||||
</label>
|
||||
<input type="password" id="password" name="password" class="form-control">
|
||||
</div>
|
||||
<div class="form-footer">
|
||||
<div class="remember-group">
|
||||
<label class="control-label">
|
||||
<input type="checkbox" id="remember_me" name="remember_me" title="for 30 days" value="1" checked="checked" /> Remember me
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
% else:
|
||||
<div id="donate-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="crypto-donate-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 Donation</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-md-12" style="text-align: center;">
|
||||
<h4>
|
||||
<strong>Thank you for supporting Tautulli!</strong>
|
||||
</h4>
|
||||
<p>
|
||||
Please select a donation method.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<ul id="donation_type" class="nav nav-pills" role="tablist" style="display: flex; justify-content: center; margin: 10px 0;">
|
||||
<li class="active"><a href="#patreon-donation" role="tab" data-toggle="tab">Patreon</a></li>
|
||||
<li><a href="#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="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>
|
||||
<a href="${anon_url('https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=6XPPKTDSX9QFL&lc=US&item_name=Tautulli¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_LG%2egif%3aNonHosted')}" target="_blank">
|
||||
<img src="images/gold-rect-paypal-34px.png" alt="PayPal">
|
||||
</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>
|
||||
<label><span id="crypto_type_label"></span> Address</label>
|
||||
<pre id="crypto_address" style="text-align: center"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<input type="button" class="btn btn-bright" data-dismiss="modal" value="Close">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
|
||||
<div id="confirm-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="confirm-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">Confirm</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="confirm-message" style="text-align: center; margin-top: 20px; margin-bottom: 20px;">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-dark" data-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-danger btn-ok" data-dismiss="modal" id="confirm-button">Confirm</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="${http_root}js/jquery-2.1.4.min.js"></script>
|
||||
<script src="${http_root}js/bootstrap.min.js"></script>
|
||||
<script src="${http_root}js/bootstrap-hover-dropdown.min.js"></script>
|
||||
<script src="${http_root}js/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>
|
||||
$('#updateDismiss').click(function() {
|
||||
$('#updatebar').slideUp('slow');
|
||||
% if _session['user_group'] == 'admin':
|
||||
$('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();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
|
||||
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() {
|
||||
$("#confirm-message").text("Are you sure you want to shutdown Tautulli?");
|
||||
$('#confirm-modal').modal();
|
||||
$('#confirm-modal').one('click', '#confirm-button', function () {
|
||||
window.location.href = "shutdown";
|
||||
});
|
||||
});
|
||||
|
||||
$("#nav-restart").click(function() {
|
||||
$("#confirm-message").text("Are you sure you want to restart Tautulli?");
|
||||
$('#confirm-modal').modal();
|
||||
$('#confirm-modal').one('click', '#confirm-button', function () {
|
||||
window.location.href = "restart";
|
||||
});
|
||||
});
|
||||
|
||||
$('#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 () {
|
||||
var crypto_coin = $(this).data('coin');
|
||||
var crypto_name = $(this).data('name');
|
||||
var crypto_address = $(this).data('address')
|
||||
$('#crypto_qr_code').empty().qrcode({
|
||||
text: crypto_coin + ":" + crypto_address
|
||||
});
|
||||
$('#crypto_type_label').html(crypto_name);
|
||||
$('#crypto_address').html(crypto_address);
|
||||
});
|
||||
% endif
|
||||
|
||||
$('.dropdown-toggle').click(function (e) {
|
||||
if (!(('ontouchstart' in window) || (navigator.MaxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0))) {
|
||||
window.location.href = $(this).data('href');
|
||||
}
|
||||
});
|
||||
|
||||
$('#search_form').submit(function (e) {
|
||||
if ($('#query').hasClass('active') && $('#query').val().trim() != '') {
|
||||
$.ajax({
|
||||
@@ -260,10 +400,9 @@ ${next.headerIncludes()}
|
||||
}).removeClass('active');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
// Work around for iOS web app links opening in Safari
|
||||
|
||||
$(document).ready(function () {
|
||||
// Work around for iOS web app links opening in Safari
|
||||
if (("standalone" in window.navigator) && window.navigator.standalone) {
|
||||
// For iOS Apps
|
||||
$('a').on('click', function (e) {
|
||||
@@ -274,12 +413,54 @@ ${next.headerIncludes()}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Allow stacked bootstrap modals
|
||||
$(document).on('show.bs.modal', '.modal', function (event) {
|
||||
var zIndex = 1040 + (10 * $('.modal:visible').length);
|
||||
$(this).css('z-index', zIndex);
|
||||
setTimeout(function() {
|
||||
$('.modal-backdrop').not('.modal-stack').css('z-index', zIndex - 1).addClass('modal-stack');
|
||||
}, 0);
|
||||
});
|
||||
$(document).on('hidden.bs.modal', '.modal', function () {
|
||||
$('.modal:visible').length && $(document.body).addClass('modal-open');
|
||||
});
|
||||
});
|
||||
|
||||
% if _session['user_group'] != 'admin':
|
||||
$('#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()}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<%def name="modalIncludes()"></%def>
|
||||
<%def name="javascriptIncludes()"></%def>
|
||||
<%def name="headIncludes()"></%def>
|
||||
<%def name="headerIncludes()"></%def>
|
||||
|
135
data/interfaces/default/configuration_table.html
Normal file
@@ -0,0 +1,135 @@
|
||||
<%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: configuration_table.html
|
||||
Version: 0.1
|
||||
|
||||
DOCUMENTATION :: END
|
||||
</%doc>
|
||||
|
||||
<%!
|
||||
import os
|
||||
import sys
|
||||
import plexpy
|
||||
from plexpy import common, logger
|
||||
from plexpy.helpers import anon_url
|
||||
%>
|
||||
|
||||
<table class="config-info-table small-muted">
|
||||
<tbody>
|
||||
% if plexpy.CURRENT_VERSION:
|
||||
<tr>
|
||||
<td>Git Branch:</td>
|
||||
<td><a class="no-highlight" href="${anon_url('https://github.com/%s/%s/tree/%s' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO, plexpy.CONFIG.GIT_BRANCH))}">${plexpy.CONFIG.GIT_BRANCH}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Git Commit Hash:</td>
|
||||
<td><a class="no-highlight" href="${anon_url('https://github.com/%s/%s/commit/%s' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO, plexpy.CURRENT_VERSION))}">${plexpy.CURRENT_VERSION}</a></td>
|
||||
</tr>
|
||||
% endif
|
||||
<tr>
|
||||
<td>Configuration File:</td>
|
||||
<td><a class="no-highlight" href="download_config" data-toggle="tooltip" data-placement="right" title="Download Configuration">${plexpy.CONFIG_FILE}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Database File:</td>
|
||||
<td><a class="no-highlight" href="download_database" data-toggle="tooltip" data-placement="right" title="Download Database">${plexpy.DB_FILE}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Log File:</td>
|
||||
<td><a class="no-highlight" href="download_log" data-toggle="tooltip" data-placement="right" title="Download Log">${os.path.join(plexpy.CONFIG.LOG_DIR, logger.FILENAME)}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Backup Directory:</td>
|
||||
<td>${plexpy.CONFIG.BACKUP_DIR}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Cache Directory:</td>
|
||||
<td>${plexpy.CONFIG.CACHE_DIR}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>GeoLite2 Database:</td>
|
||||
% if plexpy.CONFIG.GEOIP_DB:
|
||||
<td>${plexpy.CONFIG.GEOIP_DB} | <a class="no-highlight" href="#" id="reinstall_geoip_db">Reinstall / Update</a> | <a class="no-highlight" href="#" id="uninstall_geoip_db">Uninstall</a></td>
|
||||
% else:
|
||||
<td><a class="no-highlight" href="#" id="install_geoip_db">Click here to install the GeoLite2 database.</a></td>
|
||||
% endif
|
||||
</tr>
|
||||
% if plexpy.ARGS:
|
||||
<tr>
|
||||
<td>Arguments:</td>
|
||||
<td>${plexpy.ARGS}</td>
|
||||
</tr>
|
||||
% endif
|
||||
<tr>
|
||||
<td>Platform:</td>
|
||||
<td>${common.PLATFORM} ${common.PLATFORM_VERSION}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Python Version:</td>
|
||||
<td>${sys.version}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="top-line">Resources:</td>
|
||||
<td class="top-line">
|
||||
<a class="no-highlight" href="${anon_url('http://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/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/307821/tautulli-monitor-your-plex-media-server')}" target="_blank">Plex Forums</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$("#install_geoip_db, #reinstall_geoip_db").click(function () {
|
||||
var msg = 'Are you sure you want to install the GeoLite2 database?<br /><br />' +
|
||||
'The database is used to lookup IP address geolocation info.<br />' +
|
||||
'The database will be downloaded from <a href="${anon_url("https://dev.maxmind.com/geoip/geoip2/geolite2/")}" target="_blank">MaxMind</a>, <br />' +
|
||||
'and requires <strong>100MB</strong> of free space to install in your Tautulli directory.<br />'
|
||||
var url = 'install_geoip_db';
|
||||
confirmAjaxCall(url, msg, null, 'Installing GeoLite2 database.', getConfigurationTable);
|
||||
});
|
||||
|
||||
$("#uninstall_geoip_db").click(function () {
|
||||
var msg = 'Are you sure you want to uninstall the GeoLite2 database?<br /><br />' +
|
||||
'You will not be able to lookup IP address geolocation info.';
|
||||
var url = 'uninstall_geoip_db';
|
||||
confirmAjaxCall(url, msg, null, 'Uninstalling GeoLite2 database.', getConfigurationTable);
|
||||
});
|
||||
|
||||
$('.guidelines-modal-link').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
$('#guidelines-type').text($(this).data('id'))
|
||||
$('#guidelines-modal').modal();
|
||||
$('#guidelines-continue').attr('href', $(this).attr('href')).on('click', function () {
|
||||
$('#guidelines-modal').modal('hide');
|
||||
});
|
||||
});
|
||||
$('.support-modal-link').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
$('#support-modal').modal();
|
||||
$('#support-continue').attr('href', $(this).attr('href')).on('click', function () {
|
||||
$('#support-modal').modal('hide');
|
||||
});
|
||||
});
|
||||
|
||||
$('body').tooltip({
|
||||
selector: '[data-toggle="tooltip"]',
|
||||
container: 'body'
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
904
data/interfaces/default/css/jquery.scrollbar.css
Normal file
@@ -0,0 +1,904 @@
|
||||
/*************** SCROLLBAR BASE CSS ***************/
|
||||
.scroll-wrapper {
|
||||
overflow: hidden !important;
|
||||
padding: 0 !important;
|
||||
position: relative;
|
||||
}
|
||||
.scroll-wrapper > .scroll-content {
|
||||
border: none !important;
|
||||
box-sizing: content-box !important;
|
||||
height: auto;
|
||||
left: 0;
|
||||
margin: 0;
|
||||
max-height: none;
|
||||
max-width: none !important;
|
||||
overflow: scroll !important;
|
||||
padding: 0;
|
||||
position: relative !important;
|
||||
top: 0;
|
||||
width: auto !important;
|
||||
}
|
||||
.scroll-wrapper > .scroll-content::-webkit-scrollbar {
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
.scroll-wrapper.scroll--rtl {
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.scroll-element {
|
||||
box-sizing: content-box;
|
||||
display: none;
|
||||
}
|
||||
.scroll-element div {
|
||||
box-sizing: content-box;
|
||||
}
|
||||
.scroll-element .scroll-bar,
|
||||
.scroll-element .scroll-arrow {
|
||||
cursor: default;
|
||||
}
|
||||
.scroll-element.scroll-x.scroll-scrollx_visible, .scroll-element.scroll-y.scroll-scrolly_visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.scroll-textarea {
|
||||
border: 1px solid #cccccc;
|
||||
border-top-color: #999999;
|
||||
}
|
||||
.scroll-textarea > .scroll-content {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
.scroll-textarea > .scroll-content > textarea {
|
||||
border: none !important;
|
||||
box-sizing: border-box;
|
||||
height: 100% !important;
|
||||
margin: 0;
|
||||
max-height: none !important;
|
||||
max-width: none !important;
|
||||
overflow: scroll !important;
|
||||
outline: none;
|
||||
padding: 2px;
|
||||
position: relative !important;
|
||||
top: 0;
|
||||
width: 100% !important;
|
||||
}
|
||||
.scroll-textarea > .scroll-content > textarea::-webkit-scrollbar {
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
/*************** SIMPLE INNER SCROLLBAR ***************/
|
||||
.scrollbar-inner > .scroll-element,
|
||||
.scrollbar-inner > .scroll-element div {
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.scrollbar-inner > .scroll-element div {
|
||||
display: block;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.scrollbar-inner > .scroll-element.scroll-x {
|
||||
bottom: 2px;
|
||||
height: 8px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.scrollbar-inner > .scroll-element.scroll-y {
|
||||
height: 100%;
|
||||
right: 2px;
|
||||
top: 0;
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.scrollbar-inner > .scroll-element .scroll-element_outer {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.scrollbar-inner > .scroll-element .scroll-element_outer,
|
||||
.scrollbar-inner > .scroll-element .scroll-element_track,
|
||||
.scrollbar-inner > .scroll-element .scroll-bar {
|
||||
-webkit-border-radius: 8px;
|
||||
-moz-border-radius: 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.scrollbar-inner > .scroll-element .scroll-element_track,
|
||||
.scrollbar-inner > .scroll-element .scroll-bar {
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=40)";
|
||||
filter: alpha(opacity=40);
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.scrollbar-inner > .scroll-element .scroll-element_track {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.scrollbar-inner > .scroll-element .scroll-bar {
|
||||
background-color: #c2c2c2;
|
||||
}
|
||||
|
||||
.scrollbar-inner > .scroll-element:hover .scroll-bar {
|
||||
background-color: #919191;
|
||||
}
|
||||
|
||||
.scrollbar-inner > .scroll-element.scroll-draggable .scroll-bar {
|
||||
background-color: #919191;
|
||||
}
|
||||
|
||||
/* update scrollbar offset if both scrolls are visible */
|
||||
.scrollbar-inner > .scroll-element.scroll-x.scroll-scrolly_visible .scroll-element_track {
|
||||
left: -12px;
|
||||
}
|
||||
|
||||
.scrollbar-inner > .scroll-element.scroll-y.scroll-scrollx_visible .scroll-element_track {
|
||||
top: -12px;
|
||||
}
|
||||
|
||||
.scrollbar-inner > .scroll-element.scroll-x.scroll-scrolly_visible .scroll-element_size {
|
||||
left: -12px;
|
||||
}
|
||||
|
||||
.scrollbar-inner > .scroll-element.scroll-y.scroll-scrollx_visible .scroll-element_size {
|
||||
top: -12px;
|
||||
}
|
||||
|
||||
/*************** SIMPLE OUTER SCROLLBAR ***************/
|
||||
.scrollbar-outer > .scroll-element,
|
||||
.scrollbar-outer > .scroll-element div {
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.scrollbar-outer > .scroll-element {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.scrollbar-outer > .scroll-element div {
|
||||
display: block;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.scrollbar-outer > .scroll-element.scroll-x {
|
||||
bottom: 0;
|
||||
height: 12px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.scrollbar-outer > .scroll-element.scroll-y {
|
||||
height: 100%;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
.scrollbar-outer > .scroll-element.scroll-x .scroll-element_outer {
|
||||
height: 8px;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
.scrollbar-outer > .scroll-element.scroll-y .scroll-element_outer {
|
||||
left: 2px;
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.scrollbar-outer > .scroll-element .scroll-element_outer {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.scrollbar-outer > .scroll-element .scroll-element_track {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
|
||||
.scrollbar-outer > .scroll-element .scroll-element_outer,
|
||||
.scrollbar-outer > .scroll-element .scroll-element_track,
|
||||
.scrollbar-outer > .scroll-element .scroll-bar {
|
||||
-webkit-border-radius: 8px;
|
||||
-moz-border-radius: 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.scrollbar-outer > .scroll-element .scroll-bar {
|
||||
background-color: #d9d9d9;
|
||||
}
|
||||
|
||||
.scrollbar-outer > .scroll-element .scroll-bar:hover {
|
||||
background-color: #c2c2c2;
|
||||
}
|
||||
|
||||
.scrollbar-outer > .scroll-element.scroll-draggable .scroll-bar {
|
||||
background-color: #919191;
|
||||
}
|
||||
|
||||
/* scrollbar height/width & offset from container borders */
|
||||
.scrollbar-outer > .scroll-content.scroll-scrolly_visible {
|
||||
left: -12px;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.scrollbar-outer > .scroll-content.scroll-scrollx_visible {
|
||||
top: -12px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.scrollbar-outer > .scroll-element.scroll-x .scroll-bar {
|
||||
min-width: 10px;
|
||||
}
|
||||
|
||||
.scrollbar-outer > .scroll-element.scroll-y .scroll-bar {
|
||||
min-height: 10px;
|
||||
}
|
||||
|
||||
/* update scrollbar offset if both scrolls are visible */
|
||||
.scrollbar-outer > .scroll-element.scroll-x.scroll-scrolly_visible .scroll-element_track {
|
||||
left: -14px;
|
||||
}
|
||||
|
||||
.scrollbar-outer > .scroll-element.scroll-y.scroll-scrollx_visible .scroll-element_track {
|
||||
top: -14px;
|
||||
}
|
||||
|
||||
.scrollbar-outer > .scroll-element.scroll-x.scroll-scrolly_visible .scroll-element_size {
|
||||
left: -14px;
|
||||
}
|
||||
|
||||
.scrollbar-outer > .scroll-element.scroll-y.scroll-scrollx_visible .scroll-element_size {
|
||||
top: -14px;
|
||||
}
|
||||
|
||||
/*************** SCROLLBAR MAC OS X ***************/
|
||||
.scrollbar-macosx > .scroll-element,
|
||||
.scrollbar-macosx > .scroll-element div {
|
||||
background: none;
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.scrollbar-macosx > .scroll-element div {
|
||||
display: block;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.scrollbar-macosx > .scroll-element .scroll-element_track {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.scrollbar-macosx > .scroll-element .scroll-bar {
|
||||
background-color: #6C6E71;
|
||||
display: block;
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
|
||||
filter: alpha(opacity=0);
|
||||
opacity: 0;
|
||||
-webkit-border-radius: 7px;
|
||||
-moz-border-radius: 7px;
|
||||
border-radius: 7px;
|
||||
-webkit-transition: opacity 0.2s linear;
|
||||
-moz-transition: opacity 0.2s linear;
|
||||
-o-transition: opacity 0.2s linear;
|
||||
-ms-transition: opacity 0.2s linear;
|
||||
transition: opacity 0.2s linear;
|
||||
}
|
||||
|
||||
.scrollbar-macosx:hover > .scroll-element .scroll-bar,
|
||||
.scrollbar-macosx > .scroll-element.scroll-draggable .scroll-bar {
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=70)";
|
||||
filter: alpha(opacity=70);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.scrollbar-macosx > .scroll-element.scroll-x {
|
||||
bottom: 0px;
|
||||
height: 0px;
|
||||
left: 0;
|
||||
min-width: 100%;
|
||||
overflow: visible;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.scrollbar-macosx > .scroll-element.scroll-y {
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
right: 0px;
|
||||
top: 0;
|
||||
width: 0px;
|
||||
}
|
||||
|
||||
/* scrollbar height/width & offset from container borders */
|
||||
.scrollbar-macosx > .scroll-element.scroll-x .scroll-bar {
|
||||
height: 7px;
|
||||
min-width: 10px;
|
||||
top: -9px;
|
||||
}
|
||||
|
||||
.scrollbar-macosx > .scroll-element.scroll-y .scroll-bar {
|
||||
left: -9px;
|
||||
min-height: 10px;
|
||||
width: 7px;
|
||||
}
|
||||
|
||||
.scrollbar-macosx > .scroll-element.scroll-x .scroll-element_outer {
|
||||
left: 2px;
|
||||
}
|
||||
|
||||
.scrollbar-macosx > .scroll-element.scroll-x .scroll-element_size {
|
||||
left: -4px;
|
||||
}
|
||||
|
||||
.scrollbar-macosx > .scroll-element.scroll-y .scroll-element_outer {
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
.scrollbar-macosx > .scroll-element.scroll-y .scroll-element_size {
|
||||
top: -4px;
|
||||
}
|
||||
|
||||
/* update scrollbar offset if both scrolls are visible */
|
||||
.scrollbar-macosx > .scroll-element.scroll-x.scroll-scrolly_visible .scroll-element_size {
|
||||
left: -11px;
|
||||
}
|
||||
|
||||
.scrollbar-macosx > .scroll-element.scroll-y.scroll-scrollx_visible .scroll-element_size {
|
||||
top: -11px;
|
||||
}
|
||||
|
||||
/*************** SCROLLBAR LIGHT ***************/
|
||||
.scrollbar-light > .scroll-element,
|
||||
.scrollbar-light > .scroll-element div {
|
||||
border: none;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.scrollbar-light > .scroll-element {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.scrollbar-light > .scroll-element div {
|
||||
display: block;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.scrollbar-light > .scroll-element .scroll-element_outer {
|
||||
-webkit-border-radius: 10px;
|
||||
-moz-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.scrollbar-light > .scroll-element .scroll-element_size {
|
||||
background: #dbdbdb;
|
||||
background: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2RiZGJkYiIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNlOGU4ZTgiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+");
|
||||
background: -moz-linear-gradient(left, #dbdbdb 0%, #e8e8e8 100%);
|
||||
background: -webkit-gradient(linear, left top, right top, color-stop(0%, #dbdbdb), color-stop(100%, #e8e8e8));
|
||||
background: -webkit-linear-gradient(left, #dbdbdb 0%, #e8e8e8 100%);
|
||||
background: -o-linear-gradient(left, #dbdbdb 0%, #e8e8e8 100%);
|
||||
background: -ms-linear-gradient(left, #dbdbdb 0%, #e8e8e8 100%);
|
||||
background: linear-gradient(to right, #dbdbdb 0%, #e8e8e8 100%);
|
||||
-webkit-border-radius: 10px;
|
||||
-moz-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.scrollbar-light > .scroll-element.scroll-x {
|
||||
bottom: 0;
|
||||
height: 17px;
|
||||
left: 0;
|
||||
min-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.scrollbar-light > .scroll-element.scroll-y {
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 17px;
|
||||
}
|
||||
|
||||
.scrollbar-light > .scroll-element .scroll-bar {
|
||||
background: #fefefe;
|
||||
background: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZlZmVmZSIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNmNWY1ZjUiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+");
|
||||
background: -moz-linear-gradient(left, #fefefe 0%, #f5f5f5 100%);
|
||||
background: -webkit-gradient(linear, left top, right top, color-stop(0%, #fefefe), color-stop(100%, #f5f5f5));
|
||||
background: -webkit-linear-gradient(left, #fefefe 0%, #f5f5f5 100%);
|
||||
background: -o-linear-gradient(left, #fefefe 0%, #f5f5f5 100%);
|
||||
background: -ms-linear-gradient(left, #fefefe 0%, #f5f5f5 100%);
|
||||
background: linear-gradient(to right, #fefefe 0%, #f5f5f5 100%);
|
||||
border: 1px solid #dbdbdb;
|
||||
-webkit-border-radius: 10px;
|
||||
-moz-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
/* scrollbar height/width & offset from container borders */
|
||||
.scrollbar-light > .scroll-content.scroll-scrolly_visible {
|
||||
left: -17px;
|
||||
margin-left: 17px;
|
||||
}
|
||||
|
||||
.scrollbar-light > .scroll-content.scroll-scrollx_visible {
|
||||
top: -17px;
|
||||
margin-top: 17px;
|
||||
}
|
||||
|
||||
.scrollbar-light > .scroll-element.scroll-x .scroll-bar {
|
||||
height: 10px;
|
||||
min-width: 10px;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
.scrollbar-light > .scroll-element.scroll-y .scroll-bar {
|
||||
left: 0px;
|
||||
min-height: 10px;
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.scrollbar-light > .scroll-element.scroll-x .scroll-element_outer {
|
||||
height: 12px;
|
||||
left: 2px;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
.scrollbar-light > .scroll-element.scroll-x .scroll-element_size {
|
||||
left: -4px;
|
||||
}
|
||||
|
||||
.scrollbar-light > .scroll-element.scroll-y .scroll-element_outer {
|
||||
left: 2px;
|
||||
top: 2px;
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
.scrollbar-light > .scroll-element.scroll-y .scroll-element_size {
|
||||
top: -4px;
|
||||
}
|
||||
|
||||
/* update scrollbar offset if both scrolls are visible */
|
||||
.scrollbar-light > .scroll-element.scroll-x.scroll-scrolly_visible .scroll-element_size {
|
||||
left: -19px;
|
||||
}
|
||||
|
||||
.scrollbar-light > .scroll-element.scroll-y.scroll-scrollx_visible .scroll-element_size {
|
||||
top: -19px;
|
||||
}
|
||||
|
||||
.scrollbar-light > .scroll-element.scroll-x.scroll-scrolly_visible .scroll-element_track {
|
||||
left: -19px;
|
||||
}
|
||||
|
||||
.scrollbar-light > .scroll-element.scroll-y.scroll-scrollx_visible .scroll-element_track {
|
||||
top: -19px;
|
||||
}
|
||||
|
||||
/*************** SCROLLBAR RAIL ***************/
|
||||
.scrollbar-rail > .scroll-element,
|
||||
.scrollbar-rail > .scroll-element div {
|
||||
border: none;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.scrollbar-rail > .scroll-element {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.scrollbar-rail > .scroll-element div {
|
||||
display: block;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.scrollbar-rail > .scroll-element .scroll-element_size {
|
||||
background-color: #999;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.scrollbar-rail > .scroll-element .scroll-element_outer:hover .scroll-element_size {
|
||||
background-color: #666;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.scrollbar-rail > .scroll-element.scroll-x {
|
||||
bottom: 0;
|
||||
height: 12px;
|
||||
left: 0;
|
||||
min-width: 100%;
|
||||
padding: 3px 0 2px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.scrollbar-rail > .scroll-element.scroll-y {
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
padding: 0 2px 0 3px;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
.scrollbar-rail > .scroll-element .scroll-bar {
|
||||
background-color: #d0b9a0;
|
||||
-webkit-border-radius: 2px;
|
||||
-moz-border-radius: 2px;
|
||||
border-radius: 2px;
|
||||
box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.scrollbar-rail > .scroll-element .scroll-element_outer:hover .scroll-bar {
|
||||
box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
/* scrollbar height/width & offset from container borders */
|
||||
.scrollbar-rail > .scroll-content.scroll-scrolly_visible {
|
||||
left: -17px;
|
||||
margin-left: 17px;
|
||||
}
|
||||
|
||||
.scrollbar-rail > .scroll-content.scroll-scrollx_visible {
|
||||
margin-top: 17px;
|
||||
top: -17px;
|
||||
}
|
||||
|
||||
.scrollbar-rail > .scroll-element.scroll-x .scroll-bar {
|
||||
height: 10px;
|
||||
min-width: 10px;
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
.scrollbar-rail > .scroll-element.scroll-y .scroll-bar {
|
||||
left: 1px;
|
||||
min-height: 10px;
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.scrollbar-rail > .scroll-element.scroll-x .scroll-element_outer {
|
||||
height: 15px;
|
||||
left: 5px;
|
||||
}
|
||||
|
||||
.scrollbar-rail > .scroll-element.scroll-x .scroll-element_size {
|
||||
height: 2px;
|
||||
left: -10px;
|
||||
top: 5px;
|
||||
}
|
||||
|
||||
.scrollbar-rail > .scroll-element.scroll-y .scroll-element_outer {
|
||||
top: 5px;
|
||||
width: 15px;
|
||||
}
|
||||
|
||||
.scrollbar-rail > .scroll-element.scroll-y .scroll-element_size {
|
||||
left: 5px;
|
||||
top: -10px;
|
||||
width: 2px;
|
||||
}
|
||||
|
||||
/* update scrollbar offset if both scrolls are visible */
|
||||
.scrollbar-rail > .scroll-element.scroll-x.scroll-scrolly_visible .scroll-element_size {
|
||||
left: -25px;
|
||||
}
|
||||
|
||||
.scrollbar-rail > .scroll-element.scroll-y.scroll-scrollx_visible .scroll-element_size {
|
||||
top: -25px;
|
||||
}
|
||||
|
||||
.scrollbar-rail > .scroll-element.scroll-x.scroll-scrolly_visible .scroll-element_track {
|
||||
left: -25px;
|
||||
}
|
||||
|
||||
.scrollbar-rail > .scroll-element.scroll-y.scroll-scrollx_visible .scroll-element_track {
|
||||
top: -25px;
|
||||
}
|
||||
|
||||
/*************** SCROLLBAR DYNAMIC ***************/
|
||||
.scrollbar-dynamic > .scroll-element,
|
||||
.scrollbar-dynamic > .scroll-element div {
|
||||
background: none;
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.scrollbar-dynamic > .scroll-element div {
|
||||
display: block;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.scrollbar-dynamic > .scroll-element.scroll-x {
|
||||
bottom: 2px;
|
||||
height: 7px;
|
||||
left: 0;
|
||||
min-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.scrollbar-dynamic > .scroll-element.scroll-y {
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
right: 2px;
|
||||
top: 0;
|
||||
width: 7px;
|
||||
}
|
||||
|
||||
.scrollbar-dynamic > .scroll-element .scroll-element_outer {
|
||||
opacity: 0.3;
|
||||
-webkit-border-radius: 12px;
|
||||
-moz-border-radius: 12px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.scrollbar-dynamic > .scroll-element .scroll-element_size {
|
||||
background-color: #cccccc;
|
||||
opacity: 0;
|
||||
-webkit-border-radius: 12px;
|
||||
-moz-border-radius: 12px;
|
||||
border-radius: 12px;
|
||||
-webkit-transition: opacity 0.2s;
|
||||
-moz-transition: opacity 0.2s;
|
||||
-o-transition: opacity 0.2s;
|
||||
-ms-transition: opacity 0.2s;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.scrollbar-dynamic > .scroll-element .scroll-bar {
|
||||
background-color: #6c6e71;
|
||||
-webkit-border-radius: 7px;
|
||||
-moz-border-radius: 7px;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
/* scrollbar height/width & offset from container borders */
|
||||
.scrollbar-dynamic > .scroll-element.scroll-x .scroll-bar {
|
||||
bottom: 0;
|
||||
height: 7px;
|
||||
min-width: 24px;
|
||||
top: auto;
|
||||
}
|
||||
|
||||
.scrollbar-dynamic > .scroll-element.scroll-y .scroll-bar {
|
||||
left: auto;
|
||||
min-height: 24px;
|
||||
right: 0;
|
||||
width: 7px;
|
||||
}
|
||||
|
||||
.scrollbar-dynamic > .scroll-element.scroll-x .scroll-element_outer {
|
||||
bottom: 0;
|
||||
top: auto;
|
||||
left: 2px;
|
||||
-webkit-transition: height 0.2s;
|
||||
-moz-transition: height 0.2s;
|
||||
-o-transition: height 0.2s;
|
||||
-ms-transition: height 0.2s;
|
||||
transition: height 0.2s;
|
||||
}
|
||||
|
||||
.scrollbar-dynamic > .scroll-element.scroll-y .scroll-element_outer {
|
||||
left: auto;
|
||||
right: 0;
|
||||
top: 2px;
|
||||
-webkit-transition: width 0.2s;
|
||||
-moz-transition: width 0.2s;
|
||||
-o-transition: width 0.2s;
|
||||
-ms-transition: width 0.2s;
|
||||
transition: width 0.2s;
|
||||
}
|
||||
|
||||
.scrollbar-dynamic > .scroll-element.scroll-x .scroll-element_size {
|
||||
left: -4px;
|
||||
}
|
||||
|
||||
.scrollbar-dynamic > .scroll-element.scroll-y .scroll-element_size {
|
||||
top: -4px;
|
||||
}
|
||||
|
||||
/* update scrollbar offset if both scrolls are visible */
|
||||
.scrollbar-dynamic > .scroll-element.scroll-x.scroll-scrolly_visible .scroll-element_size {
|
||||
left: -11px;
|
||||
}
|
||||
|
||||
.scrollbar-dynamic > .scroll-element.scroll-y.scroll-scrollx_visible .scroll-element_size {
|
||||
top: -11px;
|
||||
}
|
||||
|
||||
/* hover & drag */
|
||||
.scrollbar-dynamic > .scroll-element:hover .scroll-element_outer,
|
||||
.scrollbar-dynamic > .scroll-element.scroll-draggable .scroll-element_outer {
|
||||
overflow: hidden;
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=70)";
|
||||
filter: alpha(opacity=70);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.scrollbar-dynamic > .scroll-element:hover .scroll-element_outer .scroll-element_size,
|
||||
.scrollbar-dynamic > .scroll-element.scroll-draggable .scroll-element_outer .scroll-element_size {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.scrollbar-dynamic > .scroll-element:hover .scroll-element_outer .scroll-bar,
|
||||
.scrollbar-dynamic > .scroll-element.scroll-draggable .scroll-element_outer .scroll-bar {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
-webkit-border-radius: 12px;
|
||||
-moz-border-radius: 12px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.scrollbar-dynamic > .scroll-element.scroll-x:hover .scroll-element_outer,
|
||||
.scrollbar-dynamic > .scroll-element.scroll-x.scroll-draggable .scroll-element_outer {
|
||||
height: 20px;
|
||||
min-height: 7px;
|
||||
}
|
||||
|
||||
.scrollbar-dynamic > .scroll-element.scroll-y:hover .scroll-element_outer,
|
||||
.scrollbar-dynamic > .scroll-element.scroll-y.scroll-draggable .scroll-element_outer {
|
||||
min-width: 7px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
/*************** SCROLLBAR GOOGLE CHROME ***************/
|
||||
.scrollbar-chrome > .scroll-element,
|
||||
.scrollbar-chrome > .scroll-element div {
|
||||
border: none;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.scrollbar-chrome > .scroll-element {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.scrollbar-chrome > .scroll-element div {
|
||||
display: block;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.scrollbar-chrome > .scroll-element .scroll-element_track {
|
||||
background: #f1f1f1;
|
||||
border: 1px solid #dbdbdb;
|
||||
}
|
||||
|
||||
.scrollbar-chrome > .scroll-element.scroll-x {
|
||||
bottom: 0;
|
||||
height: 16px;
|
||||
left: 0;
|
||||
min-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.scrollbar-chrome > .scroll-element.scroll-y {
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.scrollbar-chrome > .scroll-element .scroll-bar {
|
||||
background-color: #d9d9d9;
|
||||
border: 1px solid #bdbdbd;
|
||||
cursor: default;
|
||||
-webkit-border-radius: 2px;
|
||||
-moz-border-radius: 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.scrollbar-chrome > .scroll-element .scroll-bar:hover {
|
||||
background-color: #c2c2c2;
|
||||
border-color: #a9a9a9;
|
||||
}
|
||||
|
||||
.scrollbar-chrome > .scroll-element.scroll-draggable .scroll-bar {
|
||||
background-color: #919191;
|
||||
border-color: #7e7e7e;
|
||||
}
|
||||
|
||||
/* scrollbar height/width & offset from container borders */
|
||||
.scrollbar-chrome > .scroll-content.scroll-scrolly_visible {
|
||||
left: -16px;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.scrollbar-chrome > .scroll-content.scroll-scrollx_visible {
|
||||
top: -16px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.scrollbar-chrome > .scroll-element.scroll-x .scroll-bar {
|
||||
height: 8px;
|
||||
min-width: 10px;
|
||||
top: 3px;
|
||||
}
|
||||
|
||||
.scrollbar-chrome > .scroll-element.scroll-y .scroll-bar {
|
||||
left: 3px;
|
||||
min-height: 10px;
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.scrollbar-chrome > .scroll-element.scroll-x .scroll-element_outer {
|
||||
border-left: 1px solid #dbdbdb;
|
||||
}
|
||||
|
||||
.scrollbar-chrome > .scroll-element.scroll-x .scroll-element_track {
|
||||
height: 14px;
|
||||
left: -3px;
|
||||
}
|
||||
|
||||
.scrollbar-chrome > .scroll-element.scroll-x .scroll-element_size {
|
||||
height: 14px;
|
||||
left: -4px;
|
||||
}
|
||||
|
||||
.scrollbar-chrome > .scroll-element.scroll-y .scroll-element_outer {
|
||||
border-top: 1px solid #dbdbdb;
|
||||
}
|
||||
|
||||
.scrollbar-chrome > .scroll-element.scroll-y .scroll-element_track {
|
||||
top: -3px;
|
||||
width: 14px;
|
||||
}
|
||||
|
||||
.scrollbar-chrome > .scroll-element.scroll-y .scroll-element_size {
|
||||
top: -4px;
|
||||
width: 14px;
|
||||
}
|
||||
|
||||
/* update scrollbar offset if both scrolls are visible */
|
||||
.scrollbar-chrome > .scroll-element.scroll-x.scroll-scrolly_visible .scroll-element_size {
|
||||
left: -19px;
|
||||
}
|
||||
|
||||
.scrollbar-chrome > .scroll-element.scroll-y.scroll-scrollx_visible .scroll-element_size {
|
||||
top: -19px;
|
||||
}
|
||||
|
||||
.scrollbar-chrome > .scroll-element.scroll-x.scroll-scrolly_visible .scroll-element_track {
|
||||
left: -19px;
|
||||
}
|
||||
|
||||
.scrollbar-chrome > .scroll-element.scroll-y.scroll-scrollx_visible .scroll-element_track {
|
||||
top: -19px;
|
||||
}
|
1
data/interfaces/default/css/opensans.min.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
@font-face{font-family:'Open Sans';font-weight:400;font-style:normal;src:url(../fonts/Open-Sans-regular/Open-Sans-regular.eot);src:url(../fonts/Open-Sans-regular/Open-Sans-regular.eot?#iefix) format('embedded-opentype'),local('Open Sans'),local('Open-Sans-regular'),url(../fonts/Open-Sans-regular/Open-Sans-regular.woff2) format('woff2'),url(../fonts/Open-Sans-regular/Open-Sans-regular.woff) format('woff'),url(../fonts/Open-Sans-regular/Open-Sans-regular.ttf) format('truetype'),url(../fonts/Open-Sans-regular/Open-Sans-regular.svg#OpenSans) format('svg')}@font-face{font-family:'Open Sans';font-weight:600;font-style:normal;src:url(../fonts/Open-Sans-600/Open-Sans-600.eot);src:url(../fonts/Open-Sans-600/Open-Sans-600.eot?#iefix) format('embedded-opentype'),local('Open Sans Semibold'),local('Open-Sans-600'),url(../fonts/Open-Sans-600/Open-Sans-600.woff2) format('woff2'),url(../fonts/Open-Sans-600/Open-Sans-600.woff) format('woff'),url(../fonts/Open-Sans-600/Open-Sans-600.ttf) format('truetype'),url(../fonts/Open-Sans-600/Open-Sans-600.svg#OpenSans) format('svg')}
|
1
data/interfaces/default/css/pnotify.custom.min.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.ui-pnotify{top:36px;right:36px;position:absolute;height:auto;z-index:2}body>.ui-pnotify{position:fixed;z-index:100040}.ui-pnotify-modal-overlay{background-color:rgba(0,0,0,.4);top:0;left:0;position:absolute;height:100%;width:100%;z-index:1}body>.ui-pnotify-modal-overlay{position:fixed;z-index:100039}.ui-pnotify.ui-pnotify-in{display:block!important}.ui-pnotify.ui-pnotify-move{transition:left .5s ease,top .5s ease,right .5s ease,bottom .5s ease}.ui-pnotify.ui-pnotify-fade-slow{transition:opacity .6s linear;opacity:0}.ui-pnotify.ui-pnotify-fade-slow.ui-pnotify.ui-pnotify-move{transition:opacity .6s linear,left .5s ease,top .5s ease,right .5s ease,bottom .5s ease}.ui-pnotify.ui-pnotify-fade-normal{transition:opacity .4s linear;opacity:0}.ui-pnotify.ui-pnotify-fade-normal.ui-pnotify.ui-pnotify-move{transition:opacity .4s linear,left .5s ease,top .5s ease,right .5s ease,bottom .5s ease}.ui-pnotify.ui-pnotify-fade-fast{transition:opacity .2s linear;opacity:0}.ui-pnotify.ui-pnotify-fade-fast.ui-pnotify.ui-pnotify-move{transition:opacity .2s linear,left .5s ease,top .5s ease,right .5s ease,bottom .5s ease}.ui-pnotify.ui-pnotify-fade-in{opacity:1}.ui-pnotify .ui-pnotify-shadow{-webkit-box-shadow:0 6px 28px 0 rgba(0,0,0,.1);-moz-box-shadow:0 6px 28px 0 rgba(0,0,0,.1);box-shadow:0 6px 28px 0 rgba(0,0,0,.1)}.ui-pnotify-container{background-position:0 0;padding:.8em;height:100%;margin:0}.ui-pnotify-container:after{content:" ";visibility:hidden;display:block;height:0;clear:both}.ui-pnotify-container.ui-pnotify-sharp{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.ui-pnotify-title{display:block;margin-bottom:.4em;margin-top:0}.ui-pnotify-text{display:block}.ui-pnotify-icon,.ui-pnotify-icon span{display:block;float:left;margin-right:.2em}.ui-pnotify.stack-bottomleft,.ui-pnotify.stack-topleft{left:25px;right:auto}.ui-pnotify.stack-bottomleft,.ui-pnotify.stack-bottomright{bottom:25px;top:auto}.ui-pnotify.stack-modal{left:50%;right:auto;margin-left:-150px}.ui-pnotify-closer,.ui-pnotify-sticker{float:right;margin-left:.2em}.ui-pnotify-container{position:relative;left:0}@media (max-width:480px){.ui-pnotify-mobile-able.ui-pnotify{position:fixed;top:0;right:0;left:0;width:auto!important;font-size:1.2em;-webkit-font-smoothing:antialiased;-moz-font-smoothing:antialiased;-ms-font-smoothing:antialiased;font-smoothing:antialiased}.ui-pnotify-mobile-able.ui-pnotify .ui-pnotify-shadow{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;border-bottom-width:5px}.ui-pnotify-mobile-able .ui-pnotify-container{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.ui-pnotify-mobile-able.ui-pnotify.stack-bottomleft,.ui-pnotify-mobile-able.ui-pnotify.stack-topleft{left:0;right:0}.ui-pnotify-mobile-able.ui-pnotify.stack-bottomleft,.ui-pnotify-mobile-able.ui-pnotify.stack-bottomright{left:0;right:0;bottom:0;top:auto}.ui-pnotify-mobile-able.ui-pnotify.stack-bottomleft .ui-pnotify-shadow,.ui-pnotify-mobile-able.ui-pnotify.stack-bottomright .ui-pnotify-shadow{border-top-width:5px;border-bottom-width:1px}}
|
1
data/interfaces/default/css/selectize.min.css
vendored
Normal file
@@ -34,7 +34,7 @@ table.display {
|
||||
margin: 0 auto;
|
||||
clear: both;
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
font-size: 12px;
|
||||
line-height: 25px;
|
||||
/* Note Firefox 3.5 and before have a bug with border-collapse
|
||||
* ( https://bugzilla.mozilla.org/show%5Fbug.cgi?id=155955 )
|
||||
@@ -332,4 +332,88 @@ div.box {
|
||||
}
|
||||
td.no-wrap, th.no-wrap {
|
||||
white-space:nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Custom styles
|
||||
*/
|
||||
|
||||
table.display,
|
||||
table.display tr.shown + tr table[id^='history_child'],
|
||||
table.display tr.shown + tr table[id^='media_info_child'],
|
||||
table.display tr.shown + tr table[id^='media_info_child'] tr.shown + tr table[id^='media_info_child'] {
|
||||
table-layout: auto;
|
||||
}
|
||||
table.display tr.shown + tr div.slider {
|
||||
display: none;
|
||||
}
|
||||
table.display tr.shown + tr > td {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
table.display tr.shown + tr:hover {
|
||||
background-color: rgba(255,255,255,0);
|
||||
}
|
||||
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;
|
||||
}
|
||||
table.display tr.shown + tr .pagination > .disabled > a,
|
||||
table.display tr.shown + tr .pagination > .disabled > a:hover {
|
||||
color: #444444;
|
||||
}
|
||||
table.display tr.shown + tr .pagination > li > a:hover {
|
||||
color: #e9a049;
|
||||
}
|
||||
table.display tr.odd td,
|
||||
table.display tr.even td {
|
||||
padding: 5px 10px !important;
|
||||
}
|
||||
table[id^='history_child'] {
|
||||
margin-top: 0;
|
||||
opacity: .6;
|
||||
}
|
||||
table[id^='media_info_child'] {
|
||||
margin-top: 0;
|
||||
}
|
||||
div[id^='history_child'] thead th,
|
||||
div[id^='media_info_child'] thead th {
|
||||
line-height: 0;
|
||||
height: 0 !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
div[id^='history_child'] div.row,
|
||||
div[id^='media_info_child'] div.row {
|
||||
margin: 0;
|
||||
}
|
||||
div[id^='history_child'] div.col-sm-12,
|
||||
div[id^='media_info_child'] div.col-sm-12 {
|
||||
padding: 0;
|
||||
}
|
||||
div[id^='history_child'] div.dataTables_scrollBody,
|
||||
div[id^='media_info_child'] div.dataTables_scrollBody {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
div[id^='media_info_child'] div[id^='media_info_child'] div.dataTables_scrollHead thead th {
|
||||
line-height: 25px;
|
||||
height: 35px !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
.current-activity-row {
|
||||
background-color: rgba(255,255,255,.1) !important;
|
||||
}
|
||||
.current-activity-row:hover {
|
||||
background-color: rgba(255,255,255,0.125) !important;
|
||||
}
|
@@ -1,305 +0,0 @@
|
||||
<%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: current_activity.html
|
||||
Version: 0.1
|
||||
Variable names: data [list]
|
||||
|
||||
data :: Usable parameters
|
||||
|
||||
data['stream_count'] Returns the current number of active streams
|
||||
data['sessions'] Returns an array containing session data
|
||||
|
||||
data[sessions] :: Usable parameters
|
||||
|
||||
== Global keys ==
|
||||
session_key Returns a unique session id for the active stream
|
||||
rating_key Returns the unique identifier for the media item.
|
||||
media_index Returns the index of the media item.
|
||||
parent_media_index Returns the index of the media item's parent.
|
||||
media_type Returns the type of session. Either 'track', 'episode' or 'movie'.
|
||||
thumb Returns the location of the item's thumbnail. Use with pms_image_proxy.
|
||||
bif_thumb Returns the location of the item's bif thumbnail. Use with pms_image_proxy.
|
||||
art Returns the location of the item's artwork
|
||||
progress_percent Returns the current progress of the item. 0 to 100.
|
||||
user Returns the name of the user owning the session.
|
||||
user_id Returns the Plex user id if available.
|
||||
machine_id Returns the machine id of the players being used.
|
||||
friendly_name Returns the friendlly name of the user owning the session.
|
||||
user_thumb Returns the profile picture of the user owning the session.
|
||||
state Returns the state of the current session. Either 'playing', 'paused' or 'buffering'.
|
||||
title Returns the name of the episode, movie or music track.
|
||||
year Returns the year of the episode, movie, or clip.
|
||||
ip_address Returns the ip address of the stream.
|
||||
player Returns the name of the platform used to play the stream.
|
||||
platform Returns the type of platform used to play the stream.
|
||||
throttled Returns true if the transcode session is throttled.
|
||||
transcode_progress Returns the current transcode progress of the item. 0 to 100.
|
||||
transcode_speed Returns the current transcode speed of the item.
|
||||
audio_decision Returns the audio transcode decision. Either 'transcode', 'copy' or 'direct play'.
|
||||
audio_codec Returns the name of the audio codec.
|
||||
audio_channels Returns the number of audio channels.
|
||||
grandparent_title Returns the title of the item's grandparent.
|
||||
parent_title Returns the title of the item's parent.
|
||||
video_decision Returns the video transcode decision. Either 'transcode', 'copy' or 'direct play'.
|
||||
video_codec Returns the name of the video codec.
|
||||
height Returns the value of the video height.
|
||||
width Returns the value of the video width.
|
||||
container Returns the value of the media container.
|
||||
bitrate Returns the value of the media bitrate.
|
||||
video_resolution Returns the value of the video resolution.
|
||||
video_framerate Returns the value of the video framerate.
|
||||
video_aspect_ratio Returns the value of the video aspect ratio.
|
||||
transcode_audio_channels Returns the amount of audio channels if there is a transcode session.
|
||||
transcode_audio_codec Returns the name of the audio codec if there is a transcode session.
|
||||
transcode_video_codec Returns the name of the video codec if there is a transcode session.
|
||||
transcode_width Returns the video width if there is a transcode session.
|
||||
transcode_height Returns the video height if there is a transcode session.
|
||||
transcode_container Returns the value of media container if there is a transcode session.
|
||||
transcode_protocol Returns the value of media protocol if there is a transcode session.
|
||||
indexes Returns true if the media has media indexes and are enabled
|
||||
|
||||
DOCUMENTATION :: END
|
||||
</%doc>
|
||||
|
||||
% if data is not None:
|
||||
% if data['stream_count'] != '0':
|
||||
% for a in data['sessions']:
|
||||
<div class="dashboard-instance" id="instance-${a['session_key']}">
|
||||
% if a['media_type'] == 'movie' or a['media_type'] == 'episode' or a['media_type'] == 'track':
|
||||
<a href="info?rating_key=${a['rating_key']}">
|
||||
% endif
|
||||
<div class="dashboard-activity-poster">
|
||||
% if (a['media_type'] == 'movie' and not a['indexes']) or (a['indexes'] and not a['view_offset']):
|
||||
<div class="dashboard-activity-poster-face" style="background-image: url(pms_image_proxy?img=${a['art']}&width=500&height=280);"></div>
|
||||
% elif (a['media_type'] == 'episode' and not a['indexes']) or (a['indexes'] and not a['view_offset']):
|
||||
<div class="dashboard-activity-poster-face" style="background-image: url(pms_image_proxy?img=${a['art']}&width=500&height=280);"></div>
|
||||
% elif a['indexes']:
|
||||
<div class="dashboard-activity-poster-face bif" style="background-image: url(pms_image_proxy?img=${a['bif_thumb']}&width=500&height=280); display: none;"></div>
|
||||
% else:
|
||||
% if a['media_type'] == 'track':
|
||||
<div class="dashboard-activity-cover-face-bg" style="background-image: url(pms_image_proxy?img=${a['thumb']}&width=300&height=300);"></div>
|
||||
<div class="dashboard-activity-cover-face" style="background-image: url(pms_image_proxy?img=${a['thumb']}&width=300&height=300);"></div>
|
||||
% elif a['media_type'] == 'clip':
|
||||
% if a['art'][:4] == 'http':
|
||||
<div class="dashboard-activity-poster-face" style="background-image: url(${a['art']});"></div>
|
||||
% elif a['thumb'][:4] == 'http':
|
||||
<div class="dashboard-activity-poster-face" style="background-image: url(${a['thumb']});"></div>
|
||||
% else:
|
||||
% if a['art']:
|
||||
<div class="dashboard-activity-poster-face" style="background-image: url(pms_image_proxy?img=${a['art']}&width=500&height=280);"></div>
|
||||
% else:
|
||||
<div class="dashboard-activity-poster-face" style="background-image: url(pms_image_proxy?img=${a['thumb']}&width=500&height=280);"></div>
|
||||
% endif
|
||||
% endif
|
||||
% elif a['media_type'] == 'photo':
|
||||
<div class="dashboard-activity-poster-face bif" style="background-image: url(pms_image_proxy?img=${a['thumb']}&width=500&height=500);"></div>
|
||||
% else:
|
||||
<div class="dashboard-activity-cover-face" style="background-image: url(pms_image_proxy?img=${a['thumb']}&width=300&height=300&fallback=cover);"></div>
|
||||
% endif
|
||||
% endif
|
||||
<div class="dashboard-activity-button-info">
|
||||
<button type="button" class="btn btn-activity-info btn-lg" data-target="#stream-${a['session_key']}">
|
||||
<i class="fa fa-info-circle"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="stream-${a['session_key']}" class="dashboard-activity-info-details-overlay">
|
||||
<div class="dashboard-activity-info-details-content">
|
||||
<div id="platform-${a['session_key']}" title="${a['platform']}">
|
||||
<script>
|
||||
$("#platform-${a['session_key']}").html("<div class='dashboard-activity-info-platform-box' style='background-image: url(" + getPlatformImagePath('${a['platform']}') + ");'>");
|
||||
</script>
|
||||
</div>
|
||||
<div class="dashboard-activity-info-platform">
|
||||
<strong>${a['player']}</strong><br />
|
||||
% if a['state'] == 'playing':
|
||||
State <strong>Playing</strong>
|
||||
% elif a['state'] == 'paused':
|
||||
State <strong>Paused</strong>
|
||||
% elif a['state'] == 'buffering':
|
||||
State <strong>Buffering</strong>
|
||||
% endif
|
||||
</div>
|
||||
% if a['media_type'] == 'track':
|
||||
% if a['audio_decision'] == 'direct play':
|
||||
Stream <strong>Direct Play</strong>
|
||||
% elif a['audio_decision'] == 'copy':
|
||||
Stream <strong>Direct Stream</strong>
|
||||
% else:
|
||||
Stream <strong>Transcoding
|
||||
(Speed: ${a['transcode_speed']})
|
||||
% if a['throttled'] == '1':
|
||||
(Throttled)
|
||||
% endif
|
||||
</strong>
|
||||
% endif
|
||||
<br />
|
||||
% if a['audio_decision'] == 'direct play':
|
||||
Audio <strong>Direct Play (${a['audio_codec']}) (${a['audio_channels']}ch)</strong>
|
||||
% elif a['audio_decision'] == 'copy':
|
||||
Audio <strong>Direct Stream (${a['transcode_audio_codec']}) (${a['transcode_audio_channels']}ch)</strong>
|
||||
% elif a['audio_decision'] == 'transcode':
|
||||
Audio <strong>Transcode (${a['transcode_audio_codec']}) (${a['transcode_audio_channels']}ch)</strong>
|
||||
% endif
|
||||
% elif a['media_type'] == 'episode' or a['media_type'] == 'movie' or a['media_type'] == 'clip':
|
||||
% if a['video_decision'] == 'direct play' and a['audio_decision'] == 'direct play':
|
||||
Stream <strong>Direct Play</strong>
|
||||
% elif a['video_decision'] == 'copy' and a['audio_decision'] == 'copy':
|
||||
Stream <strong>Direct Stream</strong>
|
||||
% else:
|
||||
Stream <strong>Transcoding
|
||||
(Speed: ${a['transcode_speed']})
|
||||
% if a['throttled'] == '1':
|
||||
(Throttled)
|
||||
% endif
|
||||
</strong>
|
||||
% endif
|
||||
<br />
|
||||
% if a['video_decision'] == 'direct play':
|
||||
Video <strong>Direct Play (${a['video_codec']}) (${a['width']}x${a['height']})</strong>
|
||||
% elif a['video_decision'] == 'copy':
|
||||
Video <strong>Direct Stream (${a['transcode_video_codec']}) (${a['width']}x${a['height']})</strong>
|
||||
% elif a['video_decision'] == 'transcode':
|
||||
Video <strong>Transcode (${a['transcode_video_codec']}) (${a['transcode_width']}x${a['transcode_height']})</strong>
|
||||
% endif
|
||||
<br />
|
||||
% if a['audio_decision'] == 'direct play':
|
||||
Audio <strong>Direct Play (${a['audio_codec']}) (${a['audio_channels']}ch)</strong>
|
||||
% elif a['audio_decision'] == 'copy':
|
||||
Audio <strong>Direct Stream (${a['transcode_audio_codec']}) (${a['transcode_audio_channels']}ch)</strong>
|
||||
% elif a['audio_decision'] == 'transcode':
|
||||
Audio <strong>Transcode (${a['transcode_audio_codec']}) (${a['transcode_audio_channels']}ch)</strong>
|
||||
% endif
|
||||
% elif a['media_type'] == 'photo':
|
||||
% if a['video_decision'] == 'direct play':
|
||||
Stream <strong>Direct Play</strong>
|
||||
% elif a['video_decision'] == 'copy':
|
||||
Stream <strong>Direct Stream</strong>
|
||||
% else:
|
||||
Stream <strong>
|
||||
Transcoding
|
||||
(Speed: ${a['transcode_speed']})
|
||||
% if a['throttled'] == '1':
|
||||
(Throttled)
|
||||
% endif
|
||||
</strong>
|
||||
% endif
|
||||
% endif
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
% if a['media_type'] != 'photo':
|
||||
<div class="dashboard-activity-poster-info-bar">
|
||||
<div class="dashboard-activity-poster-info-ip-address">
|
||||
% if a['ip_address']:
|
||||
<span>IP: ${a['ip_address']}</span>
|
||||
% else:
|
||||
<span>IP: N/A</span>
|
||||
% endif
|
||||
<br />
|
||||
ETA:
|
||||
<span id="stream-eta-${a['session_key']}">
|
||||
<script>
|
||||
$("#stream-eta-${a['session_key']}").html(moment().add(parseInt(${a['duration']}) - parseInt(${a['view_offset']}), 'milliseconds').format(time_format));
|
||||
</script>
|
||||
</span>
|
||||
</div>
|
||||
<div class="dashboard-activity-poster-info-time">
|
||||
<span class="progress_time">${a['view_offset']}</span>/<span class="progress_time">${a['duration']}</span>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
</div>
|
||||
% if a['media_type'] == 'movie' or a['media_type'] == 'episode' or a['media_type'] == 'track':
|
||||
</a>
|
||||
% endif
|
||||
<div class="dashboard-activity-progress">
|
||||
<div class="dashboard-activity-progress-bar">
|
||||
<div class="bufferbar" style="width: ${a['transcode_progress']}%">${a['transcode_progress']}%</div>
|
||||
<div class="bar" style="width: ${a['progress_percent']}%">${a['progress_percent']}%</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard-activity-metadata-wrapper">
|
||||
<a href="user?user_id=${a['user_id']}">
|
||||
<div class="dashboard-activity-metadata-user-thumb" style="background-image: url(${a['user_thumb']});"></div>
|
||||
</a>
|
||||
<div class="dashboard-activity-metadata-title">
|
||||
% if a['state'] == 'playing':
|
||||
<i class="fa fa-play"></i>
|
||||
% elif a['state'] == 'paused':
|
||||
<i class="fa fa-pause"></i>
|
||||
% elif a['state'] == 'buffering':
|
||||
<i class="fa fa-spinner"></i>
|
||||
% endif
|
||||
% if a['media_type'] == 'episode':
|
||||
<a href="info?rating_key=${a['rating_key']}" title="${a['grandparent_title']} - ${a['title']}">${a['grandparent_title']} - ${a['title']}</a>
|
||||
% elif a['media_type'] == 'movie':
|
||||
<a href="info?rating_key=${a['rating_key']}" title="${a['title']}">${a['title']}</a>
|
||||
% elif a['media_type'] == 'clip':
|
||||
<span title="${a['title']}">${a['title']}</span>
|
||||
% elif a['media_type'] == 'track':
|
||||
<a href="info?rating_key=${a['rating_key']}" title="${a['grandparent_title']} - ${a['title']}">${a['grandparent_title']} - ${a['title']}</a>
|
||||
% elif a['media_type'] == 'photo':
|
||||
<span title="${a['parent_title']}">${a['parent_title']}</span>
|
||||
% else:
|
||||
<span title="${a['title']}">${a['title']}</span>
|
||||
% endif
|
||||
</div>
|
||||
<div class="dashboard-activity-metadata-subtitle">
|
||||
% if a['media_type'] == 'episode':
|
||||
<span title="S${a['parent_media_index']} · E${a['media_index']}">S${a['parent_media_index']} · E${a['media_index']}</span>
|
||||
% elif a['media_type'] == 'movie':
|
||||
<span title="${a['year']}">${a['year']}</span>
|
||||
% elif a['media_type'] == 'track':
|
||||
<a href="info?rating_key=${a['parent_rating_key']}" title="${a['parent_title']}">${a['parent_title']}</a>
|
||||
% elif a['media_type'] == 'photo':
|
||||
<span title="${a['title']}">${a['title']}</span>
|
||||
% else:
|
||||
<span title="${a['year']}">${a['year']}</span>
|
||||
% endif
|
||||
</div>
|
||||
<div class="dashboard-activity-metadata-user">
|
||||
% if a['user_id']:
|
||||
<a href="user?user_id=${a['user_id']}" title="${a['friendly_name']}">${a['friendly_name']}</a>
|
||||
% else:
|
||||
<a href="user?user=${a['user']}" title="${a['friendly_name']}">${a['friendly_name']}</a>
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
% endfor
|
||||
<script>
|
||||
// When using bif indexes make the image transition a little smoother.
|
||||
$('.bif').each(function() {
|
||||
$(this).hide().fadeIn(1000);
|
||||
});
|
||||
|
||||
// Convert timestamps to readable times
|
||||
$('.progress_time').each(function(index) {
|
||||
$(this).html(millisecondsToMinutes($(this).text(), false));
|
||||
});
|
||||
|
||||
// Show/Hide activity info
|
||||
$('.btn-activity-info').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
$($(this).attr('data-target')).toggle();
|
||||
});
|
||||
|
||||
// Add hover class to dashboard-instance
|
||||
$('.dashboard-activity-poster').hover(function() {
|
||||
$(this).closest('.dashboard-instance').addClass('hover');
|
||||
}, function() {
|
||||
$(this).closest('.dashboard-instance').removeClass('hover');
|
||||
});
|
||||
</script>
|
||||
% else:
|
||||
<div class="text-muted">Nothing is currently being watched.</div><br>
|
||||
% endif
|
||||
% else:
|
||||
<div class="text-muted">There was an error communicating with your Plex Server. Please check your <a
|
||||
href="settings">settings</a>.
|
||||
</div><br>
|
||||
% endif
|
@@ -15,12 +15,23 @@ DOCUMENTATION :: END
|
||||
</%doc>
|
||||
|
||||
% if data != None:
|
||||
% if data == '0':
|
||||
<%
|
||||
s = '('
|
||||
if data['direct_play']:
|
||||
s += str(data['direct_play']) + ' direct play' + ('s' if data['direct_play'] > 1 else '') + ', '
|
||||
if data['direct_stream']:
|
||||
s += str(data['direct_stream']) + ' direct stream' + ('s' if data['direct_stream'] > 1 else '') + ', '
|
||||
if data['transcode']:
|
||||
s += str(data['transcode']) + ' transcode' + ('s' if data['transcode'] > 1 else '') + ', '
|
||||
s = s.rstrip(', ')
|
||||
s += ')'
|
||||
%>
|
||||
% if data['stream_count'] == '0':
|
||||
<h3>Activity</h3>
|
||||
% elif data == '1':
|
||||
<h3>Activity <small>${data} stream</small></h3>
|
||||
% elif data['stream_count'] == '1':
|
||||
<h3>Activity <small>${data['stream_count']} stream ${s}</small></h3>
|
||||
% else:
|
||||
<h3>Activity <small>${data} streams</small></h3>
|
||||
<h3>Activity <small>${data['stream_count']} streams ${s}</small></h3>
|
||||
% endif
|
||||
% else:
|
||||
<h3>Activity</h3>
|
||||
|
477
data/interfaces/default/current_activity_instance.html
Normal file
@@ -0,0 +1,477 @@
|
||||
<%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: current_activity_instance.html
|
||||
Version: 0.1
|
||||
Variable names: data {dict}
|
||||
|
||||
data :: Usable parameters
|
||||
|
||||
== Global keys ==
|
||||
session_key Returns a unique session id for the active stream
|
||||
rating_key Returns the unique identifier for the media item.
|
||||
media_index Returns the index of the media item.
|
||||
parent_media_index Returns the index of the media item's parent.
|
||||
media_type Returns the type of session. Either 'track', 'episode' or 'movie'.
|
||||
thumb Returns the location of the item's thumbnail. Use with pms_image_proxy.
|
||||
bif_thumb Returns the location of the item's bif thumbnail. Use with pms_image_proxy.
|
||||
art Returns the location of the item's artwork
|
||||
progress_percent Returns the current progress of the item. 0 to 100.
|
||||
user Returns the name of the user owning the session.
|
||||
user_id Returns the Plex user id if available.
|
||||
machine_id Returns the machine id of the players being used.
|
||||
friendly_name Returns the friendlly name of the user owning the session.
|
||||
user_thumb Returns the profile picture of the user owning the session.
|
||||
state Returns the state of the current session. Either 'playing', 'paused' or 'buffering'.
|
||||
title Returns the name of the episode, movie or music track.
|
||||
year Returns the year of the episode, movie, or clip.
|
||||
ip_address Returns the ip address of the stream.
|
||||
player Returns the name of the platform used to play the stream.
|
||||
platform Returns the type of platform used to play the stream.
|
||||
transcode_throttled Returns true if the transcode session is throttled.
|
||||
transcode_progress Returns the current transcode progress of the item. 0 to 100.
|
||||
transcode_speed Returns the current transcode speed of the item.
|
||||
audio_decision Returns the audio transcode decision. Either 'transcode', 'copy' or 'direct play'.
|
||||
audio_codec Returns the name of the audio codec.
|
||||
audio_channels Returns the number of audio channels.
|
||||
grandparent_title Returns the title of the item's grandparent.
|
||||
parent_title Returns the title of the item's parent.
|
||||
video_decision Returns the video transcode decision. Either 'transcode', 'copy' or 'direct play'.
|
||||
video_codec Returns the name of the video codec.
|
||||
height Returns the value of the video height.
|
||||
width Returns the value of the video width.
|
||||
container Returns the value of the media container.
|
||||
bitrate Returns the value of the media bitrate.
|
||||
video_resolution Returns the value of the video resolution.
|
||||
video_framerate Returns the value of the video framerate.
|
||||
video_aspect_ratio Returns the value of the video aspect ratio.
|
||||
transcode_audio_channels Returns the amount of audio channels if there is a transcode session.
|
||||
transcode_audio_codec Returns the name of the audio codec if there is a transcode session.
|
||||
transcode_video_codec Returns the name of the video codec if there is a transcode session.
|
||||
transcode_width Returns the video width if there is a transcode session.
|
||||
transcode_height Returns the video height if there is a transcode session.
|
||||
transcode_container Returns the value of media container if there is a transcode session.
|
||||
transcode_protocol Returns the value of media protocol if there is a transcode session.
|
||||
indexes Returns true if the media has media indexes and are enabled
|
||||
|
||||
DOCUMENTATION :: END
|
||||
</%doc>
|
||||
|
||||
% if session is not None:
|
||||
<%
|
||||
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']
|
||||
|
||||
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
|
||||
<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>
|
||||
% endif
|
||||
% if data['channel_stream'] == 0:
|
||||
% if data['media_type'] == 'movie':
|
||||
<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="${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="${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'):
|
||||
% 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
|
||||
% else:
|
||||
% if data['channel_icon'].startswith('http'):
|
||||
<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-cover" style="background-image: url(pms_image_proxy?img=${data['channel_icon']}&width=300&height=300&fallback=cover&refresh=true);"></div>
|
||||
% endif
|
||||
% endif
|
||||
</div>
|
||||
<div class="dashboard-activity-info-icon">
|
||||
<%
|
||||
if not _session['user_group'] == 'admin' or not data['session_id']:
|
||||
no_terminate = '-no-terminate'
|
||||
else:
|
||||
no_terminate = ''
|
||||
%>
|
||||
<div id="platform-${sk}" class="dashboard-activity-info-platform${no_terminate} svg-icon platform-${data['platform_name']}" title="${data['platform']}"></div>
|
||||
% if _session['user_group'] == 'admin' and plexpy.CONFIG.PMS_PLEXPASS and data['session_id']:
|
||||
<div class="dashboard-activity-terminate-session" id="terminate-button-${sk}" data-key="${sk}" data-id="${data['session_id']}" data-toggle="tooltip" title="Terminate Stream">
|
||||
<i class="fa fa-times" style="padding-top: 8px;"></i>
|
||||
</div>
|
||||
% endif
|
||||
</div>
|
||||
<div class="dashboard-activity-info-container">
|
||||
<div class="dashboard-activity-info-scroller scrollbar-macosx">
|
||||
<div class="dashboard-activity-info scoller-content">
|
||||
<ul class="list-unstyled dashboard-activity-info-list">
|
||||
<li class="dashboard-activity-info-item">
|
||||
<div class="sub-heading">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 platform-right">${data['player']}</div>
|
||||
</li>
|
||||
<li class="dashboard-activity-info-item">
|
||||
<div class="sub-heading">Quality</div>
|
||||
<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 ''
|
||||
if br:
|
||||
if br > 1000:
|
||||
br = '(' + str(round(br / 1000.0, 1)) + ' Mbps)'
|
||||
else:
|
||||
br = '(' + str(br) + ' kbps)'
|
||||
%>
|
||||
${data['quality_profile']} ${br}
|
||||
% else:
|
||||
${data['quality_profile']}
|
||||
% endif
|
||||
</div>
|
||||
</li>
|
||||
% 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_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
|
||||
</ul>
|
||||
<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>
|
||||
% 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:
|
||||
(Throttled)
|
||||
% else:
|
||||
(Speed: ${data['transcode_speed']})
|
||||
% endif
|
||||
% elif data['transcode_decision'] == 'copy':
|
||||
Direct Stream
|
||||
% 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['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
|
||||
</div>
|
||||
</li>
|
||||
% if data['media_type'] in ('movie', 'episode', 'clip', 'photo'):
|
||||
<li class="dashboard-activity-info-item">
|
||||
<div class="sub-heading">Video</div>
|
||||
<div class="sub-value" id="video_decision-${sk}">
|
||||
% if data['media_type'] in ('movie', 'episode', 'clip'):
|
||||
% if data['stream_video_decision'] == 'transcode':
|
||||
<%
|
||||
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} ${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()} ${VIDEO_RESOLUTION_OVERRIDES.get(data['video_resolution'], data['video_resolution'])})
|
||||
% endif
|
||||
% elif data['media_type'] == 'photo':
|
||||
Direct Play (${data['width']}x${data['height']})
|
||||
% endif
|
||||
</div>
|
||||
</li>
|
||||
% endif
|
||||
% if data['media_type'] in ('movie', 'episode', 'clip', 'track'):
|
||||
<li class="dashboard-activity-info-item">
|
||||
<div class="sub-heading">Audio</div>
|
||||
<div class="sub-value" id="audio_decision-${sk}">
|
||||
% 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 (${AUDIO_CODEC_OVERRIDES.get(data['audio_codec'], data['audio_codec'].upper())} ${data['audio_channel_layout'].split('(')[0].capitalize()})
|
||||
% endif
|
||||
</div>
|
||||
</li>
|
||||
% endif
|
||||
% if data['media_type'] in ('movie', 'episode', 'clip'):
|
||||
<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['stream_subtitle_decision'] == 'transcode':
|
||||
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['stream_subtitle_codec'].upper() if data['synced_version'] else data['subtitle_codec'].upper()})
|
||||
% endif
|
||||
% else:
|
||||
None
|
||||
% endif
|
||||
</div>
|
||||
</li>
|
||||
% endif
|
||||
</ul>
|
||||
<ul class="list-unstyled dashboard-activity-info-list">
|
||||
<li class="dashboard-activity-info-item">
|
||||
<div class="sub-heading">Location</div>
|
||||
<div class="sub-value time-right">
|
||||
<span id="location-${sk}">${data['location'].upper()}</span>:
|
||||
% if data['ip_address'] != 'N/A':
|
||||
<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
|
||||
</div>
|
||||
</li>
|
||||
<li class="dashboard-activity-info-item">
|
||||
<div class="sub-heading">Bandwidth</div>
|
||||
<div class="sub-value time-right">
|
||||
% if data['media_type'] != 'photo' and data['bandwidth'] != 'Unknown':
|
||||
<%
|
||||
bw = helpers.cast_to_int(data['bandwidth'])
|
||||
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 (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>
|
||||
% endif
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
% if data['media_type'] != 'photo':
|
||||
<div class="dashboard-activity-info-time">
|
||||
% if data['live'] == 1:
|
||||
<br />Live
|
||||
% elif data['view_offset']:
|
||||
ETA:
|
||||
<span id="stream-eta-${sk}">
|
||||
<script>
|
||||
$("#stream-eta-${sk}").html(moment().add(parseInt("${data['stream_duration']}") - parseInt("${data['view_offset']}"), 'milliseconds').format(time_format));
|
||||
</script>
|
||||
</span><br /><span class="progress_time_offset" id="stream-view-offset-${sk}" data-last_view_offset="${data['view_offset']}" data-view_offset="${data['view_offset']}" data-stream_duration="${data['stream_duration']}" data-state="${data['state']}">
|
||||
<script>
|
||||
$("#stream-view-offset-${sk}").html(millisecondsToMinutes(parseInt("${data['view_offset']}"), false));
|
||||
</script>
|
||||
</span> / <span class="progress_time_total" id="stream-duration-${sk}">
|
||||
<script>
|
||||
$("#stream-duration-${sk}").html(millisecondsToMinutes(parseInt("${data['stream_duration']}"), false));
|
||||
</script>
|
||||
</span>
|
||||
% else:
|
||||
ETA: Unknown<br />0:00 / <span class="progress_time_total" id="stream-duration-${sk}">
|
||||
<script>
|
||||
$("#stream-duration-${sk}").html(millisecondsToMinutes(parseInt("${data['stream_duration']}"), false));
|
||||
</script>
|
||||
</span>
|
||||
% endif
|
||||
</div>
|
||||
% endif
|
||||
</div>
|
||||
</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">
|
||||
<a href="${user_href}" title="${data['friendly_name']}">
|
||||
<div class="dashboard-activity-metadata-user-thumb" style="background-image: url(${data['user_thumb']});"></div>
|
||||
</a>
|
||||
<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':
|
||||
<i class="fa fa-fw fa-play"></i>
|
||||
% elif data['state'] == 'paused':
|
||||
<i class="fa fa-fw fa-pause"></i>
|
||||
% elif data['state'] == 'buffering':
|
||||
<i class="fa fa-fw fa-spinner"></i>
|
||||
% endif
|
||||
</div>
|
||||
<div class="dashboard-activity-metadata-title">
|
||||
% 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>
|
||||
% else:
|
||||
<span title="${data['title']}">${data['title']}</span>
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard-activity-metadata-subtitle-container">
|
||||
% 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>
|
||||
% elif data['media_type'] == 'episode':
|
||||
<i class="fa fa-fw fa-television"></i>
|
||||
% elif data['media_type'] == 'track':
|
||||
<i class="fa fa-fw fa-music"></i>
|
||||
% elif data['media_type'] == 'photo':
|
||||
<i class="fa fa-fw fa-picture-o"></i>
|
||||
% elif data['media_type'] == 'clip':
|
||||
<i class="fa fa-fw fa-video-camera"></i>
|
||||
% endif
|
||||
</div>
|
||||
% else:
|
||||
<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['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="${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="${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:
|
||||
% 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>
|
||||
% if data['media_type'] == 'episode' and data['parent_media_index'] and data['media_index']:
|
||||
(<span title="Season ${data['parent_media_index']}" class="sub-heading">S${data['parent_media_index']}</span>
|
||||
· <span title="Episode ${data['media_index']}" class="sub-heading">E${data['media_index']}</span>)
|
||||
% elif data['media_type'] == 'episode' and data['originally_available_at']:
|
||||
(<span title="${data['originally_available_at']}" class="sub-heading">${data['originally_available_at']}</span>)
|
||||
% endif
|
||||
% else:
|
||||
<span title="Channel" class="sub-heading">Channel</span>
|
||||
% if data['media_type'] == 'episode' and data['parent_media_index'] and data['media_index']:
|
||||
(<span title="Season ${data['parent_media_index']}" class="sub-heading">S${data['parent_media_index']}</span>
|
||||
· <span title="Episode ${data['media_index']}" class="sub-heading">E${data['media_index']}</span>)
|
||||
% elif data['media_type'] == 'episode' and data['originally_available_at']:
|
||||
(<span title="${data['originally_available_at']}" class="sub-heading">${data['originally_available_at']}</span>)
|
||||
% endif
|
||||
% endif
|
||||
</div>
|
||||
<div class="dashboard-activity-metadata-user">
|
||||
<a href="${user_href}" title="${data['friendly_name']}">${data['friendly_name']}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
@@ -45,13 +45,7 @@ DOCUMENTATION :: END
|
||||
<input type="text" class="form-control" id="custom_thumb_url" name="custom_thumb_url" value="${data['library_thumb']}">
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">Change the library's picture in PlexPy. 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>
|
||||
<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>
|
||||
@@ -59,12 +53,6 @@ DOCUMENTATION :: END
|
||||
</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>
|
||||
@@ -81,37 +69,11 @@ DOCUMENTATION :: END
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="confirm-modal-purge" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="confirm-modal-purge">
|
||||
<div class="modal-dialog">
|
||||
<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="myModalLabel">Confirm Purge</h4>
|
||||
</div>
|
||||
<div class="modal-body" style="text-align: center;">
|
||||
<p>Are you REALLY sure you want to purge all history for this library?</p>
|
||||
<p>This is permanent and cannot be undone!</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-dark" data-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-danger btn-ok" data-dismiss="modal" id="confirm-purge">Purge</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
// 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;
|
||||
}
|
||||
@@ -121,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,
|
||||
@@ -133,47 +93,19 @@ DOCUMENTATION :: END
|
||||
});
|
||||
});
|
||||
|
||||
$("#delete-all-history").on('click', function() {
|
||||
$('#confirm-modal-purge').modal();
|
||||
$('#confirm-modal-purge').one('click', '#confirm-purge', function () {
|
||||
$.ajax({
|
||||
url: 'delete_all_library_history',
|
||||
data: { section_id: '${data["section_id"]}' },
|
||||
cache: false,
|
||||
async: true,
|
||||
success: function(data) {
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
$('#delete-all-history').click(function () {
|
||||
var msg = 'Are you REALLY sure you want to purge all history for the <strong>${data["section_name"]}</strong> library?<br>' +
|
||||
'This is permanent and cannot be undone!';
|
||||
var url = 'delete_all_library_history';
|
||||
confirmAjaxCall(url, msg, { section_id: '${data["section_id"]}' }, null, function () { location.reload(); });
|
||||
});
|
||||
|
||||
|
||||
$(document).ready(function() {
|
||||
// Move #confirm-modal to parent container
|
||||
if (!($('#edit-library-modal').next().is('#confirm-modal-purge'))) {
|
||||
$('#confirm-modal-purge').appendTo($('#edit-library-modal').parent());
|
||||
}
|
||||
$('#edit-library-modal > #confirm-modal-purge').remove();
|
||||
|
||||
$('#edit-library-modal').css('z-index', '1050');
|
||||
$('.modal-backdrop').not('.modal-backdrop-stack').css('z-index', '1049');
|
||||
$('.modal-backdrop').not('.modal-backdrop-stack').addClass('modal-backdrop-stack');
|
||||
|
||||
$('#confirm-modal-purge').on('show.bs.modal', function () {
|
||||
// Fix position to match parent modal
|
||||
var currentPadding = parseInt($('body').css('padding-right'));
|
||||
$(this).children('.modal-dialog').css('left', -currentPadding/2);
|
||||
$('#edit-library-modal').css('overflow-y', 'hidden');
|
||||
});
|
||||
$('#confirm-modal-purge').on('shown.bs.modal', function () {
|
||||
$(this).css('z-index', '1060');
|
||||
$('.modal-backdrop').not('.modal-backdrop-stack').css('z-index', '1059');
|
||||
$('.modal-backdrop').not('.modal-backdrop-stack').addClass('modal-backdrop-stack');
|
||||
});
|
||||
$('#confirm-modal-purge').on('hidden.bs.modal', function () {
|
||||
$('body').addClass('modal-open');
|
||||
$('#edit-library-modal').css('overflow-y', 'auto');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
% endif
|
@@ -20,6 +20,7 @@ is_allow_sync Returns bool value for whether the user has sync rights.
|
||||
is_restricted Returns bool value for whether the user account is restricted.
|
||||
do_notify Returns bool value for whether to send notifications for the user.
|
||||
keep_history Returns bool value for whether to keep history for the user.
|
||||
allow_guest Returns bool value for whether to allow guest access for the user.
|
||||
|
||||
DOCUMENTATION :: END
|
||||
</%doc>
|
||||
@@ -44,7 +45,7 @@ DOCUMENTATION :: END
|
||||
<input type="text" class="form-control" id="friendly_name" name="friendly_name" value="${data['friendly_name']}" size="30">
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">Replace all occurances of the username with this name.</p>
|
||||
<p class="help-block">Replace all occurrences of the username with this name.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="profile_url">Profile Picture URL</label>
|
||||
@@ -53,13 +54,7 @@ DOCUMENTATION :: END
|
||||
<input type="text" class="form-control" id="custom_avatar_url" name="custom_avatar_url" value="${data['user_thumb']}">
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">Change the users profile picture in PlexPy. 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>
|
||||
<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>
|
||||
@@ -67,6 +62,12 @@ DOCUMENTATION :: END
|
||||
</label>
|
||||
<p class="help-block">Uncheck this if you do not want to keep any history on this user's activity.</p>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" id="allow_guest" name="allow_guest" value="1" ${helpers.checked(data['allow_guest'])}> Allow Guest Access
|
||||
</label>
|
||||
<p class="help-block">Uncheck this if you do not want to allow this user to login to Tautulli.</p>
|
||||
</div>
|
||||
% if data['user_id']:
|
||||
<div class="form-group">
|
||||
<button class="btn btn-danger" id="delete-all-history">Purge</button>
|
||||
@@ -83,37 +84,19 @@ DOCUMENTATION :: END
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="confirm-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="confirm-modal">
|
||||
<div class="modal-dialog">
|
||||
<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="myModalLabel">Confirm Purge</h4>
|
||||
</div>
|
||||
<div class="modal-body" style="text-align: center;">
|
||||
<p>Are you REALLY sure you want to purge all history for this user?</p>
|
||||
<p>This is permanent and cannot be undone!</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-dark" data-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-danger btn-ok" data-dismiss="modal" id="confirm-purge">Purge</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
// Set user options
|
||||
$("#save_user").on('click', function () {
|
||||
var friendly_name = $("#friendly_name").val();
|
||||
var friendly_name = $("input#friendly_name").val();
|
||||
var custom_thumb = $("#custom_avatar_url").val();
|
||||
var do_notify = 0;
|
||||
var keep_history = 0;
|
||||
if ($("#do_notify").is(":checked")) {
|
||||
do_notify = 1;
|
||||
}
|
||||
var allow_guest = 0;
|
||||
if ($("#keep_history").is(":checked")) {
|
||||
keep_history = 1;
|
||||
}
|
||||
if ($("#allow_guest").is(":checked")) {
|
||||
allow_guest = 1;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: 'edit_user',
|
||||
@@ -121,8 +104,8 @@ DOCUMENTATION :: END
|
||||
user_id: '${data["user_id"]}',
|
||||
friendly_name: friendly_name,
|
||||
custom_thumb: custom_thumb,
|
||||
do_notify: do_notify,
|
||||
keep_history: keep_history
|
||||
keep_history: keep_history,
|
||||
allow_guest: allow_guest
|
||||
},
|
||||
cache: false,
|
||||
async: true,
|
||||
@@ -132,47 +115,19 @@ DOCUMENTATION :: END
|
||||
});
|
||||
});
|
||||
|
||||
$("#delete-all-history").on('click', function() {
|
||||
$('#confirm-modal').modal();
|
||||
$('#confirm-modal').one('click', '#confirm-purge', function () {
|
||||
$.ajax({
|
||||
url: 'delete_all_user_history',
|
||||
data: { user_id: '${data["user_id"]}' },
|
||||
cache: false,
|
||||
async: true,
|
||||
success: function(data) {
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
$('#delete-all-history').click(function () {
|
||||
var msg = 'Are you REALLY sure you want to purge all history for the <strong>${data["username"]}</strong> user?<br>' +
|
||||
'This is permanent and cannot be undone!';
|
||||
var url = 'delete_all_user_history';
|
||||
confirmAjaxCall(url, msg, { user_id: '${data["user_id"]}' }, null, function () { location.reload(); });
|
||||
});
|
||||
|
||||
|
||||
$(document).ready(function() {
|
||||
// Move #confirm-modal to parent container
|
||||
if(!($('#edit-user-modal').next().is('#confirm-modal'))) {
|
||||
$('#confirm-modal').appendTo($('#edit-user-modal').parent());
|
||||
// Move #confirm-modal-purge to parent container
|
||||
if (!($('#edit-user-modal').next().is('#confirm-modal-purge'))) {
|
||||
$('#confirm-modal-purge').appendTo($('#edit-user-modal').parent());
|
||||
}
|
||||
$('#edit-user-modal > #confirm-modal').remove();
|
||||
|
||||
$('#edit-user-modal').css('z-index', '1050');
|
||||
$('.modal-backdrop').not('.modal-backdrop-stack').css('z-index', '1049');
|
||||
$('.modal-backdrop').not('.modal-backdrop-stack').addClass('modal-backdrop-stack');
|
||||
|
||||
$('#confirm-modal').on('show.bs.modal', function () {
|
||||
// Fix position to match parent modal
|
||||
var currentPadding = parseInt($('body').css('padding-right'));
|
||||
$(this).children('.modal-dialog').css('left', -currentPadding/2);
|
||||
$('#edit-user-modal').css('overflow-y', 'hidden');
|
||||
});
|
||||
$('#confirm-modal').on('shown.bs.modal', function () {
|
||||
$(this).css('z-index', '1060');
|
||||
$('.modal-backdrop').not('.modal-backdrop-stack').css('z-index', '1059');
|
||||
$('.modal-backdrop').not('.modal-backdrop-stack').addClass('modal-backdrop-stack');
|
||||
});
|
||||
$('#confirm-modal').on('hidden.bs.modal', function() {
|
||||
$('body').addClass('modal-open');
|
||||
$('#edit-user-modal').css('overflow-y', 'auto');
|
||||
});
|
||||
$('#edit-user-modal > #confirm-modal-purge').remove();
|
||||
});
|
||||
</script>
|
||||
% endif
|
202
data/interfaces/default/fonts/Open-Sans-600/LICENSE.txt
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
BIN
data/interfaces/default/fonts/Open-Sans-600/Open-Sans-600.eot
Normal file
1637
data/interfaces/default/fonts/Open-Sans-600/Open-Sans-600.svg
Normal file
After Width: | Height: | Size: 104 KiB |
BIN
data/interfaces/default/fonts/Open-Sans-600/Open-Sans-600.ttf
Normal file
BIN
data/interfaces/default/fonts/Open-Sans-600/Open-Sans-600.woff
Normal file
BIN
data/interfaces/default/fonts/Open-Sans-600/Open-Sans-600.woff2
Normal file
202
data/interfaces/default/fonts/Open-Sans-regular/LICENSE.txt
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
After Width: | Height: | Size: 105 KiB |
@@ -1,8 +1,8 @@
|
||||
<%inherit file="base.html"/>
|
||||
|
||||
<%def name="headIncludes()">
|
||||
<link rel="stylesheet" href="interfaces/default/css/dataTables.bootstrap.css">
|
||||
<link rel="stylesheet" href="interfaces/default/css/plexpy-dataTables.css">
|
||||
<link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css">
|
||||
<link rel="stylesheet" href="${http_root}css/tautulli-dataTables.css">
|
||||
</%def>
|
||||
|
||||
<%def name="body()">
|
||||
@@ -11,8 +11,16 @@
|
||||
<div class="header-bar">
|
||||
<span><i class="fa fa-bar-chart"></i> Graphs</span>
|
||||
</div>
|
||||
<div class="button-bar hidden-xs">
|
||||
<div class="btn-group" data-toggle="buttons" id="yaxis-selection">
|
||||
<div class="button-bar">
|
||||
<div class="btn-group" id="user-selection">
|
||||
<label>
|
||||
<select name="graph-user" id="graph-user" class="btn" style="color: inherit;">
|
||||
<option value="">All Users</option>
|
||||
<option disabled>────────────</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div class="btn-group" style="margin-right: 2px;" data-toggle="buttons" id="yaxis-selection">
|
||||
% if config['graph_type'] == 'duration':
|
||||
<label class="btn btn-dark">
|
||||
<input type="radio" name="yaxis-options" id="yaxis-count" value="plays" autocomplete="off"> Play Count
|
||||
@@ -29,11 +37,16 @@
|
||||
</label>
|
||||
% endif
|
||||
</div>
|
||||
<div class="btn-group" id="days-selection">
|
||||
<label>
|
||||
<input type="number" name="graph-days" id="graph-days" value="${config['graph_days']}" min="1" /> days
|
||||
</label>
|
||||
<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 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 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>
|
||||
</div>
|
||||
<div class='table-card-back'>
|
||||
@@ -218,7 +231,7 @@
|
||||
% endif
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h4><i class="fa fa-calendar"></i> Plays by Month <small>Last 12 months</small></h4>
|
||||
<h4><i class="fa fa-calendar"></i> Plays by month <small>Last <span class="months">12</span> months</small></h4>
|
||||
<p class="help-block">
|
||||
The combined total of tv, movies, and music by month.
|
||||
</p>
|
||||
@@ -232,24 +245,31 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="history-modal" tabindex="-1" role="dialog" aria-labelledby="history-modal">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
<%def name="modalIncludes()">
|
||||
<div class="modal fade" id="history-modal" tabindex="-1" role="dialog" aria-labelledby="history-modal">
|
||||
</div>
|
||||
<div class="modal fade" id="info-modal" tabindex="-1" role="dialog" aria-labelledby="info-modal">
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
<%def name="javascriptIncludes()">
|
||||
<script src="interfaces/default/js/moment-with-locale.js"></script>
|
||||
<script src="interfaces/default/js/moment-duration-format.js"></script>
|
||||
<script src="interfaces/default/js/highcharts/js/highcharts.js"></script>
|
||||
<script src="interfaces/default/js/jquery.dataTables.min.js"></script>
|
||||
<script src="interfaces/default/js/dataTables.bootstrap.min.js"></script>
|
||||
<script src="interfaces/default/js/dataTables.bootstrap.pagination.js"></script>
|
||||
<script src="${http_root}js/moment-with-locale.js"></script>
|
||||
<script src="${http_root}js/moment-duration-format.js"></script>
|
||||
<script src="${http_root}js/highcharts/js/highcharts.js"></script>
|
||||
<script src="${http_root}js/jquery.dataTables.min.js"></script>
|
||||
<script src="${http_root}js/dataTables.bootstrap.min.js"></script>
|
||||
<script src="${http_root}js/dataTables.bootstrap.pagination.js"></script>
|
||||
|
||||
<script>
|
||||
var selected_user_id = null
|
||||
|
||||
// Modal popup dialog
|
||||
function selectHandler(selectedDate) {
|
||||
function selectHandler(selectedDate, selectedSeries) {
|
||||
|
||||
try
|
||||
{
|
||||
@@ -259,67 +279,116 @@
|
||||
var y = dateValue.getFullYear();
|
||||
var dateString = '' + y + '-' + (m<=9 ? '0' + m : m) + '-' + (d <= 9 ? '0' + d : d);
|
||||
|
||||
var media_type = null;
|
||||
var transcode_decision = null;
|
||||
switch(selectedSeries) {
|
||||
case "TV": media_type = 'episode'; break;
|
||||
case "Movies": media_type = 'movie'; break;
|
||||
case "Music": media_type = 'track'; break;
|
||||
case "Direct Play": transcode_decision = 'direct play'; break;
|
||||
case "Direct Stream": transcode_decision = 'copy'; break;
|
||||
case "Transcode": transcode_decision = 'transcode'; break;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
"url": "history_table_modal",
|
||||
type: 'post',
|
||||
data: { 'start_date': dateString },
|
||||
url: "history_table_modal",
|
||||
cache: false,
|
||||
async: true,
|
||||
data: {
|
||||
user_id: selected_user_id,
|
||||
start_date: dateString,
|
||||
media_type: media_type,
|
||||
transcode_decision: transcode_decision
|
||||
},
|
||||
complete: function(xhr, status) {
|
||||
$('#history-modal').modal('show');
|
||||
$("#history-modal").html(xhr.responseText);
|
||||
$('#history-modal').modal();
|
||||
}
|
||||
});
|
||||
}
|
||||
catch(err)
|
||||
{
|
||||
console.log("Failed to retrieve data");
|
||||
console.log("Failed to retrieve history modal data.");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script src="interfaces/default/js/graphs/plays_by_day.js"></script>
|
||||
<script src="interfaces/default/js/graphs/plays_by_dayofweek.js"></script>
|
||||
<script src="interfaces/default/js/graphs/plays_by_hourofday.js"></script>
|
||||
<script src="interfaces/default/js/graphs/plays_by_platform.js"></script>
|
||||
<script src="interfaces/default/js/graphs/plays_by_user.js"></script>
|
||||
<script src="interfaces/default/js/graphs/plays_by_stream_type.js"></script>
|
||||
<script src="interfaces/default/js/graphs/plays_by_source_resolution.js"></script>
|
||||
<script src="interfaces/default/js/graphs/plays_by_stream_resolution.js"></script>
|
||||
<script src="interfaces/default/js/graphs/plays_by_platform_by_stream_type.js"></script>
|
||||
<script src="interfaces/default/js/graphs/plays_by_user_by_stream_type.js"></script>
|
||||
<script src="interfaces/default/js/graphs/plays_by_month.js"></script>
|
||||
<script src="${http_root}js/graphs/plays_by_day.js"></script>
|
||||
<script src="${http_root}js/graphs/plays_by_dayofweek.js"></script>
|
||||
<script src="${http_root}js/graphs/plays_by_hourofday.js"></script>
|
||||
<script src="${http_root}js/graphs/plays_by_platform.js"></script>
|
||||
<script src="${http_root}js/graphs/plays_by_user.js"></script>
|
||||
<script src="${http_root}js/graphs/plays_by_stream_type.js"></script>
|
||||
<script src="${http_root}js/graphs/plays_by_source_resolution.js"></script>
|
||||
<script src="${http_root}js/graphs/plays_by_stream_resolution.js"></script>
|
||||
<script src="${http_root}js/graphs/plays_by_platform_by_stream_type.js"></script>
|
||||
<script src="${http_root}js/graphs/plays_by_user_by_stream_type.js"></script>
|
||||
<script src="${http_root}js/graphs/plays_by_month.js"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
|
||||
// Initial values for graph from config
|
||||
var yaxis = "${config['graph_type']}";
|
||||
var current_range = ${config['graph_days']};
|
||||
var current_day_range = ${config['graph_days']};
|
||||
var current_month_range = ${config['graph_months']};
|
||||
var current_tab = "${'#' + config['graph_tab']}";
|
||||
|
||||
$('.days').html(current_range);
|
||||
$('.days').html(current_day_range);
|
||||
$('.months').html(current_month_range);
|
||||
|
||||
// Load user ids and names (for the selector)
|
||||
$.ajax({
|
||||
url: 'get_user_names',
|
||||
type: 'get',
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
var select = $('#graph-user');
|
||||
data.sort(function(a, b) {
|
||||
return a.friendly_name.localeCompare(b.friendly_name);
|
||||
});
|
||||
data.forEach(function(item) {
|
||||
select.append('<option value="' + item.user_id + '">' +
|
||||
item.friendly_name + '</option>');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var music_visible = (${config['music_logging_enable']} == 1 ? true : false);
|
||||
|
||||
function dataSecondsToHours(data) {
|
||||
$.each(data.series, function (i, series) {
|
||||
series.data = $.map(series.data, function (value) {
|
||||
return value / 60 / 60;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function loadGraphsTab1(time_range, yaxis) {
|
||||
$('#days-selection').show();
|
||||
$('#months-selection').hide();
|
||||
|
||||
setGraphFormat(yaxis);
|
||||
|
||||
$.ajax({
|
||||
url: "get_plays_by_date",
|
||||
type: 'get',
|
||||
data: { time_range: time_range, y_axis: yaxis },
|
||||
data: { time_range: time_range, y_axis: yaxis, user_id: selected_user_id },
|
||||
dataType: "json",
|
||||
success: function(data) {
|
||||
var dateArray = [];
|
||||
for (var i = 0; i < data.categories.length; i++) {
|
||||
dateArray.push(moment(data.categories[i], 'YYYY-MM-DD').valueOf());
|
||||
$.each(data.categories, function (i, day) {
|
||||
dateArray.push(moment(day, 'YYYY-MM-DD').valueOf());
|
||||
// Highlight the weekend
|
||||
if ((moment(data.categories[i], 'YYYY-MM-DD').format('ddd') == 'Sat') ||
|
||||
(moment(data.categories[i], 'YYYY-MM-DD').format('ddd') == 'Sun')) {
|
||||
if ((moment(day, 'YYYY-MM-DD').format('ddd') == 'Sat') ||
|
||||
(moment(day, 'YYYY-MM-DD').format('ddd') == 'Sun')) {
|
||||
hc_plays_by_day_options.xAxis.plotBands.push({
|
||||
from: i-0.5,
|
||||
to: i+0.5,
|
||||
color: 'rgba(80,80,80,0.3)'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
if (yaxis === 'duration') { dataSecondsToHours(data); }
|
||||
hc_plays_by_day_options.yAxis.min = 0;
|
||||
hc_plays_by_day_options.xAxis.categories = dateArray;
|
||||
hc_plays_by_day_options.series = data.series;
|
||||
@@ -331,9 +400,10 @@
|
||||
$.ajax({
|
||||
url: "get_plays_by_dayofweek",
|
||||
type: 'get',
|
||||
data: { time_range: time_range, y_axis: yaxis },
|
||||
data: { time_range: time_range, y_axis: yaxis, user_id: selected_user_id },
|
||||
dataType: "json",
|
||||
success: function(data) {
|
||||
if (yaxis === 'duration') { dataSecondsToHours(data); }
|
||||
hc_plays_by_dayofweek_options.xAxis.categories = data.categories;
|
||||
hc_plays_by_dayofweek_options.series = data.series;
|
||||
hc_plays_by_dayofweek_options.series[2].visible = music_visible;
|
||||
@@ -344,9 +414,10 @@
|
||||
$.ajax({
|
||||
url: "get_plays_by_hourofday",
|
||||
type: 'get',
|
||||
data: { time_range: time_range, y_axis: yaxis },
|
||||
data: { time_range: time_range, y_axis: yaxis, user_id: selected_user_id },
|
||||
dataType: "json",
|
||||
success: function(data) {
|
||||
if (yaxis === 'duration') { dataSecondsToHours(data); }
|
||||
hc_plays_by_hourofday_options.xAxis.categories = data.categories;
|
||||
hc_plays_by_hourofday_options.series = data.series;
|
||||
hc_plays_by_hourofday_options.series[2].visible = music_visible;
|
||||
@@ -357,9 +428,10 @@
|
||||
$.ajax({
|
||||
url: "get_plays_by_top_10_platforms",
|
||||
type: 'get',
|
||||
data: { time_range: time_range, y_axis: yaxis },
|
||||
data: { time_range: time_range, y_axis: yaxis, user_id: selected_user_id },
|
||||
dataType: "json",
|
||||
success: function(data) {
|
||||
if (yaxis === 'duration') { dataSecondsToHours(data); }
|
||||
hc_plays_by_platform_options.xAxis.categories = data.categories;
|
||||
hc_plays_by_platform_options.series = data.series;
|
||||
hc_plays_by_platform_options.series[2].visible = music_visible;
|
||||
@@ -370,9 +442,10 @@
|
||||
$.ajax({
|
||||
url: "get_plays_by_top_10_users",
|
||||
type: 'get',
|
||||
data: { time_range: time_range, y_axis: yaxis },
|
||||
data: { time_range: time_range, y_axis: yaxis, user_id: selected_user_id },
|
||||
dataType: "json",
|
||||
success: function(data) {
|
||||
if (yaxis === 'duration') { dataSecondsToHours(data); }
|
||||
hc_plays_by_user_options.xAxis.categories = data.categories;
|
||||
hc_plays_by_user_options.series = data.series;
|
||||
hc_plays_by_user_options.series[2].visible = music_visible;
|
||||
@@ -382,27 +455,31 @@
|
||||
}
|
||||
|
||||
function loadGraphsTab2(time_range, yaxis) {
|
||||
$('#days-selection').show();
|
||||
$('#months-selection').hide();
|
||||
|
||||
setGraphFormat(yaxis);
|
||||
|
||||
$.ajax({
|
||||
url: "get_plays_by_stream_type",
|
||||
type: 'get',
|
||||
data: { time_range: time_range, y_axis: yaxis },
|
||||
data: { time_range: time_range, y_axis: yaxis, user_id: selected_user_id },
|
||||
dataType: "json",
|
||||
success: function(data) {
|
||||
var dateArray = [];
|
||||
for (var i = 0; i < data.categories.length; i++) {
|
||||
dateArray.push(moment(data.categories[i], 'YYYY-MM-DD').valueOf());
|
||||
$.each(data.categories, function (i, day) {
|
||||
dateArray.push(moment(day, 'YYYY-MM-DD').valueOf());
|
||||
// Highlight the weekend
|
||||
if ((moment(data.categories[i], 'YYYY-MM-DD').format('ddd') == 'Sat') ||
|
||||
(moment(data.categories[i], 'YYYY-MM-DD').format('ddd') == 'Sun')) {
|
||||
hc_plays_by_stream_type_options.xAxis.plotBands.push({
|
||||
if ((moment(day, 'YYYY-MM-DD').format('ddd') == 'Sat') ||
|
||||
(moment(day, 'YYYY-MM-DD').format('ddd') == 'Sun')) {
|
||||
hc_plays_by_day_options.xAxis.plotBands.push({
|
||||
from: i-0.5,
|
||||
to: i+0.5,
|
||||
color: 'rgba(80,80,80,0.3)'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
if (yaxis === 'duration') { dataSecondsToHours(data); }
|
||||
hc_plays_by_stream_type_options.yAxis.min = 0;
|
||||
hc_plays_by_stream_type_options.xAxis.categories = dateArray;
|
||||
hc_plays_by_stream_type_options.series = data.series;
|
||||
@@ -413,9 +490,10 @@
|
||||
$.ajax({
|
||||
url: "get_plays_by_source_resolution",
|
||||
type: 'get',
|
||||
data: { time_range: time_range, y_axis: yaxis },
|
||||
data: { time_range: time_range, y_axis: yaxis, user_id: selected_user_id },
|
||||
dataType: "json",
|
||||
success: function(data) {
|
||||
if (yaxis === 'duration') { dataSecondsToHours(data); }
|
||||
hc_plays_by_source_resolution_options.xAxis.categories = data.categories;
|
||||
hc_plays_by_source_resolution_options.series = data.series;
|
||||
var hc_plays_by_source_resolution = new Highcharts.Chart(hc_plays_by_source_resolution_options);
|
||||
@@ -425,9 +503,10 @@
|
||||
$.ajax({
|
||||
url: "get_plays_by_stream_resolution",
|
||||
type: 'get',
|
||||
data: { time_range: time_range, y_axis: yaxis },
|
||||
data: { time_range: time_range, y_axis: yaxis, user_id: selected_user_id },
|
||||
dataType: "json",
|
||||
success: function(data) {
|
||||
if (yaxis === 'duration') { dataSecondsToHours(data); }
|
||||
hc_plays_by_stream_resolution_options.xAxis.categories = data.categories;
|
||||
hc_plays_by_stream_resolution_options.series = data.series;
|
||||
var hc_plays_by_stream_resolution = new Highcharts.Chart(hc_plays_by_stream_resolution_options);
|
||||
@@ -437,9 +516,10 @@
|
||||
$.ajax({
|
||||
url: "get_stream_type_by_top_10_platforms",
|
||||
type: 'get',
|
||||
data: { time_range: time_range, y_axis: yaxis },
|
||||
data: { time_range: time_range, y_axis: yaxis, user_id: selected_user_id },
|
||||
dataType: "json",
|
||||
success: function(data) {
|
||||
if (yaxis === 'duration') { dataSecondsToHours(data); }
|
||||
hc_plays_by_platform_by_stream_type_options.xAxis.categories = data.categories;
|
||||
hc_plays_by_platform_by_stream_type_options.series = data.series;
|
||||
var hc_plays_by_platform_by_stream_type = new Highcharts.Chart(hc_plays_by_platform_by_stream_type_options);
|
||||
@@ -449,9 +529,10 @@
|
||||
$.ajax({
|
||||
url: "get_stream_type_by_top_10_users",
|
||||
type: 'get',
|
||||
data: { time_range: time_range, y_axis: yaxis },
|
||||
data: { time_range: time_range, y_axis: yaxis, user_id: selected_user_id },
|
||||
dataType: "json",
|
||||
success: function(data) {
|
||||
if (yaxis === 'duration') { dataSecondsToHours(data); }
|
||||
hc_plays_by_user_by_stream_type_options.xAxis.categories = data.categories;
|
||||
hc_plays_by_user_by_stream_type_options.series = data.series;
|
||||
var hc_plays_by_user_by_stream_type = new Highcharts.Chart(hc_plays_by_user_by_stream_type_options);
|
||||
@@ -459,15 +540,19 @@
|
||||
});
|
||||
}
|
||||
|
||||
function loadGraphsTab3(yaxis) {
|
||||
function loadGraphsTab3(time_range, yaxis) {
|
||||
$('#days-selection').hide();
|
||||
$('#months-selection').show();
|
||||
|
||||
setGraphFormat(yaxis);
|
||||
|
||||
$.ajax({
|
||||
url: "get_plays_per_month",
|
||||
type: 'get',
|
||||
data: { y_axis: yaxis },
|
||||
data: { time_range: time_range, y_axis: yaxis, user_id: selected_user_id },
|
||||
dataType: "json",
|
||||
success: function(data) {
|
||||
if (yaxis === 'duration') { dataSecondsToHours(data); }
|
||||
hc_plays_by_month_options.yAxis.min = 0;
|
||||
hc_plays_by_month_options.xAxis.categories = data.categories;
|
||||
hc_plays_by_month_options.series = data.series;
|
||||
@@ -478,130 +563,125 @@
|
||||
}
|
||||
|
||||
// Set initial state
|
||||
if (current_tab == '#tabs-1') { loadGraphsTab1(current_range, yaxis); }
|
||||
if (current_tab == '#tabs-2') { loadGraphsTab2(current_range, yaxis); }
|
||||
if (current_tab == '#tabs-3') { loadGraphsTab3(yaxis); }
|
||||
if (current_tab == '#tabs-1') { loadGraphsTab1(current_day_range, yaxis); }
|
||||
if (current_tab == '#tabs-2') { loadGraphsTab2(current_day_range, yaxis); }
|
||||
if (current_tab == '#tabs-3') { loadGraphsTab3(current_month_range, yaxis); }
|
||||
|
||||
// Tab1 opened
|
||||
$('#graph-tabs a[href="#tabs-1"]').on('shown.bs.tab', function (e) {
|
||||
e.preventDefault();
|
||||
current_tab = $(this).attr('href');
|
||||
loadGraphsTab1(current_range, yaxis);
|
||||
$.ajax({
|
||||
url: 'set_graph_config',
|
||||
data: { graph_tab: current_tab.replace('#','') },
|
||||
async: true
|
||||
});
|
||||
loadGraphsTab1(current_day_range, yaxis);
|
||||
$.post('set_graph_config', { graph_tab: current_tab.replace('#','') });
|
||||
})
|
||||
|
||||
// Tab2 opened
|
||||
$('#graph-tabs a[href="#tabs-2"]').on('shown.bs.tab', function (e) {
|
||||
e.preventDefault();
|
||||
current_tab = $(this).attr('href');
|
||||
loadGraphsTab2(current_range, yaxis);
|
||||
$.ajax({
|
||||
url: 'set_graph_config',
|
||||
data: { graph_tab: current_tab.replace('#','') },
|
||||
async: true
|
||||
});
|
||||
loadGraphsTab2(current_day_range, yaxis);
|
||||
$.post('set_graph_config', { graph_tab: current_tab.replace('#','') });
|
||||
})
|
||||
|
||||
// Tab3 opened
|
||||
$('#graph-tabs a[href="#tabs-3"]').on('shown.bs.tab', function (e) {
|
||||
e.preventDefault();
|
||||
current_tab = $(this).attr('href');
|
||||
loadGraphsTab3(yaxis);
|
||||
$.ajax({
|
||||
url: 'set_graph_config',
|
||||
data: { graph_tab: current_tab.replace('#','') },
|
||||
async: true
|
||||
});
|
||||
loadGraphsTab3(current_month_range, yaxis);
|
||||
$.post('set_graph_config', { graph_tab: current_tab.replace('#','') });
|
||||
})
|
||||
|
||||
// Date range changed
|
||||
$('#graph-days').tooltip({ container: 'body', placement: 'top', html: true });
|
||||
$('#graph-days').on('change', function() {
|
||||
current_range = $(this).val();
|
||||
if (current_range < 1) {
|
||||
$(this).val(7);
|
||||
current_range = 7;
|
||||
}
|
||||
if (current_tab == '#tabs-1') { loadGraphsTab1(current_range, yaxis); }
|
||||
if (current_tab == '#tabs-2') { loadGraphsTab2(current_range, yaxis); }
|
||||
$('.days').html(current_range);
|
||||
$.ajax({
|
||||
url: 'set_graph_config',
|
||||
data: { graph_days: current_range},
|
||||
async: true
|
||||
});
|
||||
forceMinMax($(this));
|
||||
current_day_range = $(this).val();
|
||||
if (current_tab == '#tabs-1') { loadGraphsTab1(current_day_range, yaxis); }
|
||||
if (current_tab == '#tabs-2') { loadGraphsTab2(current_day_range, yaxis); }
|
||||
$('.days').html(current_day_range);
|
||||
$.post('set_graph_config', { graph_days: current_day_range });
|
||||
});
|
||||
|
||||
// Month range changed
|
||||
$('#graph-months').tooltip({ container: 'body', placement: 'top', html: true });
|
||||
$('#graph-months').on('change', function() {
|
||||
forceMinMax($(this));
|
||||
current_month_range = $(this).val();
|
||||
if (current_tab == '#tabs-3') { loadGraphsTab3(current_month_range, yaxis); }
|
||||
$('.months').html(current_month_range);
|
||||
$.post('set_graph_config', { graph_months: current_month_range });
|
||||
});
|
||||
|
||||
// User changed
|
||||
$('#graph-user').on('change', function() {
|
||||
selected_user_id = $(this).val() || null;
|
||||
if (current_tab == '#tabs-1') { loadGraphsTab1(current_day_range, yaxis); }
|
||||
if (current_tab == '#tabs-2') { loadGraphsTab2(current_day_range, yaxis); }
|
||||
if (current_tab == '#tabs-3') { loadGraphsTab3(current_month_range, yaxis); }
|
||||
});
|
||||
|
||||
// Y-axis changed
|
||||
$('#yaxis-selection').on('change', function() {
|
||||
yaxis = $('input[name=yaxis-options]:checked', '#yaxis-selection').val();
|
||||
if (current_tab == '#tabs-1') { loadGraphsTab1(current_range, yaxis); }
|
||||
if (current_tab == '#tabs-2') { loadGraphsTab2(current_range, yaxis); }
|
||||
if (current_tab == '#tabs-3') { loadGraphsTab3(yaxis); }
|
||||
$.ajax({
|
||||
url: 'set_graph_config',
|
||||
data: { graph_type: yaxis},
|
||||
async: true
|
||||
});
|
||||
if (current_tab == '#tabs-1') { loadGraphsTab1(current_day_range, yaxis); }
|
||||
if (current_tab == '#tabs-2') { loadGraphsTab2(current_day_range, yaxis); }
|
||||
if (current_tab == '#tabs-3') { loadGraphsTab3(current_month_range, yaxis); }
|
||||
$.post('set_graph_config', { graph_type: yaxis });
|
||||
});
|
||||
|
||||
function setGraphFormat(type) {
|
||||
if (type === 'plays') {
|
||||
yaxis_format = function() { return this.value; };
|
||||
tooltip_format = function() {
|
||||
if (moment(this.x, 'X').isValid() && (this.x > 946684800)) {
|
||||
var s = '<b>'+ moment(this.x).format("ddd MMM D") +'</b>';
|
||||
} else {
|
||||
var s = '<b>'+ this.x +'</b>';
|
||||
}
|
||||
if (this.points.length > 1) {
|
||||
var total = 0;
|
||||
$.each(this.points, function(i, point) {
|
||||
s += '<br/>'+point.series.name+': '+point.y;
|
||||
total += point.y;
|
||||
});
|
||||
s += '<br><b>Total: '+total+'</b>';
|
||||
} else {
|
||||
$.each(this.points, function(i, point) {
|
||||
s += '<br/>'+point.series.name+': '+point.y;
|
||||
});
|
||||
}
|
||||
return s;
|
||||
}
|
||||
if (moment(this.x, 'X').isValid() && (this.x > 946684800)) {
|
||||
var s = '<b>'+ moment(this.x).format('ddd MMM D') +'</b>';
|
||||
} else {
|
||||
var s = '<b>'+ this.x +'</b>';
|
||||
}
|
||||
if (this.points.length > 1) {
|
||||
var total = 0;
|
||||
$.each(this.points, function(i, point) {
|
||||
s += '<br/>'+point.series.name+': '+point.y;
|
||||
total += point.y;
|
||||
});
|
||||
s += '<br><b>Total: '+total+'</b>';
|
||||
} else {
|
||||
$.each(this.points, function(i, point) {
|
||||
s += '<br/>'+point.series.name+': '+point.y;
|
||||
});
|
||||
}
|
||||
return s;
|
||||
}
|
||||
stack_labels_format = function() {
|
||||
return this.total;
|
||||
}
|
||||
|
||||
return this.total;
|
||||
}
|
||||
$('.yaxis-text').html('Play count');
|
||||
} else {
|
||||
yaxis_format = function() { return moment.duration(this.value, 'seconds').format("m [mins]"); };
|
||||
yaxis_format = function() { return moment.duration(this.value, 'hours').format('H [h] m [m]'); };
|
||||
tooltip_format = function() {
|
||||
if (moment(this.x, 'X').isValid() && (this.x > 946684800)) {
|
||||
var s = '<b>'+ moment(this.x).format("ddd MMM D") +'</b>';
|
||||
} else {
|
||||
var s = '<b>'+ this.x +'</b>';
|
||||
}
|
||||
if (this.points.length > 1) {
|
||||
var total = 0;
|
||||
$.each(this.points, function(i, point) {
|
||||
s += '<br/>'+point.series.name+': '+moment.duration(point.y, 'seconds').format('D [days] H [hrs] m [mins]');
|
||||
total += point.y;
|
||||
});
|
||||
s += '<br/><b>Total: '+moment.duration(total, 'seconds').format('D [days] H [hrs] m [mins]')+'</b>';
|
||||
} else {
|
||||
$.each(this.points, function(i, point) {
|
||||
s += '<br/>'+point.series.name+': '+moment.duration(point.y, 'seconds').format('D [days] H [hrs] m [mins]');
|
||||
});
|
||||
}
|
||||
return s;
|
||||
}
|
||||
if (moment(this.x, 'X').isValid() && (this.x > 946684800)) {
|
||||
var s = '<b>'+ moment(this.x).format('ddd MMM D') +'</b>';
|
||||
} else {
|
||||
var s = '<b>'+ this.x +'</b>';
|
||||
}
|
||||
if (this.points.length > 1) {
|
||||
var total = 0;
|
||||
$.each(this.points, function(i, point) {
|
||||
s += '<br/>'+point.series.name+': '+moment.duration(point.y, 'hours').format('D [days] H [hrs] m [mins]');
|
||||
total += point.y;
|
||||
});
|
||||
s += '<br/><b>Total: '+moment.duration(total, 'hours').format('D [days] H [hrs] m [mins]')+'</b>';
|
||||
} else {
|
||||
$.each(this.points, function(i, point) {
|
||||
s += '<br/>'+point.series.name+': '+moment.duration(point.y, 'hours').format('D [days] H [hrs] m [mins]');
|
||||
});
|
||||
}
|
||||
return s;
|
||||
}
|
||||
stack_labels_format = function() {
|
||||
var s = moment.duration(this.total, 'seconds').format("H [hrs] m [mins]");
|
||||
return s;
|
||||
}
|
||||
var s = moment.duration(this.total, 'hours').format('H [h] m [m]');
|
||||
return s;
|
||||
}
|
||||
$('.yaxis-text').html('Play duration');
|
||||
}
|
||||
|
||||
|
@@ -1,32 +1,62 @@
|
||||
<%inherit file="base.html"/>
|
||||
|
||||
<%def name="headIncludes()">
|
||||
<link rel="stylesheet" href="interfaces/default/css/dataTables.bootstrap.css">
|
||||
<link rel="stylesheet" href="interfaces/default/css/dataTables.colVis.css">
|
||||
<link rel="stylesheet" href="interfaces/default/css/plexpy-dataTables.css">
|
||||
<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/tautulli-dataTables.css">
|
||||
</%def>
|
||||
|
||||
<%def name="body()">
|
||||
|
||||
<div class='container-fluid'>
|
||||
<div class='table-card-header'>
|
||||
<div class="header-bar">
|
||||
<span><i class="fa fa-history"></i> History</span>
|
||||
</div>
|
||||
<div class="button-bar">
|
||||
<div class="colvis-button-bar hidden-xs"></div>
|
||||
<button class="btn btn-danger btn-edit" data-toggle="button" aria-pressed="false" autocomplete="off" id="row-edit-mode">
|
||||
<i class="fa fa-trash-o"></i> Delete mode
|
||||
</button>
|
||||
% if _session['user_group'] == 'admin':
|
||||
<div class="alert alert-danger alert-edit" role="alert" id="row-edit-mode-alert"><i class="fa fa-exclamation-triangle"></i> Select rows to delete. Data is deleted upon exiting delete mode.</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-danger btn-edit" data-toggle="button" aria-pressed="false" autocomplete="off" id="row-edit-mode">
|
||||
<i class="fa fa-trash-o"></i> Delete mode
|
||||
</button> 
|
||||
</div>
|
||||
% endif
|
||||
% if _session['user_group'] == 'admin':
|
||||
<div class="btn-group" id="user-selection">
|
||||
<label>
|
||||
<select name="history-user" id="history-user" class="btn" style="color: inherit;">
|
||||
<option value="">All Users</option>
|
||||
<option disabled>────────────</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
% endif
|
||||
<div class="btn-group" data-toggle="buttons" id="media_type-selection">
|
||||
<label class="btn btn-dark active">
|
||||
<input type="radio" name="media_type-filter" id="history-all" value="" autocomplete="off"> All
|
||||
</label>
|
||||
<label class="btn btn-dark">
|
||||
<input type="radio" name="media_type-filter" id="history-movies" value="movie" autocomplete="off"> Movies
|
||||
</label>
|
||||
<label class="btn btn-dark">
|
||||
<input type="radio" name="media_type-filter" id="history-tv_shows" value="episode" autocomplete="off"> TV Shows
|
||||
</label>
|
||||
<label class="btn btn-dark">
|
||||
<input type="radio" name="media_type-filter" id="history-music" value="track" autocomplete="off"> Music
|
||||
</label>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-dark refresh-history-button" id="refresh-history-list"><i class="fa fa-refresh"></i> Refresh history</button>
|
||||
</div>
|
||||
<div class="btn-group colvis-button-bar"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='table-card-back'>
|
||||
<table class="display" id="history_table" width="100%">
|
||||
<div class="table-card-back">
|
||||
<table class="display history_table" id="history_table" width="100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th align="left" id="delete_row">Delete</th>
|
||||
<th align="left" id="time">Time</th>
|
||||
<th align="left" id="date">Date</th>
|
||||
<th align="left" id="friendly_name">User</th>
|
||||
<th align="left" id="ip_address">IP Address</th>
|
||||
<th align="left" id="platform">Platform</th>
|
||||
@@ -42,26 +72,29 @@
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
<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>
|
||||
<div class="modal fade" id="confirm-modal" tabindex="-1" role="dialog" aria-labelledby="confirm-modal">
|
||||
<div class="modal-dialog">
|
||||
<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="myModalLabel">Confirm Delete</h4>
|
||||
</div>
|
||||
<div class="modal-body" style="text-align: center;">
|
||||
<p>Are you REALLY sure you want to delete <strong><span id="deleteCount"></span></strong> history item(s)?</p>
|
||||
<p>This is permanent and cannot be undone!</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-dark" data-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-danger btn-ok" data-dismiss="modal" id="confirm-delete">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
<%def name="modalIncludes()">
|
||||
<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>
|
||||
<div class="modal fade" id="confirm-modal-delete" tabindex="-1" role="dialog" aria-labelledby="confirm-modal-delete">
|
||||
<div class="modal-dialog">
|
||||
<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="myModalLabel">Confirm Delete</h4>
|
||||
</div>
|
||||
<div class="modal-body" style="text-align: center;">
|
||||
<p>Are you REALLY sure you want to delete <strong><span id="deleteCount"></span></strong> history item(s)?</p>
|
||||
<p>This is permanent and cannot be undone!</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-dark" data-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-danger btn-ok" data-dismiss="modal" id="confirm-delete">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -69,46 +102,53 @@
|
||||
</%def>
|
||||
|
||||
<%def name="javascriptIncludes()">
|
||||
<script src="interfaces/default/js/jquery.dataTables.min.js"></script>
|
||||
<script src="interfaces/default/js/dataTables.colVis.js"></script>
|
||||
<script src="interfaces/default/js/dataTables.bootstrap.min.js"></script>
|
||||
<script src="interfaces/default/js/dataTables.bootstrap.pagination.js"></script>
|
||||
<script src="interfaces/default/js/moment-with-locale.js"></script>
|
||||
<script src="interfaces/default/js/tables/history_table.js"></script>
|
||||
<script src="${http_root}js/jquery.dataTables.min.js"></script>
|
||||
<script src="${http_root}js/dataTables.colVis.js"></script>
|
||||
<script src="${http_root}js/dataTables.bootstrap.min.js"></script>
|
||||
<script src="${http_root}js/dataTables.bootstrap.pagination.js"></script>
|
||||
<script src="${http_root}js/moment-with-locale.js"></script>
|
||||
<script src="${http_root}js/tables/history_table.js${cache_param}"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
function loadHistoryTable(media_type) {
|
||||
// Load user ids and names (for the selector)
|
||||
$.ajax({
|
||||
url: 'get_user_names',
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
success: function (data) {
|
||||
var select = $('#history-user');
|
||||
data.sort(function (a, b) {
|
||||
return a.friendly_name.localeCompare(b.friendly_name);
|
||||
});
|
||||
data.forEach(function (item) {
|
||||
select.append('<option value="' + item.user_id + '">' +
|
||||
item.friendly_name + '</option>');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
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),
|
||||
media_type: media_type
|
||||
media_type: media_type,
|
||||
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);
|
||||
|
||||
$('#history_table_filter').prepend('<div class="btn-group" data-toggle="buttons" id="media_type-selection" style="padding-right: 15px;"> \
|
||||
<label class="btn btn-dark active"> \
|
||||
<input type="radio" name="media_type-filter" id="history-all" value="all" autocomplete="off"> All \
|
||||
</label> \
|
||||
<label class="btn btn-dark"> \
|
||||
<input type="radio" name="media_type-filter" id="history-movies" value="movie" autocomplete="off"> Movies \
|
||||
</label> \
|
||||
<label class="btn btn-dark"> \
|
||||
<input type="radio" name="media_type-filter" id="history-tv_shows" value="episode" autocomplete="off"> TV Shows \
|
||||
</label> \
|
||||
<label class="btn btn-dark"> \
|
||||
<input type="radio" name="media_type-filter" id="history-music" value="track" autocomplete="off"> Music \
|
||||
</label> \
|
||||
</div>');
|
||||
|
||||
$('#media_type-selection').on('change', function () {
|
||||
$('#media_type-selection > label').removeClass('active');
|
||||
selected_filter = $('input[name=media_type-filter]:checked', '#media_type-selection');
|
||||
@@ -116,30 +156,38 @@
|
||||
media_type = $(selected_filter).val();
|
||||
history_table.draw();
|
||||
});
|
||||
|
||||
$('#history-user').on('change', function () {
|
||||
selected_user_id = $(this).val() || null;
|
||||
history_table.draw();
|
||||
});
|
||||
}
|
||||
|
||||
var media_type = 'all';
|
||||
loadHistoryTable(media_type);
|
||||
var media_type = null;
|
||||
var selected_user_id = "${_session['user_group']}" == "admin" ? null : "${_session['user_id']}";
|
||||
loadHistoryTable(media_type, selected_user_id);
|
||||
|
||||
% if _session['user_group'] == 'admin':
|
||||
$('#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').modal();
|
||||
$('#confirm-modal').one('click', '#confirm-delete', function () {
|
||||
for (var i = 0; i < history_to_delete.length; i++) {
|
||||
$('#confirm-modal-delete').modal();
|
||||
$('#confirm-modal-delete').one('click', '#confirm-delete', function () {
|
||||
history_to_delete.forEach(function(row, idx) {
|
||||
$.ajax({
|
||||
url: 'delete_history_rows',
|
||||
data: { row_id: history_to_delete[i] },
|
||||
type: 'POST',
|
||||
data: { row_id: row },
|
||||
async: true,
|
||||
success: function (data) {
|
||||
var msg = "History deleted";
|
||||
showMsg(msg, false, true, 2000);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
history_table.draw();
|
||||
});
|
||||
}
|
||||
@@ -157,6 +205,11 @@
|
||||
});
|
||||
}
|
||||
});
|
||||
% endif
|
||||
});
|
||||
|
||||
$("#refresh-history-list").click(function() {
|
||||
history_table.draw();
|
||||
});
|
||||
</script>
|
||||
</%def>
|
||||
|
@@ -5,19 +5,34 @@
|
||||
<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">
|
||||
<i class="fa fa-history"></i> History for <span id="date-header">${data}</span>
|
||||
% 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>
|
||||
<div class="modal-body" id="modal-text">
|
||||
<table class="display" id="history_table" width="100%">
|
||||
<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>
|
||||
@@ -27,52 +42,31 @@
|
||||
<div class="modal-footer"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="info-modal" tabindex="-1" role="dialog" aria-labelledby="info-modal">
|
||||
</div>
|
||||
<script src="interfaces/default/js/tables/history_table_modal.js"></script>
|
||||
|
||||
<script src="${http_root}js/tables/history_table.js${cache_param}"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('#date-header').html(moment('${data}','YYYY-MM-DD').format('ddd MMM Do YYYY'));
|
||||
history_table_modal_options.ajax = {
|
||||
$('#date-header').html(moment('${data["start_date"]}','YYYY-MM-DD').format('ddd MMM Do YYYY'));
|
||||
history_table_options.ajax = {
|
||||
url: 'get_history',
|
||||
type: 'post',
|
||||
data: function ( d ) {
|
||||
return {
|
||||
json_data: JSON.stringify(d),
|
||||
grouping: false,
|
||||
start_date: '${data}'
|
||||
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').DataTable(history_table_modal_options);
|
||||
|
||||
clearSearchButton('history_table', history_table);
|
||||
history_table = $('#history_table_modal').DataTable(history_table_options);
|
||||
history_table.columns([0, 3, 4, 8, 10, 11]).visible(false);
|
||||
|
||||
// Move #info-modal to parent container
|
||||
if (!($('#history-modal').next().is('#info-modal'))) {
|
||||
$('#info-modal').appendTo($('#history-modal').parent());
|
||||
}
|
||||
$('#history-modal > #info-modal').remove();
|
||||
clearSearchButton('history_table_modal', history_table);
|
||||
|
||||
$('#history-modal').css('z-index', '1050');
|
||||
$('.modal-backdrop').not('.modal-backdrop-stack').css('z-index', '1049');
|
||||
$('.modal-backdrop').not('.modal-backdrop-stack').addClass('modal-backdrop-stack');
|
||||
|
||||
$('#info-modal').on('show.bs.modal', function () {
|
||||
// Fix position to match parent modal
|
||||
var currentPadding = parseInt($('body').css('padding-right'));
|
||||
$(this).children('.modal-dialog').css('left', -currentPadding / 2);
|
||||
$('#history-modal').css('overflow-y', 'hidden');
|
||||
setTimeout(function () {
|
||||
$('#info-modal').css('z-index', '1060');
|
||||
$('.modal-backdrop').not('.modal-backdrop-stack').css('z-index', '1059');
|
||||
$('.modal-backdrop').not('.modal-backdrop-stack').addClass('modal-backdrop-stack');
|
||||
}, 0);
|
||||
});
|
||||
$('#info-modal').on('hidden.bs.modal', function () {
|
||||
$('body').addClass('modal-open');
|
||||
$('#history-modal').css('overflow-y', 'auto');
|
||||
$('#history-modal').on('shown.bs.modal', function() {
|
||||
history_table.columns.adjust().draw();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@@ -57,816 +57,190 @@ DOCUMENTATION :: END
|
||||
|
||||
# Human readable duration
|
||||
def hd(seconds):
|
||||
minutes = helpers.cast_to_float(seconds) / 60
|
||||
if minutes > 60:
|
||||
hours = int(minutes / 60)
|
||||
minutes = int(minutes % 60)
|
||||
if minutes > 0:
|
||||
return "<h3>" + str(hours) + "</h3><p>hrs</p><h3>" + str(minutes) + "</h3><p>mins</p>"
|
||||
else:
|
||||
return "<h3>" + str(hours) + "</h3><p>hrs</p>"
|
||||
else:
|
||||
return "<h3>" + str(int(minutes)) + "</h3><p>mins</p>"
|
||||
m, s = divmod(helpers.cast_to_int(seconds), 60)
|
||||
h, m = divmod(m, 60)
|
||||
return str(h).zfill(1) + ':' + str(m).zfill(2)
|
||||
%>
|
||||
|
||||
% if data:
|
||||
<ul class="list-unstyled">
|
||||
% if any(top_stat['rows'] for top_stat in data):
|
||||
% for top_stat in data:
|
||||
% if top_stat['stat_id'] == 'top_tv' and top_stat['rows']:
|
||||
<div class="home-platforms-instance">
|
||||
<li>
|
||||
<div class="home-platforms-instance-info">
|
||||
<div class="home-platforms-instance-name">
|
||||
<h4>Most Watched TV</h4>
|
||||
</div>
|
||||
<div class="home-platforms-instance-playcount">
|
||||
<h4>
|
||||
<a href="info?rating_key=${top_stat['rows'][0]['rating_key']}" title="${top_stat['rows'][0]['title']}">
|
||||
${top_stat['rows'][0]['title']}
|
||||
</a>
|
||||
</h4>
|
||||
% if top_stat['stat_type'] == 'total_plays':
|
||||
<h3>${top_stat['rows'][0]['total_plays']}</h3>
|
||||
<p> plays</p>
|
||||
% else:
|
||||
${top_stat['rows'][0]['total_duration'] | hd}
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
<a href="info?rating_key=${top_stat['rows'][0]['rating_key']}" title="${top_stat['rows'][0]['title']}">
|
||||
% if top_stat['rows'][0]['grandparent_thumb']:
|
||||
<div class="home-platforms-instance-poster">
|
||||
<div class="home-platforms-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][0]['grandparent_thumb']}&width=300&height=450&fallback=poster);"></div>
|
||||
</div>
|
||||
% else:
|
||||
<div class="home-platforms-instance-poster">
|
||||
<div class="home-platforms-poster-face" style="background-image: url(interfaces/default/images/poster.png);"></div>
|
||||
</div>
|
||||
% endif
|
||||
</a>
|
||||
% if len(top_stat['rows']) > 1:
|
||||
<div class="home-platforms-instance-list-chevron"><i class="fa fa-chevron-down"></i></div>
|
||||
<ul class="list-unstyled">
|
||||
<div class="slider">
|
||||
<div class="home-platforms-instance-list">
|
||||
% for row in top_stat['rows']:
|
||||
% if loop.index > 0:
|
||||
<li>
|
||||
<div class="home-platforms-instance-list-info">
|
||||
<div class="home-platforms-instance-list-name">
|
||||
<h5>
|
||||
<a href="info?rating_key=${top_stat['rows'][loop.index]['rating_key']}" title="${top_stat['rows'][loop.index]['title']}">
|
||||
${top_stat['rows'][loop.index]['title']}
|
||||
</a>
|
||||
</h5>
|
||||
</div>
|
||||
<div class="home-platforms-instance-list-playcount">
|
||||
% if top_stat['stat_type'] == 'total_plays':
|
||||
<h3>${top_stat['rows'][loop.index]['total_plays']}</h3>
|
||||
<p> plays</p>
|
||||
% else:
|
||||
${top_stat['rows'][loop.index]['total_duration'] | hd}
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
<a href="info?rating_key=${top_stat['rows'][loop.index]['rating_key']}" title="${top_stat['rows'][loop.index]['title']}">
|
||||
% if top_stat['rows'][loop.index]['grandparent_thumb']:
|
||||
<div class="home-platforms-instance-list-poster">
|
||||
<div class="home-platforms-list-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][loop.index]['grandparent_thumb']}&width=300&height=450&fallback=poster);"></div>
|
||||
</div>
|
||||
% else:
|
||||
<div class="home-platforms-instance-poster2">
|
||||
<div class="home-platforms-list-poster-face" style="background-image: url(interfaces/default/images/poster.png);"></div>
|
||||
</div>
|
||||
% endif
|
||||
</a>
|
||||
<div class="home-platforms-instance-list-number">
|
||||
<h4>${loop.index + 1}</h4>
|
||||
</div>
|
||||
</li>
|
||||
% endif
|
||||
% endfor
|
||||
</div>
|
||||
</div>
|
||||
</ul>
|
||||
% endif
|
||||
</li>
|
||||
</div>
|
||||
% elif top_stat['stat_id'] == 'popular_tv' and top_stat['rows']:
|
||||
<div class="home-platforms-instance">
|
||||
<li>
|
||||
<div class="home-platforms-instance-info">
|
||||
<div class="home-platforms-instance-name">
|
||||
<h4>Most Popular TV</h4>
|
||||
</div>
|
||||
<div class="home-platforms-instance-playcount">
|
||||
<h4>
|
||||
<a href="info?rating_key=${top_stat['rows'][0]['rating_key']}" title="${top_stat['rows'][0]['title']}">
|
||||
${top_stat['rows'][0]['title']}
|
||||
</a>
|
||||
</h4>
|
||||
<h3>${top_stat['rows'][0]['users_watched']}</h3>
|
||||
<p> users</p>
|
||||
</div>
|
||||
</div>
|
||||
<a href="info?rating_key=${top_stat['rows'][0]['rating_key']}" title="${top_stat['rows'][0]['title']}">
|
||||
% if top_stat['rows'][0]['grandparent_thumb'] != '':
|
||||
<div class="home-platforms-instance-poster">
|
||||
<div class="home-platforms-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][0]['grandparent_thumb']}&width=300&height=450&fallback=poster);"></div>
|
||||
</div>
|
||||
% else:
|
||||
<div class="home-platforms-instance-poster">
|
||||
<div class="home-platforms-poster-face" style="background-image: url(interfaces/default/images/poster.png);"></div>
|
||||
</div>
|
||||
% endif
|
||||
</a>
|
||||
% if len(top_stat['rows']) > 1:
|
||||
<div class="home-platforms-instance-list-chevron"><i class="fa fa-chevron-down"></i></div>
|
||||
<ul class="list-unstyled">
|
||||
<div class="slider">
|
||||
<div class="home-platforms-instance-list">
|
||||
% for row in top_stat['rows']:
|
||||
% if loop.index > 0:
|
||||
<li>
|
||||
<div class="home-platforms-instance-list-info">
|
||||
<div class="home-platforms-instance-list-name">
|
||||
<h5>
|
||||
<a href="info?rating_key=${top_stat['rows'][loop.index]['rating_key']}" title="${top_stat['rows'][loop.index]['title']}">
|
||||
${top_stat['rows'][loop.index]['title']}
|
||||
</a>
|
||||
</h5>
|
||||
</div>
|
||||
<div class="home-platforms-instance-list-playcount">
|
||||
<h3>${top_stat['rows'][loop.index]['users_watched']}</h3>
|
||||
<p> users</p>
|
||||
</div>
|
||||
</div>
|
||||
<a href="info?rating_key=${top_stat['rows'][loop.index]['rating_key']}" title="${top_stat['rows'][loop.index]['title']}">
|
||||
% if top_stat['rows'][loop.index]['grandparent_thumb']:
|
||||
<div class="home-platforms-instance-list-poster">
|
||||
<div class="home-platforms-list-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][loop.index]['grandparent_thumb']}&width=300&height=450&fallback=poster);"></div>
|
||||
</div>
|
||||
% else:
|
||||
<div class="home-platforms-instance-poster2">
|
||||
<div class="home-platforms-list-poster-face" style="background-image: url(interfaces/default/images/poster.png);"></div>
|
||||
</div>
|
||||
% endif
|
||||
</a>
|
||||
<div class="home-platforms-instance-list-number">
|
||||
<h4>${loop.index + 1}</h4>
|
||||
</div>
|
||||
</li>
|
||||
% endif
|
||||
% endfor
|
||||
</div>
|
||||
</div>
|
||||
</ul>
|
||||
% endif
|
||||
</li>
|
||||
</div>
|
||||
% elif top_stat['stat_id'] == 'top_movies' and top_stat['rows']:
|
||||
<div class="home-platforms-instance">
|
||||
<li>
|
||||
<div class="home-platforms-instance-info">
|
||||
<div class="home-platforms-instance-name">
|
||||
<h4>Most Watched Movie</h4>
|
||||
</div>
|
||||
<div class="home-platforms-instance-playcount">
|
||||
<h4>
|
||||
<a href="info?rating_key=${top_stat['rows'][0]['rating_key']}" title="${top_stat['rows'][0]['title']}">
|
||||
${top_stat['rows'][0]['title']}
|
||||
</a>
|
||||
</h4>
|
||||
% if top_stat['stat_type'] == 'total_plays':
|
||||
<h3>${top_stat['rows'][0]['total_plays']}</h3>
|
||||
<p> plays</p>
|
||||
% else:
|
||||
${top_stat['rows'][0]['total_duration'] | hd}
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
<a href="info?rating_key=${top_stat['rows'][0]['rating_key']}" title="${top_stat['rows'][0]['title']}">
|
||||
% if top_stat['rows'][0]['thumb']:
|
||||
<div class="home-platforms-instance-poster">
|
||||
<div class="home-platforms-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][0]['thumb']}&width=300&height=450&fallback=poster);"></div>
|
||||
</div>
|
||||
% else:
|
||||
<div class="home-platforms-instance-poster">
|
||||
<div class="home-platforms-poster-face" style="background-image: url(interfaces/default/images/poster.png);"></div>
|
||||
</div>
|
||||
% endif
|
||||
</a>
|
||||
% if len(top_stat['rows']) > 1:
|
||||
<div class="home-platforms-instance-list-chevron"><i class="fa fa-chevron-down"></i></div>
|
||||
<ul class="list-unstyled">
|
||||
<div class="slider">
|
||||
<div class="home-platforms-instance-list">
|
||||
% for row in top_stat['rows']:
|
||||
% if loop.index > 0:
|
||||
<li>
|
||||
<div class="home-platforms-instance-list-info">
|
||||
<div class="home-platforms-instance-list-name">
|
||||
<h5>
|
||||
<a href="info?rating_key=${top_stat['rows'][loop.index]['rating_key']}" title="${top_stat['rows'][loop.index]['title']}">
|
||||
${top_stat['rows'][loop.index]['title']}
|
||||
</a>
|
||||
</h5>
|
||||
</div>
|
||||
<div class="home-platforms-instance-list-playcount">
|
||||
% if top_stat['stat_type'] == 'total_plays':
|
||||
<h3>${top_stat['rows'][loop.index]['total_plays']}</h3>
|
||||
<p> plays</p>
|
||||
% else:
|
||||
${top_stat['rows'][loop.index]['total_duration'] | hd}
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
<a href="info?rating_key=${top_stat['rows'][loop.index]['rating_key']}" title="${top_stat['rows'][loop.index]['title']}">
|
||||
% if top_stat['rows'][loop.index]['thumb']:
|
||||
<div class="home-platforms-instance-list-poster">
|
||||
<div class="home-platforms-list-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][loop.index]['thumb']}&width=300&height=450&fallback=poster);"></div>
|
||||
</div>
|
||||
% else:
|
||||
<div class="home-platforms-instance-poster2">
|
||||
<div class="home-platforms-list-poster-face" style="background-image: url(interfaces/default/images/poster.png);"></div>
|
||||
</div>
|
||||
% endif
|
||||
</a>
|
||||
<div class="home-platforms-instance-list-number">
|
||||
<h4>${loop.index + 1}</h4>
|
||||
</div>
|
||||
</li>
|
||||
% endif
|
||||
% endfor
|
||||
</div>
|
||||
</div>
|
||||
</ul>
|
||||
% endif
|
||||
</li>
|
||||
</div>
|
||||
% elif top_stat['stat_id'] == 'popular_movies' and top_stat['rows']:
|
||||
<div class="home-platforms-instance">
|
||||
<li>
|
||||
<div class="home-platforms-instance-info">
|
||||
<div class="home-platforms-instance-name">
|
||||
<h4>Most Popular Movie</h4>
|
||||
</div>
|
||||
<div class="home-platforms-instance-playcount">
|
||||
<h4>
|
||||
<a href="info?rating_key=${top_stat['rows'][0]['rating_key']}" title="${top_stat['rows'][0]['title']}">
|
||||
${top_stat['rows'][0]['title']}
|
||||
</a>
|
||||
</h4>
|
||||
<h3>${top_stat['rows'][0]['users_watched']}</h3>
|
||||
<p> users</p>
|
||||
</div>
|
||||
</div>
|
||||
<a href="info?rating_key=${top_stat['rows'][0]['rating_key']}" title="${top_stat['rows'][0]['title']}">
|
||||
% if top_stat['rows'][0]['thumb']:
|
||||
<div class="home-platforms-instance-poster">
|
||||
<div class="home-platforms-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][0]['thumb']}&width=300&height=450&fallback=poster);"></div>
|
||||
</div>
|
||||
% else:
|
||||
<div class="home-platforms-instance-poster">
|
||||
<div class="home-platforms-poster-face" style="background-image: url(interfaces/default/images/poster.png);"></div>
|
||||
</div>
|
||||
% endif
|
||||
</a>
|
||||
% if len(top_stat['rows']) > 1:
|
||||
<div class="home-platforms-instance-list-chevron"><i class="fa fa-chevron-down"></i></div>
|
||||
<ul class="list-unstyled">
|
||||
<div class="slider">
|
||||
<div class="home-platforms-instance-list">
|
||||
% for row in top_stat['rows']:
|
||||
% if loop.index > 0:
|
||||
<li>
|
||||
<div class="home-platforms-instance-list-info">
|
||||
<div class="home-platforms-instance-list-name">
|
||||
<h5>
|
||||
<a href="info?rating_key=${top_stat['rows'][loop.index]['rating_key']}" title="${top_stat['rows'][loop.index]['title']}">
|
||||
${top_stat['rows'][loop.index]['title']}
|
||||
</a>
|
||||
</h5>
|
||||
</div>
|
||||
<div class="home-platforms-instance-list-playcount">
|
||||
<h3>${top_stat['rows'][loop.index]['users_watched']}</h3>
|
||||
<p> users</p>
|
||||
</div>
|
||||
</div>
|
||||
<a href="info?rating_key=${top_stat['rows'][loop.index]['rating_key']}" title="${top_stat['rows'][loop.index]['title']}">
|
||||
% if top_stat['rows'][loop.index]['thumb']:
|
||||
<div class="home-platforms-instance-list-poster">
|
||||
<div class="home-platforms-list-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][loop.index]['thumb']}&width=300&height=450&fallback=poster);"></div>
|
||||
</div>
|
||||
% else:
|
||||
<div class="home-platforms-instance-poster2">
|
||||
<div class="home-platforms-list-poster-face" style="background-image: url(interfaces/default/images/poster.png);"></div>
|
||||
</div>
|
||||
% endif
|
||||
</a>
|
||||
<div class="home-platforms-instance-list-number">
|
||||
<h4>${loop.index + 1}</h4>
|
||||
</div>
|
||||
</li>
|
||||
% endif
|
||||
% endfor
|
||||
</div>
|
||||
</div>
|
||||
</ul>
|
||||
% endif
|
||||
</li>
|
||||
</div>
|
||||
% elif top_stat['stat_id'] == 'top_music' and top_stat['rows']:
|
||||
<div class="home-platforms-instance">
|
||||
<li>
|
||||
<div class="home-platforms-instance-info">
|
||||
<div class="home-platforms-instance-name">
|
||||
<h4>Most Listened to Artist</h4>
|
||||
</div>
|
||||
<div class="home-platforms-instance-playcount">
|
||||
<h4>
|
||||
<a href="info?rating_key=${top_stat['rows'][0]['rating_key']}" title="${top_stat['rows'][0]['title']}">
|
||||
${top_stat['rows'][0]['title']}
|
||||
</a>
|
||||
</h4>
|
||||
% if top_stat['stat_type'] == 'total_plays':
|
||||
<h3>${top_stat['rows'][0]['total_plays']}</h3>
|
||||
<p> plays</p>
|
||||
% else:
|
||||
${top_stat['rows'][0]['total_duration'] | hd}
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
<a href="info?rating_key=${top_stat['rows'][0]['rating_key']}" title="${top_stat['rows'][0]['title']}">
|
||||
% if top_stat['rows'][0]['grandparent_thumb']:
|
||||
<div class="home-platforms-instance-poster">
|
||||
<div class="home-platforms-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][0]['grandparent_thumb']}&width=300&height=300&fallback=poster);"></div>
|
||||
</div>
|
||||
% else:
|
||||
<div class="home-platforms-instance-poster">
|
||||
<div class="home-platforms-poster-face" style="background-image: url(interfaces/default/images/poster.png);"></div>
|
||||
</div>
|
||||
% endif
|
||||
</a>
|
||||
% if len(top_stat['rows']) > 1:
|
||||
<div class="home-platforms-instance-list-chevron"><i class="fa fa-chevron-down"></i></div>
|
||||
<ul class="list-unstyled">
|
||||
<div class="slider">
|
||||
<div class="home-platforms-instance-list">
|
||||
% for row in top_stat['rows']:
|
||||
% if loop.index > 0:
|
||||
<li>
|
||||
<div class="home-platforms-instance-list-info">
|
||||
<div class="home-platforms-instance-list-name">
|
||||
<h5>
|
||||
<a href="info?rating_key=${top_stat['rows'][loop.index]['rating_key']}" title="${top_stat['rows'][loop.index]['title']}">
|
||||
${top_stat['rows'][loop.index]['title']}
|
||||
</a>
|
||||
</h5>
|
||||
</div>
|
||||
<div class="home-platforms-instance-list-playcount">
|
||||
% if top_stat['stat_type'] == 'total_plays':
|
||||
<h3>${top_stat['rows'][loop.index]['total_plays']}</h3>
|
||||
<p> plays</p>
|
||||
% else:
|
||||
${top_stat['rows'][loop.index]['total_duration'] | hd}
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
<a href="info?rating_key=${top_stat['rows'][loop.index]['rating_key']}" title="${top_stat['rows'][loop.index]['title']}">
|
||||
% if top_stat['rows'][loop.index]['grandparent_thumb']:
|
||||
<div class="home-platforms-instance-list-poster">
|
||||
<div class="home-platforms-list-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][loop.index]['grandparent_thumb']}&width=300&height=300&fallback=poster);"></div>
|
||||
</div>
|
||||
% else:
|
||||
<div class="home-platforms-instance-poster2">
|
||||
<div class="home-platforms-list-poster-face" style="background-image: url(interfaces/default/images/poster.png);"></div>
|
||||
</div>
|
||||
% endif
|
||||
</a>
|
||||
<div class="home-platforms-instance-list-number">
|
||||
<h4>${loop.index + 1}</h4>
|
||||
</div>
|
||||
</li>
|
||||
% endif
|
||||
% endfor
|
||||
</div>
|
||||
</div>
|
||||
</ul>
|
||||
% endif
|
||||
</li>
|
||||
</div>
|
||||
% elif top_stat['stat_id'] == 'popular_music' and top_stat['rows']:
|
||||
<div class="home-platforms-instance">
|
||||
<li>
|
||||
<div class="home-platforms-instance-info">
|
||||
<div class="home-platforms-instance-name">
|
||||
<h4>Most Popular Artist</h4>
|
||||
</div>
|
||||
<div class="home-platforms-instance-playcount">
|
||||
<h4>
|
||||
<a href="info?rating_key=${top_stat['rows'][0]['rating_key']}" title="${top_stat['rows'][0]['title']}">
|
||||
${top_stat['rows'][0]['title']}
|
||||
</a>
|
||||
</h4>
|
||||
<h3>${top_stat['rows'][0]['users_watched']}</h3>
|
||||
<p> users</p>
|
||||
</div>
|
||||
</div>
|
||||
<a href="info?rating_key=${top_stat['rows'][0]['rating_key']}" title="${top_stat['rows'][0]['title']}">
|
||||
% if top_stat['rows'][0]['grandparent_thumb'] != '':
|
||||
<div class="home-platforms-instance-poster">
|
||||
<div class="home-platforms-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][0]['grandparent_thumb']}&width=300&height=300&fallback=poster);"></div>
|
||||
</div>
|
||||
% else:
|
||||
<div class="home-platforms-instance-poster">
|
||||
<div class="home-platforms-poster-face" style="background-image: url(interfaces/default/images/poster.png);"></div>
|
||||
</div>
|
||||
% endif
|
||||
</a>
|
||||
% if len(top_stat['rows']) > 1:
|
||||
<div class="home-platforms-instance-list-chevron"><i class="fa fa-chevron-down"></i></div>
|
||||
<ul class="list-unstyled">
|
||||
<div class="slider">
|
||||
<div class="home-platforms-instance-list">
|
||||
% for row in top_stat['rows']:
|
||||
% if loop.index > 0:
|
||||
<li>
|
||||
<div class="home-platforms-instance-list-info">
|
||||
<div class="home-platforms-instance-list-name">
|
||||
<h5>
|
||||
<a href="info?rating_key=${top_stat['rows'][loop.index]['rating_key']}" title="${top_stat['rows'][loop.index]['title']}">
|
||||
${top_stat['rows'][loop.index]['title']}
|
||||
</a>
|
||||
</h5>
|
||||
</div>
|
||||
<div class="home-platforms-instance-list-playcount">
|
||||
<h3>${top_stat['rows'][loop.index]['users_watched']}</h3>
|
||||
<p> users</p>
|
||||
</div>
|
||||
</div>
|
||||
<a href="info?rating_key=${top_stat['rows'][loop.index]['rating_key']}" title="${top_stat['rows'][loop.index]['title']}">
|
||||
% if top_stat['rows'][loop.index]['grandparent_thumb']:
|
||||
<div class="home-platforms-instance-list-poster">
|
||||
<div class="home-platforms-list-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][loop.index]['grandparent_thumb']}&width=300&height=300&fallback=poster);"></div>
|
||||
</div>
|
||||
% else:
|
||||
<div class="home-platforms-instance-poster2">
|
||||
<div class="home-platforms-list-poster-face" style="background-image: url(interfaces/default/images/poster.png);"></div>
|
||||
</div>
|
||||
% endif
|
||||
</a>
|
||||
<div class="home-platforms-instance-list-number">
|
||||
<h4>${loop.index + 1}</h4>
|
||||
</div>
|
||||
</li>
|
||||
% endif
|
||||
% endfor
|
||||
</div>
|
||||
</div>
|
||||
</ul>
|
||||
% endif
|
||||
</li>
|
||||
</div>
|
||||
% elif top_stat['stat_id'] == 'top_users' and top_stat['rows']:
|
||||
<div class="home-platforms-instance">
|
||||
<li>
|
||||
<div class="home-platforms-instance-info">
|
||||
<div class="home-platforms-instance-name">
|
||||
<h4>Most Active User</h4>
|
||||
</div>
|
||||
<div class="home-platforms-instance-playcount">
|
||||
<h4>
|
||||
% if top_stat['rows'][0]['user_id']:
|
||||
<a href="user?user_id=${top_stat['rows'][0]['user_id']}" title="${top_stat['rows'][0]['friendly_name']}">
|
||||
% else:
|
||||
<a href="user?user=${top_stat['rows'][0]['user']}" title="${top_stat['rows'][0]['friendly_name']}">
|
||||
% endif
|
||||
${top_stat['rows'][0]['friendly_name']}
|
||||
</a>
|
||||
</h4>
|
||||
% if top_stat['stat_type'] == 'total_plays':
|
||||
<h3>${top_stat['rows'][0]['total_plays']}</h3>
|
||||
<p> plays</p>
|
||||
% else:
|
||||
${top_stat['rows'][0]['total_duration'] | hd}
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
% if top_stat['rows'][0]['user_id']:
|
||||
<a href="user?user_id=${top_stat['rows'][0]['user_id']}" title="${top_stat['rows'][0]['friendly_name']}">
|
||||
% if data and any(top_stat['rows'] for top_stat in data):
|
||||
% for top_stat in data:
|
||||
% if top_stat['rows']:
|
||||
<%
|
||||
stat_id = top_stat['stat_id']
|
||||
row0 = top_stat['rows'][0]
|
||||
%>
|
||||
<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:
|
||||
<a href="user?user=${top_stat['rows'][0]['user']}" title="${top_stat['rows'][0]['friendly_name']}">
|
||||
<div id="stats-background-${stat_id}" class="dashboard-stats-background" style="background-image: url(images/art.png);"></div>
|
||||
% endif
|
||||
% if top_stat['rows'][0]['user_thumb'] != '':
|
||||
<div class="home-platforms-instance-poster">
|
||||
<div class="home-platforms-instance-oval" style="background-image: url(${top_stat['rows'][0]['user_thumb']});"></div>
|
||||
</div>
|
||||
% else:
|
||||
<div class="home-platforms-instance-poster">
|
||||
<div class="home-platforms-instance-oval" style="background-image: url(interfaces/default/images/gravatar-default.png);"></div>
|
||||
</div>
|
||||
% 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'):
|
||||
<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>
|
||||
% endif
|
||||
</a>
|
||||
% if len(top_stat['rows']) > 1:
|
||||
<div class="home-platforms-instance-list-chevron"><i class="fa fa-chevron-down"></i></div>
|
||||
<ul class="list-unstyled">
|
||||
<div class="slider">
|
||||
<div class="home-platforms-instance-list">
|
||||
% for row in top_stat['rows']:
|
||||
% if loop.index > 0:
|
||||
<li>
|
||||
<div class="home-platforms-instance-list-info">
|
||||
<div class="home-platforms-instance-list-name">
|
||||
<h5>
|
||||
% if top_stat['rows'][loop.index]['user_id']:
|
||||
<a href="user?user_id=${top_stat['rows'][loop.index]['user_id']}" title="${top_stat['rows'][loop.index]['friendly_name']}">
|
||||
% else:
|
||||
<a href="user?user=${top_stat['rows'][loop.index]['user']}" title="${top_stat['rows'][loop.index]['friendly_name']}">
|
||||
% endif
|
||||
${top_stat['rows'][loop.index]['friendly_name']}
|
||||
</a>
|
||||
</h5>
|
||||
</div>
|
||||
<div class="home-platforms-instance-list-playcount">
|
||||
% if top_stat['stat_type'] == 'total_plays':
|
||||
<h3>${top_stat['rows'][loop.index]['total_plays']}</h3>
|
||||
<p> plays</p>
|
||||
% else:
|
||||
${top_stat['rows'][loop.index]['total_duration'] | hd}
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
% if top_stat['rows'][loop.index]['user_id']:
|
||||
<a href="user?user_id=${top_stat['rows'][loop.index]['user_id']}" title="${top_stat['rows'][loop.index]['friendly_name']}">
|
||||
% else:
|
||||
<a href="user?user=${top_stat['rows'][loop.index]['user']}" title="${top_stat['rows'][loop.index]['friendly_name']}">
|
||||
% endif
|
||||
% if top_stat['rows'][loop.index]['user_thumb'] != '':
|
||||
<div class="home-platforms-instance-poster">
|
||||
<div class="home-platforms-instance-list-oval" style="background-image: url(${top_stat['rows'][loop.index]['user_thumb']});"></div>
|
||||
</div>
|
||||
% else:
|
||||
<div class="home-platforms-instance-poster">
|
||||
<div class="home-platforms-instance-list-oval" style="background-image: url(interfaces/default/images/gravatar-default.png);"></div>
|
||||
</div>
|
||||
% endif
|
||||
</a>
|
||||
<div class="home-platforms-instance-list-number">
|
||||
<h4>${loop.index + 1}</h4>
|
||||
</div>
|
||||
</li>
|
||||
% endif
|
||||
% endfor
|
||||
</div>
|
||||
</div>
|
||||
</ul>
|
||||
% endif
|
||||
</li>
|
||||
</div>
|
||||
% elif top_stat['stat_id'] == 'top_platforms' and top_stat['rows']:
|
||||
<div class="home-platforms-instance">
|
||||
<li>
|
||||
<div class="home-platforms-instance-info">
|
||||
<div class="home-platforms-instance-name">
|
||||
<h4>Most Active Platform</h4>
|
||||
</div>
|
||||
<div class="home-platforms-instance-playcount">
|
||||
<h4 title="${top_stat['rows'][0]['platform_type']}">${top_stat['rows'][0]['platform_type']}</h4>
|
||||
% if top_stat['stat_type'] == 'total_plays':
|
||||
<h3>${top_stat['rows'][0]['total_plays']}</h3>
|
||||
<p> plays</p>
|
||||
<% 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=${height}&fallback=${type});"></div>
|
||||
% else:
|
||||
${top_stat['rows'][0]['total_duration'] | hd}
|
||||
<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':
|
||||
<% 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':
|
||||
<div id="stats-thumb-${stat_id}" class="dashboard-stats-flat svg-icon platform-${row0['platform_name']} transparent hidden-xs"></div>
|
||||
% elif stat_id == 'most_concurrent':
|
||||
<div id="stats-thumb-${stat_id}" class="dashboard-stats-flat svg-icon stats-${stat_id} transparent hidden-xs"></div>
|
||||
% endif
|
||||
<div class="dashboard-stats-info-container">
|
||||
<div id="stats-title-${stat_id}" class="dashboard-stats-info-title">
|
||||
<h4>${top_stat['stat_title']}</h4>
|
||||
% if stat_id.startswith('top') and top_stat.get('stat_type') == 'total_plays':
|
||||
<span class="dashboard-stats-stats-units">plays</span>
|
||||
% elif stat_id.startswith('top') and top_stat.get('stat_type') == 'total_duration':
|
||||
<span class="dashboard-stats-stats-units">hh:mm</span>
|
||||
% elif stat_id.startswith('popular'):
|
||||
<span class="dashboard-stats-stats-units">users</span>
|
||||
% elif stat_id == 'last_watched':
|
||||
<span class="dashboard-stats-stats-units" id="last-watched-header-info">${row0['friendly_name']}</span>
|
||||
% elif stat_id == 'most_concurrent':
|
||||
<span class="dashboard-stats-stats-units" id="most-concurrent-header-info">streams</span>
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
<div id="platform-stat" class="home-platforms-instance-poster" title="${top_stat['rows'][0]['platform_type']}">
|
||||
<script>
|
||||
$("#platform-stat").html("<div class='home-platforms-instance-box' style='background-image: url(" + getPlatformImagePath('${top_stat['rows'][0]['platform_type']}') + ");'>");
|
||||
</script>
|
||||
</div>
|
||||
% if len(top_stat['rows']) > 1:
|
||||
<div class="home-platforms-instance-list-chevron"><i class="fa fa-chevron-down"></i></div>
|
||||
<ul class="list-unstyled">
|
||||
<div class="slider">
|
||||
<div class="home-platforms-instance-list">
|
||||
% for row in top_stat['rows']:
|
||||
% if loop.index > 0:
|
||||
<li>
|
||||
<div class="home-platforms-instance-list-info">
|
||||
<div class="home-platforms-instance-list-name">
|
||||
<h5 title="${top_stat['rows'][loop.index]['platform_type']}">
|
||||
${top_stat['rows'][loop.index]['platform_type']}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="home-platforms-instance-list-playcount">
|
||||
% if top_stat['stat_type'] == 'total_plays':
|
||||
<h3>${top_stat['rows'][loop.index]['total_plays']}</h3>
|
||||
<p> plays</p>
|
||||
% else:
|
||||
${top_stat['rows'][loop.index]['total_duration'] | hd}
|
||||
<div class="dashboard-stats-info-scroller scrollbar-macosx">
|
||||
<div class="dashboard-stats-info scoller-content">
|
||||
<ul class="list-unstyled dashboard-stats-info-list">
|
||||
% 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_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'):
|
||||
<% href = 'info?rating_key={}'.format(row['rating_key']) if row['rating_key'] else '#' %>
|
||||
<a href="${href}" title="${row['title']}">
|
||||
${row['title']}
|
||||
</a>
|
||||
% elif stat_id == 'top_users':
|
||||
<% 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>
|
||||
% elif stat_id == 'top_platforms':
|
||||
${row['platform']}
|
||||
% elif stat_id == 'most_concurrent':
|
||||
${row['title']}
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="home-platforms-instance-poster" id="home-platforms-instance-poster-${loop.index + 1}" title="${top_stat['rows'][loop.index]['platform_type']}">
|
||||
<script>
|
||||
$("#home-platforms-instance-poster-${loop.index + 1}").html("<div class='home-platforms-instance-list-box' style='background-image: url(" + getPlatformImagePath('${top_stat['rows'][loop.index]['platform_type']}') + ");'>");
|
||||
</script>
|
||||
</div>
|
||||
<div class="home-platforms-instance-list-number">
|
||||
<h4>${loop.index + 1}</h4>
|
||||
</div>
|
||||
</li>
|
||||
% endif
|
||||
% endfor
|
||||
<div class="sub-count">
|
||||
% if stat_id.startswith('top') and top_stat.get('stat_type') == 'total_plays':
|
||||
${row['total_plays']}
|
||||
% elif stat_id.startswith('top') and top_stat.get('stat_type') == 'total_duration':
|
||||
${row['total_duration'] | hd}
|
||||
% elif stat_id.startswith('popular'):
|
||||
${row['users_watched']}
|
||||
% elif stat_id == 'most_concurrent':
|
||||
${row['count']}
|
||||
% endif
|
||||
</div>
|
||||
</li>
|
||||
% endfor
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</ul>
|
||||
% endif
|
||||
</li>
|
||||
</div>
|
||||
% elif top_stat['stat_id'] == 'last_watched' and top_stat['rows']:
|
||||
<div class="home-platforms-instance">
|
||||
<li>
|
||||
<div class="home-platforms-instance-info">
|
||||
<div class="home-platforms-instance-name">
|
||||
<h4>Last Watched</h4>
|
||||
</div>
|
||||
<div class="home-platforms-instance-last-user">
|
||||
<h4>
|
||||
<a href="info?source=history&rating_key=${top_stat['rows'][0]['rating_key']}" title="${top_stat['rows'][0]['title']}">
|
||||
${top_stat['rows'][0]['title']}
|
||||
</a>
|
||||
</h4>
|
||||
<h5>
|
||||
% if top_stat['rows'][0]['user_id']:
|
||||
<a href="user?user_id=${top_stat['rows'][0]['user_id']}" title="${top_stat['rows'][0]['friendly_name']}">
|
||||
% else:
|
||||
<a href="user?user=${top_stat['rows'][0]['user']}" title="${top_stat['rows'][0]['friendly_name']}">
|
||||
% endif
|
||||
${top_stat['rows'][0]['friendly_name']}
|
||||
</a>
|
||||
</h5>
|
||||
<p>
|
||||
<span id="last-watch-stat">
|
||||
<script>
|
||||
$('#last-watch-stat').text(moment(${top_stat['rows'][0]['last_watch']},"X").format(date_format));
|
||||
</script>
|
||||
</span> - ${top_stat['rows'][0]['player']}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<a href="info?source=history&rating_key=${top_stat['rows'][0]['rating_key']}" title="${top_stat['rows'][0]['title']}">
|
||||
% if top_stat['rows'][0]['thumb']:
|
||||
<div class="home-platforms-instance-poster">
|
||||
<div class="home-platforms-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][0]['thumb']}&width=300&height=450&fallback=poster);"></div>
|
||||
</div>
|
||||
% else:
|
||||
<div class="home-platforms-instance-poster">
|
||||
<div class="home-platforms-poster-face" style="background-image: url(interfaces/default/images/poster.png);"></div>
|
||||
</div>
|
||||
% endif
|
||||
</a>
|
||||
% if len(top_stat['rows']) > 1:
|
||||
<div class="home-platforms-instance-list-chevron"><i class="fa fa-chevron-down"></i></div>
|
||||
<ul class="list-unstyled">
|
||||
<div class="slider">
|
||||
<div class="home-platforms-instance-list">
|
||||
% for row in top_stat['rows']:
|
||||
% if loop.index > 0:
|
||||
<li>
|
||||
<div class="home-platforms-instance-list-info">
|
||||
<div class="home-platforms-instance-list-name">
|
||||
<h5>
|
||||
<a href="info?source=history&rating_key=${top_stat['rows'][loop.index]['rating_key']}" title="${top_stat['rows'][loop.index]['title']}">
|
||||
${top_stat['rows'][loop.index]['title']}
|
||||
</a>
|
||||
</h5>
|
||||
</div>
|
||||
<div class="home-platforms-instance-list-last-user">
|
||||
<h5>
|
||||
% if top_stat['rows'][loop.index]['user_id']:
|
||||
<a href="user?user_id=${top_stat['rows'][loop.index]['user_id']}" title="${top_stat['rows'][loop.index]['friendly_name']}">
|
||||
% else:
|
||||
<a href="user?user=${top_stat['rows'][loop.index]['user']}" title="${top_stat['rows'][loop.index]['friendly_name']}">
|
||||
% endif
|
||||
${top_stat['rows'][loop.index]['friendly_name']}
|
||||
</a>
|
||||
</h5>
|
||||
<p>
|
||||
<span id="home-platforms-instance-list-last-watch-${loop.index + 1}">
|
||||
<script>
|
||||
$('#home-platforms-instance-list-last-watch-${loop.index + 1}').text(moment(${top_stat['rows'][loop.index]['last_watch']},"X").format(date_format));
|
||||
</script>
|
||||
</span> - ${top_stat['rows'][loop.index]['player']}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<a href="info?source=history&rating_key=${top_stat['rows'][loop.index]['rating_key']}" title="${top_stat['rows'][loop.index]['title']}">
|
||||
% if top_stat['rows'][loop.index]['thumb']:
|
||||
<div class="home-platforms-instance-list-poster">
|
||||
<div class="home-platforms-list-poster-face" style="background-image: url(pms_image_proxy?img=${top_stat['rows'][loop.index]['thumb']}&width=300&height=450&fallback=poster);"></div>
|
||||
</div>
|
||||
% else:
|
||||
<div class="home-platforms-instance-list-poster">
|
||||
<div class="home-platforms-list-poster-face" style="background-image: url(interfaces/default/images/poster.png);"></div>
|
||||
</div>
|
||||
% endif
|
||||
</a>
|
||||
<div class="home-platforms-instance-list-number">
|
||||
<h4>${loop.index + 1}</h4>
|
||||
</div>
|
||||
</li>
|
||||
% endif
|
||||
% endfor
|
||||
</div>
|
||||
</div>
|
||||
</ul>
|
||||
% endif
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
% elif top_stat['stat_id'] == 'most_concurrent' and top_stat['rows']:
|
||||
<div class="home-platforms-instance">
|
||||
<li>
|
||||
<div class="home-platforms-instance-info">
|
||||
<div class="home-platforms-instance-name">
|
||||
<h4>Most Concurrent Streams</h4>
|
||||
</div>
|
||||
<div class="home-platforms-instance-playcount">
|
||||
<h4>
|
||||
<span id="most-concurrent-start">
|
||||
<script>
|
||||
$('#most-concurrent-start').text(moment(${top_stat['rows'][0]['started']},"X").format(date_format + ' ' + time_format));
|
||||
</script>
|
||||
</span>
|
||||
</h4>
|
||||
<h3>${top_stat['rows'][0]['count']}</h3>
|
||||
<p> streams</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="home-platforms-instance-poster">
|
||||
<div class="home-platforms-instance-box" style="background-image: url(interfaces/default/images/home-stat_most-concurrent.png);"></div>
|
||||
</div>
|
||||
% if len(top_stat['rows']) > 1:
|
||||
<div class="home-platforms-instance-list-chevron"><i class="fa fa-chevron-down"></i></div>
|
||||
<ul class="list-unstyled">
|
||||
<div class="slider">
|
||||
<div class="home-platforms-instance-list">
|
||||
% for row in top_stat['rows']:
|
||||
% if loop.index > 0:
|
||||
<li>
|
||||
<div class="home-platforms-instance-list-info">
|
||||
<div class="home-platforms-instance-list-name">
|
||||
<h5>
|
||||
${top_stat['rows'][loop.index]['title']}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="home-platforms-instance-list-playcount">
|
||||
<h3>${top_stat['rows'][loop.index]['count']}</h3>
|
||||
<p> streams
|
||||
% if top_stat['rows'][loop.index]['started']:
|
||||
- <span id="most-concurrent-start-${loop.index + 1}">
|
||||
<script>
|
||||
$('#most-concurrent-start-${loop.index + 1}').text(moment(${top_stat['rows'][loop.index]['started']},"X").format(date_format + ' ' + time_format));
|
||||
</script>
|
||||
</span>
|
||||
% else:
|
||||
- N/A
|
||||
% endif
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="home-platforms-instance-poster">
|
||||
<div class="home-platforms-instance-list-box" style="background-image: url(interfaces/default/images/home-stat_most-concurrent.png);"></div>
|
||||
</div>
|
||||
</li>
|
||||
% endif
|
||||
% endfor
|
||||
</div>
|
||||
</div>
|
||||
</ul>
|
||||
% endif
|
||||
</li>
|
||||
</div>
|
||||
% endif
|
||||
% endfor
|
||||
% else:
|
||||
<div class="text-muted">No stats to show for the selected period.</div><br>
|
||||
% endif
|
||||
</ul>
|
||||
</div>
|
||||
% endif
|
||||
% endfor
|
||||
<script>
|
||||
var topZIndex = 2;
|
||||
$('.home-platforms-instance-list-chevron').on('click', function() {
|
||||
var instanceBoxChevron = $(this);
|
||||
var instanceBox = $(this).parents('.home-platforms-instance');
|
||||
var instanceBoxSlider = instanceBox.find('.slider');
|
||||
|
||||
topZIndex++;
|
||||
instanceBoxChevron.toggleClass('active');
|
||||
instanceBoxSlider.css('z-index', topZIndex);
|
||||
instanceBoxSlider.stop().slideToggle(500);
|
||||
$('.dashboard-stats-instance .dashboard-stats-info-scroller').scrollbar()
|
||||
|
||||
function changeImages(elem) {
|
||||
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(' + (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'));
|
||||
} else {
|
||||
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)');
|
||||
} 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 + ')');
|
||||
} else {
|
||||
$('#stats-thumb-' + stat_id).css('background-image', 'url(images/' + fallback + '.png)');
|
||||
$('#stats-thumb-' + stat_id + '-bg').css('background-image', 'url(images/' + fallback + '.png)');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$('.dashboard-stats-info-item').mouseenter(function () {
|
||||
changeImages(this)
|
||||
if ($(this).data('stat_id') == 'last_watched') {
|
||||
var friendly_name = $(this).data('friendly_name');
|
||||
var last_watch = moment($(this).data('last_watch'), 'X').format(date_format);
|
||||
$('#last-watched-header-info').html(friendly_name);
|
||||
} else if ($(this).data('stat_id') == 'most_concurrent') {
|
||||
var started = moment($(this).data('started'), 'X').format(date_format + ' ' + time_format);
|
||||
$('#most-concurrent-header-info').html(started);
|
||||
}
|
||||
});
|
||||
$('.dashboard-stats-instance').mouseleave(function () {
|
||||
changeImages($(this).find('.dashboard-stats-info-item').first())
|
||||
if ($(this).data('stat_id') == 'last_watched') {
|
||||
$('#last-watched-header-info').text($(this).find('.dashboard-stats-info-item').first().data('friendly_name'));
|
||||
} else if ($(this).data('stat_id') == 'most_concurrent') {
|
||||
$('#most-concurrent-header-info').text('streams');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
% else:
|
||||
|
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/art.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
data/interfaces/default/images/become_a_patron_button.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
data/interfaces/default/images/en-play-badge.png
Normal file
After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 4.3 KiB |
BIN
data/interfaces/default/images/favicon/apple-touch-icon.png
Normal file
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>
|
BIN
data/interfaces/default/images/favicon/favicon-16x16.png
Normal file
After Width: | Height: | Size: 553 B |
BIN
data/interfaces/default/images/favicon/favicon-32x32.png
Normal file
After Width: | Height: | Size: 971 B |
BIN
data/interfaces/default/images/favicon/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
18
data/interfaces/default/images/favicon/manifest.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "Tautulli",
|
||||
"icons": [
|
||||
{
|
||||
"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.5",
|
||||
"sizes": "256x256",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#282a2d",
|
||||
"background_color": "#282a2d",
|
||||
"display": "standalone"
|
||||
}
|
BIN
data/interfaces/default/images/favicon/mstile-150x150.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
@@ -0,0 +1 @@
|
||||
<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>
|
After Width: | Height: | Size: 2.9 KiB |
BIN
data/interfaces/default/images/gold-rect-paypal-34px.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 9.4 KiB |
@@ -0,0 +1,58 @@
|
||||
<?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="483.904px" height="409.539px" viewBox="66.712 194.513 483.904 409.539"
|
||||
enable-background="new 66.712 194.513 483.904 409.539" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path fill="none" d="M41.209,627.586c0-151.979,0-303.963,0-456.118c177.822,0,355.688,0,533.881,0c0,151.922,0,304.021,0,456.118
|
||||
C397.214,627.586,219.233,627.586,41.209,627.586z M461.314,421.571c-110.652,0-220.389,0-329.873,0c0,18.87,0,36.226,0,54.729
|
||||
c110.29,0,219.734,0,329.824,0c0,6.858-0.271,12.563,0.058,18.231c0.659,11.344,8.853,15.958,18.112,9.46
|
||||
c22.087-15.499,43.824-31.503,65.474-47.611c7.678-5.715,7.621-10.63-0.234-16.467c-21.352-15.862-42.87-31.508-64.563-46.902
|
||||
c-9.542-6.773-17.488-2.864-18.691,8.976C460.813,407.959,461.314,414.046,461.314,421.571z M130.996,574.741
|
||||
c74.536,0,148.057,0,222.423,0c0,6.464-0.617,11.836,0.192,16.98c0.662,4.21,2.17,10.746,4.826,11.622
|
||||
c4.329,1.427,10.994,0.66,14.77-1.943c21.297-14.688,42.098-30.1,62.911-45.474c8.887-6.564,8.992-11.503,0.233-18.001
|
||||
c-20.777-15.419-41.647-30.727-62.83-45.579c-3.569-2.503-10.381-4.832-13.03-3.118c-3.676,2.376-5.784,8.447-6.81,13.308
|
||||
c-1.126,5.336-0.257,11.091-0.257,17.757c-75.009,0-148.525,0-222.43,0C130.996,538.558,130.996,555.663,130.996,574.741z
|
||||
M130.83,281.392c49.453,0,97.705,0,146.88,0c0,6.838-0.232,12.506,0.049,18.148c0.585,11.758,9.078,16.246,18.887,9.342
|
||||
c21.767-15.316,43.232-31.064,64.624-46.902c8.01-5.932,7.995-11.301-0.134-17.342c-21.053-15.65-42.257-31.103-63.625-46.318
|
||||
c-10.735-7.647-18.836-3.478-19.734,9.846c-0.378,5.611-0.063,11.27-0.063,18.431c-49.546,0-98.1,0-146.885,0
|
||||
C130.83,245.229,130.83,262.325,130.83,281.392z M188.205,379.701c0,6.939-0.606,12.297,0.19,17.438
|
||||
c0.649,4.198,2.088,10.688,4.753,11.61c4.316,1.496,11.054,0.904,14.8-1.673c21.612-14.857,42.736-30.435,63.834-46.026
|
||||
c8.481-6.265,8.471-11.812-0.159-18.227c-21.054-15.647-42.241-31.123-63.625-46.315c-10.656-7.571-18.899-3.211-19.724,10.07
|
||||
c-0.365,5.875-0.062,11.793-0.062,18.885c-20.45,0-38.938,0-57.202,0c0,18.619,0,35.973,0,54.238
|
||||
C149.953,379.701,168.131,379.701,188.205,379.701z M66.712,280.18c17.568,0,33.699,0,49.59,0c0-18.261,0-35.579,0-53.046
|
||||
c-16.925,0-33.07,0-49.59,0C66.712,245.159,66.712,262.481,66.712,280.18z M66.869,325.488c0,18.556,0,35.876,0,53.543
|
||||
c16.862,0,33,0,49.278,0c0-18.274,0-35.852,0-53.543C99.359,325.488,83.5,325.488,66.869,325.488z M116.673,422.747
|
||||
c-17.484,0-33.644,0-49.668,0c0,17.835,0,34.781,0,51.779c17.017,0,33.178,0,49.668,0
|
||||
C116.673,456.924,116.673,440.208,116.673,422.747z M116.497,520.842c-17.01,0-33.43,0-49.552,0c0,18.244,0,35.566,0,53.021
|
||||
c16.944,0,33.084,0,49.552,0C116.497,555.793,116.497,538.471,116.497,520.842z"/>
|
||||
<path fill="#FFFFFF" d="M461.314,421.571c0-7.525-0.503-13.612,0.105-19.586c1.203-11.839,9.149-15.749,18.691-8.976
|
||||
c21.691,15.396,43.211,31.04,64.563,46.902c7.855,5.837,7.912,10.752,0.234,16.467c-21.648,16.108-43.387,32.112-65.474,47.611
|
||||
c-9.261,6.498-17.453,1.884-18.112-9.46c-0.329-5.67-0.058-11.373-0.058-18.231c-110.09,0-219.534,0-329.824,0
|
||||
c0-18.503,0-35.857,0-54.729C240.927,421.571,350.662,421.571,461.314,421.571z"/>
|
||||
<path fill="#FFFFFF" d="M130.996,574.741c0-19.078,0-36.185,0-54.448c73.904,0,147.42,0,222.43,0
|
||||
c0-6.666-0.869-12.421,0.257-17.757c1.024-4.859,3.134-10.932,6.81-13.308c2.649-1.714,9.461,0.615,13.029,3.118
|
||||
c21.184,14.854,42.054,30.16,62.831,45.579c8.759,6.498,8.652,11.437-0.233,18.001c-20.813,15.374-41.614,30.786-62.911,45.474
|
||||
c-3.774,2.604-10.439,3.369-14.77,1.943c-2.656-0.876-4.165-7.412-4.826-11.622c-0.811-5.146-0.192-10.518-0.192-16.98
|
||||
C279.053,574.741,205.532,574.741,130.996,574.741z"/>
|
||||
<path fill="#FFFFFF" d="M130.83,281.392c0-19.067,0-36.163,0-54.795c48.785,0,97.339,0,146.885,0
|
||||
c0-7.161-0.315-12.82,0.063-18.431c0.898-13.323,8.999-17.493,19.734-9.846c21.368,15.215,42.572,30.668,63.625,46.318
|
||||
c8.129,6.041,8.144,11.411,0.133,17.342c-21.391,15.838-42.855,31.586-64.623,46.902c-9.81,6.904-18.302,2.417-18.887-9.342
|
||||
c-0.281-5.643-0.049-11.31-0.049-18.148C228.535,281.392,180.283,281.392,130.83,281.392z"/>
|
||||
<path fill="#FFFFFF" d="M188.205,379.701c-20.074,0-38.252,0-57.192,0c0-18.265,0-35.619,0-54.238c18.264,0,36.752,0,57.202,0
|
||||
c0-7.092-0.303-13.01,0.062-18.885c0.825-13.281,9.067-17.641,19.724-10.07c21.383,15.193,42.57,30.668,63.625,46.315
|
||||
c8.63,6.416,8.64,11.962,0.159,18.227c-21.099,15.591-42.222,31.169-63.834,46.026c-3.746,2.577-10.484,3.169-14.8,1.673
|
||||
c-2.666-0.923-4.104-7.412-4.753-11.61C187.599,391.999,188.205,386.64,188.205,379.701z"/>
|
||||
<path fill="#FFFFFF" d="M66.712,280.18c0-17.699,0-35.021,0-53.046c16.52,0,32.665,0,49.59,0c0,17.468,0,34.786,0,53.046
|
||||
C100.411,280.18,84.281,280.18,66.712,280.18z"/>
|
||||
<path fill="#FFFFFF" d="M66.869,325.488c16.63,0,32.49,0,49.278,0c0,17.692,0,35.27,0,53.543c-16.278,0-32.416,0-49.278,0
|
||||
C66.869,361.364,66.869,344.044,66.869,325.488z"/>
|
||||
<path fill="#E5A00D" d="M116.673,422.747c0,17.461,0,34.177,0,51.779c-16.49,0-32.652,0-49.668,0c0-16.998,0-33.944,0-51.779
|
||||
C83.029,422.747,99.188,422.747,116.673,422.747z"/>
|
||||
<path fill="#FFFFFF" d="M116.497,520.842c0,17.629,0,34.951,0,53.021c-16.468,0-32.608,0-49.552,0c0-17.455,0-34.777,0-53.021
|
||||
C83.067,520.842,99.487,520.842,116.497,520.842z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.4 KiB |
5
data/interfaces/default/images/libraries/artist.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||
<title>artist</title>
|
||||
<path fill="#fff" d="M9.201 24.681c0-6.699 0-13.358 0-20.035 7.594-1.505 15.157-3.004 22.768-4.513 0 0.172 0 0.319 0 0.465 0 7.067-0.026 14.135 0.021 21.202 0.010 1.498-0.59 2.57-1.716 3.423-1.999 1.512-4.26 2.145-6.751 1.88-0.504-0.054-1.020-0.205-1.481-0.418-1.502-0.695-1.856-2.122-0.908-3.48 0.826-1.184 1.99-1.924 3.302-2.433 1.362-0.528 2.774-0.843 4.252-0.719 0.324 0.027 0.646 0.084 0.994 0.13 0-4.345 0-8.679 0-13.050-6.062 1.204-12.099 2.404-18.16 3.608 0 0.196 0 0.345 0 0.495 0 5.174-0.006 10.349 0.004 15.523 0.003 1.409-0.802 2.302-1.854 3.056-0.889 0.637-1.859 1.114-2.906 1.426-1.524 0.453-3.067 0.627-4.619 0.169-0.952-0.281-1.789-0.736-2.010-1.83-0.136-0.673 0.098-1.269 0.459-1.822 0.772-1.183 1.947-1.853 3.193-2.388 1.662-0.714 3.394-1.043 5.207-0.698 0.048 0.009 0.099 0.005 0.206 0.010z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 979 B |
5
data/interfaces/default/images/libraries/movie.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||
<title>movie</title>
|
||||
<path fill="#fff" d="M0 1.416c10.695 0 21.315 0 32 0 0 9.719 0 19.417 0 29.168-10.635 0-21.293 0-32 0 0-9.699 0-19.399 0-29.168zM9.202 15.129c4.535 0 9.065 0 13.609 0 0-3.798 0-7.579 0-11.355-4.563 0-9.075 0-13.609 0 0 3.801 0 7.551 0 11.355zM9.215 28.909c4.581 0 9.093 0 13.61 0 0-3.818 0-7.585 0-11.382-4.549 0-9.062 0-13.61 0 0 3.803 0 7.57 0 11.382zM6.813 5.983c0-0.753 0-1.467 0-2.209-1.138 0-2.235 0-3.33 0 0 0.766 0 1.48 0 2.209 1.131 0 2.21 0 3.33 0zM25.231 3.754c0 0.783 0 1.494 0 2.219 1.141 0 2.235 0 3.33 0 0-0.763 0-1.476 0-2.219-1.12 0-2.198 0-3.33 0zM25.233 12.938c0 0.777 0 1.492 0 2.19 1.149 0 2.248 0 3.335 0 0-0.754 0-1.452 0-2.19-1.116 0-2.197 0-3.335 0zM25.227 22.074c0 0.783 0 1.496 0 2.218 1.139 0 2.235 0 3.335 0 0-0.758 0-1.471 0-2.218-1.119 0-2.196 0-3.335 0zM3.472 26.689c0 0.768 0 1.481 0 2.221 1.133 0 2.226 0 3.34 0 0-0.763 0-1.475 0-2.221-1.119 0-2.197 0-3.34 0zM28.579 26.711c-1.112 0-2.225 0-3.353 0 0 0.749 0 1.462 0 2.202 1.137 0 2.233 0 3.353 0 0-0.748 0-1.447 0-2.202zM6.817 15.155c0-0.774 0-1.468 0-2.21-1.133 0-2.229 0-3.338 0 0 0.761 0 1.473 0 2.21 1.127 0 2.207 0 3.338 0zM3.463 19.73c1.165 0 2.242 0 3.346 0 0-0.759 0-1.469 0-2.189-1.143 0-2.239 0-3.346 0 0 0.748 0 1.446 0 2.189zM28.559 19.733c0-0.764 0-1.479 0-2.188-1.144 0-2.241 0-3.34 0 0 0.752 0 1.448 0 2.188 1.114 0 2.195 0 3.34 0zM6.791 24.304c0-0.798 0-1.511 0-2.215-1.145 0-2.225 0-3.312 0 0 0.766 0 1.481 0 2.215 1.129 0 2.211 0 3.312 0zM3.489 8.378c0 0.771 0 1.464 0 2.145 1.138 0 2.219 0 3.319 0 0-0.74 0-1.431 0-2.145-1.129 0-2.211 0-3.319 0zM28.554 10.538c0-0.775 0-1.464 0-2.137-1.146 0-2.242 0-3.319 0 0 0.747 0 1.438 0 2.137 1.131 0 2.21 0 3.319 0z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
6
data/interfaces/default/images/libraries/photo.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||
<title>photo</title>
|
||||
<path fill="#fff" d="M25.208 2.266c0 1.514 0 2.976 0 4.513 2.281 0 4.505 0 6.792 0 0 7.681 0 15.287 0 22.955-10.643 0-21.3 0-32 0 0-7.626 0-15.236 0-22.913 2.258 0 4.486 0 6.792 0 0-1.546 0-3.029 0-4.555 6.152 0 12.226 0 18.415 0zM16.008 9.209c-5.026-0.004-9.12 4.079-9.123 9.099-0.004 4.961 4.099 9.069 9.074 9.085 5.022 0.016 9.124-4.047 9.159-9.070 0.035-4.985-4.087-9.109-9.109-9.114z"></path>
|
||||
<path fill="#fff" d="M20.601 18.292c0.003 2.551-2.070 4.626-4.61 4.613-2.558-0.013-4.595-2.069-4.591-4.634 0.003-2.524 2.038-4.557 4.577-4.572 2.562-0.015 4.621 2.030 4.624 4.593z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 746 B |
12
data/interfaces/default/images/libraries/playlist.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||
<title>playlist</title>
|
||||
<path fill="#fff" d="M9.167 10.268c7.653 0 15.208 0 22.82 0 0 1.147 0 2.268 0 3.451-7.583 0-15.172 0-22.82 0 0-1.134 0-2.274 0-3.451z"></path>
|
||||
<path fill="#fff" d="M9.14 21.73c0-1.152 0-2.257 0-3.424 7.609 0 15.212 0 22.86 0 0 1.132 0 2.252 0 3.424-7.607 0-15.191 0-22.86 0z"></path>
|
||||
<path fill="#fff" d="M9.217 5.679c0-1.145 0-2.23 0-3.363 7.562 0 15.101 0 22.683 0 0 1.113 0 2.213 0 3.363-7.539 0-15.080 0-22.683 0z"></path>
|
||||
<path fill="#fff" d="M9.225 29.708c0-1.132 0-2.214 0-3.336 7.558 0 15.086 0 22.668 0 0 1.086 0 2.186 0 3.336-7.526 0-15.071 0-22.668 0z"></path>
|
||||
<path fill="#fff" d="M0.007 10.279c1.58 0 3.043 0 4.551 0 0 1.153 0 2.272 0 3.444-1.511 0-3.011 0-4.551 0 0-1.157 0-2.292 0-3.444z"></path>
|
||||
<path fill="#fff" d="M4.527 2.292c0 1.183 0 2.284 0 3.424-1.496 0-2.957 0-4.479 0 0-1.142 0-2.276 0-3.424 1.51 0 2.971 0 4.479 0z"></path>
|
||||
<path fill="#fff" d="M4.571 18.3c0 1.151 0 2.254 0 3.416-1.515 0-3.019 0-4.571 0 0-1.127 0-2.247 0-3.416 1.513 0 3.001 0 4.571 0z"></path>
|
||||
<path fill="#fff" d="M4.54 26.352c0 1.137 0 2.218 0 3.354-1.494 0-2.975 0-4.506 0 0-1.094 0-2.192 0-3.354 1.489 0 2.965 0 4.506 0z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
6
data/interfaces/default/images/libraries/show.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||
<title>show</title>
|
||||
<path fill="#fff" d="M0 26.565c0-8.38 0-16.706 0-25.089 10.661 0 21.307 0 32 0 0 8.343 0 16.686 0 25.089-10.637 0-21.283 0-32 0zM3.514 4.937c0 5.355 0 10.634 0 15.901 8.375 0 16.691 0 24.994 0 0-5.331 0-10.612 0-15.901-8.356 0-16.656 0-24.994 0z"></path>
|
||||
<path fill="#fff" d="M6.874 30.524c0-0.553 0-1.056 0-1.602 6.084 0 12.136 0 18.25 0 0 0.509 0 1.029 0 1.602-6.050 0-12.12 0-18.25 0z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 555 B |
5
data/interfaces/default/images/libraries/video.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||
<title>video</title>
|
||||
<path fill="#fff" d="M20.662 27.439c-6.971 0-13.797 0-20.662 0 0-7.639 0-15.235 0-22.878 6.846 0 13.672 0 20.588 0 0 2.962 0 5.932 0 9.091 3.87-2.316 7.591-4.542 11.412-6.828 0 6.153 0 12.176 0 18.33-3.769-2.257-7.494-4.488-11.338-6.789 0 3.070 0 6.035 0 9.075z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 430 B |
BIN
data/interfaces/default/images/logo-circle.png
Normal file
After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 8.2 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 |
BIN
data/interfaces/default/images/logo-tautulli.png
Normal file
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 |
BIN
data/interfaces/default/images/logo.png
Normal file
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 |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 5.1 KiB |
BIN
data/interfaces/default/images/media_flags/studio/aic.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 8.4 KiB |
After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 3.3 KiB |
BIN
data/interfaces/default/images/media_flags/studio/chiller.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
data/interfaces/default/images/media_flags/studio/dave.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 5.5 KiB |
BIN
data/interfaces/default/images/media_flags/studio/defa.png
Normal file
After Width: | Height: | Size: 879 B |
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 3.8 KiB |
BIN
data/interfaces/default/images/media_flags/studio/emi.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
data/interfaces/default/images/media_flags/studio/epix.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
data/interfaces/default/images/media_flags/studio/freeform.png
Normal file
After Width: | Height: | Size: 2.3 KiB |