mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-08 06:00:51 -07:00
Merge pull request #145 from JonnyWong16/server-stats
Initial implementation of server statistics
This commit is contained in:
commit
dceab3791f
5 changed files with 363 additions and 15 deletions
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
78
data/interfaces/default/server_stats.html
Normal file
78
data/interfaces/default/server_stats.html
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue