Compare commits

...

6 Commits

Author SHA1 Message Date
JonnyWong16
e908c45cf2 v2.0.4-beta 2017-12-29 16:35:24 -08:00
JonnyWong16
c37e934f42 Check concurrent streams from the server instead of database 2017-12-29 16:29:20 -08:00
JonnyWong16
41f91956b8 Make sure current activity is loaded before refreshing 2017-12-29 16:26:50 -08:00
JonnyWong16
8b2bd5ce79 Add collections info pages 2017-12-29 15:52:08 -08:00
JonnyWong16
28e4151157 Remember tab when going back on library and user pages 2017-12-29 11:34:41 -08:00
JonnyWong16
66d45293e6 Add test Plex Web URL button 2017-12-29 10:47:31 -08:00
16 changed files with 969 additions and 547 deletions

View File

@@ -1,5 +1,17 @@
# Changelog # Changelog
## 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) ## v2.0.3-beta (2017-12-25)
* Monitoring: * Monitoring:

View File

@@ -1808,7 +1808,8 @@ a:hover .summary-poster-face-track .summary-poster-face-overlay span {
margin-left: 2px; margin-left: 2px;
color: #999; color: #999;
} }
#children-list, #search-results-list { .children-list,
.search-results-list {
position: relative; position: relative;
z-index: 0; z-index: 0;
} }
@@ -1874,15 +1875,15 @@ a:hover .item-children-poster {
box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1);
z-index: -2; z-index: -2;
} }
.item-children-poster-face.season-poster { .item-children-poster-face.poster-item {
width: 150px; width: 150px;
height: 225px; height: 225px;
} }
.item-children-poster-face.episode-poster { .item-children-poster-face.episode-item {
width: 250px; width: 250px;
height: 140px; height: 140px;
} }
.item-children-poster-face.album-poster { .item-children-poster-face.cover-item {
width: 150px; width: 150px;
height: 150px; height: 150px;
} }
@@ -1915,15 +1916,13 @@ a:hover .item-children-poster {
margin-bottom: 20px; margin-bottom: 20px;
clear: both; clear: both;
} }
.item-children-instance-text-wrapper.season-item { .item-children-instance-text-wrapper.poster-item,
.item-children-instance-text-wrapper.cover-item {
width: 150px; width: 150px;
} }
.item-children-instance-text-wrapper.episode-item { .item-children-instance-text-wrapper.episode-item {
width: 250px; width: 250px;
} }
.item-children-instance-text-wrapper.album-item {
width: 150px;
}
.item-children-instance-text-wrapper h3 { .item-children-instance-text-wrapper h3 {
width: 100%; width: 100%;
padding: 5px 3px 0 3px; padding: 5px 3px 0 3px;

View File

@@ -235,7 +235,12 @@
} }
}; };
var create_instances = [];
var activity_ready = true;
function getCurrentActivity() { function getCurrentActivity() {
activity_ready = false;
$.ajax({ $.ajax({
url: 'get_activity', url: 'get_activity',
type: 'GET', type: 'GET',
@@ -298,6 +303,7 @@
// Create a new instance if it doesn't exist // Create a new instance if it doesn't exist
if (!(instance.length)) { if (!(instance.length)) {
create_instances.push(key);
getActivityInstance(key); getActivityInstance(key);
return; return;
} }
@@ -501,6 +507,8 @@
$('#currentActivityHeader').text(''); $('#currentActivityHeader').text('');
$('#currentActivity').html('<div id="dashboard-no-activity" class="text-muted">Nothing is currently being played.</div>'); $('#currentActivity').html('<div id="dashboard-no-activity" class="text-muted">Nothing is currently being played.</div>');
} }
activity_ready = true;
} }
}); });
} }
@@ -520,13 +528,20 @@
$('#activity-instance-' + session_key + ' [data-toggle=tooltip]').tooltip({ container: 'body', placement: 'right', delay: 50 }); $('#activity-instance-' + session_key + ' [data-toggle=tooltip]').tooltip({ container: 'body', placement: 'right', delay: 50 });
$('#terminate-button-' + session_key).tooltip('destroy').tooltip({ container: 'body', placement: 'left', delay: 50 }); $('#terminate-button-' + session_key).tooltip('destroy').tooltip({ container: 'body', placement: 'left', delay: 50 });
lockScroll('#activity-instance-' + session_key + ' .dashboard-activity-info-scroller'); lockScroll('#activity-instance-' + session_key + ' .dashboard-activity-info-scroller');
var index = create_instances.indexOf(session_key);
if (index > -1) {
create_instances.splice(index, 1);
}
} }
}); });
} }
getCurrentActivity(); getCurrentActivity();
setInterval(function () { setInterval(function () {
getCurrentActivity(); if (!(create_instances.length) && activity_ready) {
getCurrentActivity();
}
}, 2000); }, 2000);
setInterval(function(){ setInterval(function(){

View File

@@ -80,7 +80,7 @@ DOCUMENTATION :: END
<div class="col-md-12"> <div class="col-md-12">
<div class="summary-navbar-list"> <div class="summary-navbar-list">
<ul class="list-unstyled breadcrumb"> <ul class="list-unstyled breadcrumb">
% if data['media_type'] == 'movie': % if data['media_type'] in ('movie', 'collection'):
<li><a href="library?section_id=${data['section_id']}">${data['library_name']}</a></li> <li><a href="library?section_id=${data['section_id']}">${data['library_name']}</a></li>
<li class="active">${data['title']}</li> <li class="active">${data['title']}</li>
% elif data['media_type'] == 'show': % elif data['media_type'] == 'show':
@@ -116,9 +116,9 @@ DOCUMENTATION :: END
<div class="col-md-9"> <div class="col-md-9">
<div class="summary-content-poster hidden-xs hidden-sm"> <div class="summary-content-poster hidden-xs hidden-sm">
% if data['media_type'] == 'track': % if data['media_type'] == 'track':
<a href="${config['pms_web_url'] or 'https://app.plex.tv/desktop'}#!/server/${config['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${data['parent_rating_key']}" target="_blank" title="View in Plex Web"> <a href="${config['pms_web_url']}#!/server/${config['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${data['parent_rating_key']}" target="_blank" title="View in Plex Web">
% else: % else:
<a href="${config['pms_web_url'] or 'https://app.plex.tv/desktop'}#!/server/${config['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${data['rating_key']}" target="_blank" title="View in Plex Web"> <a href="${config['pms_web_url']}#!/server/${config['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${data['rating_key']}" target="_blank" title="View in Plex Web">
% endif % endif
% if data['media_type'] == 'episode': % if data['media_type'] == 'episode':
<div class="summary-poster-face-episode" style="background-image: url(pms_image_proxy?img=${data['thumb']}&width=500&height=280&fallback=art);"> <div class="summary-poster-face-episode" style="background-image: url(pms_image_proxy?img=${data['thumb']}&width=500&height=280&fallback=art);">
@@ -151,7 +151,7 @@ DOCUMENTATION :: END
</a> </a>
</div> </div>
<div class="summary-content-title"> <div class="summary-content-title">
% if data['media_type'] in ('movie', 'show', 'artist'): % if data['media_type'] in ('movie', 'show', 'artist', 'collection'):
<h1>&nbsp;</h1><h1>${data['title']}</h1> <h1>&nbsp;</h1><h1>${data['title']}</h1>
% elif data['media_type'] == 'season': % elif data['media_type'] == 'season':
<h1>&nbsp;</h1><h1><a href="info?rating_key=${data['parent_rating_key']}">${data['parent_title']}</a></h1> <h1>&nbsp;</h1><h1><a href="info?rating_key=${data['parent_rating_key']}">${data['parent_title']}</a></h1>
@@ -175,7 +175,7 @@ DOCUMENTATION :: END
<div class="col-md-9"> <div class="col-md-9">
% if data['media_type'] == 'movie': % if data['media_type'] == 'movie':
<div class="summary-content-padding hidden-xs hidden-sm" style="height: 305px;"> <div class="summary-content-padding hidden-xs hidden-sm" style="height: 305px;">
% elif data['media_type'] == 'show' or data['media_type'] == 'season': % elif data['media_type'] in ('show', 'season', 'collection'):
<div class="summary-content-padding hidden-xs hidden-sm" style="height: 270px;"> <div class="summary-content-padding hidden-xs hidden-sm" style="height: 270px;">
% elif data['media_type'] == 'episode': % elif data['media_type'] == 'episode':
<div class="summary-content-padding hidden-xs hidden-sm" style="height: 70px;"> <div class="summary-content-padding hidden-xs hidden-sm" style="height: 70px;">
@@ -235,6 +235,8 @@ DOCUMENTATION :: END
Aired <strong> <span id="airdate">${data['originally_available_at']}</span></strong> Aired <strong> <span id="airdate">${data['originally_available_at']}</span></strong>
% elif data['media_type'] == 'album' or data['media_type'] == 'track': % elif data['media_type'] == 'album' or data['media_type'] == 'track':
Released <strong> ${data['year']}</strong> Released <strong> ${data['year']}</strong>
% elif data['media_type'] == 'collection':
Year <strong> ${data['min_year']} - ${data['max_year']}</strong>
% endif % endif
</div> </div>
<div class="summary-content-details-tag"> <div class="summary-content-details-tag">
@@ -308,51 +310,65 @@ DOCUMENTATION :: END
</div> </div>
% if data['media_type'] == 'show': % if data['media_type'] == 'show':
<div class="col-md-12"> <div class="col-md-12">
<div class='table-card-header'> <div class="table-card-header">
<div class="header-bar"> <div class="header-bar">
<span>Season List for <strong>${data['title']}</strong></span> <span>Season List for <strong>${data['title']}</strong></span>
</div> </div>
</div> </div>
<div class='table-card-back'> <div class="table-card-back">
<div id="children-list"><i class="fa fa-refresh fa-spin"></i>&nbsp; Loading season list...</div> <div id="children-list" class="children-list"><i class="fa fa-refresh fa-spin"></i>&nbsp; Loading season list...</div>
</div> </div>
</div> </div>
% elif data['media_type'] == 'season': % elif data['media_type'] == 'season':
<div class="col-md-12"> <div class="col-md-12">
<div class='table-card-header'> <div class="table-card-header">
<div class="header-bar"> <div class="header-bar">
<span>Episode List for <strong>${data['title']}</strong></span> <span>Episode List for <strong>${data['title']}</strong></span>
</div> </div>
</div> </div>
<div class='table-card-back'> <div class="table-card-back">
<div id="children-list"><i class="fa fa-refresh fa-spin"></i>&nbsp; Loading episode list...</div> <div id="children-list" class="children-list"><i class="fa fa-refresh fa-spin"></i>&nbsp; Loading episode list...</div>
</div> </div>
</div> </div>
% elif data['media_type'] == 'artist': % elif data['media_type'] == 'artist':
<div class="col-md-12"> <div class="col-md-12">
<div class='table-card-header'> <div class="table-card-header">
<div class="header-bar"> <div class="header-bar">
<span>Album List for <strong>${data['title']}</strong></span> <span>Album List for <strong>${data['title']}</strong></span>
</div> </div>
</div> </div>
<div class='table-card-back'> <div class="table-card-back">
<div id="children-list"><i class="fa fa-refresh fa-spin"></i>&nbsp; Loading album list...</div> <div id="children-list" class="children-list"><i class="fa fa-refresh fa-spin"></i>&nbsp; Loading album list...</div>
</div> </div>
</div> </div>
% elif data['media_type'] == 'album': % elif data['media_type'] == 'album':
<div class="col-md-12"> <div class="col-md-12">
<div class='table-card-header'> <div class="table-card-header">
<div class="header-bar"> <div class="header-bar">
<span>Track List for <strong>${data['title']}</strong></span> <span>Track List for <strong>${data['title']}</strong></span>
</div> </div>
</div> </div>
<div class='table-card-back'> <div class="table-card-back">
<div id="children-list"><i class="fa fa-refresh fa-spin"></i>&nbsp; Loading track list...</div> <div id="children-list" class="children-list"><i class="fa fa-refresh fa-spin"></i>&nbsp; Loading track list...</div>
</div> </div>
</div> </div>
% endif % elif data['media_type'] == 'collection':
<div class="col-md-12"> <div class="col-md-12">
<div class='table-card-header'> <div class="table-card-header">
<div class="header-bar">
<span>Movies in <strong>${data['title']}</strong> collection</span>
</div>
</div>
<div class="table-card-back">
<div id="children-list" class="children-list"><i class="fa fa-refresh fa-spin"></i>&nbsp; Loading movies list...</div>
</div>
</div>
<div id="collection-related-list-container" style="display: none;">
</div>
% endif
% if data['media_type'] != 'collection':
<div class="col-md-12">
<div class="table-card-header">
<div class="header-bar"> <div class="header-bar">
<span>Watch History for <strong>${data['title']}</strong></span> <span>Watch History for <strong>${data['title']}</strong></span>
</div> </div>
@@ -420,6 +436,7 @@ DOCUMENTATION :: END
</table> </table>
</div> </div>
</div> </div>
% endif
</div> </div>
</div> </div>
</div> </div>
@@ -562,6 +579,7 @@ DOCUMENTATION :: END
} }
</script> </script>
% endif % endif
% if data['media_type'] != 'collection':
<script> <script>
$(document).ready(function () { $(document).ready(function () {
get_history(); get_history();
@@ -638,7 +656,8 @@ DOCUMENTATION :: END
}); });
}); });
</script> </script>
% if data['media_type'] in ('show', 'season', 'artist', 'album'): % endif
% if data['media_type'] in ('show', 'season', 'artist', 'album', 'collection'):
<script> <script>
$.ajax({ $.ajax({
url: 'get_item_children', url: 'get_item_children',
@@ -646,7 +665,24 @@ DOCUMENTATION :: END
async: true, async: true,
data: { rating_key : "${data['rating_key']}" }, data: { rating_key : "${data['rating_key']}" },
complete: function(xhr, status) { complete: function(xhr, status) {
$("#children-list").html(xhr.responseText); } $("#children-list").html(xhr.responseText);
}
});
</script>
% endif
% if data['media_type'] == 'collection':
<script>
$.ajax({
url: 'get_item_children_related',
type: 'GET',
async: true,
data: {
rating_key : "${data['rating_key']}",
title: "${data['title']}"
},
complete: function(xhr, status) {
$("#collection-related-list-container").html(xhr.responseText).show();
}
}); });
</script> </script>
% endif % endif

View File

@@ -37,13 +37,42 @@ DOCUMENTATION :: END
% else: % else:
<li> <li>
% endif % endif
%if data['children_type'] == 'season': % if data['children_type'] == 'movie':
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">
<div class="item-children-poster">
<div class="item-children-poster-face poster-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div>
% if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif
</div>
</a>
<div class="item-children-instance-text-wrapper poster-item">
<h3>
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">${child['title']}</a>
</h3>
<h3 class="text-muted">${child['year']}</h3>
</div>
% elif data['children_type'] == 'show':
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">
<div class="item-children-poster">
<div class="item-children-poster-face poster-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div>
% if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif
</div>
</a>
<div class="item-children-instance-text-wrapper poster-item">
<h3>
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">${child['title']}</a>
</h3>
</div>
% elif data['children_type'] == 'season':
<a href="info?rating_key=${child['rating_key']}" title="Season ${child['media_index']}"> <a href="info?rating_key=${child['rating_key']}" title="Season ${child['media_index']}">
<div class="item-children-poster"> <div class="item-children-poster">
% if child['thumb']: % if child['thumb']:
<div class="item-children-poster-face season-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"> <div class="item-children-poster-face poster-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);">
% else: % else:
<div class="item-children-poster-face season-poster" style="background-image: url(pms_image_proxy?img=${child['parent_thumb']}&width=300&height=450&fallback=poster);"> <div class="item-children-poster-face poster-item" style="background-image: url(pms_image_proxy?img=${child['parent_thumb']}&width=300&height=450&fallback=poster);">
% endif % endif
<div class="item-children-card-overlay"> <div class="item-children-card-overlay">
<div class="item-children-overlay-text"> <div class="item-children-overlay-text">
@@ -59,7 +88,7 @@ DOCUMENTATION :: END
% elif data['children_type'] == 'episode': % elif data['children_type'] == 'episode':
<a href="info?rating_key=${child['rating_key']}" title="Episode ${child['media_index']}"> <a href="info?rating_key=${child['rating_key']}" title="Episode ${child['media_index']}">
<div class="item-children-poster"> <div class="item-children-poster">
<div class="item-children-poster-face episode-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=500&height=250&fallback=art);"> <div class="item-children-poster-face episode-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=500&height=250&fallback=art);">
<div class="item-children-card-overlay"> <div class="item-children-card-overlay">
<div class="item-children-overlay-text"> <div class="item-children-overlay-text">
Episode ${child['media_index']} Episode ${child['media_index']}
@@ -79,13 +108,13 @@ DOCUMENTATION :: END
% elif data['children_type'] == 'album': % elif data['children_type'] == 'album':
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}"> <a href="info?rating_key=${child['rating_key']}" title="${child['title']}">
<div class="item-children-poster"> <div class="item-children-poster">
<div class="item-children-poster-face album-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=300&fallback=cover);"></div> <div class="item-children-poster-face cover-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=300&fallback=cover);"></div>
% if _session['user_group'] == 'admin': % if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span> <span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif % endif
</div> </div>
</a> </a>
<div class="item-children-instance-text-wrapper album-item"> <div class="item-children-instance-text-wrapper cover-item">
<h3> <h3>
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">${child['title']}</a> <a href="info?rating_key=${child['rating_key']}" title="${child['title']}">${child['title']}</a>
</h3> </h3>

View File

@@ -0,0 +1,99 @@
<%doc>
USAGE DOCUMENTATION :: PLEASE LEAVE THIS AT THE TOP OF THIS FILE
For Mako templating syntax documentation please visit: http://docs.makotemplates.org/en/latest/
Filename: info_collection_list.html
Version: 0.1
Variable names: data [list]
data :: Usable parameters
== Global keys ==
children_type Returns the type of children in the array.
children_count Returns the number of episodes in the array.
children_list Returns an array of episodes.
data['children_list'] :: Usable paramaters
== Global keys ==
rating_key Returns the unique identifier for the media item.
media_index Returns the episode number.
title Returns the name of the episode.
thumb Returns the location of the item's thumbnail. Use with pms_image_proxy.
parent_thumb Returns the location of the item's parent thumbnail. Use with pms_image_proxy.
DOCUMENTATION :: END
</%doc>
% if data != None:
<%
types = ('movie', 'show', 'artist', 'album')
headers = {'movie': 'Movies',
'show': 'TV Shows',
'season': 'Seasons',
'episode': 'Episodes',
'artist': 'Artists',
'album': 'Albums',
'track': 'Tracks',
}
%>
% for media_type in types:
% if data['results_list'][media_type]:
<div class="col-md-12">
<div class="table-card-header">
<div class="header-bar">
<span>${headers[media_type]} in <strong>${title}</strong> collection</span>
</div>
</div>
<div class="table-card-back">
<div id="children-list" class="children-list">
<div class="item-children-wrapper">
<ul class="item-children-instance list-unstyled">
% for child in data['results_list'][media_type]:
<li>
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">
<div class="item-children-poster">
% if media_type in ('artist', 'album'):
<div class="item-children-poster-face cover-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=300&fallback=cover);"></div>
% else:
<div class="item-children-poster-face poster-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div>
% endif
% if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif
</div>
</a>
% if media_type == 'artist':
<div class="item-children-instance-text-wrapper cover-item">
<h3>
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">${child['title']}</a>
</h3>
</div>
% elif media_type == 'album':
<div class="item-children-instance-text-wrapper cover-item">
<h3>
<a href="info?rating_key=${child['parent_rating_key']}" title="${child['parent_title']}">${child['parent_title']}</a>
</h3>
<h3>
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">${child['title']}</a>
</h3>
</div>
% else:
<div class="item-children-instance-text-wrapper poster-item">
<h3>
<a href="info?rating_key=${child['rating_key']}" title="${child['title']}">${child['title']}</a>
</h3>
<h3 class="text-muted">${child['year']}</h3>
</div>
% endif
</li>
% endfor
</ul>
</div>
</div>
</div>
</div>
% endif
% endfor
% endif

View File

@@ -54,6 +54,31 @@ DOCUMENTATION :: END
% if data != None: % if data != None:
% if data['results_count'] > 0: % if data['results_count'] > 0:
% if 'collection' in data['results_list'] and data['results_list']['collection']:
<div class="item-children-wrapper">
<div class="item-children-section-title">
<h4>Collections</h4>
</div>
<ul class="item-children-instance list-unstyled">
% for child in data['results_list']['collection']:
<li>
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<div class="item-children-poster">
<div class="item-children-poster-face poster-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div>
% if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif
</div>
<div class="item-children-instance-text-wrapper poster-item">
<h3 title="${child['title']}">${child['title']}</h3>
<h3 class="text-muted">${child['min_year']} - ${child['max_year']}</h3>
</div>
</a>
</li>
% endfor
</ul>
</div>
% endif
% if 'movie' in data['results_list'] and data['results_list']['movie']: % if 'movie' in data['results_list'] and data['results_list']['movie']:
<div class="item-children-wrapper"> <div class="item-children-wrapper">
<div class="item-children-section-title"> <div class="item-children-section-title">
@@ -64,12 +89,12 @@ DOCUMENTATION :: END
<li> <li>
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}"> <a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<div class="item-children-poster"> <div class="item-children-poster">
<div class="item-children-poster-face season-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div> <div class="item-children-poster-face poster-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div>
% if _session['user_group'] == 'admin': % if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span> <span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif % endif
</div> </div>
<div class="item-children-instance-text-wrapper season-item"> <div class="item-children-instance-text-wrapper poster-item">
<h3 title="${child['title']}">${child['title']}</h3> <h3 title="${child['title']}">${child['title']}</h3>
<h3 class="text-muted">${child['year']}</h3> <h3 class="text-muted">${child['year']}</h3>
</div> </div>
@@ -89,12 +114,12 @@ DOCUMENTATION :: END
<li> <li>
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}"> <a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<div class="item-children-poster"> <div class="item-children-poster">
<div class="item-children-poster-face season-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div> <div class="item-children-poster-face poster-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div>
% if _session['user_group'] == 'admin': % if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span> <span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif % endif
</div> </div>
<div class="item-children-instance-text-wrapper season-item"> <div class="item-children-instance-text-wrapper poster-item">
<h3 title="${child['title']}">${child['title']}</h3> <h3 title="${child['title']}">${child['title']}</h3>
<h3 class="text-muted">${child['year']}</h3> <h3 class="text-muted">${child['year']}</h3>
</div> </div>
@@ -114,12 +139,12 @@ DOCUMENTATION :: END
<li> <li>
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}"> <a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<div class="item-children-poster"> <div class="item-children-poster">
<div class="item-children-poster-face season-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div> <div class="item-children-poster-face poster-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=450&fallback=poster);"></div>
% if _session['user_group'] == 'admin': % if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span> <span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif % endif
</div> </div>
<div class="item-children-instance-text-wrapper season-item"> <div class="item-children-instance-text-wrapper poster-item">
<h3 title="${child['parent_title']}">${child['parent_title']}</h3> <h3 title="${child['parent_title']}">${child['parent_title']}</h3>
<h3 class="text-muted">S${child['media_index']}</h3> <h3 class="text-muted">S${child['media_index']}</h3>
</div> </div>
@@ -139,7 +164,7 @@ DOCUMENTATION :: END
<li> <li>
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}"> <a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<div class="item-children-poster"> <div class="item-children-poster">
<div class="item-children-poster-face episode-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=500&height=250&fallback=art);"></div> <div class="item-children-poster-face episode-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=500&height=250&fallback=art);"></div>
% if _session['user_group'] == 'admin': % if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span> <span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif % endif
@@ -165,7 +190,7 @@ DOCUMENTATION :: END
<li> <li>
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}"> <a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<div class="item-children-poster"> <div class="item-children-poster">
<div class="item-children-poster-face album-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=300&fallback=cover);"></div> <div class="item-children-poster-face cover-item style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=300&fallback=cover);"></div>
% if _session['user_group'] == 'admin': % if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span> <span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif % endif
@@ -189,7 +214,7 @@ DOCUMENTATION :: END
<li> <li>
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}"> <a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<div class="item-children-poster"> <div class="item-children-poster">
<div class="item-children-poster-face album-poster" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=300&fallback=cover);"></div> <div class="item-children-poster-face cover-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=300&fallback=cover);"></div>
% if _session['user_group'] == 'admin': % if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span> <span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif % endif
@@ -214,7 +239,7 @@ DOCUMENTATION :: END
<li> <li>
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}"> <a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<div class="item-children-poster"> <div class="item-children-poster">
<div class="item-children-poster-face album-poster" style="background-image: url(pms_image_proxy?img=${child['parent_thumb']}&width=300&height=300&fallback=cover);"> <div class="item-children-poster-face cover-item" style="background-image: url(pms_image_proxy?img=${child['parent_thumb']}&width=300&height=300&fallback=cover);">
<div class="item-children-card-overlay"> <div class="item-children-card-overlay">
<div class="item-children-overlay-text"> <div class="item-children-overlay-text">
Track ${child['media_index']} Track ${child['media_index']}

View File

@@ -71,11 +71,11 @@ DOCUMENTATION :: END
% endif % endif
</div> </div>
<div class="user-info-nav"> <div class="user-info-nav">
<ul class="user-info-nav"> <ul class="user-info-nav" role="tablist">
<li class="active"><a href="#profile" data-toggle="tab">Profile</a></li> <li class="active"><a href="#tabs-profile" role="tab" data-toggle="tab">Profile</a></li>
<li><a id="history-tab-btn" href="#libraryHistory" data-toggle="tab">History</a></li> <li><a id="history-tab-btn" href="#tabs-history" role="tab" data-toggle="tab">History</a></li>
% if _session['user_group'] == 'admin': % if _session['user_group'] == 'admin':
<li><a id="media-info-tab-btn" href="#libraryMediaInfo" data-toggle="tab">Media Info</a></li> <li><a id="media-info-tab-btn" href="#tabs-mediainfo" role="tab" data-toggle="tab">Media Info</a></li>
% endif % endif
</ul> </ul>
</div> </div>
@@ -83,7 +83,7 @@ DOCUMENTATION :: END
</div> </div>
</div> </div>
<div class="tab-content"> <div class="tab-content">
<div class="tab-pane active" id="profile"> <div role="tabpanel" class="tab-pane active" id="tabs-profile">
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
@@ -169,7 +169,7 @@ DOCUMENTATION :: END
</div> </div>
</div> </div>
</div> </div>
<div class="tab-pane" id="libraryHistory"> <div role="tabpanel" class="tab-pane" id="tabs-history">
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
@@ -221,7 +221,7 @@ DOCUMENTATION :: END
</div> </div>
</div> </div>
</div> </div>
<div class="tab-pane" id="libraryMediaInfo"> <div role="tabpanel" class="tab-pane" id="tabs-mediainfo">
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
@@ -366,8 +366,253 @@ DOCUMENTATION :: END
<script src="${http_root}js/tables/history_table.js${cache_param}"></script> <script src="${http_root}js/tables/history_table.js${cache_param}"></script>
<script src="${http_root}js/tables/media_info_table.js${cache_param}"></script> <script src="${http_root}js/tables/media_info_table.js${cache_param}"></script>
<script> <script>
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
$.fn.dataTable.tables({ visible: true, api: true }).columns.adjust();
});
function loadHistoryTable() {
// Build watch history table
history_table_options.ajax = {
url: 'get_history',
type: 'post',
data: function ( d ) {
return {
json_data: JSON.stringify( d ),
section_id: section_id,
user_id: "${_session['user_id']}" == "None" ? null : "${_session['user_id']}"
};
}
}
history_table = $('#history_table-SID-${data["section_id"]}').DataTable(history_table_options);
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] });
$(colvis.button()).appendTo('#button-bar-history');
clearSearchButton('history_table-SID-${data["section_id"]}', history_table);
}
$('a[href="#tabs-history"]').on('shown.bs.tab', function() {
loadHistoryTable();
});
% if _session['user_group'] == 'admin':
function loadMediaInfoTable() {
// Build media info table
media_info_table_options.ajax = {
url: 'get_library_media_info',
type: 'post',
data: function ( d ) {
return {
json_data: JSON.stringify( d ),
section_id: section_id,
refresh: refresh_table
};
}
}
media_info_table = $('#media_info_table-SID-${data["section_id"]}').DataTable(media_info_table_options);
var colvis = new $.fn.dataTable.ColVis(media_info_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark' });
$(colvis.button()).appendTo('#button-bar-media-info');
clearSearchButton('media_info_table-SID-${data["section_id"]}', media_info_table);
}
$('a[href="#tabs-mediainfo"]').on('shown.bs.tab', function() {
loadMediaInfoTable();
});
$("#refresh-media-info-table").click(function () {
media_info_child_table = {};
refresh_table = true;
refresh_child_tables = true;
media_info_table.draw();
refresh_table = false;
});
$("#edit-library-tooltip").tooltip();
// Load edit library modal
$("#toggle-edit-library-modal").click(function() {
$("#edit-library-tooltip").tooltip('hide');
$.ajax({
url: 'edit_library_dialog',
data: { section_id: section_id },
cache: false,
async: true,
complete: function(xhr, status) {
$("#edit-library-modal").html(xhr.responseText);
}
});
});
$('#row-edit-mode').on('click', function() {
$('#row-edit-mode-alert').fadeIn(200);
if ($(this).hasClass('active')) {
if (history_to_delete.length > 0) {
$('#deleteCount').text(history_to_delete.length);
$('#confirm-modal-delete').modal();
$('#confirm-modal-delete').one('click', '#confirm-delete', function () {
history_to_delete.forEach(function(row, idx) {
$.ajax({
url: 'delete_history_rows',
type: 'POST',
data: { row_id: row },
async: true,
success: function (data) {
var msg = "History deleted";
showMsg(msg, false, true, 2000);
}
});
});
history_table.draw();
});
}
$('.delete-control').each(function () {
$(this).addClass('hidden');
$('#row-edit-mode-alert').fadeOut(200);
});
} else {
history_to_delete = [];
$('.delete-control').each(function() {
$(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger');
$(this).removeClass('hidden');
});
}
});
% endif
$("#refresh-history-list").click(function () {
history_table.draw();
});
function recentlyWatched() {
// Populate recently watched
$.ajax({
url: 'library_recently_watched',
async: true,
data: {
section_id: section_id,
limit: 50
},
complete: function(xhr, status) {
$("#library-recently-watched").html(xhr.responseText);
highlightWatchedScrollerButton();
}
});
}
function recentlyAdded() {
// Populate recently added
$.ajax({
url: 'library_recently_added',
async: true,
data: {
section_id: section_id,
limit: 50
},
complete: function(xhr, status) {
$("#library-recently-added").html(xhr.responseText);
highlightAddedScrollerButton();
}
});
}
recentlyWatched();
recentlyAdded();
function highlightWatchedScrollerButton() {
var scroller = $("#recently-watched-row-scroller");
var numElems = scroller.find("li").length;
scroller.width(numElems * 175);
if (scroller.width() > $("#library-recently-watched").width()) {
$("#recently-watched-page-right").removeClass("disabled");
} else {
$("#recently-watched-page-right").addClass("disabled");
}
}
function highlightAddedScrollerButton() {
var scroller = $("#recently-added-row-scroller");
var numElems = scroller.find("li").length;
scroller.width(numElems * 175);
if (scroller.width() > $("#library-recently-added").width()) {
$("#recently-added-page-right").removeClass("disabled");
} else {
$("#recently-added-page-right").addClass("disabled");
}
}
$(window).resize(function() {
highlightWatchedScrollerButton();
highlightAddedScrollerButton();
});
$('div.art-face').animate({ opacity: 0.2 }, { duration: 1000 });
var leftTotalWatched = 0;
$(".paginate-watched").click(function (e) {
e.preventDefault();
var scroller = $("#recently-watched-row-scroller");
var containerWidth = $("#library-recently-watched").width();
var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175;
var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0);
leftTotalWatched = Math.max(Math.min(leftTotalWatched + scrollAmount, 0), leftMax);
scroller.animate({ left: leftTotalWatched }, 250);
if (leftTotalWatched == 0) {
$("#recently-watched-page-left").addClass("disabled").blur();
} else {
$("#recently-watched-page-left").removeClass("disabled");
}
if (leftTotalWatched == leftMax) {
$("#recently-watched-page-right").addClass("disabled").blur();
} else {
$("#recently-watched-page-right").removeClass("disabled");
}
});
var leftTotalAdded = 0;
$(".paginate-added").click(function (e) {
e.preventDefault();
var scroller = $("#recently-added-row-scroller");
var containerWidth = $("#library-recently-added").width();
var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175;
var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0);
leftTotalAdded = Math.max(Math.min(leftTotalAdded + scrollAmount, 0), leftMax);
scroller.animate({ left: leftTotalAdded }, 250);
if (leftTotalAdded == 0) {
$("#recently-added-page-left").addClass("disabled").blur();
} else {
$("#recently-added-page-left").removeClass("disabled");
}
if (leftTotalAdded == leftMax) {
$("#recently-added-page-right").addClass("disabled").blur();
} else {
$("#recently-added-page-right").removeClass("disabled");
}
});
$(document).ready(function () { $(document).ready(function () {
$("#edit-library-tooltip").tooltip();
// Javascript to enable link to tab
var hash = document.location.hash;
var prefix = "tab_";
if (hash) {
$('.user-info-nav a[href='+hash.replace(prefix,"")+']').tab('show').trigger('show.bs.tab');
}
// Change hash for page-reload
$('.user-info-nav a').on('shown.bs.tab', function (e) {
window.location.hash = e.target.hash.replace("#", "#" + prefix);
});
// Populate watch time stats // Populate watch time stats
$.ajax({ $.ajax({
@@ -389,237 +634,6 @@ DOCUMENTATION :: END
} }
}); });
function loadHistoryTable() {
// Build watch history table
history_table_options.ajax = {
url: 'get_history',
type: 'post',
data: function ( d ) {
return {
json_data: JSON.stringify( d ),
section_id: section_id,
user_id: "${_session['user_id']}" == "None" ? null : "${_session['user_id']}"
};
}
}
history_table = $('#history_table-SID-${data["section_id"]}').DataTable(history_table_options);
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] });
$(colvis.button()).appendTo('#button-bar-history');
clearSearchButton('history_table-SID-${data["section_id"]}', history_table);
}
$( "#history-tab-btn" ).one( "click", function() {
loadHistoryTable();
});
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
$.fn.dataTable.tables({ visible: true, api: true }).columns.adjust();
});
% if _session['user_group'] == 'admin':
function loadMediaInfoTable() {
// Build media info table
media_info_table_options.ajax = {
url: 'get_library_media_info',
type: 'post',
data: function ( d ) {
return {
json_data: JSON.stringify( d ),
section_id: section_id,
refresh: refresh_table
};
}
}
media_info_table = $('#media_info_table-SID-${data["section_id"]}').DataTable(media_info_table_options);
var colvis = new $.fn.dataTable.ColVis(media_info_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark' });
$(colvis.button()).appendTo('#button-bar-media-info');
clearSearchButton('media_info_table-SID-${data["section_id"]}', media_info_table);
}
$( "#media-info-tab-btn" ).one( "click", function() {
loadMediaInfoTable();
});
$("#refresh-media-info-table").click(function () {
media_info_child_table = {};
refresh_table = true;
refresh_child_tables = true;
media_info_table.draw();
refresh_table = false;
});
// Load edit library modal
$("#toggle-edit-library-modal").click(function() {
$("#edit-library-tooltip").tooltip('hide');
$.ajax({
url: 'edit_library_dialog',
data: { section_id: section_id },
cache: false,
async: true,
complete: function(xhr, status) {
$("#edit-library-modal").html(xhr.responseText);
}
});
});
$('#row-edit-mode').on('click', function() {
$('#row-edit-mode-alert').fadeIn(200);
if ($(this).hasClass('active')) {
if (history_to_delete.length > 0) {
$('#deleteCount').text(history_to_delete.length);
$('#confirm-modal-delete').modal();
$('#confirm-modal-delete').one('click', '#confirm-delete', function () {
history_to_delete.forEach(function(row, idx) {
$.ajax({
url: 'delete_history_rows',
type: 'POST',
data: { row_id: row },
async: true,
success: function (data) {
var msg = "History deleted";
showMsg(msg, false, true, 2000);
}
});
});
history_table.draw();
});
}
$('.delete-control').each(function () {
$(this).addClass('hidden');
$('#row-edit-mode-alert').fadeOut(200);
});
} else {
history_to_delete = [];
$('.delete-control').each(function() {
$(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger');
$(this).removeClass('hidden');
});
}
});
% endif
$("#refresh-history-list").click(function () {
history_table.draw();
});
function recentlyWatched() {
// Populate recently watched
$.ajax({
url: 'library_recently_watched',
async: true,
data: {
section_id: section_id,
limit: 50
},
complete: function(xhr, status) {
$("#library-recently-watched").html(xhr.responseText);
highlightWatchedScrollerButton();
}
});
}
function recentlyAdded() {
// Populate recently added
$.ajax({
url: 'library_recently_added',
async: true,
data: {
section_id: section_id,
limit: 50
},
complete: function(xhr, status) {
$("#library-recently-added").html(xhr.responseText);
highlightAddedScrollerButton();
}
});
}
recentlyWatched();
recentlyAdded();
function highlightWatchedScrollerButton() {
var scroller = $("#recently-watched-row-scroller");
var numElems = scroller.find("li").length;
scroller.width(numElems * 175);
if (scroller.width() > $("#library-recently-watched").width()) {
$("#recently-watched-page-right").removeClass("disabled");
} else {
$("#recently-watched-page-right").addClass("disabled");
}
}
function highlightAddedScrollerButton() {
var scroller = $("#recently-added-row-scroller");
var numElems = scroller.find("li").length;
scroller.width(numElems * 175);
if (scroller.width() > $("#library-recently-added").width()) {
$("#recently-added-page-right").removeClass("disabled");
} else {
$("#recently-added-page-right").addClass("disabled");
}
}
$(window).resize(function() {
highlightWatchedScrollerButton();
highlightAddedScrollerButton();
});
$('div.art-face').animate({ opacity: 0.2 }, { duration: 1000 });
var leftTotalWatched = 0;
$(".paginate-watched").click(function (e) {
e.preventDefault();
var scroller = $("#recently-watched-row-scroller");
var containerWidth = $("#library-recently-watched").width();
var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175;
var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0);
leftTotalWatched = Math.max(Math.min(leftTotalWatched + scrollAmount, 0), leftMax);
scroller.animate({ left: leftTotalWatched }, 250);
if (leftTotalWatched == 0) {
$("#recently-watched-page-left").addClass("disabled").blur();
} else {
$("#recently-watched-page-left").removeClass("disabled");
}
if (leftTotalWatched == leftMax) {
$("#recently-watched-page-right").addClass("disabled").blur();
} else {
$("#recently-watched-page-right").removeClass("disabled");
}
});
var leftTotalAdded = 0;
$(".paginate-added").click(function (e) {
e.preventDefault();
var scroller = $("#recently-added-row-scroller");
var containerWidth = $("#library-recently-added").width();
var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175;
var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0);
leftTotalAdded = Math.max(Math.min(leftTotalAdded + scrollAmount, 0), leftMax);
scroller.animate({ left: leftTotalAdded }, 250);
if (leftTotalAdded == 0) {
$("#recently-added-page-left").addClass("disabled").blur();
} else {
$("#recently-added-page-left").removeClass("disabled");
}
if (leftTotalAdded == leftMax) {
$("#recently-added-page-right").addClass("disabled").blur();
} else {
$("#recently-added-page-right").removeClass("disabled");
}
});
}); });
</script> </script>
% endif % endif

View File

@@ -18,7 +18,7 @@
</div> </div>
</div> </div>
<div class='table-card-back'> <div class='table-card-back'>
<div id="search-results-list"><i class="fa fa-refresh fa-spin"></i>&nbsp; Loading search results...</div> <div id="search-results-list" class="search-results-list"><i class="fa fa-refresh fa-spin"></i>&nbsp; Loading search results...</div>
</div> </div>
</div> </div>
</%def> </%def>

View File

@@ -559,7 +559,7 @@
<p class="help-block">Enable to have Tautulli check if remote access to the Plex Media Server goes down.</p> <p class="help-block">Enable to have Tautulli check if remote access to the Plex Media Server goes down.</p>
</div> </div>
<div class="form-group has-feedback" id="pms-ip-group"> <div class="form-group has-feedback" id="pms_ip_group">
<label for="pms_ip">Plex IP or Hostname</label> <label for="pms_ip">Plex IP or Hostname</label>
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
@@ -569,7 +569,7 @@
<button class="btn btn-form" type="button" id="verify_server_button">Verify Server</button> <button class="btn btn-form" type="button" id="verify_server_button">Verify Server</button>
</span> </span>
</div> </div>
<span class="form-control-feedback" id="pms-verify" aria-hidden="true" style="display: none; right: 110px;"></span> <span class="form-control-feedback" id="pms_verify" aria-hidden="true" style="display: none; right: 110px;"></span>
</div> </div>
<div id="pms_ip_error" class="alert alert-danger settings-alert" role="alert"></div> <div id="pms_ip_error" class="alert alert-danger settings-alert" role="alert"></div>
</div> </div>
@@ -607,7 +607,12 @@
<label for="pms_logs_folder">Plex Web URL</label> <label for="pms_logs_folder">Plex Web URL</label>
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<input type="text" class="form-control" id="pms_web_url" name="pms_web_url" value="${config['pms_web_url']}" size="30" data-parsley-trigger="change" data-parsley-pattern="^https?:\/\/.+\/web\/index\.html$|^https:\/\/app.plex.tv\/desktop$" data-parsley-errors-container="#pms_web_url_error" data-parsley-error-message="Invalid Plex Web URL." required> <div class="input-group">
<input type="text" class="form-control" id="pms_web_url" name="pms_web_url" value="${config['pms_web_url']}" size="30" data-parsley-trigger="change" data-parsley-pattern="^https?:\/\/\S+$|^https:\/\/app.plex.tv\/desktop$" data-parsley-errors-container="#pms_web_url_error" data-parsley-error-message="Invalid Plex Web URL.">
<span class="input-group-btn">
<button class="btn btn-form" type="button" id="test_pms_web_button">Test URL</button>
</span>
</div>
</div> </div>
<div id="pms_web_url_error" class="alert alert-danger settings-alert" role="alert"></div> <div id="pms_web_url_error" class="alert alert-danger settings-alert" role="alert"></div>
</div> </div>
@@ -1580,6 +1585,17 @@ $(document).ready(function() {
} }
} }
function preSaveChecks(_callback) {
if ($("#pms_identifier").val() == "") {
verifyServer();
}
verifyPMSWebURL();
if (_callback) {
_callback();
}
}
// Alert the user that their changes require a restart. // Alert the user that their changes require a restart.
function postSaveChecks() { function postSaveChecks() {
if (serverChanged || authChanged || httpChanged || directoryChanged) { if (serverChanged || authChanged || httpChanged || directoryChanged) {
@@ -1609,11 +1625,7 @@ $(document).ready(function() {
} }
$('.save-button').click(function() { $('.save-button').click(function() {
if ($("#pms_identifier").val() == "") { preSaveChecks(function () { saveSettings() });
verifyServer(function () { saveSettings() });
} else {
saveSettings();
}
}); });
initConfigCheckbox('#api_enabled'); initConfigCheckbox('#api_enabled');
@@ -1758,43 +1770,45 @@ $(document).ready(function() {
var pms_identifier = $("#pms_identifier").val(); var pms_identifier = $("#pms_identifier").val();
var pms_ssl = $("#pms_ssl").is(':checked') ? 1 : 0; var pms_ssl = $("#pms_ssl").is(':checked') ? 1 : 0;
var pms_is_remote = $("#pms_is_remote").is(':checked') ? 1 : 0; var pms_is_remote = $("#pms_is_remote").is(':checked') ? 1 : 0;
if (($("#pms_ip").val() !== '') || ($("#pms_port").val() !== '')) { if (($("#pms_ip").val() !== '') || ($("#pms_port").val() !== '')) {
$("#pms-verify").html('<i class="fa fa-refresh fa-spin"></i>'); $("#pms_verify").html('<i class="fa fa-refresh fa-spin"></i>').fadeIn('fast');
$('#pms-verify').fadeIn('fast');
$.ajax({ $.ajax({
url: 'get_server_id', url: 'get_server_id',
data : { hostname: pms_ip, port: pms_port, identifier: pms_identifier, ssl: pms_ssl, remote: pms_is_remote }, data: {
hostname: pms_ip,
port: pms_port,
identifier: pms_identifier,
ssl: pms_ssl,
remote: pms_is_remote
},
cache: true, cache: true,
async: true, async: true,
timeout: 10000, timeout: 10000,
error: function(jqXHR, textStatus, errorThrown) { error: function (jqXHR, textStatus, errorThrown) {
$("#pms-verify").html('<i class="fa fa-close"></i>'); $("#pms_verify").html('<i class="fa fa-close"></i>').fadeIn('fast');
$('#pms-verify').fadeIn('fast'); $("#pms_ip_group").addClass("has-error");
$("#pms-ip-group").addClass("has-error");
}, },
success: function (json) { success: function (json) {
var machine_identifier = json; var machine_identifier = json;
if (machine_identifier) { if (machine_identifier) {
$("#pms_identifier").val(machine_identifier); $("#pms_identifier").val(machine_identifier);
$("#pms-verify").html('<i class="fa fa-check"></i>'); $("#pms_verify").html('<i class="fa fa-check"></i>').fadeIn('fast');
$('#pms-verify').fadeIn('fast'); $("#pms_ip_group").removeClass("has-error");
$("#pms-ip-group").removeClass("has-error");
if (_callback) { if (_callback) {
_callback(); _callback();
} }
} else { } else {
$("#pms-verify").html('<i class="fa fa-close"></i>'); $("#pms_verify").html('<i class="fa fa-close"></i>').fadeIn('fast');
$('#pms-verify').fadeIn('fast'); $("#pms_ip_group").addClass("has-error");
$("#pms-ip-group").addClass("has-error");
showMsg('<i class="fa fa-exclamation-circle"></i> Could not verify your server.', false, true, 5000, true) showMsg('<i class="fa fa-exclamation-circle"></i> Could not verify your server.', false, true, 5000, true)
} }
} }
}); });
} else { } else {
$("#pms-verify").html('<i class="fa fa-close"></i>'); $("#pms_verify").html('<i class="fa fa-close"></i>').fadeIn('fast');
$('#pms-verify').fadeIn('fast'); $("#pms_ip_group").addClass("has-error");
$("#pms-ip-group").addClass("has-error");
showMsg('<i class="fa fa-exclamation-circle"></i> Could not verify your server.', false, true, 5000, true) showMsg('<i class="fa fa-exclamation-circle"></i> Could not verify your server.', false, true, 5000, true)
} }
} }
@@ -1804,11 +1818,21 @@ $(document).ready(function() {
verifyServer(); verifyServer();
}); });
function verifyPMSWebURL() {
var pms_web_url = $.trim($("#pms_web_url").val());
$("#pms_web_url").val(pms_web_url || 'https://app.plex.tv/desktop');
}
$('#test_pms_web_button').on('click', function(){
var pms_web_url = $.trim($("#pms_web_url").val());
window.open(pms_web_url, '_blank');
});
// Plex.tv auth token fetch // Plex.tv auth token fetch
$("#get-pms-auth-token").click(function() { $("#get-pms-auth-token").click(function() {
$("#pms-token-status").html('<i class="fa fa-refresh fa-spin"></i> Fetching token...'); $("#pms-token-status").html('<i class="fa fa-refresh fa-spin"></i> Fetching token...');
var pms_username = $("#pms_username").val().trim(); var pms_username = $.trim($("#pms_username").val());
var pms_password = $("#pms_password").val().trim(); var pms_password = $.trim($("#pms_password").val());
if ((pms_username !== '') && (pms_password !== '')) { if ((pms_username !== '') && (pms_password !== '')) {
$.ajax({ $.ajax({
type: 'GET', type: 'GET',

View File

@@ -61,19 +61,19 @@ DOCUMENTATION :: END
% endif % endif
</div> </div>
<div class="user-info-nav"> <div class="user-info-nav">
<ul class="user-info-nav"> <ul class="user-info-nav" role="tablist">
<li class="active"><a href="#profile" data-toggle="tab">Profile</a></li> <li class="active"><a href="#tabs-profile" role="tab" data-toggle="tab">Profile</a></li>
<li><a id="history-tab-btn" href="#userHistory" data-toggle="tab">History</a></li> <li><a id="history-tab-btn" href="#tabs-history" role="tab" data-toggle="tab">History</a></li>
<li><a id="sync-tab-btn" href="#userSyncItems" data-toggle="tab">Synced Items</a></li> <li><a id="sync-tab-btn" href="#tabs-synceditems" role="tab" data-toggle="tab">Synced Items</a></li>
<li><a id="ip-tab-btn" href="#userAddresses" data-toggle="tab">IP Addresses</a></li> <li><a id="ip-tab-btn" href="#tabs-ipaddresses" role="tab" data-toggle="tab">IP Addresses</a></li>
<li><a id="login-tab-btn" href="#userLogins" data-toggle="tab">Tautulli Logins</a></li> <li><a id="login-tab-btn" href="#tabs-tautullilogins" role="tab" data-toggle="tab">Tautulli Logins</a></li>
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="tab-content"> <div class="tab-content">
<div class="tab-pane active" id="profile"> <div role="tabpanel" class="tab-pane active" id="tabs-profile">
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
@@ -134,7 +134,7 @@ DOCUMENTATION :: END
</div> </div>
</div> </div>
</div> </div>
<div class="tab-pane" id="userHistory"> <div role="tabpanel" class="tab-pane" id="tabs-history">
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
@@ -200,7 +200,7 @@ DOCUMENTATION :: END
</div> </div>
</div> </div>
</div> </div>
<div class="tab-pane" id="userSyncItems"> <div role="tabpanel" class="tab-pane" id="tabs-synceditems">
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
@@ -240,7 +240,7 @@ DOCUMENTATION :: END
</div> </div>
</div> </div>
</div> </div>
<div class="tab-pane" id="userAddresses"> <div role="tabpanel" class="tab-pane" id="tabs-ipaddresses">
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
@@ -271,7 +271,7 @@ DOCUMENTATION :: END
</div> </div>
</div> </div>
</div> </div>
<div class="tab-pane" id="userLogins"> <div role="tabpanel" class="tab-pane" id="tabs-tautullilogins">
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
@@ -369,23 +369,246 @@ DOCUMENTATION :: END
<script src="${http_root}js/dataTables.bootstrap.min.js"></script> <script src="${http_root}js/dataTables.bootstrap.min.js"></script>
<script src="${http_root}js/dataTables.bootstrap.pagination.js"></script> <script src="${http_root}js/dataTables.bootstrap.pagination.js"></script>
% if data: % if data:
<script>
% if str(data['user_id']).isdigit():
var user_id = ${data['user_id']};
% else:
var user_id = null;
% endif
var username = '${data['username'].replace("'", "\\'")}';
</script>
<script src="${http_root}js/moment-with-locale.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 src="${http_root}js/tables/history_table.js${cache_param}"></script>
<script src="${http_root}js/tables/user_ips.js${cache_param}"></script> <script src="${http_root}js/tables/user_ips.js${cache_param}"></script>
<script src="${http_root}js/tables/sync_table.js${cache_param}"></script> <script src="${http_root}js/tables/sync_table.js${cache_param}"></script>
<script src="${http_root}js/tables/login_logs.js${cache_param}"></script> <script src="${http_root}js/tables/login_logs.js${cache_param}"></script>
<script> <script>
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
$.fn.dataTable.tables({ visible: true, api: true }).columns.adjust();
});
$('a[href="#tabs-profile"]').on('shown.bs.tab', function() {
var media_type = null;
loadHistoryTable(media_type);
});
function loadHistoryTable(media_type) {
// Build watch history table
history_table_options.ajax = {
url: 'get_history',
type: 'post',
data: function ( d ) {
return {
json_data: JSON.stringify( d ),
user_id: user_id,
media_type: media_type
};
}
}
history_table = $('#history_table-UID-${data["user_id"]}').DataTable(history_table_options);
history_table.column(2).visible(false);
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] });
$(colvis.button()).appendTo('#button-bar-history');
clearSearchButton('history_table-UID-${data["user_id"]}', history_table);
$('#media_type-selection').on('change', function () {
$('#media_type-selection > label').removeClass('active');
selected_filter = $('input[name=media_type-filter]:checked', '#media_type-selection');
$(selected_filter).closest('label').addClass('active');
media_type = $(selected_filter).val();
history_table.draw();
});
}
$('a[href="#tabs-history"]').on('shown.bs.tab', function() {
var media_type = null;
loadHistoryTable(media_type);
});
$('a[href="#tabs-synceditems"]').on('shown.bs.tab', function() {
// Build user sync table
sync_table_options.ajax = {
url: 'get_sync',
data: function(d) {
d.user_id = user_id;
}
}
sync_table = $('#sync_table-UID-${data["user_id"]}').DataTable(sync_table_options);
sync_table.column(1).visible(false);
var colvis_sync = new $.fn.dataTable.ColVis( sync_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark' } );
$( colvis_sync.button() ).appendTo('#button-bar-sync');
clearSearchButton('sync_table-UID-${data["user_id"]}', sync_table);
});
$('a[href="#tabs-ipaddresses"]').on('shown.bs.tab', function() {
// Build user IP table
user_ip_table_options.ajax = {
url: 'get_user_ips',
type: 'post',
data: function ( d ) {
return {
json_data: JSON.stringify( d ),
user_id: user_id
};
}
}
user_ip_table = $('#user_ip_table-UID-${data["user_id"]}').DataTable(user_ip_table_options);
clearSearchButton('user_ip_table-UID-${data["user_id"]}', user_ip_table);
});
$('a[href="#tabs-tautullilogins"]').on('shown.bs.tab', function() {
// Build user login table
login_log_table_options.ajax = {
url: 'get_user_logins',
data: function(d) {
d.user_id = user_id;
}
}
login_log_table = $('#login_log_table-UID-${data["user_id"]}').DataTable(login_log_table_options);
login_log_table.columns([1, 2]).visible(false);
var colvis_login = new $.fn.dataTable.ColVis( login_log_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark' } );
$( colvis_login.button() ).appendTo('#button-bar-login');
clearSearchButton('login_log_table-UID-${data["user_id"]}', login_log_table);
});
% if _session['user_group'] == 'admin':
$("#edit-user-tooltip").tooltip();
// Load edit user modal
$("#toggle-edit-user-modal").click(function() {
$("#edit-user-tooltip").tooltip('hide');
$.ajax({
url: 'edit_user_dialog',
data: { user_id: user_id },
cache: false,
async: true,
complete: function(xhr, status) {
$("#edit-user-modal").html(xhr.responseText);
}
});
});
$('#row-edit-mode').on('click', function() {
$('#row-edit-mode-alert').fadeIn(200);
if ($(this).hasClass('active')) {
if (history_to_delete.length > 0) {
$('#deleteCount').text(history_to_delete.length);
$('#confirm-modal-delete').modal();
$('#confirm-modal-delete').one('click', '#confirm-delete', function () {
history_to_delete.forEach(function(row, idx) {
$.ajax({
url: 'delete_history_rows',
type: 'POST',
data: { row_id: row },
async: true,
success: function (data) {
var msg = "History deleted";
showMsg(msg, false, true, 2000);
}
});
});
history_table.draw();
});
}
$('.delete-control').each(function () {
$(this).addClass('hidden');
$('#row-edit-mode-alert').fadeOut(200);
});
} else {
history_to_delete = [];
$('.delete-control').each(function() {
$(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger');
$(this).removeClass('hidden');
});
}
});
% endif
$("#refresh-history-list").click(function () {
history_table.draw();
});
function recentlyWatched() {
// Populate recently watched
$.ajax({
url: 'get_user_recently_watched',
async: true,
data: {
user_id: user_id,
limit: 50
},
complete: function(xhr, status) {
$("#user-recently-watched").html(xhr.responseText);
highlightWatchedScrollerButton();
}
});
}
recentlyWatched();
function highlightWatchedScrollerButton() {
var scroller = $("#recently-watched-row-scroller");
var numElems = scroller.find("li").length;
scroller.width(numElems * 175);
if (scroller.width() > $("#user-recently-watched").width()) {
$("#recently-watched-page-right").removeClass("disabled");
} else {
$("#recently-watched-page-right").addClass("disabled");
}
}
$(window).resize(function() {
highlightWatchedScrollerButton();
});
var leftTotal = 0;
$(".paginate").click(function (e) {
e.preventDefault();
var scroller = $("#recently-watched-row-scroller");
var containerWidth = $("#user-recently-watched").width();
var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175;
var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0);
leftTotal = Math.max(Math.min(leftTotal + scrollAmount, 0), leftMax);
scroller.animate({ left: leftTotal }, 250);
if (leftTotal == 0) {
$("#recently-watched-page-left").addClass("disabled").blur();
} else {
$("#recently-watched-page-left").removeClass("disabled");
}
if (leftTotal == leftMax) {
$("#recently-watched-page-right").addClass("disabled").blur();
} else {
$("#recently-watched-page-right").removeClass("disabled");
}
});
$(document).ready(function () { $(document).ready(function () {
% if str(data['user_id']).isdigit(): // Javascript to enable link to tab
var user_id = ${data['user_id']}; var hash = document.location.hash;
% else: var prefix = "tab_";
var user_id = null; if (hash) {
% endif $('.user-info-nav a[href='+hash.replace(prefix,"")+']').tab('show').trigger('show.bs.tab');
}
var username = '${data['username'].replace("'", "\\'")}'; // Change hash for page-reload
$('.user-info-nav a').on('shown.bs.tab', function (e) {
$("#edit-user-tooltip").tooltip(); window.location.hash = e.target.hash.replace("#", "#" + prefix);
});
// Populate watch time stats // Populate watch time stats
$.ajax({ $.ajax({
@@ -407,210 +630,6 @@ DOCUMENTATION :: END
} }
}); });
function loadHistoryTable(media_type) {
// Build watch history table
history_table_options.ajax = {
url: 'get_history',
type: 'post',
data: function ( d ) {
return {
json_data: JSON.stringify( d ),
user_id: user_id,
media_type: media_type
};
}
}
history_table = $('#history_table-UID-${data["user_id"]}').DataTable(history_table_options);
history_table.column(2).visible(false);
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] });
$(colvis.button()).appendTo('#button-bar-history');
clearSearchButton('history_table-UID-${data["user_id"]}', history_table);
$('#media_type-selection').on('change', function () {
$('#media_type-selection > label').removeClass('active');
selected_filter = $('input[name=media_type-filter]:checked', '#media_type-selection');
$(selected_filter).closest('label').addClass('active');
media_type = $(selected_filter).val();
history_table.draw();
});
}
$( "#history-tab-btn" ).one( "click", function() {
var media_type = null;
loadHistoryTable(media_type);
});
$( "#ip-tab-btn" ).one( "click", function() {
// Build user IP table
user_ip_table_options.ajax = {
url: 'get_user_ips',
type: 'post',
data: function ( d ) {
return {
json_data: JSON.stringify( d ),
user_id: user_id
};
}
}
user_ip_table = $('#user_ip_table-UID-${data["user_id"]}').DataTable(user_ip_table_options);
clearSearchButton('user_ip_table-UID-${data["user_id"]}', user_ip_table);
});
$( "#sync-tab-btn" ).one( "click", function() {
// Build user sync table
sync_table_options.ajax = {
url: 'get_sync',
data: function(d) {
d.user_id = user_id;
}
}
sync_table = $('#sync_table-UID-${data["user_id"]}').DataTable(sync_table_options);
sync_table.column(1).visible(false);
var colvis_sync = new $.fn.dataTable.ColVis( sync_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark' } );
$( colvis_sync.button() ).appendTo('#button-bar-sync');
clearSearchButton('sync_table-UID-${data["user_id"]}', sync_table);
});
$( "#login-tab-btn" ).one( "click", function() {
// Build user login table
login_log_table_options.ajax = {
url: 'get_user_logins',
data: function(d) {
d.user_id = user_id;
}
}
login_log_table = $('#login_log_table-UID-${data["user_id"]}').DataTable(login_log_table_options);
login_log_table.columns([1, 2]).visible(false);
var colvis_login = new $.fn.dataTable.ColVis( login_log_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark' } );
$( colvis_login.button() ).appendTo('#button-bar-login');
clearSearchButton('login_log_table-UID-${data["user_id"]}', login_log_table);
});
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
$.fn.dataTable.tables({ visible: true, api: true }).columns.adjust();
});
% if _session['user_group'] == 'admin':
// Load edit user modal
$("#toggle-edit-user-modal").click(function() {
$("#edit-user-tooltip").tooltip('hide');
$.ajax({
url: 'edit_user_dialog',
data: { user_id: user_id },
cache: false,
async: true,
complete: function(xhr, status) {
$("#edit-user-modal").html(xhr.responseText);
}
});
});
$('#row-edit-mode').on('click', function() {
$('#row-edit-mode-alert').fadeIn(200);
if ($(this).hasClass('active')) {
if (history_to_delete.length > 0) {
$('#deleteCount').text(history_to_delete.length);
$('#confirm-modal-delete').modal();
$('#confirm-modal-delete').one('click', '#confirm-delete', function () {
history_to_delete.forEach(function(row, idx) {
$.ajax({
url: 'delete_history_rows',
type: 'POST',
data: { row_id: row },
async: true,
success: function (data) {
var msg = "History deleted";
showMsg(msg, false, true, 2000);
}
});
});
history_table.draw();
});
}
$('.delete-control').each(function () {
$(this).addClass('hidden');
$('#row-edit-mode-alert').fadeOut(200);
});
} else {
history_to_delete = [];
$('.delete-control').each(function() {
$(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger');
$(this).removeClass('hidden');
});
}
});
% endif
$("#refresh-history-list").click(function () {
history_table.draw();
});
function recentlyWatched() {
// Populate recently watched
$.ajax({
url: 'get_user_recently_watched',
async: true,
data: {
user_id: user_id,
limit: 50
},
complete: function(xhr, status) {
$("#user-recently-watched").html(xhr.responseText);
highlightWatchedScrollerButton();
}
});
}
recentlyWatched();
function highlightWatchedScrollerButton() {
var scroller = $("#recently-watched-row-scroller");
var numElems = scroller.find("li").length;
scroller.width(numElems * 175);
if (scroller.width() > $("#user-recently-watched").width()) {
$("#recently-watched-page-right").removeClass("disabled");
} else {
$("#recently-watched-page-right").addClass("disabled");
}
}
$(window).resize(function() {
highlightWatchedScrollerButton();
});
var leftTotal = 0;
$(".paginate").click(function (e) {
e.preventDefault();
var scroller = $("#recently-watched-row-scroller");
var containerWidth = $("#user-recently-watched").width();
var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175;
var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0);
leftTotal = Math.max(Math.min(leftTotal + scrollAmount, 0), leftMax);
scroller.animate({ left: leftTotal }, 250);
if (leftTotal == 0) {
$("#recently-watched-page-left").addClass("disabled").blur();
} else {
$("#recently-watched-page-left").removeClass("disabled");
}
if (leftTotal == leftMax) {
$("#recently-watched-page-right").addClass("disabled").blur();
} else {
$("#recently-watched-page-right").removeClass("disabled");
}
});
}); });
</script> </script>
% endif % endif

View File

@@ -1104,7 +1104,7 @@ class DataFactory(object):
if str(rating_key).isdigit(): if str(rating_key).isdigit():
poster_key = rating_key poster_key = rating_key
elif metadata: elif metadata:
if metadata['media_type'] in ('movie', 'show', 'artist'): if metadata['media_type'] in ('movie', 'show', 'artist', 'collection'):
poster_key = metadata['rating_key'] poster_key = metadata['rating_key']
elif metadata['media_type'] in ('season', 'album'): elif metadata['media_type'] in ('season', 'album'):
poster_key = metadata['rating_key'] poster_key = metadata['rating_key']

View File

@@ -16,6 +16,7 @@
import arrow import arrow
import bleach import bleach
from collections import Counter
from itertools import groupby from itertools import groupby
import json import json
from operator import itemgetter from operator import itemgetter
@@ -145,10 +146,17 @@ def notify_conditions(notify_action=None, stream_data=None, timeline_data=None):
return False return False
if notify_action == 'on_concurrent': if notify_action == 'on_concurrent':
ap = activity_processor.ActivityProcessor() pms_connect = pmsconnect.PmsConnect()
user_sessions = ap.get_sessions(user_id=stream_data['user_id'], result = pms_connect.get_current_activity()
ip_address=plexpy.CONFIG.NOTIFY_CONCURRENT_BY_IP)
return len(user_sessions) >= plexpy.CONFIG.NOTIFY_CONCURRENT_THRESHOLD user_sessions = []
if result:
user_sessions = [s for s in result['sessions'] if s['user_id'] == stream_data['user_id']]
if plexpy.CONFIG.NOTIFY_CONCURRENT_BY_IP:
return len(Counter(s['ip_address'] for s in user_sessions)) >= plexpy.CONFIG.NOTIFY_CONCURRENT_THRESHOLD
else:
return len(user_sessions) >= plexpy.CONFIG.NOTIFY_CONCURRENT_THRESHOLD
elif notify_action == 'on_newdevice': elif notify_action == 'on_newdevice':
data_factory = datafactory.DataFactory() data_factory = datafactory.DataFactory()
@@ -504,7 +512,7 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
# Build Plex URL # Build Plex URL
metadata['plex_url'] = '{web_url}#!/server/{pms_identifier}/details?key=%2Flibrary%2Fmetadata%2F{rating_key}'.format( metadata['plex_url'] = '{web_url}#!/server/{pms_identifier}/details?key=%2Flibrary%2Fmetadata%2F{rating_key}'.format(
web_url=plexpy.CONFIG.PMS_WEB_URL or 'https://app.plex.tv/desktop', web_url=plexpy.CONFIG.PMS_WEB_URL,
pms_identifier=plexpy.CONFIG.PMS_IDENTIFIER, pms_identifier=plexpy.CONFIG.PMS_IDENTIFIER,
rating_key=rating_key) rating_key=rating_key)

View File

@@ -257,6 +257,23 @@ class PmsConnect(object):
return request return request
def get_children_list_related(self, rating_key='', output_format=''):
"""
Return list of related children in requested collection item.
Parameters required: rating_key { ratingKey of parent }
Optional parameters: output_format { dict, json }
Output: array
"""
uri = '/hubs/metadata/' + rating_key + '/related'
request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET',
output_format=output_format)
return request
def get_childrens_list(self, rating_key='', output_format=''): def get_childrens_list(self, rating_key='', output_format=''):
""" """
Return list of children in requested library item. Return list of children in requested library item.
@@ -414,7 +431,7 @@ class PmsConnect(object):
Output: array Output: array
""" """
uri = '/hubs/search?query=' + urllib.quote(query.encode('utf8')) + '&limit=' + limit uri = '/hubs/search?query=' + urllib.quote(query.encode('utf8')) + '&limit=' + limit + '&includeCollections=1'
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol, proto=self.protocol,
request_type='GET', request_type='GET',
@@ -607,7 +624,7 @@ class PmsConnect(object):
try: try:
xml_head = metadata.getElementsByTagName('MediaContainer') xml_head = metadata.getElementsByTagName('MediaContainer')
except Exception as e: except Exception as e:
logger.warn(u"Tautulli Pmsconnect :: Unable to parse XML for get_metadata: %s." % e) logger.warn(u"Tautulli Pmsconnect :: Unable to parse XML for get_metadata_details: %s." % e)
return {} return {}
metadata = {} metadata = {}
@@ -1039,6 +1056,50 @@ class PmsConnect(object):
helpers.get_xml_attr(metadata_main, 'title')) helpers.get_xml_attr(metadata_main, 'title'))
} }
elif metadata_type == 'collection':
metadata = {'media_type': metadata_type,
'sub_media_type': helpers.get_xml_attr(metadata_main, 'subtype'),
'section_id': section_id,
'library_name': library_name,
'rating_key': helpers.get_xml_attr(metadata_main, 'ratingKey'),
'parent_rating_key': helpers.get_xml_attr(metadata_main, 'parentRatingKey'),
'grandparent_rating_key': helpers.get_xml_attr(metadata_main, 'grandparentRatingKey'),
'title': helpers.get_xml_attr(metadata_main, 'title'),
'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'),
'media_index': helpers.get_xml_attr(metadata_main, 'index'),
'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
'studio': helpers.get_xml_attr(metadata_main, 'studio'),
'content_rating': helpers.get_xml_attr(metadata_main, 'contentRating'),
'summary': helpers.get_xml_attr(metadata_main, 'summary'),
'tagline': helpers.get_xml_attr(metadata_main, 'tagline'),
'rating': helpers.get_xml_attr(metadata_main, 'rating'),
'audience_rating': helpers.get_xml_attr(metadata_main, 'audienceRating'),
'user_rating': helpers.get_xml_attr(metadata_main, 'userRating'),
'duration': helpers.get_xml_attr(metadata_main, 'duration'),
'year': helpers.get_xml_attr(metadata_main, 'year'),
'min_year': helpers.get_xml_attr(metadata_main, 'minYear'),
'max_year': helpers.get_xml_attr(metadata_main, 'maxYear'),
'thumb': helpers.get_xml_attr(metadata_main, 'thumb').split('?')[0],
'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb'),
'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'),
'art': helpers.get_xml_attr(metadata_main, 'art'),
'banner': helpers.get_xml_attr(metadata_main, 'banner'),
'originally_available_at': helpers.get_xml_attr(metadata_main, 'originallyAvailableAt'),
'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'),
'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
'last_viewed_at': helpers.get_xml_attr(metadata_main, 'lastViewedAt'),
'guid': helpers.get_xml_attr(metadata_main, 'guid'),
'child_count': helpers.get_xml_attr(metadata_main, 'childCount'),
'directors': directors,
'writers': writers,
'actors': actors,
'genres': genres,
'labels': labels,
'full_title': helpers.get_xml_attr(metadata_main, 'title')
}
elif metadata_type == 'clip': elif metadata_type == 'clip':
metadata = {'media_type': metadata_type, metadata = {'media_type': metadata_type,
'section_id': section_id, 'section_id': section_id,
@@ -1795,7 +1856,6 @@ class PmsConnect(object):
else: else:
return False return False
def get_item_children(self, rating_key=''): def get_item_children(self, rating_key=''):
""" """
Return processed and validated children list. Return processed and validated children list.
@@ -1836,8 +1896,11 @@ class PmsConnect(object):
for result in result_data: for result in result_data:
children_output = {'section_id': section_id, children_output = {'section_id': section_id,
'rating_key': helpers.get_xml_attr(result, 'ratingKey'), 'rating_key': helpers.get_xml_attr(result, 'ratingKey'),
'parent_rating_key': helpers.get_xml_attr(result, 'parentRatingKey'),
'media_index': helpers.get_xml_attr(result, 'index'), 'media_index': helpers.get_xml_attr(result, 'index'),
'title': helpers.get_xml_attr(result, 'title'), 'title': helpers.get_xml_attr(result, 'title'),
'parent_title': helpers.get_xml_attr(result, 'parentTitle'),
'year': helpers.get_xml_attr(result, 'year'),
'thumb': helpers.get_xml_attr(result, 'thumb'), 'thumb': helpers.get_xml_attr(result, 'thumb'),
'parent_thumb': helpers.get_xml_attr(a, 'thumb'), 'parent_thumb': helpers.get_xml_attr(a, 'thumb'),
'duration': helpers.get_xml_attr(result, 'duration') 'duration': helpers.get_xml_attr(result, 'duration')
@@ -1852,6 +1915,72 @@ class PmsConnect(object):
return output return output
def get_item_children_related(self, rating_key=''):
"""
Return processed and validated children list.
Output: array
"""
children_data = self.get_children_list_related(rating_key, output_format='xml')
try:
xml_head = children_data.getElementsByTagName('MediaContainer')
except Exception as e:
logger.warn(u"Tautulli Pmsconnect :: Unable to parse XML for get_item_children_related: %s." % e)
return []
children_results_list = {'movie': [],
'show': [],
'season': [],
'episode': [],
'artist': [],
'album': [],
'track': [],
}
for a in xml_head:
section_id = helpers.get_xml_attr(a, 'librarySectionID')
hubs = a.getElementsByTagName('Hub')
for h in hubs:
size = helpers.get_xml_attr(h, 'size')
media_type = helpers.get_xml_attr(h, 'type')
title = helpers.get_xml_attr(h, 'title')
hub_identifier = helpers.get_xml_attr(h, 'hubIdentifier')
if size == '0' or not hub_identifier.startswith('collection.related') or \
media_type not in children_results_list.keys():
continue
result_data = []
if h.getElementsByTagName('Video'):
result_data = h.getElementsByTagName('Video')
if h.getElementsByTagName('Directory'):
result_data = h.getElementsByTagName('Directory')
if h.getElementsByTagName('Track'):
result_data = h.getElementsByTagName('Track')
for result in result_data:
children_output = {'section_id': section_id,
'rating_key': helpers.get_xml_attr(result, 'ratingKey'),
'parent_rating_key': helpers.get_xml_attr(result, 'parentRatingKey'),
'media_index': helpers.get_xml_attr(result, 'index'),
'title': helpers.get_xml_attr(result, 'title'),
'parent_title': helpers.get_xml_attr(result, 'parentTitle'),
'year': helpers.get_xml_attr(result, 'year'),
'thumb': helpers.get_xml_attr(result, 'thumb'),
'parent_thumb': helpers.get_xml_attr(a, 'thumb'),
'duration': helpers.get_xml_attr(result, 'duration')
}
children_results_list[media_type].append(children_output)
output = {'results_count': sum(len(s) for s in children_results_list.items()),
'results_list': children_results_list,
}
return output
def get_servers_info(self): def get_servers_info(self):
""" """
Return the list of local servers. Return the list of local servers.
@@ -2237,7 +2366,8 @@ class PmsConnect(object):
'episode': [], 'episode': [],
'artist': [], 'artist': [],
'album': [], 'album': [],
'track': [] 'track': [],
'collection': []
} }
for a in xml_head: for a in xml_head:

View File

@@ -1,2 +1,2 @@
PLEXPY_BRANCH = "beta" PLEXPY_BRANCH = "beta"
PLEXPY_RELEASE_VERSION = "v2.0.3-beta" PLEXPY_RELEASE_VERSION = "v2.0.4-beta"

View File

@@ -3588,7 +3588,7 @@ class WebInterface(object):
def get_item_children(self, rating_key='', **kwargs): def get_item_children(self, rating_key='', **kwargs):
pms_connect = pmsconnect.PmsConnect() pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_item_children(rating_key) result = pms_connect.get_item_children(rating_key=rating_key)
if result: if result:
return serve_template(templatename="info_children_list.html", data=result, title="Children List") return serve_template(templatename="info_children_list.html", data=result, title="Children List")
@@ -3596,6 +3596,18 @@ class WebInterface(object):
logger.warn(u"Unable to retrieve data for get_item_children.") logger.warn(u"Unable to retrieve data for get_item_children.")
return serve_template(templatename="info_children_list.html", data=None, title="Children List") return serve_template(templatename="info_children_list.html", data=None, title="Children List")
@cherrypy.expose
@requireAuth()
def get_item_children_related(self, rating_key='', title='', **kwargs):
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_item_children_related(rating_key=rating_key)
if result:
return serve_template(templatename="info_collection_list.html", data=result, title=title)
else:
return serve_template(templatename="info_collection_list.html", data=None, title=title)
@cherrypy.expose @cherrypy.expose
@cherrypy.tools.json_out() @cherrypy.tools.json_out()
@requireAuth() @requireAuth()