Merge pull request #145 from JonnyWong16/server-stats

Initial implementation of server statistics
This commit is contained in:
drzoidberg33 2015-08-30 17:46:04 +02:00
commit dceab3791f
5 changed files with 363 additions and 15 deletions

View file

@ -1433,7 +1433,7 @@ a .season-episodes-card-overlay:hover {
font-size: 12px;
float: left;
position: relative;
top: 14px;
top: 15px;
left: 0px;
}
.user-overview-stats-instance h3 strong{
@ -1516,7 +1516,7 @@ a .season-episodes-card-overlay:hover {
font-size: 12px;
float: left;
position: relative;
top: 14px;
top: 15px;
left: 0px;
}
.home-platforms {
@ -1572,6 +1572,38 @@ a .season-episodes-card-overlay:hover {
overflow: hidden;
text-overflow: ellipsis;
}
.home-platforms-instance-name2 {
position: absolute;
top: 34px;
left: 215px;
}
.home-platforms-instance-name2 h5 {
font-size: 14px;
line-height: 16px;
margin: 15px 0 2px 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.home-platforms-instance-name2 h3 {
font-size: 30px;
font-weight: bold;
color: #F9AA03;
line-height: 22px;
position: relative;
top: 5px;
margin: 0 5px 0 0;
padding-top: 6px;
float: left;
}
.home-platforms-instance-name2 p {
color: #aaa;
font-size: 12px;
float: left;
position: relative;
top: 21px;
left: 0px;
}
.home-platforms-instance-playcount {
float: left;
position: relative;
@ -1593,7 +1625,7 @@ a .season-episodes-card-overlay:hover {
font-size: 12px;
float: left;
position: relative;
top: 14px;
top: 15px;
left: 0px;
margin-right: 5px;
}
@ -1606,7 +1638,7 @@ a .season-episodes-card-overlay:hover {
.home-platforms-instance-last-user h5 {
font-size: 12px;
position: relative;
margin: 0 0 3px 0;
margin: 0 0 2px 0;
float: left;
}
.home-platforms-instance-last-user p {
@ -1633,6 +1665,13 @@ a .season-episodes-card-overlay:hover {
-moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1);
box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1);
}
.home-platforms-instance-poster .home-platforms-server-thumb {
background-position: center;
background-size: cover;
height: 80px;
width: 80px;
margin-top: 20px;
}
.home-platforms-instance-box {
background-position: center;
background-size: cover;

View file

@ -19,7 +19,7 @@
<div class="row">
<div class="col-md-12">
<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 id="home-stats" class="home-platforms">
<div class='text-muted'><i class="fa fa-refresh fa-spin"></i> Loading stats...</div>
@ -27,6 +27,17 @@
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="padded-header" id="server-statistics-header">
<h3>Server Statistics</h3>
</div>
<div id="server-stats" class="server-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="col-md-12">
<div class="padded-header">
@ -45,17 +56,18 @@
<script src="interfaces/default/js/moment-with-locale.js"></script>
<script>
function getHomeStats(days, stat_type, stat_count) {
function currentActivityHeader() {
$.ajax({
url: 'home_stats',
url: 'get_current_activity_header',
cache: false,
async: true,
data: {time_range: days, stat_type: stat_type, stat_count: stat_count},
complete: function(xhr, status) {
$("#home-stats").html(xhr.responseText);
$("#current-activity-header").html(xhr.responseText);
}
});
}
currentActivityHeader();
setInterval(currentActivityHeader, 15000);
function currentActivity() {
$.ajax({
@ -70,18 +82,50 @@
currentActivity();
setInterval(currentActivity, 15000);
function currentActivityHeader() {
function getHomeStats(days, stat_type, stat_count) {
$.ajax({
url: 'get_current_activity_header',
url: 'home_stats',
cache: false,
async: true,
data: {time_range: days, stat_type: stat_type, stat_count: stat_count},
complete: function(xhr, status) {
$("#current-activity-header").html(xhr.responseText);
$("#home-stats").html(xhr.responseText);
}
});
}
function getServerStatsHeader() {
$.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;
}
}
$('#server-statistics-header h3').append(' <small>' + server_name + '</small>')
}
});
}
function getServerStats() {
$.ajax({
url: 'server_stats',
cache: false,
async: true,
data: { },
complete: function(xhr, status) {
$("#server-stats").html(xhr.responseText);
}
});
}
currentActivityHeader();
setInterval(currentActivityHeader, 15000);
function recentlyAdded() {
var widthVal = $('body').find(".container-fluid").width();
@ -122,6 +166,8 @@
});
getHomeStats(${config['home_stats_length']}, ${config['home_stats_type']}, ${config['home_stats_count']});
getServerStatsHeader();
getServerStats();
</script>

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: server_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-server-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-server-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

@ -171,6 +171,38 @@ class PmsConnect(object):
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.
@ -984,6 +1016,151 @@ class PmsConnect(object):
logger.debug(u"Server preferences queried but no parameter received.")
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_server_stats(self):
server_info = self.get_servers_info()
server_libraries = self.get_server_children()
server_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_stats.append({'type': library_type,
'rows': library_stats})
return server_stats
"""
Return image data as array.
Array contains the image content type and image binary

View file

@ -67,7 +67,8 @@ class WebInterface(object):
config = {
"home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH,
"home_stats_type": plexpy.CONFIG.HOME_STATS_TYPE,
"home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT
"home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT,
"pms_identifier": plexpy.CONFIG.PMS_IDENTIFIER,
}
return serve_template(templatename="index.html", title="Home", config=config)
@ -126,6 +127,13 @@ class WebInterface(object):
return serve_template(templatename="home_stats.html", title="Stats", data=stats_data)
@cherrypy.expose
def server_stats(self, **kwargs):
pms_connect = pmsconnect.PmsConnect()
stats_data = pms_connect.get_server_stats()
return serve_template(templatename="server_stats.html", title="Server Stats", data=stats_data)
@cherrypy.expose
def history(self):
return serve_template(templatename="history.html", title="History")