Merge branch 'dev'

# Conflicts:
#	data/interfaces/default/info.html
This commit is contained in:
Tim 2015-09-06 15:23:38 +02:00
commit 6b75a55ce8
25 changed files with 2678 additions and 1039 deletions

View file

@ -1,5 +1,22 @@
# Changelog # Changelog
## v1.1.6 (2015-09-06)
* Home stats cards are now expandable to show multiple items. Configurable in settings. Thanks @JonnyWong.
* Completely redesigned media info pages. Thanks @JonnyWong.
* Redesigned activity pane to match Plex Web more closely. Thanks @JonnyWong.
* New Library stats on home page, shows total item counts per library. Thanks @JonnyWong.
* New last watched card in home stats. Shows last watched items. Thanks @JonnyWong.
* Improved some layout issues on mobile devices. Thanks @JonnyWong.
* Fixed issue where some clip/channel items are reported as episodes and causing exceptions.
* Many styling improvements and fixes. Thanks @JonnyWong.
* Fixed incorrect sort on home stats platform count by duration. Thanks @JonnyWong.
* Fix issue where user refresh would continually be called as "Local" user didn't exist in database.
* Fixed styling on graph stream modal. Thanks @JonnyWong.
* Fixed some issues with users page editing. Thanks @JonnyWong.
* Fix error page when clicking through to an item that no longer exists.
## v1.1.5 (2015-08-27) ## v1.1.5 (2015-08-27)
* Fix git tag being one release behind. * Fix git tag being one release behind.

View file

@ -1,4 +1,4 @@
<% <%
import plexpy import plexpy
from plexpy import version from plexpy import version
%> %>
@ -52,7 +52,7 @@ from plexpy import version
<span class="icon-bar"></span> <span class="icon-bar"></span>
</button> </button>
<a class="navbar-brand" href="home"> <a class="navbar-brand" href="home">
<img alt="PlexPy" src="interfaces/default/images/logo-plexpy@2x.png" height="40px"> <img alt="PlexPy" src="interfaces/default/images/logo-plexpy@2x.png" height="40">
</a> </a>
</div> </div>
<div class="collapse navbar-collapse navbar-right" id="navbar-collapse-1"> <div class="collapse navbar-collapse navbar-right" id="navbar-collapse-1">
@ -99,7 +99,9 @@ from plexpy import version
</div> </div>
${next.headerIncludes()} ${next.headerIncludes()}
${next.body()} <div class="body-container">
${next.body()}
</div>
<script src="interfaces/default/js/jquery-2.1.4.min.js"></script> <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/bootstrap3/bootstrap.min.js"></script>

File diff suppressed because it is too large Load diff

View file

@ -28,8 +28,10 @@ user Returns the name of the user owning the session.
user_id Returns the Plex user id if available. user_id Returns the Plex user id if available.
machine_id Returns the machine id of the players being used. machine_id Returns the machine id of the players being used.
friendly_name Returns the friendlly name of the user owning the session. 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'. state Returns the state of the current session. Either 'playing', 'paused' or 'buffering'.
title Returns the name of the episode, movie or music track. title Returns the name of the episode, movie or music track.
year Returns the year of the episode, movie, or clip.
player Returns the name of the platform used to play 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. platform Returns the type of platform used to play the stream.
audio_decision Returns the audio transcode decision. Either 'transcode', 'copy' or 'direct play'. audio_decision Returns the audio transcode decision. Either 'transcode', 'copy' or 'direct play'.
@ -61,159 +63,166 @@ DOCUMENTATION :: END
% if data is not None: % if data is not None:
% if data['stream_count'] != '0': % if data['stream_count'] != '0':
% for a in data['sessions']: % for a in data['sessions']:
<div class="instance" id="instance-${a['session_key']}"> <div class="dashboard-instance" id="instance-${a['session_key']}">
<div class="poster"> % if a['type'] == 'movie' or a['type'] == 'episode':
<div class="dashboard-activity-poster-face"> <a href="info?item_id=${a['rating_key']}">
% endif
<div class="dashboard-activity-poster">
<script>console.log('${a['indexes']}');</script>
% if a['type'] == 'movie' and not a['indexes']: % if a['type'] == 'movie' and not a['indexes']:
<img src="pms_image_proxy?img=${a['art']}&width=410&height=230"/> <div class="dashboard-activity-poster-face" style="background-image: url(pms_image_proxy?img=${a['art']}&width=500&height=280);"></div>
% elif a['type'] == 'episode' and not a['indexes']:
<div class="dashboard-activity-poster-face" style="background-image: url(pms_image_proxy?img=${a['art']}&width=500&height=280);"></div>
% elif a['indexes']: % elif a['indexes']:
<img onload="fadeIn(this)" src="pms_image_proxy?img=${a['bif_thumb']}&width=300&height=169" style="display: none;"/> <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: % else:
% if a['type'] == 'track': % if a['type'] == 'track':
<div class="dashboard-activity-poster-music-bg" style="background-image: url('pms_image_proxy?img=${a['thumb']}&width=300&height=300');"></div> <div class="dashboard-activity-cover-face-bg" style="background-image: url(pms_image_proxy?img=${a['thumb']}&width=300&height=300);"></div>
% endif <div class="dashboard-activity-cover-face" style="background-image: url(pms_image_proxy?img=${a['thumb']}&width=300&height=300);"></div>
% if a['type'] == 'clip': % elif a['type'] == 'clip':
<img src="${a['thumb']}"/> % if a['art'][:4] == 'http':
% else: <div class="dashboard-activity-poster-face" style="background-image: url(${a['art']});"></div>
<img src="pms_image_proxy?img=${a['thumb']}&width=410&height=230&fallback=cover"/> % elif a['thumb'][:4] == 'http':
% endif <div class="dashboard-activity-poster-face" style="background-image: url(${a['thumb']});"></div>
% endif
<div class="dashboard-activity-poster-info-bar">
<div class="dashboard-activity-poster-info-text">
% if a['type'] == 'track':
Track ${a['media_index']}
% elif a['type'] == 'episode':
Season ${a['parent_media_index']}, Episode ${a['media_index']}
% else: % else:
${a['title']} % 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 % endif
% 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 class="dashboard-activity-info-platform" id="platform-${a['session_key']}">
<strong>${a['player']}</strong><br />
% if a['state'] == 'playing':
State &nbsp;<strong>Playing</strong>
% elif a['state'] == 'paused':
State &nbsp;<strong>Paused</strong>
% elif a['state'] == 'buffering':
State &nbsp;<strong>Buffering</strong>
% endif
</div>
% if a['type'] == 'track':
% if a['audio_decision'] == 'direct play':
Stream &nbsp;<strong>Direct Play</strong>
% else:
Stream &nbsp;<strong>Transcoding</strong>
% endif
<br />
% if a['audio_decision'] == 'direct play':
Audio &nbsp;<strong>Direct Play (${a['audio_codec']}) (${a['audio_channels']}ch)</strong>
% elif a['audio_decision'] == 'Copy':
Audio &nbsp;<strong>Direct Stream (${a['transcode_audio_codec']}) (${a['transcode_audio_channels']}ch)</strong>
% elif a['audio_decision'] != 'transcode':
Audio &nbsp;<strong>Transcode (${a['transcode_audio_codec']}) (${a['transcode_audio_channels']}ch)</strong>
% endif
% elif a['type'] == 'episode' or a['type'] == 'movie' or a['type'] == 'clip':
% if a['video_decision'] == 'direct play':
Stream &nbsp;<strong>Direct Play</strong>
% else:
Stream &nbsp;<strong>Transcoding</strong>
% endif
<br />
% if a['video_decision'] == 'direct play':
Video &nbsp;<strong>Direct Play (${a['video_codec']}) (${a['width']}x${a['height']})</strong>
% elif a['video_decision'] == 'copy':
Video &nbsp;<strong>Direct Stream (${a['transcode_video_codec']}) (${a['width']}x${a['height']})</strong>
% elif a['video_decision'] == 'transcode':
Video &nbsp;<strong>Transcode (${a['transcode_video_codec']}) (${a['transcode_width']}x${a['transcode_height']})</strong>
% endif
<br />
% if a['audio_decision'] == 'direct play':
Audio &nbsp;<strong>Direct Play (${a['audio_codec']}) (${a['audio_channels']}ch)</strong>
% elif a['audio_decision'] == 'copy':
Audio &nbsp;<strong>Direct Stream (${a['transcode_audio_codec']}) (${a['transcode_audio_channels']}ch)</strong>
% elif a['audio_decision'] == 'transcode':
Audio &nbsp;<strong>Transcode (${a['transcode_audio_codec']}) (${a['transcode_audio_channels']}ch)</strong>
% endif
% endif
<br>
</div> </div>
</div>
<div class="dashboard-activity-poster-info-bar">
<div class="dashboard-activity-poster-info-time"> <div class="dashboard-activity-poster-info-time">
<span class="progress_time">${a['progress']}</span>/<span class="progress_time">${a['duration']}</span> <span class="progress_time">${a['progress']}</span>/<span class="progress_time">${a['duration']}</span>
</div> </div>
</div> </div>
</div> </div>
<div class='dashboard-activity-metadata-wrapper'> % if a['type'] == 'movie' or a['type'] == 'episode':
<div class='dashboard-activity-instance-overlay'> </a>
<div class='dashboard-activity-metadata-progress-minutes'> % endif
<div class='activity-progress'> <div class="dashboard-activity-progress">
<div class="bar" style="width: ${a['progress_percent']}%">${a['progress_percent']}%</div> <div class="dashboard-activity-progress-bar">
</div> <div class="bar" style="width: ${a['progress_percent']}%">${a['progress_percent']}%</div>
</div>
<div class="dashboard-activity-metadata-platform" data-toggle="tooltip" data-placement="right" title data-title="${a['player']}" id="platform-${a['session_key']}">
</div>
<div class="dashboard-activity-metadata-user">
% if a['user_id']:
<a href="user?user_id=${a['user_id']}">${a['friendly_name']}</a> is ${a['state']}
% else:
<a href="user?user=${a['user']}">${a['friendly_name']}</a> is ${a['state']}
% endif
</div>
<div class="dashboard-activity-metadata-title">
% if a['type'] == 'episode':
<a href="info?item_id=${a['rating_key']}">${a['grandparent_title']} - ${a['title']}</a>
% elif a['type'] == 'movie':
<a href="info?item_id=${a['rating_key']}">${a['title']}</a>
% elif a['type'] == 'clip':
${a['title']}
% elif a['type'] == 'track':
${a['grandparent_title']} - ${a['title']}
% else:
${a['grandparent_title']} - ${a['title']}
% endif
</div>
</div>
<div id="stream-${a['session_key']}" class="collapse out">
<div class='dashboard-activity-info-details-overlay'>
<div class='dashboard-activity-info-details-content'>
% if a['type'] == 'track':
Artist: <strong>${a['grandparent_title']}</strong>
<br>
Album: <strong>${a['parent_title']}</strong>
<br>
% endif
% 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
<br>
% if a['type'] == 'track':
% if a['audio_decision'] == 'direct play':
Stream: <strong>Direct Play</strong>
% else:
Stream: <strong>Transcoding</strong>
% endif
<br/>
% if a['audio_decision'] != 'direct play':
Audio: <strong>${a['transcode_audio_codec']} (${a['transcode_audio_channels']}ch)</strong>
% elif a['audio_decision'] == 'direct play':
Audio: <strong>${a['audio_codec']} (${a['audio_channels']}ch)</strong>
% endif
% elif a['type'] == 'episode' or a['type'] == 'movie' or a['type'] == 'clip':
% if a['video_decision'] == 'direct play':
Stream: <strong>Direct Play</strong>
% else:
Stream: <strong>Transcoding</strong>
% endif
<br/>
% if a['video_decision'] != 'direct play':
Video: <strong>${a['video_decision']} (${a['transcode_video_codec']})
(${a['transcode_width']}x${a['transcode_height']})</strong>
% elif a['audio_decision'] == 'direct play':
Video: <strong>${a['video_decision']} (${a['video_codec']})
(${a['width']}x${a['height']})</strong>
% endif
<br/>
% if a['audio_decision'] != 'direct play':
Audio: <strong>${a['audio_decision']} ${a['transcode_audio_codec']} (${a['transcode_audio_channels']}ch)</strong>
% elif a['audio_decision'] == 'direct play':
Audio: <strong>${a['audio_codec']} (${a['audio_channels']}ch)</strong>
% endif
% endif
<br>
</div>
</div>
</div>
</div> </div>
</div> </div>
<div class="dashboard-activity-button-info"> <div class="dashboard-activity-metadata-wrapper">
<button type="button" class="btn btn-activity-info btn-sm" data-toggle="collapse" data-target="#stream-${a['session_key']}"> <a href="user?user_id=${a['user_id']}">
<i class='fa fa-info-circle'></i> <div class="dashboard-activity-metadata-user-thumb" style="background-image: url(${a['user_thumb']});"></div>
</button> </a>
</div> <div class="dashboard-activity-metadata-title">
% if a['type'] == 'episode':
<a href="info?item_id=${a['rating_key']}">${a['grandparent_title']} - ${a['title']}</a>
% elif a['type'] == 'movie':
<a href="info?item_id=${a['rating_key']}">${a['title']}</a>
% elif a['type'] == 'clip':
${a['title']}
% elif a['type'] == 'track':
${a['grandparent_title']} - ${a['title']}
% else:
${a['grandparent_title']} - ${a['title']}
% endif
</div>
<div class="dashboard-activity-metadata-subtitle">
% if a['type'] == 'episode':
S${a['parent_media_index']} &middot; E${a['media_index']}
% elif a['type'] == 'movie':
${a['year']}
% elif a['type'] == 'track':
${a['parent_title']}
% else:
${a['year']}
% endif
</div>
<div class="dashboard-activity-metadata-user">
% if a['user_id']:
<a href="user?user_id=${a['user_id']}">${a['friendly_name']}</a>
% else:
<a href="user?user=${a['user']}">${a['friendly_name']}</a>
% endif
</div>
</div>
</div> </div>
<script> <script>
$("#platform-${a['session_key']}").html("<img src='" + getPlatformImagePath('${a['platform']}') + "'>"); $("#platform-${a['session_key']}").prepend("<div class='dashboard-activity-info-platform-box' style='background-image: url(" + getPlatformImagePath('${a['platform']}') + ");'>");
</script> </script>
% endfor % endfor
<script> <script>
// When using bif indexes make the image transition a little smoother. // When using bif indexes make the image transition a little smoother.
function fadeIn(obj) { $('.bif').each(function() {
$(obj).fadeIn(450); $(this).fadeIn(1000);
} });
// Convert timestamps to readable times // Convert timestamps to readable times
$('.progress_time').each(function(index) { $('.progress_time').each(function(index) {
$(this).html(millisecondsToMinutes($(this).text(), false)); $(this).html(millisecondsToMinutes($(this).text(), false));
}); });
// Hide the info bar on page load // Show/Hide activity info
$('.dashboard-activity-poster-info-bar').hide(); $('.btn-activity-info').on('click', function(e) {
e.preventDefault();
// When mouse over the activity pane, show an info bar with extra info. $($(this).attr('data-target')).toggle();
$('.dashboard-activity-poster-face').mouseenter(function() {
$('.dashboard-activity-poster-info-bar', this).slideDown('fast');
});
$('.dashboard-activity-poster-face').mouseleave(function() {
$('.dashboard-activity-poster-info-bar', this).slideUp('fast');
});
// Tooltips
$('.dashboard-activity-metadata-platform').each(function() {
$(this).tooltip();
}); });
</script> </script>
% else: % else:

View file

@ -103,7 +103,7 @@
data: { row_id: history_to_delete[i] }, data: { row_id: history_to_delete[i] },
async: true, async: true,
success: function (data) { success: function (data) {
var msg = "User history purged"; var msg = "History deleted";
showMsg(msg, false, true, 2000); showMsg(msg, false, true, 2000);
} }
}); });
@ -113,7 +113,6 @@
} }
$('.delete-control').each(function () { $('.delete-control').each(function () {
$(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger');
$(this).addClass('hidden'); $(this).addClass('hidden');
$('#row-edit-mode-alert').fadeOut(200); $('#row-edit-mode-alert').fadeOut(200);
}); });
@ -121,6 +120,7 @@
} else { } else {
history_to_delete = []; history_to_delete = [];
$('.delete-control').each(function() { $('.delete-control').each(function() {
$(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger');
$(this).removeClass('hidden'); $(this).removeClass('hidden');
}); });
} }

View file

@ -27,7 +27,8 @@
<div class="modal-footer"></div> <div class="modal-footer"></div>
</div> </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="interfaces/default/js/tables/history_table_modal.js"></script>
<script> <script>
$(document).ready(function() { $(document).ready(function() {
@ -45,6 +46,32 @@
history_table = $('#history_table').DataTable(history_table_modal_options); history_table = $('#history_table').DataTable(history_table_modal_options);
clearSearchButton('history_table', history_table); clearSearchButton('history_table', history_table);
// Move #info-modal to parent container
if (!($('#history-modal').next().is('#info-modal'))) {
$('#info-modal').appendTo($('#history-modal').parent());
}
$('#history-modal > #info-modal').remove();
$('#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');
});
}); });
</script> </script>
% else: % else:

View file

@ -17,6 +17,9 @@ data[array_index]['rows'] :: Usable parameters
row_id Return the db row id for a metadata item if one exists row_id Return the db row id for a metadata item if one exists
== Only if 'stat_id' is 'top_tv' or 'popular_tv' or 'top_movies' or 'popular_movies' or 'last_watched' ==
thumb Return the thumb for the media item.
== Only if 'stat_id' is 'top_tv' or 'popular_tv' == == Only if 'stat_id' is 'top_tv' or 'popular_tv' ==
grandparent_thumb Returns location of the item's thumbnail. Use with pms_image_proxy. grandparent_thumb Returns location of the item's thumbnail. Use with pms_image_proxy.
rating_key Returns the unique identifier for the media item. rating_key Returns the unique identifier for the media item.
@ -29,15 +32,18 @@ total_duration Returns the total duration for the associated stat.
== Only of 'stat_id' is 'popular_tv' or 'popular_movies' == == Only of 'stat_id' is 'popular_tv' or 'popular_movies' ==
users_watched Returns the count for the associated stat. users_watched Returns the count for the associated stat.
== Only if 'stat_id' is 'top_user' == == Only if 'stat_id' is 'top_user' or 'last_watched' ==
thumb Returns url of the user's gravatar. Returns '' if none exists. user_thumb Returns url of the user's gravatar. Returns '' if none exists.
user Returns the username for the associated stat. user Returns the username for the associated stat.
user_id Returns the user id for the associated stat. user_id Returns the user id for the associated stat.
friendly_name Returns the friendly name of the user for the associated stat. friendly_name Returns the friendly name of the user for the associated stat.
== Only if 'stat_id' is 'top_platform' == == Only if 'stat_id' is 'top_platform' or 'last_watched' ==
platform_type Returns the platform name for the associated stat. platform_type Returns the platform name for the associated stat.
== Only if 'stat_id' is 'last_watched' ==
last_watch Returns the time the media item was last watched.
DOCUMENTATION :: END DOCUMENTATION :: END
</%doc> </%doc>
@ -48,7 +54,7 @@ DOCUMENTATION :: END
def hd(minutes): def hd(minutes):
if int(minutes) > 60: if int(minutes) > 60:
hours = int(helpers.cast_to_float(minutes) / 60) hours = int(helpers.cast_to_float(minutes) / 60)
minutes = int(helpers.cast_to_float(minutes) % hours) minutes = int(helpers.cast_to_float(minutes) % 60 )
if minutes > 0: if minutes > 0:
return "<h3>" + str(hours) + "</h3><p>hrs</p><h3>" + str(minutes) + "</h3><p>mins</p>" return "<h3>" + str(hours) + "</h3><p>hrs</p><h3>" + str(minutes) + "</h3><p>mins</p>"
else: else:
@ -58,199 +64,587 @@ DOCUMENTATION :: END
%> %>
% if data: % if data:
% if data[0]['rows'] or data[2]['rows']: % if data[0]['rows'] or data[1]['rows'] or data[2]['rows'] or data[3]['rows'] or data[4]['rows'] or data[5]['rows']:
<ul class="list-unstyled"> <ul class="list-unstyled">
% for a in data: % for top_stat in data:
% if a['stat_id'] == 'top_tv' and a['rows']: % if top_stat['stat_id'] == 'top_tv' and top_stat['rows']:
<div class="home-platforms-instance"> <div class="home-platforms-instance">
<li> <li>
<div class="home-platforms-instance-info"> <div class="home-platforms-instance-info">
<div class="home-platforms-instance-name"> <div class="home-platforms-instance-name">
<h4>Most Watched TV</h4> <h4>Most Watched TV</h4>
<h5><a href="info?item_id=${a['rows'][0]['rating_key']}"> <h5>
${a['rows'][0]['title']} <a href="info?item_id=${top_stat['rows'][0]['rating_key']}">
</a></h5> ${top_stat['rows'][0]['title']}
</a>
</h5>
</div> </div>
<div class="user-platforms-instance-playcount"> <div class="home-platforms-instance-playcount">
% if a['stat_type'] == 'total_plays': % if top_stat['stat_type'] == 'total_plays':
<h3>${a['rows'][0]['total_plays']}</h3> <h3>${top_stat['rows'][0]['total_plays']}</h3>
<p> plays</p> <p> plays</p>
% else: % else:
${a['rows'][0]['total_duration'] | hd} ${top_stat['rows'][0]['total_duration'] | hd}
% endif % endif
</div> </div>
</div> </div>
<a href="info?item_id=${a['rows'][0]['rating_key']}"> <a href="info?item_id=${top_stat['rows'][0]['rating_key']}">
% if a['rows'][0]['grandparent_thumb']: % if top_stat['rows'][0]['grandparent_thumb']:
<div class="home-platforms-instance-poster"> <div class="home-platforms-instance-poster">
<div class="poster-face" style="background-image: url(pms_image_proxy?img=${a['rows'][0]['grandparent_thumb']}&width=300&height=450&fallback=poster);"></div> <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> </div>
% else: % else:
<div class="home-platforms-instance-poster"> <div class="home-platforms-instance-poster">
<div class="poster-face" style="background-image: url(interfaces/default/images/poster.png);"></div> <div class="home-platforms-poster-face" style="background-image: url(interfaces/default/images/poster.png);"></div>
</div> </div>
% endif % endif
</a> </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?item_id=${top_stat['rows'][loop.index]['rating_key']}">
${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?item_id=${top_stat['rows'][loop.index]['rating_key']}">
% 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> </li>
</div> </div>
% elif a['stat_id'] == 'popular_tv' and a['rows']: % elif top_stat['stat_id'] == 'popular_tv' and top_stat['rows']:
<div class="home-platforms-instance"> <div class="home-platforms-instance">
<li> <li>
<div class="home-platforms-instance-info"> <div class="home-platforms-instance-info">
<div class="home-platforms-instance-name"> <div class="home-platforms-instance-name">
<h4>Most Popular TV</h4> <h4>Most Popular TV</h4>
<h5><a href="info?item_id=${a['rows'][0]['rating_key']}"> <h5>
${a['rows'][0]['title']} <a href="info?item_id=${top_stat['rows'][0]['rating_key']}">
</a></h5> ${top_stat['rows'][0]['title']}
</a>
</h5>
</div> </div>
<div class="user-platforms-instance-playcount"> <div class="home-platforms-instance-playcount">
<h3>${a['rows'][0]['users_watched']}</h3> <h3>${top_stat['rows'][0]['users_watched']}</h3>
<p> users</p> <p> users</p>
</div> </div>
</div> </div>
<a href="info?item_id=${a['rows'][0]['rating_key']}"> <a href="info?item_id=${top_stat['rows'][0]['rating_key']}">
% if a['rows'][0]['grandparent_thumb'] != '': % if top_stat['rows'][0]['grandparent_thumb'] != '':
<div class="home-platforms-instance-poster"> <div class="home-platforms-instance-poster">
<div class="poster-face" style="background-image: url(pms_image_proxy?img=${a['rows'][0]['grandparent_thumb']}&width=300&height=450&fallback=poster);"></div> <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> </div>
% else: % else:
<div class="home-platforms-instance-poster"> <div class="home-platforms-instance-poster">
<div class="poster-face" style="background-image: url(interfaces/default/images/poster.png);"></div> <div class="home-platforms-poster-face" style="background-image: url(interfaces/default/images/poster.png);"></div>
</div> </div>
% endif % endif
</a> </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?item_id=${top_stat['rows'][loop.index]['rating_key']}">
${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?item_id=${top_stat['rows'][loop.index]['rating_key']}">
% 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> </li>
</div> </div>
% elif a['stat_id'] == 'top_movies' and a['rows']: % elif top_stat['stat_id'] == 'top_movies' and top_stat['rows']:
<div class="home-platforms-instance"> <div class="home-platforms-instance">
<li> <li>
<div class="home-platforms-instance-info"> <div class="home-platforms-instance-info">
<div class="home-platforms-instance-name"> <div class="home-platforms-instance-name">
<h4>Most Watched Movie</h4> <h4>Most Watched Movie</h4>
<h5><a href="info?item_id=${a['rows'][0]['rating_key']}"> <h5>
${a['rows'][0]['title']} <a href="info?item_id=${top_stat['rows'][0]['rating_key']}">
</a></h5> ${top_stat['rows'][0]['title']}
</a>
</h5>
</div> </div>
<div class="user-platforms-instance-playcount"> <div class="home-platforms-instance-playcount">
% if a['stat_type'] == 'total_plays': % if top_stat['stat_type'] == 'total_plays':
<h3>${a['rows'][0]['total_plays']}</h3> <h3>${top_stat['rows'][0]['total_plays']}</h3>
<p> plays</p> <p> plays</p>
% else: % else:
${a['rows'][0]['total_duration'] | hd} ${top_stat['rows'][0]['total_duration'] | hd}
% endif % endif
</div> </div>
</div> </div>
<a href="info?item_id=${a['rows'][0]['rating_key']}"> <a href="info?item_id=${top_stat['rows'][0]['rating_key']}">
% if a['rows'][0]['thumb']: % if top_stat['rows'][0]['thumb']:
<div class="home-platforms-instance-poster"> <div class="home-platforms-instance-poster">
<div class="poster-face" style="background-image: url(pms_image_proxy?img=${a['rows'][0]['thumb']}&width=300&height=450&fallback=poster);"></div> <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> </div>
% else: % else:
<div class="home-platforms-instance-poster"> <div class="home-platforms-instance-poster">
<div class="poster-face" style="background-image: url(interfaces/default/images/poster.png);"></div> <div class="home-platforms-poster-face" style="background-image: url(interfaces/default/images/poster.png);"></div>
</div> </div>
% endif % endif
</a> </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?item_id=${top_stat['rows'][loop.index]['rating_key']}">
${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?item_id=${top_stat['rows'][loop.index]['rating_key']}">
% 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> </li>
</div> </div>
% elif a['stat_id'] == 'popular_movies' and a['rows']: % elif top_stat['stat_id'] == 'popular_movies' and top_stat['rows']:
<div class="home-platforms-instance"> <div class="home-platforms-instance">
<li> <li>
<div class="home-platforms-instance-info"> <div class="home-platforms-instance-info">
<div class="home-platforms-instance-name"> <div class="home-platforms-instance-name">
<h4>Most Popular Movie</h4> <h4>Most Popular Movie</h4>
<h5><a href="info?item_id=${a['rows'][0]['rating_key']}"> <h5>
${a['rows'][0]['title']} <a href="info?item_id=${top_stat['rows'][0]['rating_key']}">
</a></h5> ${top_stat['rows'][0]['title']}
</a>
</h5>
</div> </div>
<div class="user-platforms-instance-playcount"> <div class="home-platforms-instance-playcount">
<h3>${a['rows'][0]['users_watched']}</h3> <h3>${top_stat['rows'][0]['users_watched']}</h3>
<p> users</p> <p> users</p>
</div> </div>
</div> </div>
<a href="info?item_id=${a['rows'][0]['rating_key']}"> <a href="info?item_id=${top_stat['rows'][0]['rating_key']}">
% if a['rows'][0]['thumb']: % if top_stat['rows'][0]['thumb']:
<div class="home-platforms-instance-poster"> <div class="home-platforms-instance-poster">
<div class="poster-face" style="background-image: url(pms_image_proxy?img=${a['rows'][0]['thumb']}&width=300&height=450&fallback=poster);"></div> <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> </div>
% else: % else:
<div class="home-platforms-instance-poster"> <div class="home-platforms-instance-poster">
<div class="poster-face" style="background-image: url(interfaces/default/images/poster.png);"></div> <div class="home-platforms-poster-face" style="background-image: url(interfaces/default/images/poster.png);"></div>
</div> </div>
% endif % endif
</a> </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?item_id=${top_stat['rows'][loop.index]['rating_key']}">
${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?item_id=${top_stat['rows'][loop.index]['rating_key']}">
% 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> </li>
</div> </div>
% elif a['stat_id'] == 'top_users' and a['rows']: % elif top_stat['stat_id'] == 'top_users' and top_stat['rows']:
<div class="home-platforms-instance"> <div class="home-platforms-instance">
<li> <li>
<div class="home-platforms-instance-info"> <div class="home-platforms-instance-info">
<div class="home-platforms-instance-name"> <div class="home-platforms-instance-name">
<h4>Most Active User</h4> <h4>Most Active User</h4>
<h5> <h5>
% if a['rows'][0]['user_id']: % if top_stat['rows'][0]['user_id']:
<a href="user?user_id=${a['rows'][0]['user_id']}"> <a href="user?user_id=${top_stat['rows'][0]['user_id']}">
% else: % else:
<a href="user?user=${a['rows'][0]['user']}"> <a href="user?user=${top_stat['rows'][0]['user']}">
% endif % endif
${a['rows'][0]['friendly_name']} ${top_stat['rows'][0]['friendly_name']}
</a> </a>
</h5> </h5>
</div> </div>
<div class="user-platforms-instance-playcount"> <div class="home-platforms-instance-playcount">
% if a['stat_type'] == 'total_plays': % if top_stat['stat_type'] == 'total_plays':
<h3>${a['rows'][0]['total_plays']}</h3> <h3>${top_stat['rows'][0]['total_plays']}</h3>
<p> plays</p> <p> plays</p>
% else: % else:
${a['rows'][0]['total_duration'] | hd} ${top_stat['rows'][0]['total_duration'] | hd}
% endif % endif
</div> </div>
</div> </div>
% if a['rows'][0]['user_id']: % if top_stat['rows'][0]['user_id']:
<a href="user?user_id=${a['rows'][0]['user_id']}"> <a href="user?user_id=${top_stat['rows'][0]['user_id']}">
% else: % else:
<a href="user?user=${a['rows'][0]['user']}"> <a href="user?user=${top_stat['rows'][0]['user']}">
% endif % endif
% if a['rows'][0]['thumb'] != '': % if top_stat['rows'][0]['user_thumb'] != '':
<div class="home-platforms-instance-poster"> <div class="home-platforms-instance-poster">
<div class="home-platforms-instance-oval" style="background-image: url(${a['rows'][0]['thumb']});"> <div class="home-platforms-instance-oval" style="background-image: url(${top_stat['rows'][0]['user_thumb']});"></div>
</div> </div>
% else: % else:
<div class="home-platforms-instance-poster"> <div class="home-platforms-instance-poster">
<div class="home-platforms-instance-oval" style="background-image: url(interfaces/default/images/gravatar-default.png);"> <div class="home-platforms-instance-oval" style="background-image: url(interfaces/default/images/gravatar-default.png);"></div>
</div> </div>
% endif % endif
</a> </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']}">
% else:
<a href="user?user=${top_stat['rows'][loop.index]['user']}">
% 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']}">
% else:
<a href="user?user=${top_stat['rows'][loop.index]['user']}">
% 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> </li>
</div> </div>
% elif a['stat_id'] == 'top_platforms' and a['rows']: % elif top_stat['stat_id'] == 'top_platforms' and top_stat['rows']:
<div class="home-platforms-instance"> <div class="home-platforms-instance">
<li> <li>
<div class="home-platforms-instance-info"> <div class="home-platforms-instance-info">
<div class="home-platforms-instance-name"> <div class="home-platforms-instance-name">
<h4>Most Active Platform</h4> <h4>Most Active Platform</h4>
<h5>${a['rows'][0]['platform_type']}</h5> <h5>${top_stat['rows'][0]['platform_type']}</h5>
</div> </div>
<div class="user-platforms-instance-playcount"> <div class="home-platforms-instance-playcount">
% if a['stat_type'] == 'total_plays': % if top_stat['stat_type'] == 'total_plays':
<h3>${a['rows'][0]['total_plays']}</h3> <h3>${top_stat['rows'][0]['total_plays']}</h3>
<p> plays</p> <p> plays</p>
% else: % else:
${a['rows'][0]['total_duration'] | hd} ${top_stat['rows'][0]['total_duration'] | hd}
% endif % endif
</div> </div>
</div> </div>
<div id="platform-stat" class="home-platforms-instance-poster"> <div id="platform-stat" class="home-platforms-instance-poster">
<div class="home-platforms-instance-box" style="background-image: url(interfaces/default/images/platforms/default.png);"> <script>
$("#platform-stat").html("<div class='home-platforms-instance-box' style='background-image: url(" + getPlatformImagePath('${top_stat['rows'][0]['platform_type']}') + ");'>");
</script>
</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]['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}
% endif
</div>
</div>
<div class="home-platforms-instance-poster" id="home-platforms-instance-poster-${loop.index + 1}">
<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>
</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>
<h5>
<a href="info?source=history&item_id=${top_stat['rows'][0]['row_id']}">
${top_stat['rows'][0]['title']}
</a>
</h5>
</div>
<div class="home-platforms-instance-last-user">
<h5>
% if top_stat['rows'][0]['user_id']:
<a href="user?user_id=${top_stat['rows'][0]['user_id']}">
% else:
<a href="user?user=${top_stat['rows'][0]['user']}">
% 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]['platform_type']}
</p>
</div>
</div>
<a href="info?source=history&item_id=${top_stat['rows'][0]['row_id']}">
% 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&item_id=${top_stat['rows'][loop.index]['row_id']}">
${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']}">
% else:
<a href="user?user=${top_stat['rows'][loop.index]['user']}">
% 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]['platform_type']}
</p>
</div>
</div>
<a href="info?source=history&item_id=${top_stat['rows'][loop.index]['row_id']}">
% 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> </li>
</div> </div>
<script>
$("#platform-stat").html("<div class='home-platforms-instance-box' style='background-image: url(" + getPlatformImagePath('${a['rows'][0]['platform_type']}') + ");'>");
</script>
% endif % endif
% endfor % endfor
</ul> </ul>
<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);
});
</script>
% else: % else:
<div class="text-muted">No stats for selected period.</div><br> <div class="text-muted">No stats for selected period.</div><br>
% endif % endif

View file

@ -19,7 +19,7 @@
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<div class="padded-header"> <div class="padded-header">
<h3>Statistics <small>Last ${config['home_stats_length']} days</small></h3> <h3>Watch Statistics <small>Last ${config['home_stats_length']} days</small></h3>
</div> </div>
<div id="home-stats" class="home-platforms"> <div id="home-stats" class="home-platforms">
<div class='text-muted'><i class="fa fa-refresh fa-spin"></i> Loading stats...</div> <div class='text-muted'><i class="fa fa-refresh fa-spin"></i> Loading stats...</div>
@ -27,6 +27,17 @@
</div> </div>
</div> </div>
</div> </div>
<div class="row">
<div class="col-md-12">
<div class="padded-header" id="library-statistics-header">
<h3>Library Statistics</h3>
</div>
<div id="library-stats" class="library-platforms">
<div class='text-muted'><i class="fa fa-refresh fa-spin"></i> Loading stats...</div>
<br>
</div>
</div>
</div>
<div class='row'> <div class='row'>
<div class="col-md-12"> <div class="col-md-12">
<div class="padded-header"> <div class="padded-header">
@ -45,17 +56,18 @@
<script src="interfaces/default/js/moment-with-locale.js"></script> <script src="interfaces/default/js/moment-with-locale.js"></script>
<script> <script>
function getHomeStats(days, plays) { function currentActivityHeader() {
$.ajax({ $.ajax({
url: 'home_stats', url: 'get_current_activity_header',
cache: false, cache: false,
async: true, async: true,
data: {time_range: days, stat_type: plays},
complete: function(xhr, status) { complete: function(xhr, status) {
$("#home-stats").html(xhr.responseText); $("#current-activity-header").html(xhr.responseText);
} }
}); });
} }
currentActivityHeader();
setInterval(currentActivityHeader, 15000);
function currentActivity() { function currentActivity() {
$.ajax({ $.ajax({
@ -70,18 +82,50 @@
currentActivity(); currentActivity();
setInterval(currentActivity, 15000); setInterval(currentActivity, 15000);
function currentActivityHeader() { function getHomeStats(days, stat_type, stat_count) {
$.ajax({ $.ajax({
url: 'get_current_activity_header', url: 'home_stats',
cache: false, cache: false,
async: true, async: true,
data: {time_range: days, stat_type: stat_type, stat_count: stat_count},
complete: function(xhr, status) { complete: function(xhr, status) {
$("#current-activity-header").html(xhr.responseText); $("#home-stats").html(xhr.responseText);
}
});
}
function getLibraryStatsHeader() {
$.ajax({
"url": "get_servers_info",
type: "post",
cache: false,
async: true,
data: { },
complete: function (xhr, status) {
server_info = $.parseJSON(xhr.responseText);
var server_name = 'Server name not found';
for (var i in server_info) {
if (server_info[i].machine_identifier == '${config['pms_identifier']}') {
server_name = server_info[i].name
break;
}
}
$('#library-statistics-header h3').append(' <small>' + server_name + '</small>')
}
});
}
function getLibraryStats() {
$.ajax({
url: 'library_stats',
cache: false,
async: true,
data: { },
complete: function(xhr, status) {
$("#library-stats").html(xhr.responseText);
} }
}); });
} }
currentActivityHeader();
setInterval(currentActivityHeader, 15000);
function recentlyAdded() { function recentlyAdded() {
var widthVal = $('body').find(".container-fluid").width(); var widthVal = $('body').find(".container-fluid").width();
@ -110,7 +154,20 @@
}); });
}); });
getHomeStats(${config['home_stats_length']}, ${config['home_stats_type']}); var date_format = 'YYYY-MM-DD';
var time_format = 'hh:mm a';
$.ajax({
url: 'get_date_formats',
type: 'GET',
success: function(data) {
date_format = data.date_format;
time_format = data.time_format;
}
});
getHomeStats(${config['home_stats_length']}, ${config['home_stats_type']}, ${config['home_stats_count']});
getLibraryStatsHeader();
getLibraryStats();
</script> </script>

View file

@ -47,201 +47,220 @@ DOCUMENTATION :: END
% if data: % if data:
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div> <div class="art-face" style="background-image:url(pms_image_proxy?img=${data['art']}&width=1920&height=1080)"></div>
<div class="art-face" style="background-image:url(pms_image_proxy?img=${data['art']}&width=1920&height=1080)"> <div class="summary-container">
<div class="summary-wrapper"> <div class="summary-navbar">
<div class="summary-overlay"> <div class="col-md-12">
<div class="row"> <div class="summary-navbar-list">
<div class="col-md-9"> % if data['type'] == 'movie':
<div class="summary-content-poster hidden-xs hidden-sm"> <span>Movies</span>
% if data['type'] == 'episode' and data['parent_thumb']: <span><i class="fa fa-chevron-right"></i></span>
<div class="poster-face" style="background-image: url(pms_image_proxy?img=${data['parent_thumb']}&width=300&height=450&fallback=poster);"></div> <span><a href="#">${data['title']}</a></span>
% elif data['type'] == 'episode': % elif data['type'] == 'show':
<div class="poster-face" style="background-image: url(pms_image_proxy?img=${data['grandparent_thumb']}&width=300&height=450&fallback=poster);"></div> <span>TV Shows</span>
% else: <span><i class="fa fa-chevron-right"></i></span>
<div class="poster-face" style="background-image: url(pms_image_proxy?img=${data['thumb']}&width=300&height=450&fallback=poster);"></div> <span><a href="#">${data['title']}</a></span>
% endif % elif data['type'] == 'season':
</div> <span>TV Shows</span>
<div class="summary-content"> <span><i class="fa fa-chevron-right"></i></span>
<div class="summary-content-title"> <span><a href="info?item_id=${data['parent_rating_key']}">${data['parent_title']}</a></span>
% if data['type'] == 'movie': <span><i class="fa fa-chevron-right"></i></span>
<h1>${data['title']}</h1> <span><a href="#">Season ${data['index']}</a></span>
% elif data['type'] == 'season': % elif data['type'] == 'episode':
<h1>${data['parent_title']} (${data['title']})</h1> <span>TV Shows</span>
% elif data['type'] == 'episode': <span><i class="fa fa-chevron-right"></i></span>
<h1>${data['grandparent_title']} - ${data['title']} <span><a href="info?item_id=${data['grandparent_rating_key']}">${data['grandparent_title']}</a></span>
(Season ${data['parent_index']}, Episode ${data['index']})</h1> <span><i class="fa fa-chevron-right"></i></span>
% else: <span><a href="info?item_id=${data['parent_rating_key']}">Season ${data['parent_index']}</a></span>
<h1>${data['title']}</h1> <span><i class="fa fa-chevron-right"></i></span>
% endif <span><a href="#">Episode ${data['index']} - ${data['title']}</a></span>
</div> % endif
% if (data['type'] == 'movie' or data['type'] == 'show' or data['type'] == 'episode') and data['rating']:
<div id="stars" class="rateit hidden-xs hidden-sm" data-rateit-value=""
data-rateit-ispreset="true" data-rateit-readonly="true"></div>
% endif
<div class="summary-content-details-wrapper">
<div class="summary-content-director">
% if (data['type'] == 'episode' or data['type'] == 'movie') and data['directors']:
Directed by <strong> ${data['directors'][0]}</strong>
% endif
</div>
<div class="summary-content-studio">
% if (data['type'] == 'show' or data['type'] == 'movie') and data['studio']:
Studio <strong> ${data['studio']}</strong>
% endif
</div>
<div class="summary-content-airdate">
% if data['type'] == 'movie':
Year <strong> ${data['year']}</strong>
% elif data['type'] == 'show':
Aired <strong> ${data['year']}</strong>
% elif data['type'] == 'episode':
Aired <strong> <span id="airdate">${data['originally_available_at']}</span></strong>
% endif
</div>
<div class="summary-content-duration">
Runtime <strong> <span id="runtime">${data['duration']}</span> mins</strong>
</div>
<div class="summary-content-content-rating">
% if (data['type'] == 'episode' or data['type'] == 'movie' or data['type'] == 'show') and data['content_rating']:
Rated <strong> ${data['content_rating']} </strong>
% endif
</div>
</div>
<div class="summary-content-summary">
<p> ${data['summary']} </p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="summary-content-people-wrapper hidden-xs hidden-sm">
% if (data['type'] == 'movie' or data['type'] == 'show') and data['genres']:
<div class="summary-content-genres">
<strong>Genres</strong>
<ul>
% for genre in data['genres']:
% if loop.index < 5:
<li>
${genre}
</li>
% endif
% endfor
</ul>
</div>
% endif
% if (data['type'] == 'episode' or data['type'] == 'movie') and data['writers']:
<div class="summary-content-writers">
<strong>Written by</strong>
<ul>
% for writer in data['writers']:
% if loop.index < 5:
<li>
${writer}
</li>
% endif
% endfor
</ul>
</div>
% endif
% if (data['type'] == 'movie' or data['type'] == 'show') and data['actors']:
<div class="summary-content-actors">
<strong>Starring</strong>
<ul>
% for actor in data['actors']:
% if loop.index < 5:
<li>
${actor}
</li>
% endif
% endfor
</ul>
</div>
% endif
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> <div class="summary-content-title-wrapper">
</div> <div class="col-md-9">
</div> <div class="summary-content-poster hidden-xs hidden-sm">
% if data['type'] == 'movie' or data['type'] == 'episode' or data['type'] == 'show': % if data['type'] == 'episode':
<div class='container-fluid'> <div class="summary-poster-face-episode" style="background-image: url(pms_image_proxy?img=${data['thumb']}&width=500&height=280&fallback=poster);"></div>
<div class='row'> % elif data['type'] == 'season':
<div class='col-md-12'> <div class="summary-poster-face" style="background-image: url(pms_image_proxy?img=${data['thumb']}&width=300&height=450&fallback=poster);"></div>
<div class='table-card-header'> % else:
<div class="header-bar"> <div class="summary-poster-face" style="background-image: url(pms_image_proxy?img=${data['thumb']}&width=300&height=450&fallback=poster);"></div>
<span>Watch History for <strong>${data['title']}</strong></span> % endif
</div> </div>
<div class="button-bar"> <div class="summary-content-title">
<span data-toggle="popover" data-placement="left" data-content="Select rows to delete. Data is deleted upon exiting delete mode." id="delete-message"> % if data['type'] == 'movie' or data['type'] == 'show':
<button class="btn btn-danger" data-toggle="button" aria-pressed="false" autocomplete="off" id="row-edit-mode"> <h1>&nbsp;</h1><h1>${data['title']}</h1>
<i class="fa fa-trash-o"></i> Delete mode % elif data['type'] == 'season':
</button>&nbsp <h1>&nbsp;</h1><h1><a href="info?item_id=${data['parent_rating_key']}">${data['parent_title']}</a></h1>
</span> <h3 class="hidden-xs">S${data['index']}</h3>
<div class="colvis-button-bar hidden-xs"></div> % elif data['type'] == 'episode':
</div> <h1><a href="info?item_id=${data['grandparent_rating_key']}">${data['grandparent_title']}</a></h1>
</div> <h2>${data['title']}</h2>
<div class="table-card-back"> <h3 class="hidden-xs">S${data['parent_index']} &middot; E${data['index']}</h3>
<table class="display" id="history_table" width="100%"> % endif
<thead>
<tr>
<th align='left' id="delete">Delete</th>
<th align='left' id="time">Time</th>
<th align='left' id="friendly_name">User</th>
<th align='left' id="ip_address">IP Address</th>
<th align='left' id="platform">Platform</th>
<th align='left' id="title">Title</th>
<th align='left' id="started">Started</th>
<th align='left' id="paused_counter">Paused</th>
<th align='left' id="stopped">Stopped</th>
<th align='left' id="duration">Duration</th>
<th align='left' id="percent_complete"></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<div id="info-modal" class="modal fade" 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> </div>
</div> </div>
</div> <div class="summary-content-wrapper">
</div> <div class="col-md-9">
</div> % if data['type'] == 'movie' or data['type'] == 'show' or data['type'] == 'season':
% elif data['type'] == 'season': <div class="summary-content-padding hidden-xs hidden-sm" style="height: 275px;"></div>
<div class='container-fluid'> % else:
<div class='row'> <div class="summary-content-padding hidden-xs hidden-sm"></div>
<div class='col-md-12'> % endif
<div class='table-card-header'> <div class="summary-content">
<div class="header-bar"> <div class="summary-content-details-wrapper">
<span>Episode List for <strong>${data['title']}</strong></span> % if (data['type'] == 'movie' or data['type'] == 'show' or data['type'] == 'episode') and data['rating']:
<div id="stars" class="rateit hidden-xs hidden-sm" data-rateit-value=""
data-rateit-ispreset="true" data-rateit-readonly="true"></div>
% endif
<div class="summary-content-director">
% if (data['type'] == 'episode' or data['type'] == 'movie') and data['directors']:
Directed by <strong> ${data['directors'][0]}</strong>
% endif
</div>
<div class="summary-content-studio">
% if (data['type'] == 'show' or data['type'] == 'movie') and data['studio']:
Studio <strong> ${data['studio']}</strong>
% endif
</div>
<div class="summary-content-airdate">
% if data['type'] == 'movie':
Year <strong> ${data['year']}</strong>
% elif data['type'] == 'show':
Aired <strong> ${data['year']}</strong>
% elif data['type'] == 'episode':
Aired <strong> <span id="airdate">${data['originally_available_at']}</span></strong>
% endif
</div>
<div class="summary-content-duration">
Runtime <strong> <span id="runtime">${data['duration']}</span> mins</strong>
</div>
<div class="summary-content-content-rating">
% if (data['type'] == 'episode' or data['type'] == 'movie' or data['type'] == 'show') and data['content_rating']:
Rated <strong> ${data['content_rating']} </strong>
% endif
</div>
</div>
<div class="summary-content-summary">
<p> ${data['summary']} </p>
</div>
</div>
</div> </div>
<div class="col-md-3">
<div class="summary-content-people-wrapper hidden-xs hidden-sm">
% if (data['type'] == 'episode' or data['type'] == 'movie') and data['writers']:
<div class="summary-content-writers">
<strong>Written by</strong>
<ul>
% for writer in data['writers']:
% if loop.index < 5:
<li>
${writer}
</li>
% endif
% endfor
</ul>
</div>
% endif
% if (data['type'] == 'movie' or data['type'] == 'show') and data['actors']:
<div class="summary-content-actors">
<strong>Starring</strong>
<ul>
% for actor in data['actors']:
% if loop.index < 5:
<li>
${actor}
</li>
% endif
% endfor
</ul>
</div>
% endif
</div>
<div class="summary-content-people-wrapper hidden-xs hidden-sm">
% if (data['type'] == 'movie' or data['type'] == 'show') and data['genres']:
<div class="summary-content-genres">
<strong>Genres</strong>
<ul>
% for genre in data['genres']:
% if loop.index < 5:
<li>
${genre}
</li>
% endif
% endfor
</ul>
</div>
% endif
</div>
</div>
% if data['type'] == 'show':
<div class='col-md-12'>
<div class='table-card-header'>
<div class="header-bar">
<span>Season List for <strong>${data['title']}</strong></span>
</div>
</div>
<div class='table-card-back'>
<div id="season-list"></div>
</div>
</div>
% elif data['type'] == 'season':
<div class='col-md-12'>
<div class='table-card-header'>
<div class="header-bar">
<span>Episode List for <strong>${data['title']}</strong></span>
</div>
</div>
<div class='table-card-back'>
<div id="episode-list"></div>
</div>
</div>
% endif
% if data['type'] == 'movie' or data['type'] == 'episode' or data['type'] == 'show' or data['type'] == 'season':
<div class='col-md-12'>
<div class='table-card-header'>
<div class="header-bar">
<span>Watch History for <strong>${data['title']}</strong></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>
<div class="alert alert-danger alert-edit" role="alert" id="row-edit-mode-alert"><i class="fa fa-exclamation-triangle"></i>&nbspSelect rows to delete. Data is deleted upon exiting delete mode.</div>
</div>
</div>
<div class="table-card-back">
<table class="display" id="history_table" width="100%">
<thead>
<tr>
<th align='left' id="delete">Delete</th>
<th align='left' id="time">Time</th>
<th align='left' id="friendly_name">User</th>
<th align='left' id="ip_address">IP Address</th>
<th align='left' id="platform">Platform</th>
<th align='left' id="title">Title</th>
<th align='left' id="started">Started</th>
<th align='left' id="paused_counter">Paused</th>
<th align='left' id="stopped">Stopped</th>
<th align='left' id="duration">Duration</th>
<th align='left' id="percent_complete"></th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<div id="info-modal" class="modal fade" 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>
% endif
</div> </div>
<div class='table-card-back'>
<div id="episode-list"></div>
</div>
% endif
</div> </div>
</div> </div>
</div> </div>
@ -249,8 +268,10 @@ DOCUMENTATION :: END
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-md-10"> <div class="col-md-10">
<h3>Error retrieving item data. This media may not be available in the Plex Media Server database <h3>
anymore.</h3> Error retrieving item data. This media may not be available in the Plex Media Server database
anymore.
</h3>
</div> </div>
</div> </div>
</div> </div>
@ -282,8 +303,7 @@ DOCUMENTATION :: END
type: 'post', type: 'post',
data: function ( d ) { data: function ( d ) {
return { 'json_data': JSON.stringify( d ), return { 'json_data': JSON.stringify( d ),
'rating_key': ${data['rating_key']} 'rating_key': ${data['rating_key']} };
};
} }
} }
history_table = $('#history_table').DataTable(history_table_options); history_table = $('#history_table').DataTable(history_table_options);
@ -293,62 +313,6 @@ DOCUMENTATION :: END
clearSearchButton('history_table', history_table); clearSearchButton('history_table', history_table);
$('#row-edit-mode').on('click', function() {
$('#delete-message').popover();
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++) {
$.ajax({
url: 'delete_history_rows',
data: { row_id: history_to_delete[i] },
async: true,
success: function (data) {
var msg = "User history purged";
showMsg(msg, false, true, 2000);
}
});
}
history_table.draw();
});
}
$('.delete-control').each(function () {
$(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger');
$(this).addClass('hidden');
});
} else {
history_to_delete = [];
$('.delete-control').each(function() {
$(this).removeClass('hidden');
});
}
});
});
</script>
% elif data['type'] == 'show':
<script src="interfaces/default/js/tables/history_table.js"></script>
<script>
$(document).ready(function () {
history_table_options.ajax = {
"url": "get_history",
type: 'post',
data: function ( d ) {
return { 'json_data': JSON.stringify( d ),
'grandparent_rating_key': ${data['rating_key']}
};
}
}
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, 10] });
$(colvis.button()).appendTo('div.colvis-button-bar');
clearSearchButton('history_table', history_table);
$('#row-edit-mode').click(function() { $('#row-edit-mode').click(function() {
if ($(this).hasClass('active')) { if ($(this).hasClass('active')) {
$('.delete-control').each(function() { $('.delete-control').each(function() {
@ -362,24 +326,143 @@ DOCUMENTATION :: END
}); });
}); });
</script> </script>
% endif % elif data['type'] == 'show':
<script src="interfaces/default/js/tables/history_table.js"></script>
% if data['type'] == 'season':
<script> <script>
$(document).ready(function () {
history_table_options.ajax = {
"url": "get_history",
type: 'post',
data: function ( d ) {
return { 'json_data': JSON.stringify( d ),
'grandparent_rating_key': ${data['rating_key']} };
}
}
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, 10] });
$(colvis.button()).appendTo('div.colvis-button-bar');
clearSearchButton('history_table', history_table);
$('#row-edit-mode').on('click', function() {
$('#row-edit-mode-alert').fadeIn(200);
if ($(this).hasClass('active')) {
if (history_to_delete.length > 0) {
$('#deleteCount').text(history_to_delete.length);
$('#confirm-modal').modal();
$('#confirm-modal').one('click', '#confirm-delete', function () {
for (var i = 0; i < history_to_delete.length; i++) {
$.ajax({
url: 'delete_history_rows',
data: { row_id: history_to_delete[i] },
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');
});
}
});
});
$.ajax({ $.ajax({
url: 'get_children', url: 'get_show_children',
type: "GET", type: "GET",
async: true, async: true,
data: { rating_key : ${data['rating_key']} }, data: { rating_key : ${data['rating_key']} },
complete: function(xhr, status) { complete: function(xhr, status) {
$("#episode-list").html(xhr.responseText); $("#season-list").html(xhr.responseText); }
});
</script>
% endif
% if data['type'] == 'season':
<script src="interfaces/default/js/tables/history_table.js"></script>
<script>
$(document).ready(function () {
history_table_options.ajax = {
"url": "get_history",
type: 'post',
data: function ( d ) {
return { 'json_data': JSON.stringify( d ),
'parent_rating_key': ${data['rating_key']} };
}
} }
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, 10] });
$(colvis.button()).appendTo('div.colvis-button-bar');
clearSearchButton('history_table', history_table);
$('#row-edit-mode').on('click', function() {
$('#row-edit-mode-alert').fadeIn(200);
if ($(this).hasClass('active')) {
if (history_to_delete.length > 0) {
$('#deleteCount').text(history_to_delete.length);
$('#confirm-modal').modal();
$('#confirm-modal').one('click', '#confirm-delete', function () {
for (var i = 0; i < history_to_delete.length; i++) {
$.ajax({
url: 'delete_history_rows',
data: { row_id: history_to_delete[i] },
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');
});
}
});
});
$.ajax({
url: 'get_season_children',
type: "GET",
async: true,
data: { rating_key : ${data['rating_key']} },
complete: function(xhr, status) {
$("#episode-list").html(xhr.responseText); }
}); });
</script> </script>
% endif % endif
<script> <script>
$("#airdate").html(moment($("#airdate")).format('MMM DD, YYYY')); $("#airdate").html(moment($("#airdate").text()).format('MMM DD, YYYY'));
$("#runtime").html(millisecondsToMinutes($("#runtime").html(), true)); $("#runtime").html(millisecondsToMinutes($("#runtime").text(), true));
</script> </script>
% endif % endif
<script>
$('div.art-face').animate({ opacity: 0.2 }, { duration: 1000 });
</script>
</%def> </%def>

View file

@ -34,7 +34,7 @@ DOCUMENTATION :: END
<div class="season-episodes-poster"> <div class="season-episodes-poster">
<div class="season-episodes-poster-face" style="background-image: url(pms_image_proxy?img=${a['thumb']}&width=500&height=280);"> <div class="season-episodes-poster-face" style="background-image: url(pms_image_proxy?img=${a['thumb']}&width=500&height=280);">
<div class="season-episodes-card-overlay"> <div class="season-episodes-card-overlay">
<div class="season-episodes-season"> <div class="season-episodes-overlay-text">
Episode ${a['index']} Episode ${a['index']}
</div> </div>
</div> </div>

View file

@ -0,0 +1,55 @@
<%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_season_list.html
Version: 0.1
Variable names: data [list]
data :: Usable parameters
== Global keys ==
season_count Returns the number of seasons in the array.
season_list Returns an array of seasons.
data['season_list'] :: Usable paramaters
== Global keys ==
rating_key Returns the unique identifier for the media item.
thumb Returns the location of the item's thumbnail. Use with pms_image_proxy.
title Returns the name of the season.
index Returns the season number.
DOCUMENTATION :: END
</%doc>
% if data != None:
% if data['season_count'] > 0:
<div class="show-seasons-wrapper">
<ul class="show-seasons-instance list-unstyled">
% for a in data['season_list']:
% if a['rating_key']:
<li>
<a href="info?item_id=${a['rating_key']}">
<div class="show-seasons-poster">
% if a['thumb']:
<div class="poster-face" style="background-image: url(pms_image_proxy?img=${a['thumb']}&width=300&height=450);">
% else:
<div class="poster-face" style="background-image: url(pms_image_proxy?img=${a['parent_thumb']}&width=300&height=450);">
% endif
<div class="show-seasons-card-overlay">
<div class="show-seasons-overlay-text">
Season ${a['index']}
</div>
</div>
</div>
</div>
</a>
</li>
% endif
% endfor
</ul>
</div>
% endif
% endif

View file

@ -74,6 +74,19 @@ history_table_modal_options = {
{ {
"targets": [3], "targets": [3],
"data":"player", "data":"player",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
var transcode_dec = '';
if (rowData['video_decision'] === 'transcode') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Transcode"><i class="fa fa-server fa-fw"></i></span>';
} else if (rowData['video_decision'] === 'copy') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Stream"><i class="fa fa-video-camera fa-fw"></i></span>';
} else if (rowData['video_decision'] === 'direct play' || rowData['video_decision'] === '') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Play"><i class="fa fa-play-circle fa-fw"></i></span>';
}
$(td).html('<div><a href="#" data-target="#info-modal" data-toggle="modal"><div style="float: left;">' + transcode_dec + '&nbsp' + cellData + '</div></a></div>');
}
},
"className": "no-wrap hidden-sm hidden-xs modal-control" "className": "no-wrap hidden-sm hidden-xs modal-control"
}, },
{ {
@ -81,14 +94,21 @@ history_table_modal_options = {
"data":"full_title", "data":"full_title",
"createdCell": function (td, cellData, rowData, row, col) { "createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') { if (cellData !== '') {
if (rowData['media_type'] === 'movie' || rowData['media_type'] === 'episode') { var media_type = '';
var transcode_dec = ''; var thumb_popover = '';
if (rowData['video_decision'] === 'transcode') { if (rowData['media_type'] === 'movie') {
transcode_dec = '<i class="fa fa-server"></i>&nbsp'; media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Movie"><i class="fa fa-film fa-fw"></i></span>';
} thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120">' + cellData + ' (' + rowData['year'] + ')</span>'
$(td).html('<div><div style="float: left;"><a href="info?source=history&item_id=' + rowData['id'] + '">' + cellData + '</a></div><div style="float: right; text-align: right; padding-right: 5px;">' + transcode_dec + '<i class="fa fa-video-camera"></i></div></div>'); $(td).html('<div class="history-title"><a href="info?source=history&item_id=' + rowData['id'] + '"><div style="float: left;">' + media_type + '&nbsp' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'episode') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Episode"><i class="fa fa-television fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120">' + cellData + ' \
(S' + ('00' + rowData['parent_media_index']).slice(-2) + 'E' + ('00' + rowData['media_index']).slice(-2) + ')</span>'
$(td).html('<div class="history-title"><a href="info?source=history&item_id=' + rowData['id'] + '"><div style="float: left;" >' + media_type + '&nbsp' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'track') { } else if (rowData['media_type'] === 'track') {
$(td).html('<div><div style="float: left;">' + cellData + '</div><div style="float: right; text-align: right; padding-right: 5px;"><i class="fa fa-music"></i></div></div>'); media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Track"><i class="fa fa-music fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=300&fallback=poster" data-height="80">' + cellData + ' (' + rowData['parent_title'] + ')</span>'
$(td).html('<div class="history-title"><div style="float: left;">' + media_type + '&nbsp' + thumb_popover + '</div></div>');
} else { } else {
$(td).html('<a href="info?item_id=' + rowData['id'] + '">' + cellData + '</a>'); $(td).html('<a href="info?item_id=' + rowData['id'] + '">' + cellData + '</a>');
} }
@ -100,9 +120,40 @@ history_table_modal_options = {
// Jump to top of page // Jump to top of page
// $('html,body').scrollTop(0); // $('html,body').scrollTop(0);
$('#ajaxMsg').fadeOut(); $('#ajaxMsg').fadeOut();
// Create the tooltips.
$('.transcode-tooltip').tooltip();
$('.media-type-tooltip').tooltip();
$('.thumb-tooltip').popover({
html: true,
trigger: 'hover',
placement: 'right',
content: function () {
return '<div class="history-thumbnail" style="background-image: url(' + $(this).data('img') + '); height: ' + $(this).data('height') + 'px;" />';
}
});
}, },
"preDrawCallback": function(settings) { "preDrawCallback": function(settings) {
var msg = "<div class='msg'><i class='fa fa-refresh fa-spin'></i>&nbspFetching rows...</div>"; var msg = "<div class='msg'><i class='fa fa-refresh fa-spin'></i>&nbspFetching rows...</div>";
showMsg(msg, false, false, 0) showMsg(msg, false, false, 0)
} }
} }
$('#history_table').on('click', 'td.modal-control', function () {
var tr = $(this).parents('tr');
var row = history_table.row(tr);
var rowData = row.data();
function showStreamDetails() {
$.ajax({
url: 'get_stream_data',
data: { row_id: rowData['id'], user: rowData['friendly_name'] },
cache: false,
async: true,
complete: function (xhr, status) {
$("#info-modal").html(xhr.responseText);
}
});
}
showStreamDetails();
});

View file

@ -1,3 +1,5 @@
var users_to_purge = [];
users_list_table_options = { users_list_table_options = {
"language": { "language": {
"search": "Search: ", "search": "Search: ",
@ -187,6 +189,11 @@ users_list_table_options = {
"preDrawCallback": function(settings) { "preDrawCallback": function(settings) {
var msg = "<div class='msg'><i class='fa fa-refresh fa-spin'></i>&nbspFetching rows...</div>"; var msg = "<div class='msg'><i class='fa fa-refresh fa-spin'></i>&nbspFetching rows...</div>";
showMsg(msg, false, false, 0) showMsg(msg, false, false, 0)
},
"rowCallback": function (row, rowData) {
if ($.inArray(rowData['user_id'], users_to_purge) !== -1) {
$(row).find('button[data-id="' + rowData['user_id'] + '"]').toggleClass('btn-warning').toggleClass('btn-danger');
}
} }
} }
@ -271,9 +278,11 @@ $('#users_list_table').on('click', 'td.edit-control > .edit-user-toggles > butto
var row = users_list_table.row(tr); var row = users_list_table.row(tr);
var rowData = row.data(); var rowData = row.data();
if ($(this).hasClass('active')) { var index = $.inArray(rowData['user_id'], users_to_purge);
$(this).toggleClass('btn-warning').toggleClass('btn-danger'); if (index === -1) {
users_to_purge.push(rowData['user_id']);
} else { } else {
$(this).toggleClass('btn-danger').toggleClass('btn-warning'); users_to_purge.splice(index, 1);
} }
$(this).toggleClass('btn-warning').toggleClass('btn-danger');
}); });

View file

@ -0,0 +1,78 @@
<%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: library_stats.html
Version: 0.1
Variable names: data [array]
data[array_index] :: Usable parameters
data['type'] Returns the type of the library. Either 'movie', 'show', 'photo', or 'artist'.
data['rows'] Returns an array containing stat data
data[array_index]['rows'] :: Usable parameters
title Returns the title of the library.
thumb Returns the thumb of the library.
count Returns the number of items in the library.
count_type Returns the sorting type for the library
== Only if 'type' is 'show'
episode_count Return the number of episodes in the library.
episode_count_type Return the sorting type for the episodes.
== Only if 'type' is 'artist'
album_count Return the number of episodes in the library.
album_count_type Return the sorting type for the episodes.
DOCUMENTATION :: END
</%doc>
% if data:
<ul class="list-unstyled">
% for library in data:
<div class="home-platforms-instance">
<li>
<div class="home-platforms-instance-info">
<div class="home-platforms-instance-name">
<h4>${library['rows']['title']}</h4>
<h5>${library['rows']['count_type']}</h5>
</div>
<div class="home-platforms-instance-playcount">
<h3>${library['rows']['count']}</h3>
<p> items</p>
</div>
% if library['type'] == 'show':
<div class="home-platforms-instance-name2">
<h5>${library['rows']['episode_count_type']}</h5>
<h3>${library['rows']['episode_count']}</h3>
<p> items</p>
</div>
% endif
% if library['type'] == 'artist':
<div class="home-platforms-instance-name2">
<h5>${library['rows']['album_count_type']}</h5>
<h3>${library['rows']['album_count']}</h3>
<p> items</p>
</div>
% endif
</div>
% if library['rows']['thumb']:
<div class="home-platforms-instance-poster">
<div class="home-platforms-library-thumb" style="background-image: url(pms_image_proxy?img=${library['rows']['thumb']}&width=300&height=300&fallback=poster);"></div>
</div>
% else:
<div class="home-platforms-instance-poster">
<div class="home-platforms-library-thumb" style="background-image: url(interfaces/default/images/poster.png);"></div>
</div>
% endif
</li>
</div>
% endfor
</ul>
% else:
<div class="text-muted">Unable to retrieve data from database. Please check your <a href="settings">settings</a>.
</div><br>
% endif

View file

@ -91,11 +91,22 @@ available_notification_agents = notifiers.available_notification_agents()
<label for="home_stats_length">Time Frame</label> <label for="home_stats_length">Time Frame</label>
<div class="row"> <div class="row">
<div class="col-md-2"> <div class="col-md-2">
<input type="text" class="form-control" data-parsley-type="integer" id="home_stats_length" name="home_stats_length" value="${config['home_stats_length']}" size="3" data-parsley-min="0" data-parsley-trigger="change" required> <input type="text" class="form-control" data-parsley-type="integer" id="home_stats_length" name="home_stats_length" value="${config['home_stats_length']}" size="3" data-parsley-min="0" data-parsley-trigger="change" data-parsley-errors-container="#home_stats_length_error" required>
</div> </div>
<div id="home_stats_length_error" class="alert alert-danger settings-alert" role="alert"></div>
</div> </div>
<p class="help-block">Specify the number of days for the statistics on the home page. Default is 30 days.</p> <p class="help-block">Specify the number of days for the statistics on the home page. Default is 30 days.</p>
</div> </div>
<div class="form-group">
<label for="home_stats_count">Top Lists</label>
<div class="row">
<div class="col-md-2">
<input type="text" class="form-control" data-parsley-type="integer" id="home_stats_count" name="home_stats_count" value="${config['home_stats_count']}" size="3" data-parsley-range="[0,10]" data-parsley-trigger="change" data-parsley-errors-container="#home_stats_count_error" required>
</div>
<div id="home_stats_count_error" class="alert alert-danger settings-alert" role="alert"></div>
</div>
<p class="help-block">Specify the number of items to show in the top lists for the statistics on the home page. Max is 10 items, default is 5 items, 0 to disable.</p>
</div>
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input type="checkbox" id="home_stats_type" name="home_stats_type" value="1" ${config['home_stats_type']}> Use play duration <input type="checkbox" id="home_stats_type" name="home_stats_type" value="1" ${config['home_stats_type']}> Use play duration
@ -122,8 +133,9 @@ available_notification_agents = notifiers.available_notification_agents()
<label for="http_port">HTTP Port</label> <label for="http_port">HTTP Port</label>
<div class="row"> <div class="row">
<div class="col-md-2"> <div class="col-md-2">
<input type="text" class="form-control" data-parsley-type="integer" id="http_port" name="http_port" value="${config['http_port']}" data-parsley-trigger="change" required> <input type="text" class="form-control" data-parsley-type="integer" id="http_port" name="http_port" value="${config['http_port']}" data-parsley-trigger="change" data-parsley-errors-container="#http_port_error" required>
</div> </div>
<div id="http_port_error" class="alert alert-danger settings-alert" role="alert"></div>
</div> </div>
<p class="help-block">Port to bind web server to. Note that ports below 1024 may require root.</p> <p class="help-block">Port to bind web server to. Note that ports below 1024 may require root.</p>
</div> </div>
@ -223,8 +235,9 @@ available_notification_agents = notifiers.available_notification_agents()
<label for="pms_port">Plex Port</label> <label for="pms_port">Plex Port</label>
<div class="row"> <div class="row">
<div class="col-md-2"> <div class="col-md-2">
<input data-parsley-type="integer" class="pms-settings form-control" type="text" id="pms_port" name="pms_port" value="${config['pms_port']}" size="30" data-parsley-trigger="change" required> <input data-parsley-type="integer" class="pms-settings form-control" type="text" id="pms_port" name="pms_port" value="${config['pms_port']}" size="30" data-parsley-trigger="change" data-parsley-errors-container="#pms_port_error" required>
</div> </div>
<div id="pms_port_error" class="alert alert-danger settings-alert" role="alert"></div>
</div> </div>
<p class="help-block">Port that Plex Media Server is listening on.</p> <p class="help-block">Port that Plex Media Server is listening on.</p>
</div> </div>
@ -286,8 +299,9 @@ available_notification_agents = notifiers.available_notification_agents()
<label for="refresh_users_interval">User list Refresh Interval</label> <label for="refresh_users_interval">User list Refresh Interval</label>
<div class="row"> <div class="row">
<div class="col-md-2"> <div class="col-md-2">
<input type="text" class="form-control" data-parsley-type="integer" id="refresh_users_interval" name="refresh_users_interval" value="${config['refresh_users_interval']}" size="5" data-parsley-range="[1,24]" data-parsley-trigger="change" required> <input type="text" class="form-control" data-parsley-type="integer" id="refresh_users_interval" name="refresh_users_interval" value="${config['refresh_users_interval']}" size="5" data-parsley-range="[1,24]" data-parsley-trigger="change" data-parsley-errors-container="#refresh_users_interval_error" required>
</div> </div>
<div id="refresh_users_interval_error" class="alert alert-danger settings-alert" role="alert"></div>
</div> </div>
<p class="help-block">The interval (in hours) PlexPy will request an updated friends list from Plex.tv. 1 minimum, 24 maximum.</p> <p class="help-block">The interval (in hours) PlexPy will request an updated friends list from Plex.tv. 1 minimum, 24 maximum.</p>
</div> </div>
@ -328,10 +342,11 @@ available_notification_agents = notifiers.available_notification_agents()
<label for="monitoring_interval">Monitoring Interval</label> <label for="monitoring_interval">Monitoring Interval</label>
<div class="row"> <div class="row">
<div class="col-md-2"> <div class="col-md-2">
<input type="text" class="form-control" data-parsley-type="integer" id="monitoring_interval" name="monitoring_interval" value="${config['monitoring_interval']}" size="5" data-parsley-min="30" data-parsley-trigger="change" required> <input type="text" class="form-control" data-parsley-type="integer" id="monitoring_interval" name="monitoring_interval" value="${config['monitoring_interval']}" size="5" data-parsley-min="30" data-parsley-trigger="change" data-parsley-errors-container="#monitoring_interval_error" required>
</div> </div>
<div id="monitoring_interval_error" class="alert alert-danger settings-alert" role="alert"></div>
</div> </div>
<p class="help-block">The interval (in seconds) PlexPy will ping your Plex Server. Min 30 seconds, Recommended 60 seconds.</p> <p class="help-block">The interval (in seconds) PlexPy will ping your Plex Server. Min 30 seconds, recommended 60 seconds.</p>
</div> </div>
<div class="padded-header"> <div class="padded-header">
@ -344,11 +359,12 @@ available_notification_agents = notifiers.available_notification_agents()
<p class="help-block">Keep records of all video items played from your Plex Media Server.</p> <p class="help-block">Keep records of all video items played from your Plex Media Server.</p>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="monitoring_interval">Ignore Interval</label> <label for="logging_ignore_interval">Ignore Interval</label>
<div class="row"> <div class="row">
<div class="col-md-2"> <div class="col-md-2">
<input type="text" class="form-control" data-parsley-type="integer" id="logging_ignore_interval" name="logging_ignore_interval" value="${config['logging_ignore_interval']}" size="5" data-parsley-min="0" data-parsley-trigger="change" required> <input type="text" class="form-control" data-parsley-type="integer" id="logging_ignore_interval" name="logging_ignore_interval" value="${config['logging_ignore_interval']}" size="5" data-parsley-min="0" data-parsley-trigger="change" data-parsley-errors-container="#logging_ignore_interval_error" required>
</div> </div>
<div id="logging_ignore_interval_error" class="alert alert-danger settings-alert" role="alert"></div>
</div> </div>
<p class="help-block">The interval (in seconds) PlexPy will wait for a video item to be active before logging it. 0 to disable.</p> <p class="help-block">The interval (in seconds) PlexPy will wait for a video item to be active before logging it. 0 to disable.</p>
</div> </div>
@ -380,8 +396,9 @@ available_notification_agents = notifiers.available_notification_agents()
<label for="buffer_threshold">Buffer Threshold</label> <label for="buffer_threshold">Buffer Threshold</label>
<div class="row"> <div class="row">
<div class="col-md-2"> <div class="col-md-2">
<input type="text" class="form-control" data-parsley-type="integer" id="buffer_threshold" name="buffer_threshold" value="${config['buffer_threshold']}" data-parsley-range="[0,50]" data-parsley-trigger="change" required> <input type="text" class="form-control" data-parsley-type="integer" id="buffer_threshold" name="buffer_threshold" value="${config['buffer_threshold']}" data-parsley-range="[0,50]" data-parsley-trigger="change" data-parsley-errors-container="#buffer_threshold_error" required>
</div> </div>
<div id="buffer_threshold_error" class="alert alert-danger settings-alert" role="alert"></div>
</div> </div>
<p class="help-block">How many buffer events should we wait before triggering the first warning. Buffer events increment on each monitor ping if play state is buffering. 0 to disable buffer warnings.</p> <p class="help-block">How many buffer events should we wait before triggering the first warning. Buffer events increment on each monitor ping if play state is buffering. 0 to disable buffer warnings.</p>
</div> </div>
@ -389,8 +406,9 @@ available_notification_agents = notifiers.available_notification_agents()
<label for="buffer_wait">Buffer Wait</label> <label for="buffer_wait">Buffer Wait</label>
<div class="row"> <div class="row">
<div class="col-md-2"> <div class="col-md-2">
<input type="text" class="form-control" data-parsley-type="integer" id="buffer_wait" name="buffer_wait" value="${config['buffer_wait']}" data-parsley-min="0" data-parsley-trigger="change" required> <input type="text" class="form-control" data-parsley-type="integer" id="buffer_wait" name="buffer_wait" value="${config['buffer_wait']}" data-parsley-min="0" data-parsley-trigger="change" data-parsley-errors-container="#buffer_wait_error" required>
</div> </div>
<div id="buffer_wait_error" class="alert alert-danger settings-alert" role="alert"></div>
</div> </div>
<p class="help-block">The value (in seconds) PlexPy should wait before triggering the next buffer warning. 0 to always trigger.</p> <p class="help-block">The value (in seconds) PlexPy should wait before triggering the next buffer warning. 0 to always trigger.</p>
</div> </div>
@ -421,8 +439,9 @@ available_notification_agents = notifiers.available_notification_agents()
<label for="notify_watched_percent">Watched Percent</label> <label for="notify_watched_percent">Watched Percent</label>
<div class="row"> <div class="row">
<div class="col-md-2"> <div class="col-md-2">
<input type="text" class="form-control" data-parsley-type="integer" id="notify_watched_percent" name="notify_watched_percent" value="${config['notify_watched_percent']}" size="5" data-parsley-range="[50,95]" data-parsley-trigger="change" required> <input type="text" class="form-control" data-parsley-type="integer" id="notify_watched_percent" name="notify_watched_percent" value="${config['notify_watched_percent']}" size="5" data-parsley-range="[50,95]" data-parsley-trigger="change" data-parsley-errors-container="#notify_watched_percent_error" required>
</div> </div>
<div id="notify_watched_percent_error" class="alert alert-danger settings-alert" role="alert"></div>
</div> </div>
<p class="help-block">Set the progress percentage of when a watched notification should be triggered. Minimum 50, Maximum 95.</p> <p class="help-block">Set the progress percentage of when a watched notification should be triggered. Minimum 50, Maximum 95.</p>
</div> </div>
@ -552,7 +571,7 @@ available_notification_agents = notifiers.available_notification_agents()
<h3>Notification Agents</h3> <h3>Notification Agents</h3>
</div> </div>
<p class="help-block"> <p class="help-block">
Toggle the desired notification options by clicking the bolt icon and configure it by selecting the settings icon to the right. Toggle the desired notification options by clicking the bell icon and configure it by clicking the settings icon to the right.
</p> </p>
<br/> <br/>
<ul class="stacked-configs list-unstyled"> <ul class="stacked-configs list-unstyled">
@ -560,9 +579,9 @@ available_notification_agents = notifiers.available_notification_agents()
<li> <li>
<span> <span>
% if agent['on_play'] or agent['on_stop'] or agent['on_pause'] or agent['on_resume'] or agent['on_buffer'] or agent['on_watched']: % if agent['on_play'] or agent['on_stop'] or agent['on_pause'] or agent['on_resume'] or agent['on_buffer'] or agent['on_watched']:
<a href="javascript:void(0)" data-target="#notification-triggers-modal" data-id="${agent['id']}" class="toggle-notification-triggers-modal toggle-left active" data-toggle="modal"><i class="fa fa-lg fa-flash"></i></a> <a href="javascript:void(0)" data-target="#notification-triggers-modal" data-id="${agent['id']}" class="toggle-notification-triggers-modal toggle-left active" data-toggle="modal"><i class="fa fa-lg fa-bell"></i></a>
% else: % else:
<a href="javascript:void(0)" data-target="#notification-triggers-modal" data-id="${agent['id']}" class="toggle-notification-triggers-modal toggle-left" data-toggle="modal"><i class="fa fa-lg fa-flash"></i></a> <a href="javascript:void(0)" data-target="#notification-triggers-modal" data-id="${agent['id']}" class="toggle-notification-triggers-modal toggle-left" data-toggle="modal"><i class="fa fa-lg fa-bell"></i></a>
% endif % endif
${agent['name']} ${agent['name']}
% if agent['has_config']: % if agent['has_config']:

View file

@ -54,6 +54,7 @@ DOCUMENTATION :: END
<div class="row"> <div class="row">
<div class="col-md-4"> <div class="col-md-4">
<h4><strong>Stream Details</strong></h4> <h4><strong>Stream Details</strong></h4>
% if data['media_type'] != 'track':
<h5>Video</h5> <h5>Video</h5>
<ul class="list-unstyled"> <ul class="list-unstyled">
% if data['transcode_video_dec'] != 'direct play': % if data['transcode_video_dec'] != 'direct play':
@ -74,6 +75,7 @@ DOCUMENTATION :: END
<li>Video Height: <strong>${data['height']}</strong></li> <li>Video Height: <strong>${data['height']}</strong></li>
% endif % endif
</ul> </ul>
% endif
<h5>Audio</h5> <h5>Audio</h5>
<ul class="list-unstyled"> <ul class="list-unstyled">
% if data['transcode_audio_dec'] != 'direct play': % if data['transcode_audio_dec'] != 'direct play':
@ -91,11 +93,14 @@ DOCUMENTATION :: END
<h4><strong>Media Source Details</strong></h4> <h4><strong>Media Source Details</strong></h4>
<ul class="list-unstyled"> <ul class="list-unstyled">
<li>Container: <strong>${data['container']}</strong></li> <li>Container: <strong>${data['container']}</strong></li>
% if data['media_type'] != 'track':
<li>Resolution: <strong>${data['height']}p</strong></li> <li>Resolution: <strong>${data['height']}p</strong></li>
% endif
<li>Bitrate: <strong>${data['bitrate']} kbps</strong></li> <li>Bitrate: <strong>${data['bitrate']} kbps</strong></li>
</ul> </ul>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
% if data['media_type'] != 'track':
<h4><strong>Video Source Details</strong></h4> <h4><strong>Video Source Details</strong></h4>
<ul class="list-unstyled"> <ul class="list-unstyled">
<li>Width: <strong>${data['width']}</strong></li> <li>Width: <strong>${data['width']}</strong></li>
@ -104,6 +109,7 @@ DOCUMENTATION :: END
<li>Video Frame Rate: <strong>${data['video_framerate']}</strong></li> <li>Video Frame Rate: <strong>${data['video_framerate']}</strong></li>
<li>Video Codec: <strong>${data['video_codec']}</strong></li> <li>Video Codec: <strong>${data['video_codec']}</strong></li>
</ul> </ul>
% endif
<h4><strong>Audio Source Details</strong></h4> <h4><strong>Audio Source Details</strong></h4>
<ul class="list-unstyled"> <ul class="list-unstyled">
<li>Audio Codec: <strong>${data['audio_codec']}</strong></li> <li>Audio Codec: <strong>${data['audio_codec']}</strong></li>

View file

@ -154,13 +154,11 @@ from plexpy import helpers
</strong></span> </strong></span>
</div> </div>
<div class="button-bar"> <div class="button-bar">
<span data-toggle="popover" data-placement="left" data-content="Select rows to delete. Data is deleted upon exiting delete mode." id="delete-message"> <div class="colvis-button-bar hidden-xs" id="button-bar-history"></div>
<button class="btn btn-danger" data-toggle="button" aria-pressed="false" autocomplete="off" id="row-edit-mode"> <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 <i class="fa fa-trash-o"></i> Delete mode
</button>&nbsp </button>
</span> <div class="alert alert-danger alert-edit" role="alert" id="row-edit-mode-alert"><i class="fa fa-exclamation-triangle"></i>&nbspSelect rows to delete. Data is deleted upon exiting delete mode.</div>
<div class="colvis-button-bar hidden-xs" id="button-bar-history">
</div>
</div> </div>
</div> </div>
<div class="table-card-back"> <div class="table-card-back">
@ -170,8 +168,8 @@ from plexpy import helpers
<th align='left' id="delete">Delete</th> <th align='left' id="delete">Delete</th>
<th align='left' id="time">Time</th> <th align='left' id="time">Time</th>
<th align='left' id="friendly_name">User</th> <th align='left' id="friendly_name">User</th>
<th align='left' id="platform">Platform</th>
<th align='left' id="ip_address">IP Address</th> <th align='left' id="ip_address">IP Address</th>
<th align='left' id="platform">Platform</th>
<th align='left' id="title">Title</th> <th align='left' id="title">Title</th>
<th align='left' id="started">Started</th> <th align='left' id="started">Started</th>
<th align='left' id="paused_counter">Paused</th> <th align='left' id="paused_counter">Paused</th>
@ -390,7 +388,7 @@ from plexpy import helpers
}); });
$('#row-edit-mode').on('click', function() { $('#row-edit-mode').on('click', function() {
$('#delete-message').popover(); $('#row-edit-mode-alert').fadeIn(200);
if ($(this).hasClass('active')) { if ($(this).hasClass('active')) {
if (history_to_delete.length > 0) { if (history_to_delete.length > 0) {
@ -403,7 +401,7 @@ from plexpy import helpers
data: { row_id: history_to_delete[i] }, data: { row_id: history_to_delete[i] },
async: true, async: true,
success: function (data) { success: function (data) {
var msg = "User history purged"; var msg = "History deleted";
showMsg(msg, false, true, 2000); showMsg(msg, false, true, 2000);
} }
}); });
@ -413,13 +411,14 @@ from plexpy import helpers
} }
$('.delete-control').each(function () { $('.delete-control').each(function () {
$(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger');
$(this).addClass('hidden'); $(this).addClass('hidden');
$('#row-edit-mode-alert').fadeOut(200);
}); });
} else { } else {
history_to_delete = []; history_to_delete = [];
$('.delete-control').each(function() { $('.delete-control').each(function() {
$(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger');
$(this).removeClass('hidden'); $(this).removeClass('hidden');
}); });
} }

View file

@ -12,12 +12,11 @@
<span><i class="fa fa-group"></i> All Users</span> <span><i class="fa fa-group"></i> All Users</span>
</div> </div>
<div class="button-bar"> <div class="button-bar">
<span data-toggle="popover" data-placement="left" data-content="Data purging does not occur until after exiting edit mode." id="purge-message"> <button class="btn btn-dark refresh-users-button" id="refresh-users-list"><i class="fa fa-refresh"></i> Refresh users</button>
<button class="btn btn-dark" data-toggle="button" aria-pressed="false" autocomplete="off" id="row-edit-mode"> <button class="btn btn-danger btn-edit" data-toggle="button" aria-pressed="false" autocomplete="off" id="row-edit-mode">
<i class="fa fa-pencil"></i> Edit mode <i class="fa fa-pencil"></i> Edit mode
</button>&nbsp </button>&nbsp
</span> <div class="alert alert-danger alert-edit" role="alert" id="row-edit-mode-alert"><i class="fa fa-exclamation-triangle"></i>&nbspSelect rows to delete. Data is deleted upon exiting delete mode.</div>
<button class="btn btn-dark" id="refresh-users-list"><i class="fa fa-refresh"></i> Refresh users</button>
</div> </div>
</div> </div>
<div class='table-card-back'> <div class='table-card-back'>
@ -85,20 +84,19 @@
clearSearchButton('users_list_table', users_list_table); clearSearchButton('users_list_table', users_list_table);
var users_to_purge = [];
$('#row-edit-mode').on('click', function () { $('#row-edit-mode').on('click', function () {
$('#purge-message').popover(); $('#row-edit-mode-alert').fadeIn(200);
$('#users-to-delete').html('');
if ($(this).hasClass('active')) { if ($(this).hasClass('active')) {
users_to_purge = [];
ul = $('#users-to-delete');
ul.html('');
$('.edit-control button.btn-danger').map(function () {
users_to_purge.push($(this).attr('data-id'));
ul.append('<li>' + $('div[data-id=' + $(this).attr('data-id') + '] > input').val() + '</li>')
});
if (users_to_purge.length > 0) { if (users_to_purge.length > 0) {
$('#users-to-delete').append $('.edit-control').each(function () {
$(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger');
});
for (var i = 0; i < users_to_purge.length; i++) {
$('#users-to-delete').append('<li>' + $('div[data-id=' + users_to_purge[i] + '] > input').val() + '</li>');
}
$('#confirm-modal').modal(); $('#confirm-modal').modal();
$('#confirm-modal').one('click', '#confirm-purge', function () { $('#confirm-modal').one('click', '#confirm-purge', function () {
for (var i = 0; i < users_to_purge.length; i++) { for (var i = 0; i < users_to_purge.length; i++) {
@ -118,35 +116,16 @@
} }
$('.edit-control').each(function () { $('.edit-control').each(function () {
$(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger');
$(this).addClass('hidden'); $(this).addClass('hidden');
}); $('#row-edit-mode-alert').fadeOut(200);
$('.edit-user-control > .edit-user-name').each(function () {
a = $(this).children('a');
input = $(this).children('input');
a.text(input.val());
a.removeClass('hidden');
input.addClass('hidden');
}); });
} else { } else {
users_to_purge = [];
$('.edit-control').each(function () { $('.edit-control').each(function () {
$(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger');
$(this).removeClass('hidden'); $(this).removeClass('hidden');
}); });
$('.edit-user-control > .edit-user-name').each(function () {
$(this).children('a').addClass('hidden');
$(this).children('input').removeClass('hidden');
});
}
});
$(window).resize(function () {
if ($('.popover').popover().is(':visible')) {
var popover = $('.popover');
popover.addClass("noTransition");
$('#purge-message').popover('show');
popover.removeClass("noTransition");
} }
}); });
}); });

View file

@ -588,6 +588,12 @@ def dbcheck():
'ALTER TABLE users ADD COLUMN custom_avatar_url TEXT' 'ALTER TABLE users ADD COLUMN custom_avatar_url TEXT'
) )
# Add "Local" user to database as default unauthenticated user.
result = c_db.execute('SELECT id FROM users WHERE username = "Local"')
if not result.fetchone():
logger.debug(u'User "Local" does not exist. Adding user.')
c_db.execute('INSERT INTO users (user_id, username) VALUES (0, "Local")')
conn_db.commit() conn_db.commit()
c_db.close() c_db.close()

View file

@ -1,4 +1,4 @@
import plexpy.logger import plexpy.logger
import itertools import itertools
import os import os
import re import re
@ -84,6 +84,7 @@ _CONFIG_DEFINITIONS = {
'GROWL_ON_WATCHED': (int, 'Growl', 0), 'GROWL_ON_WATCHED': (int, 'Growl', 0),
'HOME_STATS_LENGTH': (int, 'General', 30), 'HOME_STATS_LENGTH': (int, 'General', 30),
'HOME_STATS_TYPE': (int, 'General', 0), 'HOME_STATS_TYPE': (int, 'General', 0),
'HOME_STATS_COUNT': (int, 'General', 5),
'HTTPS_CERT': (str, 'General', ''), 'HTTPS_CERT': (str, 'General', ''),
'HTTPS_KEY': (str, 'General', ''), 'HTTPS_KEY': (str, 'General', ''),
'HTTP_HOST': (str, 'General', '0.0.0.0'), 'HTTP_HOST': (str, 'General', '0.0.0.0'),

View file

@ -55,6 +55,7 @@ class DataFactory(object):
(CASE WHEN session_history_metadata.duration IS NULL THEN 1.0 ELSE \ (CASE WHEN session_history_metadata.duration IS NULL THEN 1.0 ELSE \
session_history_metadata.duration * 1.0 END) * 100) as percent_complete', session_history_metadata.duration * 1.0 END) * 100) as percent_complete',
'session_history.grandparent_rating_key as grandparent_rating_key', 'session_history.grandparent_rating_key as grandparent_rating_key',
'session_history.parent_rating_key as parent_rating_key',
'session_history.rating_key as rating_key', 'session_history.rating_key as rating_key',
'session_history.user', 'session_history.user',
'session_history_metadata.media_type', 'session_history_metadata.media_type',
@ -112,6 +113,7 @@ class DataFactory(object):
"duration": item["duration"], "duration": item["duration"],
"percent_complete": item["percent_complete"], "percent_complete": item["percent_complete"],
"grandparent_rating_key": item["grandparent_rating_key"], "grandparent_rating_key": item["grandparent_rating_key"],
"parent_rating_key": item["parent_rating_key"],
"rating_key": item["rating_key"], "rating_key": item["rating_key"],
"user": item["user"], "user": item["user"],
"media_type": item["media_type"], "media_type": item["media_type"],
@ -129,7 +131,7 @@ class DataFactory(object):
return dict return dict
def get_home_stats(self, time_range='30', stat_type='0'): def get_home_stats(self, time_range='30', stat_type='0', stat_count='5'):
monitor_db = database.MonitorDatabase() monitor_db = database.MonitorDatabase()
if not time_range.isdigit(): if not time_range.isdigit():
@ -137,8 +139,11 @@ class DataFactory(object):
sort_type = 'total_plays' if stat_type == '0' else 'total_duration' sort_type = 'total_plays' if stat_type == '0' else 'total_duration'
if not time_range.isdigit():
stat_count = '5'
# This actually determines the output order in the home page # This actually determines the output order in the home page
stats_queries = ["top_tv", "popular_tv", "top_movies", "popular_movies", "top_users", "top_platforms"] stats_queries = ["top_tv", "popular_tv", "top_movies", "popular_movies", "top_users", "top_platforms", "last_watched"]
home_stats = [] home_stats = []
for stat in stats_queries: for stat in stats_queries:
@ -161,7 +166,7 @@ class DataFactory(object):
'>= datetime("now", "-%s days", "localtime") ' \ '>= datetime("now", "-%s days", "localtime") ' \
'AND session_history_metadata.media_type = "episode" ' \ 'AND session_history_metadata.media_type = "episode" ' \
'GROUP BY session_history_metadata.grandparent_title ' \ 'GROUP BY session_history_metadata.grandparent_title ' \
'ORDER BY %s DESC LIMIT 10' % (time_range, sort_type) 'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stat_count)
result = monitor_db.select(query) result = monitor_db.select(query)
except: except:
logger.warn("Unable to execute database query.") logger.warn("Unable to execute database query.")
@ -207,7 +212,7 @@ class DataFactory(object):
'>= datetime("now", "-%s days", "localtime") ' \ '>= datetime("now", "-%s days", "localtime") ' \
'AND session_history_metadata.media_type = "movie" ' \ 'AND session_history_metadata.media_type = "movie" ' \
'GROUP BY session_history_metadata.full_title ' \ 'GROUP BY session_history_metadata.full_title ' \
'ORDER BY %s DESC LIMIT 10' % (time_range, sort_type) 'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stat_count)
result = monitor_db.select(query) result = monitor_db.select(query)
except: except:
logger.warn("Unable to execute database query.") logger.warn("Unable to execute database query.")
@ -251,7 +256,7 @@ class DataFactory(object):
'AND session_history_metadata.media_type = "episode" ' \ 'AND session_history_metadata.media_type = "episode" ' \
'GROUP BY session_history_metadata.grandparent_title ' \ 'GROUP BY session_history_metadata.grandparent_title ' \
'ORDER BY users_watched DESC, total_plays DESC ' \ 'ORDER BY users_watched DESC, total_plays DESC ' \
'LIMIT 10' % time_range 'LIMIT %s' % (time_range, stat_count)
result = monitor_db.select(query) result = monitor_db.select(query)
except: except:
logger.warn("Unable to execute database query.") logger.warn("Unable to execute database query.")
@ -293,7 +298,7 @@ class DataFactory(object):
'AND session_history_metadata.media_type = "movie" ' \ 'AND session_history_metadata.media_type = "movie" ' \
'GROUP BY session_history_metadata.full_title ' \ 'GROUP BY session_history_metadata.full_title ' \
'ORDER BY users_watched DESC, total_plays DESC ' \ 'ORDER BY users_watched DESC, total_plays DESC ' \
'LIMIT 10' % time_range 'LIMIT %s' % (time_range, stat_count)
result = monitor_db.select(query) result = monitor_db.select(query)
except: except:
logger.warn("Unable to execute database query.") logger.warn("Unable to execute database query.")
@ -338,7 +343,7 @@ class DataFactory(object):
'WHERE datetime(session_history.stopped, "unixepoch", "localtime") >= ' \ 'WHERE datetime(session_history.stopped, "unixepoch", "localtime") >= ' \
'datetime("now", "-%s days", "localtime") '\ 'datetime("now", "-%s days", "localtime") '\
'GROUP BY session_history.user_id ' \ 'GROUP BY session_history.user_id ' \
'ORDER BY %s DESC LIMIT 10' % (time_range, sort_type) 'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stat_count)
result = monitor_db.select(query) result = monitor_db.select(query)
except: except:
logger.warn("Unable to execute database query.") logger.warn("Unable to execute database query.")
@ -356,7 +361,7 @@ class DataFactory(object):
'total_plays': item[2], 'total_plays': item[2],
'total_duration': item[3], 'total_duration': item[3],
'last_play': item[4], 'last_play': item[4],
'thumb': user_thumb, 'user_thumb': user_thumb,
'grandparent_thumb': '', 'grandparent_thumb': '',
'users_watched': '', 'users_watched': '',
'rating_key': '', 'rating_key': '',
@ -386,7 +391,7 @@ class DataFactory(object):
'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \ 'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \
'>= datetime("now", "-%s days", "localtime") ' \ '>= datetime("now", "-%s days", "localtime") ' \
'GROUP BY session_history.platform ' \ 'GROUP BY session_history.platform ' \
'ORDER BY total_plays DESC' % time_range 'ORDER BY %s DESC LIMIT %s' % (time_range, sort_type, stat_count)
result = monitor_db.select(query) result = monitor_db.select(query)
except: except:
logger.warn("Unable to execute database query.") logger.warn("Unable to execute database query.")
@ -413,6 +418,59 @@ class DataFactory(object):
'stat_type': sort_type, 'stat_type': sort_type,
'rows': top_platform}) 'rows': top_platform})
elif 'last_watched' in stat:
last_watched = []
try:
query = 'SELECT session_history_metadata.id, ' \
'session_history.user, ' \
'(case when users.friendly_name is null then session_history.user else ' \
'users.friendly_name end) as friendly_name,' \
'users.user_id, ' \
'users.custom_avatar_url as user_thumb, ' \
'session_history_metadata.full_title, ' \
'session_history_metadata.rating_key, ' \
'session_history_metadata.thumb, ' \
'session_history_metadata.grandparent_thumb, ' \
'MAX(session_history.started) as last_watch, ' \
'session_history.player as platform ' \
'FROM session_history_metadata ' \
'JOIN session_history ON session_history_metadata.id = session_history.id ' \
'LEFT OUTER JOIN users ON session_history.user_id = users.user_id ' \
'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \
'>= datetime("now", "-%s days", "localtime") ' \
'AND (session_history_metadata.media_type = "movie" ' \
'OR session_history_metadata.media_type = "episode") ' \
'GROUP BY session_history_metadata.full_title ' \
'ORDER BY last_watch DESC ' \
'LIMIT %s' % (time_range, stat_count)
result = monitor_db.select(query)
except:
logger.warn("Unable to execute database query.")
return None
for item in result:
if not item[8] or item[8] == '':
thumb = item[7]
else:
thumb = item[8]
row = {'row_id': item[0],
'user': item[1],
'friendly_name': item[2],
'user_id': item[3],
'user_thumb': item[4],
'title': item[5],
'rating_key': item[6],
'thumb': thumb,
'grandparent_thumb': item[8],
'last_watch': item[9],
'platform_type': item[10],
}
last_watched.append(row)
home_stats.append({'stat_id': stat,
'rows': last_watched})
return home_stats return home_stats
def get_stream_details(self, row_id=None): def get_stream_details(self, row_id=None):

View file

@ -27,9 +27,8 @@ def refresh_users():
if len(result) > 0: if len(result) > 0:
for item in result: for item in result:
control_value_dict = {"username": item['username']} control_value_dict = {"user_id": item['user_id']}
new_value_dict = {"user_id": item['user_id'], new_value_dict = {"username": item['username'],
"username": item['username'],
"thumb": item['thumb'], "thumb": item['thumb'],
"email": item['email'], "email": item['email'],
"is_home_user": item['is_home_user'], "is_home_user": item['is_home_user'],

View file

@ -89,6 +89,23 @@ class PmsConnect(object):
return request return request
"""
Return list of seasons in requested show.
Parameters required: rating_key { ratingKey of parent }
Optional parameters: output_format { dict, json }
Output: array
"""
def get_season_list(self, rating_key='', output_format=''):
uri = '/library/metadata/' + rating_key + '/children'
request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET',
output_format=output_format)
return request
""" """
Return list of episodes in requested season. Return list of episodes in requested season.
@ -154,6 +171,38 @@ class PmsConnect(object):
return request return request
"""
Return list of libraries on server.
Optional parameters: output_format { dict, json }
Output: array
"""
def get_libraries_list(self, output_format=''):
uri = '/library/sections'
request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET',
output_format=output_format)
return request
"""
Return list of items in library on server.
Optional parameters: output_format { dict, json }
Output: array
"""
def get_library_list(self, section_key='', list_type='all', count='0', sort_type='', output_format=''):
uri = '/library/sections/' + section_key + '/' + list_type +'?X-Plex-Container-Start=0&X-Plex-Container-Size=' + count + sort_type
request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET',
output_format=output_format)
return request
""" """
Return sync item details. Return sync item details.
@ -552,6 +601,7 @@ class PmsConnect(object):
'user': user_details['username'], 'user': user_details['username'],
'user_id': user_details['user_id'], 'user_id': user_details['user_id'],
'friendly_name': user_details['friendly_name'], 'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['thumb'],
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'), 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'),
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
'machine_id': machine_id, 'machine_id': machine_id,
@ -661,6 +711,7 @@ class PmsConnect(object):
'user': user_details['username'], 'user': user_details['username'],
'user_id': user_details['user_id'], 'user_id': user_details['user_id'],
'friendly_name': user_details['friendly_name'], 'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['thumb'],
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'), 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'),
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
'machine_id': machine_id, 'machine_id': machine_id,
@ -668,6 +719,7 @@ class PmsConnect(object):
'grandparent_title': helpers.get_xml_attr(session, 'grandparentTitle'), 'grandparent_title': helpers.get_xml_attr(session, 'grandparentTitle'),
'parent_title': helpers.get_xml_attr(session, 'parentTitle'), 'parent_title': helpers.get_xml_attr(session, 'parentTitle'),
'title': helpers.get_xml_attr(session, 'title'), 'title': helpers.get_xml_attr(session, 'title'),
'year': helpers.get_xml_attr(session, 'year'),
'rating_key': helpers.get_xml_attr(session, 'ratingKey'), 'rating_key': helpers.get_xml_attr(session, 'ratingKey'),
'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'), 'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'),
'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'), 'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'),
@ -693,9 +745,13 @@ class PmsConnect(object):
'duration': duration, 'duration': duration,
'progress': progress, 'progress': progress,
'progress_percent': str(helpers.get_percent(progress, duration)), 'progress_percent': str(helpers.get_percent(progress, duration)),
'type': helpers.get_xml_attr(session, 'type'),
'indexes': use_indexes 'indexes': use_indexes
} }
if helpers.get_xml_attr(session, 'ratingKey').isdigit():
session_output['type'] = helpers.get_xml_attr(session, 'type')
else:
session_output['type'] = 'clip'
elif helpers.get_xml_attr(session, 'type') == 'movie': elif helpers.get_xml_attr(session, 'type') == 'movie':
session_output = {'session_key': helpers.get_xml_attr(session, 'sessionKey'), session_output = {'session_key': helpers.get_xml_attr(session, 'sessionKey'),
'media_index': helpers.get_xml_attr(session, 'index'), 'media_index': helpers.get_xml_attr(session, 'index'),
@ -708,6 +764,7 @@ class PmsConnect(object):
'user': user_details['username'], 'user': user_details['username'],
'user_id': user_details['user_id'], 'user_id': user_details['user_id'],
'friendly_name': user_details['friendly_name'], 'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['thumb'],
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'), 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'),
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
'machine_id': machine_id, 'machine_id': machine_id,
@ -715,6 +772,7 @@ class PmsConnect(object):
'grandparent_title': helpers.get_xml_attr(session, 'grandparentTitle'), 'grandparent_title': helpers.get_xml_attr(session, 'grandparentTitle'),
'parent_title': helpers.get_xml_attr(session, 'parentTitle'), 'parent_title': helpers.get_xml_attr(session, 'parentTitle'),
'title': helpers.get_xml_attr(session, 'title'), 'title': helpers.get_xml_attr(session, 'title'),
'year': helpers.get_xml_attr(session, 'year'),
'rating_key': helpers.get_xml_attr(session, 'ratingKey'), 'rating_key': helpers.get_xml_attr(session, 'ratingKey'),
'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'), 'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'),
'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'), 'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'),
@ -740,9 +798,13 @@ class PmsConnect(object):
'duration': duration, 'duration': duration,
'progress': progress, 'progress': progress,
'progress_percent': str(helpers.get_percent(progress, duration)), 'progress_percent': str(helpers.get_percent(progress, duration)),
'type': helpers.get_xml_attr(session, 'type'),
'indexes': use_indexes 'indexes': use_indexes
} }
if helpers.get_xml_attr(session, 'ratingKey').isdigit():
session_output['type'] = helpers.get_xml_attr(session, 'type')
else:
session_output['type'] = 'clip'
elif helpers.get_xml_attr(session, 'type') == 'clip': elif helpers.get_xml_attr(session, 'type') == 'clip':
session_output = {'session_key': helpers.get_xml_attr(session, 'sessionKey'), session_output = {'session_key': helpers.get_xml_attr(session, 'sessionKey'),
'media_index': helpers.get_xml_attr(session, 'index'), 'media_index': helpers.get_xml_attr(session, 'index'),
@ -755,6 +817,7 @@ class PmsConnect(object):
'user': user_details['username'], 'user': user_details['username'],
'user_id': user_details['user_id'], 'user_id': user_details['user_id'],
'friendly_name': user_details['friendly_name'], 'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['thumb'],
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'), 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'),
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
'machine_id': machine_id, 'machine_id': machine_id,
@ -762,6 +825,7 @@ class PmsConnect(object):
'grandparent_title': helpers.get_xml_attr(session, 'grandparentTitle'), 'grandparent_title': helpers.get_xml_attr(session, 'grandparentTitle'),
'parent_title': helpers.get_xml_attr(session, 'parentTitle'), 'parent_title': helpers.get_xml_attr(session, 'parentTitle'),
'title': helpers.get_xml_attr(session, 'title'), 'title': helpers.get_xml_attr(session, 'title'),
'year': helpers.get_xml_attr(session, 'year'),
'rating_key': helpers.get_xml_attr(session, 'ratingKey'), 'rating_key': helpers.get_xml_attr(session, 'ratingKey'),
'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'), 'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'),
'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'), 'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'),
@ -795,6 +859,49 @@ class PmsConnect(object):
return session_output return session_output
"""
Return processed and validated season list.
Output: array
"""
def get_show_children(self, rating_key=''):
season_data = self.get_season_list(rating_key, output_format='xml')
try:
xml_head = season_data.getElementsByTagName('MediaContainer')
except:
logger.warn("Unable to parse XML for get_season_list.")
return []
season_list = []
for a in xml_head:
if a.getAttribute('size'):
if a.getAttribute('size') == '0':
logger.debug(u"No season data.")
season_list = {'season_count': '0',
'season_list': []
}
return season_list
if a.getElementsByTagName('Directory'):
result_data = a.getElementsByTagName('Directory')
for result in result_data:
season_output = {'rating_key': helpers.get_xml_attr(result, 'ratingKey'),
'index': helpers.get_xml_attr(result, 'index'),
'title': helpers.get_xml_attr(result, 'title'),
'thumb': helpers.get_xml_attr(result, 'thumb'),
'parent_thumb': helpers.get_xml_attr(a, 'thumb')
}
season_list.append(season_output)
output = {'season_count': helpers.get_xml_attr(xml_head[0], 'size'),
'title': helpers.get_xml_attr(xml_head[0], 'title2'),
'season_list': season_list
}
return output
""" """
Return processed and validated episode list. Return processed and validated episode list.
@ -914,6 +1021,150 @@ class PmsConnect(object):
logger.debug(u"Server preferences queried but no parameter received.") logger.debug(u"Server preferences queried but no parameter received.")
return None return None
"""
Return processed and validated server libraries list.
Output: array
"""
def get_server_children(self):
libraries_data = self.get_libraries_list(output_format='xml')
try:
xml_head = libraries_data.getElementsByTagName('MediaContainer')
except:
logger.warn("Unable to parse XML for get_libraries_list.")
return []
libraries_list = []
for a in xml_head:
if a.getAttribute('size'):
if a.getAttribute('size') == '0':
logger.debug(u"No libraries data.")
libraries_list = {'libraries_count': '0',
'libraries_list': []
}
return libraries_list
if a.getElementsByTagName('Directory'):
result_data = a.getElementsByTagName('Directory')
for result in result_data:
libraries_output = {'key': helpers.get_xml_attr(result, 'key'),
'type': helpers.get_xml_attr(result, 'type'),
'title': helpers.get_xml_attr(result, 'title'),
'thumb': helpers.get_xml_attr(result, 'thumb')
}
libraries_list.append(libraries_output)
output = {'libraries_count': helpers.get_xml_attr(xml_head[0], 'size'),
'title': helpers.get_xml_attr(xml_head[0], 'title1'),
'libraries_list': libraries_list
}
return output
"""
Return processed and validated server library items list.
Parameters required: library_type { movie, show, episode, artist }
section_key { unique library key }
Output: array
"""
def get_library_children(self, library_type='', section_key='', list_type='all', sort_type = ''):
# Currently only grab the library with 1 items so 'size' is not 0
count = '1'
if library_type == 'movie':
sort_type = '&type=1'
elif library_type == 'show':
sort_type = '&type=2'
elif library_type == 'episode':
sort_type = '&type=4'
elif library_type == 'album':
list_type = 'albums'
library_data = self.get_library_list(section_key, list_type, count, sort_type, output_format='xml')
try:
xml_head = library_data.getElementsByTagName('MediaContainer')
except:
logger.warn("Unable to parse XML for get_library_children.")
return []
library_list = []
for a in xml_head:
if a.getAttribute('size'):
if a.getAttribute('size') == '0':
logger.debug(u"No library data.")
library_list = {'library_count': '0',
'library_list': []
}
return library_list
if a.getElementsByTagName('Directory'):
result_data = a.getElementsByTagName('Directory')
for result in result_data:
library_output = {'key': helpers.get_xml_attr(result, 'key'),
'type': helpers.get_xml_attr(result, 'type'),
'title': helpers.get_xml_attr(result, 'title'),
'thumb': helpers.get_xml_attr(result, 'thumb')
}
library_list.append(library_output)
output = {'library_count': helpers.get_xml_attr(xml_head[0], 'totalSize'),
'count_type': helpers.get_xml_attr(xml_head[0], 'title2'),
'library_list': library_list
}
return output
"""
Return processed and validated server statistics.
Output: array
"""
def get_library_stats(self):
server_libraries = self.get_server_children()
server_library_stats = []
if server_libraries['libraries_count'] != '0':
libraries_list = server_libraries['libraries_list']
for library in libraries_list:
library_type = library['type']
section_key = library['key']
library_list = self.get_library_children(library_type, section_key)
if library_list['library_count'] != '0':
library_stats = {'title': library['title'],
'thumb': library['thumb'],
'count': library_list['library_count'],
'count_type': library_list['count_type']
}
if library_type == 'show':
episode_list = self.get_library_children(library_type='episode', section_key=section_key)
episode_stats = {'episode_count': episode_list['library_count'],
'episode_count_type': 'All Episodes'
}
library_stats.update(episode_stats)
if library_type == 'artist':
album_list = self.get_library_children(library_type='album', section_key=section_key)
album_stats = {'album_count': album_list['library_count'],
'album_count_type': 'All Albums'
}
library_stats.update(album_stats)
server_library_stats.append({'type': library_type,
'rows': library_stats})
return server_library_stats
""" """
Return image data as array. Return image data as array.
Array contains the image content type and image binary Array contains the image content type and image binary

View file

@ -1,2 +1,2 @@
PLEXPY_VERSION = "master" PLEXPY_VERSION = "master"
PLEXPY_RELEASE_VERSION = "1.1.5" PLEXPY_RELEASE_VERSION = "1.1.6"

View file

@ -1,4 +1,4 @@
# This file is part of PlexPy. # This file is part of PlexPy.
# #
# PlexPy is free software: you can redistribute it and/or modify # PlexPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -66,7 +66,9 @@ class WebInterface(object):
def home(self): def home(self):
config = { config = {
"home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH, "home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH,
"home_stats_type": plexpy.CONFIG.HOME_STATS_TYPE "home_stats_type": plexpy.CONFIG.HOME_STATS_TYPE,
"home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT,
"pms_identifier": plexpy.CONFIG.PMS_IDENTIFIER,
} }
return serve_template(templatename="index.html", title="Home", config=config) return serve_template(templatename="index.html", title="Home", config=config)
@ -119,12 +121,19 @@ class WebInterface(object):
return json.dumps(formats) return json.dumps(formats)
@cherrypy.expose @cherrypy.expose
def home_stats(self, time_range='30', stat_type='0', **kwargs): def home_stats(self, time_range='30', stat_type='0', stat_count='5', **kwargs):
data_factory = datafactory.DataFactory() data_factory = datafactory.DataFactory()
stats_data = data_factory.get_home_stats(time_range=time_range, stat_type=stat_type) stats_data = data_factory.get_home_stats(time_range=time_range, stat_type=stat_type, stat_count=stat_count)
return serve_template(templatename="home_stats.html", title="Stats", data=stats_data) return serve_template(templatename="home_stats.html", title="Stats", data=stats_data)
@cherrypy.expose
def library_stats(self, **kwargs):
pms_connect = pmsconnect.PmsConnect()
stats_data = pms_connect.get_library_stats()
return serve_template(templatename="library_stats.html", title="Library Stats", data=stats_data)
@cherrypy.expose @cherrypy.expose
def history(self): def history(self):
return serve_template(templatename="history.html", title="History") return serve_template(templatename="history.html", title="History")
@ -453,6 +462,7 @@ class WebInterface(object):
"notify_on_watched_body_text": plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT, "notify_on_watched_body_text": plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT,
"home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH, "home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH,
"home_stats_type": checked(plexpy.CONFIG.HOME_STATS_TYPE), "home_stats_type": checked(plexpy.CONFIG.HOME_STATS_TYPE),
"home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT,
"buffer_threshold": plexpy.CONFIG.BUFFER_THRESHOLD, "buffer_threshold": plexpy.CONFIG.BUFFER_THRESHOLD,
"buffer_wait": plexpy.CONFIG.BUFFER_WAIT "buffer_wait": plexpy.CONFIG.BUFFER_WAIT
} }
@ -567,6 +577,9 @@ class WebInterface(object):
if 'rating_key' in kwargs: if 'rating_key' in kwargs:
rating_key = kwargs.get('rating_key', "") rating_key = kwargs.get('rating_key', "")
custom_where = [['rating_key', rating_key]] custom_where = [['rating_key', rating_key]]
if 'parent_rating_key' in kwargs:
rating_key = kwargs.get('parent_rating_key', "")
custom_where = [['parent_rating_key', rating_key]]
if 'grandparent_rating_key' in kwargs: if 'grandparent_rating_key' in kwargs:
rating_key = kwargs.get('grandparent_rating_key', "") rating_key = kwargs.get('grandparent_rating_key', "")
custom_where = [['grandparent_rating_key', rating_key]] custom_where = [['grandparent_rating_key', rating_key]]
@ -747,16 +760,19 @@ class WebInterface(object):
@cherrypy.expose @cherrypy.expose
def info(self, item_id=None, source=None, **kwargs): def info(self, item_id=None, source=None, **kwargs):
metadata = None
if source == 'history': if source == 'history':
data_factory = datafactory.DataFactory() data_factory = datafactory.DataFactory()
result = data_factory.get_metadata_details(row_id=item_id) metadata = data_factory.get_metadata_details(row_id=item_id)
else: else:
pms_connect = pmsconnect.PmsConnect() pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_metadata_details(rating_key=item_id)['metadata'] result = pms_connect.get_metadata_details(rating_key=item_id)
if result:
metadata = result['metadata']
if result: if metadata:
return serve_template(templatename="info.html", data=result, title="Info") return serve_template(templatename="info.html", data=metadata, title="Info")
else: else:
logger.warn('Unable to retrieve data.') logger.warn('Unable to retrieve data.')
return serve_template(templatename="info.html", data=None, title="Info") return serve_template(templatename="info.html", data=None, title="Info")
@ -801,7 +817,19 @@ class WebInterface(object):
return serve_template(templatename="user_platform_stats.html", data=None, title="Platform Stats") return serve_template(templatename="user_platform_stats.html", data=None, title="Platform Stats")
@cherrypy.expose @cherrypy.expose
def get_children(self, rating_key='', **kwargs): def get_show_children(self, rating_key='', **kwargs):
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_show_children(rating_key)
if result:
return serve_template(templatename="info_season_list.html", data=result, title="Season List")
else:
logger.warn('Unable to retrieve data.')
return serve_template(templatename="info_season_list.html", data=None, title="Season List")
@cherrypy.expose
def get_season_children(self, rating_key='', **kwargs):
pms_connect = pmsconnect.PmsConnect() pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_season_children(rating_key) result = pms_connect.get_season_children(rating_key)