mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-07 13:41:15 -07:00
Add platform stats to user page
Re-add user gravatars Some error catching Some code clean-up
This commit is contained in:
parent
ba18c5b96e
commit
9364b06c99
12 changed files with 275 additions and 76 deletions
|
@ -51,7 +51,7 @@ A python based web front-end for plexWatch.
|
|||
|
||||
* full user list with general information and comparison stats
|
||||
|
||||
* individual user information **PARTIALLY IMPLEMENTED**
|
||||
* individual user information
|
||||
- username and gravatar (if available)
|
||||
- daily, weekly, monthly, all time stats for play count and duration length
|
||||
- individual platform stats for each user
|
||||
|
|
|
@ -7635,6 +7635,7 @@ button.close {
|
|||
}
|
||||
.user-info-poster-face img {
|
||||
bottom: 0;
|
||||
margin-right: 15px;
|
||||
overflow: hidden;
|
||||
float: left;
|
||||
background-color: #323232;
|
||||
|
@ -7651,12 +7652,11 @@ button.close {
|
|||
color: #fff;
|
||||
position: relative;
|
||||
top: 27px;
|
||||
left: 15px;
|
||||
}
|
||||
.user-info-nav {
|
||||
position: relative;
|
||||
top: 15px;
|
||||
left: 3px;
|
||||
left: -5px;
|
||||
}
|
||||
.user-info-nav > .active > a, .nav-tabs > .active > a:hover, .nav-tabs > .active > a:focus {
|
||||
color: #F9AA03;
|
||||
|
|
|
@ -210,6 +210,7 @@ function getPlatformImagePath(platformName) {
|
|||
}
|
||||
|
||||
function isPrivateIP(ip_address) {
|
||||
if (ip_address.indexOf(".") > -1) {
|
||||
var parts = ip_address.split('.');
|
||||
if (parts[0] === '10' ||
|
||||
(parts[0] === '172' && (parseInt(parts[1], 10) >= 16 && parseInt(parts[1], 10) <= 31)) ||
|
||||
|
@ -217,6 +218,9 @@ function isPrivateIP(ip_address) {
|
|||
return true;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function humanTime(seconds) {
|
||||
|
|
|
@ -32,7 +32,11 @@ user_ip_table_options = {
|
|||
"className": "modal-control",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (isPrivateIP(cellData)) {
|
||||
if (cellData != '') {
|
||||
$(td).html(cellData);
|
||||
} else {
|
||||
$(td).html('n/a');
|
||||
}
|
||||
} else {
|
||||
$(td).html('<a href="#ip-info-modal" data-toggle="modal"><span data-toggle="ip-tooltip" data-placement="left" title="IP Address Info" id="ip-info"><i class="icon-map-marker icon-white"></i></span> ' + cellData +'</a>');
|
||||
}
|
||||
|
|
|
@ -25,13 +25,13 @@ users_list_table_options = {
|
|||
"columnDefs": [
|
||||
{
|
||||
"targets": [0],
|
||||
"data": null,
|
||||
"data": "thumb",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
//if (rowData['user_thumb'] === '') {
|
||||
if (cellData === '') {
|
||||
$(td).html('<img src="interfaces/default/images/gravatar-default-80x80.png" alt="User Logo"/>');
|
||||
//} else {
|
||||
// $(td).html('<img src="' + rowData['user_thumb'] + '" alt="User Logo"/>');
|
||||
//}
|
||||
} else {
|
||||
$(td).html('<img src="' + cellData + '" alt="User Logo"/>');
|
||||
}
|
||||
},
|
||||
"orderable": false,
|
||||
"className": "users-poster-face",
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
<div class="user-info-wrapper">
|
||||
<div class="user-info-poster-face">
|
||||
<div class="user-info-poster-face" id="user-gravatar">
|
||||
<img src="interfaces/default/images/gravatar-default-80x80.png">
|
||||
</div>
|
||||
<div class="user-info-username">
|
||||
|
@ -42,7 +42,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div id="user-time-stats" class="user-overview-stats-wrapper">
|
||||
<div id="user-stats-spinner" class="spinner"></div>
|
||||
<div class='muted'><i class="fa fa-refresh fa-spin"></i> Loading data...</div><br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -58,7 +58,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div id="user-platform-stats" class="user-platforms">
|
||||
<div id="user-platform-spinner" class="spinner"></div>
|
||||
<div class='muted'><i class="fa fa-refresh fa-spin"></i> Loading data...</div><br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -73,8 +73,8 @@
|
|||
<h3>Recently watched</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div id="user-recently-watched" class="dashboard-recent-media-row">
|
||||
<div id="user-watched-spinner" class="spinner"></div>
|
||||
<div id="user-recently-watched">
|
||||
<div class='muted'><i class="fa fa-refresh fa-spin"></i> Loading data...</div><br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -88,7 +88,7 @@
|
|||
<div class="wellbg">
|
||||
<div class="wellheader">
|
||||
<div class="dashboard-wellheader">
|
||||
<h3>Public IP Addresses for <strong>
|
||||
<h3>IP Addresses for <strong>
|
||||
${user}
|
||||
</strong></h3>
|
||||
</div>
|
||||
|
@ -190,6 +190,7 @@
|
|||
<script>
|
||||
$(document).ready(function () {
|
||||
|
||||
// Populate watch time stats
|
||||
$.ajax({
|
||||
url: 'get_user_watch_time_stats',
|
||||
async: true,
|
||||
|
@ -199,6 +200,17 @@
|
|||
}
|
||||
});
|
||||
|
||||
// Populate platform stats
|
||||
$.ajax({
|
||||
url: 'get_user_platform_stats',
|
||||
async: true,
|
||||
data: { user: '${user}' },
|
||||
complete: function(xhr, status) {
|
||||
$("#user-platform-stats").html(xhr.responseText);
|
||||
}
|
||||
});
|
||||
|
||||
// Populate recently watched
|
||||
$.ajax({
|
||||
url: 'get_user_recently_watched',
|
||||
async: true,
|
||||
|
@ -208,6 +220,7 @@
|
|||
}
|
||||
});
|
||||
|
||||
// Build watch history table
|
||||
history_table_options.ajax = {
|
||||
"url": "get_history",
|
||||
"data": function(d) {
|
||||
|
@ -217,6 +230,7 @@
|
|||
history_table = $('#history_table').DataTable(history_table_options);
|
||||
history_table.column(2).visible(false); // Hide the title column
|
||||
|
||||
// Build user IP table
|
||||
user_ip_table_options.ajax = {
|
||||
"url": "get_user_ips",
|
||||
"data": function(d) {
|
||||
|
@ -225,6 +239,18 @@
|
|||
}
|
||||
user_ip_table = $('#user_ip_table').DataTable(user_ip_table_options);
|
||||
|
||||
// Load user gravatar image
|
||||
$.ajax({
|
||||
url: 'get_user_gravatar_image',
|
||||
async: true,
|
||||
data: { user: '${user}' },
|
||||
success: function(data) {
|
||||
if (data.user_thumb !== '') {
|
||||
thumb = data.user_thumb;
|
||||
$('#user-gravatar').html('<img src="' + thumb + '">');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</%def>
|
||||
|
|
22
data/interfaces/default/user_platform_stats.html
Normal file
22
data/interfaces/default/user_platform_stats.html
Normal file
|
@ -0,0 +1,22 @@
|
|||
% if platform_stats != None:
|
||||
% for a in platform_stats:
|
||||
<ul>
|
||||
<div class="user-platforms-instance">
|
||||
<li>
|
||||
<span id="user-platform-image-${a['result_id']}"></span>
|
||||
<div class="user-platforms-instance-name">
|
||||
${a['platform_name']}
|
||||
</div>
|
||||
<div class="user-platforms-instance-playcount">
|
||||
<h3>${a['total_plays']}</h3><p> plays</p>
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
</ul>
|
||||
<script>
|
||||
$("#user-platform-image-${a['result_id']}").html("<img class='user-platforms-instance-poster' src='" + getPlatformImagePath('${a['platform_type']}') + "'>");
|
||||
</script>
|
||||
% endfor
|
||||
% else:
|
||||
<div class="muted">There was an error loading your PlexWatch data. Please check your <a href="config">settings</a>.</div><br>
|
||||
% endif
|
|
@ -32,5 +32,5 @@
|
|||
</ul>
|
||||
</div>
|
||||
% else:
|
||||
<div class="muted">There was an error retrieving some data. Please check your <a href="config">settings</a>.</div><br>
|
||||
<div class="muted">There was an error loading your PlexWatch data. Please check your <a href="config">settings</a>.</div><br>
|
||||
% endif
|
|
@ -9,7 +9,7 @@
|
|||
% elif a['query_days'] == 1:
|
||||
<h4>Last 24 hours</h4>
|
||||
% else:
|
||||
<h4>Last ${a['query_days']} day(s)</h4>
|
||||
<h4>Last ${a['query_days']} days</h4>
|
||||
% endif
|
||||
<h3>${a['total_plays']}</h3><p>plays</p>
|
||||
<span id="total-time-${a['query_days']}"></span>
|
||||
|
@ -21,4 +21,6 @@
|
|||
</script>
|
||||
% endfor
|
||||
</ul>
|
||||
% else:
|
||||
<div class="muted">There was an error loading your PlexWatch data. Please check your <a href="config">settings</a>.</div><br>
|
||||
% endif
|
|
@ -76,8 +76,8 @@ class DataTables(object):
|
|||
order, custom_where)
|
||||
|
||||
# logger.debug(u"Query string: %s" % query)
|
||||
|
||||
filtered = self.ssp_db.select(query)
|
||||
|
||||
if search_value == '':
|
||||
totalcount = len(filtered)
|
||||
else:
|
||||
|
|
|
@ -72,7 +72,7 @@ class PlexWatch(object):
|
|||
'time',
|
||||
'ip_address',
|
||||
'COUNT(title) as plays']
|
||||
|
||||
try:
|
||||
query = data_tables.ssp_query(table_name=self.get_user_table_name(),
|
||||
columns=columns,
|
||||
start=start,
|
||||
|
@ -84,15 +84,23 @@ class PlexWatch(object):
|
|||
custom_where='',
|
||||
group_by='user',
|
||||
kwargs=kwargs)
|
||||
except:
|
||||
logger.warn("Unable to open PlexWatch database.")
|
||||
return {'recordsFiltered': 0,
|
||||
'recordsTotal': 0,
|
||||
'data': 'null'},
|
||||
|
||||
users = query['result']
|
||||
|
||||
rows = []
|
||||
for item in users:
|
||||
thumb = self.get_user_gravatar_image(item['user'])
|
||||
|
||||
row = {"plays": item['plays'],
|
||||
"time": item['time'],
|
||||
"user": item["user"],
|
||||
"ip_address": item["ip_address"]
|
||||
"ip_address": item["ip_address"],
|
||||
"thumb": thumb['user_thumb']
|
||||
}
|
||||
|
||||
rows.append(row)
|
||||
|
@ -136,6 +144,7 @@ class PlexWatch(object):
|
|||
'orig_title as last_watched'
|
||||
]
|
||||
|
||||
try:
|
||||
query = data_tables.ssp_query(table_name=self.get_user_table_name(),
|
||||
columns=columns,
|
||||
start=start,
|
||||
|
@ -147,6 +156,11 @@ class PlexWatch(object):
|
|||
custom_where=custom_where,
|
||||
group_by='ip_address',
|
||||
kwargs=kwargs)
|
||||
except:
|
||||
logger.warn("Unable to open PlexWatch database.")
|
||||
return {'recordsFiltered': 0,
|
||||
'recordsTotal': 0,
|
||||
'data': 'null'},
|
||||
|
||||
results = query['result']
|
||||
|
||||
|
@ -207,7 +221,7 @@ class PlexWatch(object):
|
|||
julianday(datetime(time, "unixepoch", "localtime"))) * 86400) - \
|
||||
(case when paused_counter is null then 0 else paused_counter end) as duration'
|
||||
]
|
||||
|
||||
try:
|
||||
query = data_tables.ssp_query(table_name=self.get_history_table_name(),
|
||||
columns=columns,
|
||||
start=start,
|
||||
|
@ -219,6 +233,11 @@ class PlexWatch(object):
|
|||
custom_where=custom_where,
|
||||
group_by='',
|
||||
kwargs=kwargs)
|
||||
except:
|
||||
logger.warn("Unable to open PlexWatch database.")
|
||||
return {'recordsFiltered': 0,
|
||||
'recordsTotal': 0,
|
||||
'data': 'null'},
|
||||
|
||||
history = query['result']
|
||||
|
||||
|
@ -380,6 +399,7 @@ class PlexWatch(object):
|
|||
if not limit.isdigit():
|
||||
limit = '10'
|
||||
|
||||
try:
|
||||
if user:
|
||||
query = 'SELECT time, user, xml FROM %s WHERE user = "%s" ORDER BY time DESC LIMIT %s' % \
|
||||
(self.get_user_table_name(), user, limit)
|
||||
|
@ -388,6 +408,9 @@ class PlexWatch(object):
|
|||
query = 'SELECT time, user, xml FROM %s ORDER BY time DESC LIMIT %s' % \
|
||||
(self.get_user_table_name(), limit)
|
||||
xml = myDB.select(query)
|
||||
except:
|
||||
logger.warn("Unable to open PlexWatch database.")
|
||||
return None
|
||||
|
||||
for row in xml:
|
||||
xml_data = helpers.latinToAscii(row[2])
|
||||
|
@ -435,10 +458,14 @@ class PlexWatch(object):
|
|||
else:
|
||||
where = 'WHERE user = "%s"' % user
|
||||
|
||||
try:
|
||||
query = 'SELECT (SUM(stopped - time) - SUM(CASE WHEN paused_counter is null THEN 0 ELSE paused_counter END)) as total_time, ' \
|
||||
'COUNT(id) AS total_plays ' \
|
||||
'FROM %s %s' % (self.get_user_table_name(), where)
|
||||
result = myDB.select(query)
|
||||
except:
|
||||
logger.warn("Unable to open PlexWatch database.")
|
||||
return None
|
||||
|
||||
for item in result:
|
||||
if item[0]:
|
||||
|
@ -456,3 +483,83 @@ class PlexWatch(object):
|
|||
user_watch_time_stats.append(row)
|
||||
|
||||
return user_watch_time_stats
|
||||
|
||||
def get_user_platform_stats(self, user=None):
|
||||
myDB = db.DBConnection()
|
||||
|
||||
platform_stats = []
|
||||
result_id = 0
|
||||
|
||||
try:
|
||||
query = 'SELECT platform, COUNT(platform) as platform_count, xml ' \
|
||||
'FROM %s ' \
|
||||
'WHERE user = "%s" ' \
|
||||
'GROUP BY platform ' \
|
||||
'ORDER BY platform_count DESC' % (self.get_user_table_name(), user)
|
||||
result = myDB.select(query)
|
||||
except:
|
||||
logger.warn("Unable to open PlexWatch database.")
|
||||
return None
|
||||
|
||||
for item in result:
|
||||
xml_data = helpers.latinToAscii(item[2])
|
||||
|
||||
try:
|
||||
xml_parse = minidom.parseString(xml_data)
|
||||
except:
|
||||
logger.warn("Error parsing XML for Plex stream data.")
|
||||
return None
|
||||
|
||||
xml_head = xml_parse.getElementsByTagName('Player')
|
||||
if not xml_head:
|
||||
logger.warn("Error parsing XML for Plex stream data.")
|
||||
return None
|
||||
|
||||
for a in xml_head:
|
||||
platform_type = self.get_xml_attr(a, 'platform')
|
||||
|
||||
row = {'platform_name': item[0],
|
||||
'platform_type': platform_type,
|
||||
'total_plays': item[1],
|
||||
'result_id': result_id
|
||||
}
|
||||
platform_stats.append(row)
|
||||
result_id += 1
|
||||
|
||||
return platform_stats
|
||||
|
||||
def get_user_gravatar_image(self, user=None):
|
||||
myDB = db.DBConnection()
|
||||
user_info = None
|
||||
|
||||
try:
|
||||
query = 'SELECT xml ' \
|
||||
'FROM %s ' \
|
||||
'WHERE user = "%s" ' \
|
||||
'ORDER BY id DESC LIMIT 1' % (self.get_user_table_name(), user)
|
||||
result = myDB.select_single(query)
|
||||
except:
|
||||
logger.warn("Unable to open PlexWatch database.")
|
||||
return None
|
||||
|
||||
xml_data = helpers.latinToAscii(result)
|
||||
|
||||
try:
|
||||
xml_parse = minidom.parseString(xml_data)
|
||||
except:
|
||||
logger.warn("Error parsing XML for Plexwatch Database.")
|
||||
return None
|
||||
|
||||
xml_head = xml_parse.getElementsByTagName('User')
|
||||
if not xml_head:
|
||||
logger.warn("Error parsing XML for Plexwatch Database.")
|
||||
return None
|
||||
|
||||
for a in xml_head:
|
||||
user_id = self.get_xml_attr(a, 'id')
|
||||
user_thumb = self.get_xml_attr(a, 'thumb')
|
||||
|
||||
user_info = {'user_id': user_id,
|
||||
'user_thumb': user_thumb}
|
||||
|
||||
return user_info
|
|
@ -434,7 +434,6 @@ class WebInterface(object):
|
|||
logger.warn(msg)
|
||||
return msg
|
||||
|
||||
|
||||
@cherrypy.expose
|
||||
def get_pms_token(self):
|
||||
|
||||
|
@ -447,7 +446,6 @@ class WebInterface(object):
|
|||
logger.warn('Unable to retrieve Plex.tv token.')
|
||||
return False
|
||||
|
||||
|
||||
@cherrypy.expose
|
||||
def get_pms_sessions_json(self, **kwargs):
|
||||
|
||||
|
@ -543,7 +541,7 @@ class WebInterface(object):
|
|||
if result:
|
||||
return serve_template(templatename="user_recently_watched.html", recently_watched=result, title="Recently Watched")
|
||||
else:
|
||||
return serve_template(templatename="user_recently_watched.html", recently_watched='', title="Recently Watched")
|
||||
return serve_template(templatename="user_recently_watched.html", recently_watched=None, title="Recently Watched")
|
||||
logger.warn('Unable to retrieve data.')
|
||||
|
||||
@cherrypy.expose
|
||||
|
@ -555,7 +553,19 @@ class WebInterface(object):
|
|||
if result:
|
||||
return serve_template(templatename="user_watch_time_stats.html", watch_stats=result, title="Watch Stats")
|
||||
else:
|
||||
return serve_template(templatename="user_watch_time_stats.html", watch_stats='', title="Watch Stats")
|
||||
return serve_template(templatename="user_watch_time_stats.html", watch_stats=None, title="Watch Stats")
|
||||
logger.warn('Unable to retrieve data.')
|
||||
|
||||
@cherrypy.expose
|
||||
def get_user_platform_stats(self, user=None, **kwargs):
|
||||
|
||||
plex_watch = plexwatch.PlexWatch()
|
||||
result = plex_watch.get_user_platform_stats(user)
|
||||
|
||||
if result:
|
||||
return serve_template(templatename="user_platform_stats.html", platform_stats=result, title="Platform Stats")
|
||||
else:
|
||||
return serve_template(templatename="user_platform_stats.html", platform_stats=None, title="Platform Stats")
|
||||
logger.warn('Unable to retrieve data.')
|
||||
|
||||
@cherrypy.expose
|
||||
|
@ -642,3 +652,27 @@ class WebInterface(object):
|
|||
return json.dumps(result)
|
||||
else:
|
||||
logger.warn('Unable to retrieve data.')
|
||||
|
||||
@cherrypy.expose
|
||||
def get_platform_stats(self, user=None, **kwargs):
|
||||
|
||||
plex_watch = plexwatch.PlexWatch()
|
||||
result = plex_watch.get_user_platform_stats(user)
|
||||
|
||||
if result:
|
||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
return json.dumps(result)
|
||||
else:
|
||||
logger.warn('Unable to retrieve data.')
|
||||
|
||||
@cherrypy.expose
|
||||
def get_user_gravatar_image(self, user=None, **kwargs):
|
||||
|
||||
plex_watch = plexwatch.PlexWatch()
|
||||
result = plex_watch.get_user_gravatar_image(user)
|
||||
|
||||
if result:
|
||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
return json.dumps(result)
|
||||
else:
|
||||
logger.warn('Unable to retrieve data.')
|
Loading…
Add table
Add a link
Reference in a new issue