Template the Datatables JS

Add Links to user screen
Add Public IP list to user screen
Add Watch history to user screen
This commit is contained in:
Tim 2015-06-20 22:27:12 +02:00
parent c4504d8be0
commit 6a026d510d
12 changed files with 536 additions and 335 deletions

View file

@ -25,7 +25,7 @@
<!-- <img src="interfaces/default/images/platforms/roku.png"> platform image -->
</div>
<div class="dashboard-activity-metadata-user">
${a['user']} is ${a['state']}
<a href="user?user=${a['user']}">${a['user']}</a> is ${a['state']}
</div>
<div class="dashboard-activity-metadata-title">
% if a['type'] == 'episode':

View file

@ -208,3 +208,13 @@ function getPlatformImagePath(platformName) {
return 'interfaces/default/images/platforms/default.png';
}
}
function isPrivateIP(ip_address) {
var parts = ip_address.split('.');
if (parts[0] === '10' ||
(parts[0] === '172' && (parseInt(parts[1], 10) >= 16 && parseInt(parts[1], 10) <= 31)) ||
(parts[0] === '192' && parts[1] === '168')) {
return true;
}
return false;
}

View file

@ -20,7 +20,7 @@ history_table_options = {
"infoFiltered":"(filtered from _MAX_ total entries)",
"emptyTable": "No data in table",
},
"stateSave": true,
"stateSave": false,
"sPaginationType": "bootstrap",
"processing": false,
"serverSide": true,
@ -48,7 +48,14 @@ history_table_options = {
},
{
"targets": [2],
"data":"user"
"data":"user",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
$(td).html('<a href="user?user=' + cellData + '">' + cellData + '</a>');
} else {
$(td).html(cellData);
}
},
},
{
"targets": [3],

View file

@ -0,0 +1,41 @@
$('#log_table').dataTable( {
"processing": false,
"serverSide": true,
"ajax": {
"url": "getLog"
},
"sPaginationType": "bootstrap",
"order": [ 0, 'desc'],
"pageLength": 10,
"stateSave": true,
"language": {
"search":"Search: ",
"lengthMenu":"Show _MENU_ lines per page",
"emptyTable": "No log information available",
"info":"Showing _START_ to _END_ of _TOTAL_ lines",
"infoEmpty":"Showing 0 to 0 of 0 lines",
"infoFiltered":"(filtered from _MAX_ total lines)"},
"columnDefs": [
{
"targets": [0],
"width": "15%"
},
{
"targets": [1],
"width": "10%"
},
{
"targets": [2],
"width": "75%"
}
],
"drawCallback": function (settings) {
// Jump to top of page
$('html,body').scrollTop(0);
$('#ajaxMsg').addClass('success').fadeOut();
},
"preDrawCallback": function(settings) {
$('#ajaxMsg').html("<div class='msg'><span class='ui-icon ui-icon-check'></span>Fetching rows...</div>");
$('#ajaxMsg').addClass('success').fadeIn();
}
});

View file

@ -0,0 +1,105 @@
user_ip_table_options = {
"destroy": true,
"language": {
"search": "Search: ",
"lengthMenu":"Show _MENU_ entries per page",
"info":"Showing _START_ to _END_ of _TOTAL_ results",
"infoEmpty":"Showing 0 to 0 of 0 entries",
"infoFiltered":"(filtered from _MAX_ total entries)",
"emptyTable": "No data in table",
},
"stateSave": false,
"sPaginationType": "bootstrap",
"processing": false,
"serverSide": true,
"pageLength": 10,
"order": [ 0, 'desc'],
"autoWidth": false,
"columnDefs": [
{
"targets": [0],
"data":"last_seen",
"render": function ( data, type, full ) {
return moment(data, "X").fromNow();
},
"searchable": false,
"width": "15%"
},
{
"targets": [1],
"data":"ip_address",
"width": "15%",
"className": "modal-control",
"createdCell": function (td, cellData, rowData, row, col) {
if (isPrivateIP(cellData)) {
$(td).html(cellData);
} 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>&nbsp' + cellData +'</a>');
}
},
"width": "15%"
},
{
"targets": [2],
"data":"play_count",
"width": "10%"
},
{
"targets": [3],
"data":"platform",
"width": "15%"
},
{
"targets": [4],
"data":"last_watched",
"width": "30%"
}
],
"drawCallback": function (settings) {
// Jump to top of page
// $('html,body').scrollTop(0);
$('#ajaxMsg').addClass('success').fadeOut();
},
"preDrawCallback": function(settings) {
$('#ajaxMsg').html("<div class='msg'><span class='ui-icon ui-icon-check'></span>Fetching rows...</div>");
$('#ajaxMsg').addClass('success').fadeIn();
}
}
$('#user_ip_table').on('mouseenter', 'td.modal-control span', function () {
$(this).tooltip();
});
$('#user_ip_table').on('click', 'td.modal-control', function () {
var tr = $(this).parents('tr');
var row = user_ip_table.row( tr );
var rowData = row.data();
function getUserLocation(ip_address) {
if (isPrivateIP(ip_address)) {
return "n/a"
} else {
$.ajax({
url: 'http://ip-api.com/json/' + ip_address,
cache: true,
async: true,
type: 'GET',
dataType: 'json',
success: function(data) {
$('#ip_address').html(ip_address);
$('#country').html(data.country);
$('#city').html(data.city);
$('#region').html(data.regionName);
$('#timezone').html(data.timezone);
$('#lat').html(data.lat);
$('#lon').html(data.lon);
$('#isp').html(data.isp);
$('#org').html(data.org);
$('#as').html(data.as);
}
});
}
}
getUserLocation(rowData['ip_address']);
});

View file

@ -0,0 +1,77 @@
users_list_table_options = {
"language": {
"search": "Search: ",
"lengthMenu":"Show _MENU_ entries per page",
"info":"Showing _START_ to _END_ of _TOTAL_ active users",
"infoEmpty":"Showing 0 to 0 of 0 entries",
"infoFiltered":"",
"emptyTable": "No data in table",
},
"destroy": true,
"processing": false,
"serverSide": true,
"pageLength": 10,
"order": [ 0, 'asc'],
"ajax": {
"url": "get_user_list"
},
"bLengthChange": true,
"bInfo": true,
"bAutoWidth": true,
"aaSorting": [[ 0, "asc" ]],
"bStateSave": true,
"bSortClasses": true,
"sPaginationType": "bootstrap",
"columnDefs": [
{
"targets": [0],
"data": null,
"createdCell": function (td, cellData, rowData, row, col) {
//if (rowData['user_thumb'] === '') {
$(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"/>');
//}
},
"orderable": false,
"className": "users-poster-face",
"width": "40px"
},
{
"targets": [1],
"data": "user",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
$(td).html('<a href="user?user=' + cellData + '">' + cellData + '</a>');
} else {
$(td).html(cellData);
}
},
},
{
"targets": [2],
"data": "time",
"render": function ( data, type, full ) {
return moment(data, "X").fromNow();
}
},
{
"targets": [3],
"data": "ip_address",
"searchable": false
},
{
"targets": [4],
"data": "plays"
}
],
"drawCallback": function (settings) {
// Jump to top of page
$('html,body').scrollTop(0);
$('#ajaxMsg').addClass('success').fadeOut();
},
"preDrawCallback": function(settings) {
$('#ajaxMsg').html("<div class='msg'><span class='ui-icon ui-icon-check'></span>Fetching rows...</div>");
$('#ajaxMsg').addClass('success').fadeIn();
}
}

View file

@ -24,7 +24,12 @@
<div class="span12">
<div class="wellheader-bg">
<div class="dashboard-wellheader-no-chevron">
<h2><i class="fa fa-book"></i> Logs</h2>
<div class="span9"><h2><i class="fa fa-book"></i> Logs</h2></div>
<div class="span3">
<div class="pull-right">
<h5><a id="menu_link_edit" href="clearLogs"><i class="fa fa-trash-o"></i> Clear log</a>
</div>
</div>
</div>
</div>
</div>
@ -68,53 +73,7 @@
<%def name="javascriptIncludes()">
<script src="interfaces/default/js/jquery.dataTables.min.js"></script>
<script src="interfaces/default/js/jquery.dataTables.bootstrap.pagination.integration.js"></script>
<script>
$(document).ready(function() {
$('#log_table').dataTable( {
"processing": false,
"serverSide": true,
"ajax": {
"url": "getLog"
},
"sPaginationType": "bootstrap",
"order": [ 0, 'desc'],
"pageLength": 10,
"stateSave": true,
"language": {
"search":"Search: ",
"lengthMenu":"Show _MENU_ lines per page",
"emptyTable": "No log information available",
"info":"Showing _START_ to _END_ of _TOTAL_ lines",
"infoEmpty":"Showing 0 to 0 of 0 lines",
"infoFiltered":"(filtered from _MAX_ total lines)"},
"columnDefs": [
{
"targets": [0],
"width": "15%"
},
{
"targets": [1],
"width": "10%"
},
{
"targets": [2],
"width": "75%"
}
],
"drawCallback": function (settings) {
// Jump to top of page
$('html,body').scrollTop(0);
$('#ajaxMsg').addClass('success').fadeOut();
},
"preDrawCallback": function(settings) {
$('#ajaxMsg').html("<div class='msg'><span class='ui-icon ui-icon-check'></span>Fetching rows...</div>");
$('#ajaxMsg').addClass('success').fadeIn();
}
});
});
</script>
<script src="interfaces/default/js/tables/logs.js"></script>
<script>
var timer;
function setRefresh()

View file

@ -7,8 +7,9 @@
<link rel="stylesheet" href="interfaces/default/css/plexwatch-tables.css">
</%def>
<%def name="body()">
<div class="container-fluid">
% if user != None:
<%def name="body()">
<div class="container-fluid">
<div class="row-fluid">
<div class="span12">
<div class="user-info-wrapper">
@ -16,7 +17,7 @@
<img src="interfaces/default/images/gravatar-default-80x80.png">
</div>
<div class="user-info-username">
Username
${user}
</div>
<div class="user-info-nav">
<ul class="user-info-nav">
@ -28,8 +29,8 @@
</div>
</div>
</div>
</div>
<div class="tab-content">
</div>
<div class="tab-content">
<div class="tab-pane active" id="profile">
<div class="container-fluid">
<div class="row-fluid">
@ -88,22 +89,49 @@
<div class="wellheader">
<div class="dashboard-wellheader">
<h3>Public IP Addresses for <strong>
Username
${user}
</strong></h3>
</div>
</div>
<table id="tableUserIpAddresses" class="display" width="100%">
<table id="user_ip_table" class="display" width="100%">
<thead>
<tr>
<th align="left"><i class="fa fa-sort"></i> Last seen</th>
<th align="left"><i class="fa fa-sort"></i> IP Address</th>
<th align="left"><i class="fa fa-sort"></i> Play Count</th>
<th align="left"><i class="fa fa-sort"></i> Platform (Last Seen)</th>
<th align="left"><i class="fa fa-sort"></i> Location</th>
<th align="left"><i class="fa fa-sort"></i> Location</th>
<th align="left"> Last Watched</th>
</tr>
</thead>
</table>
<div id="ip-info-modal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="ip-info-modal" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
<h3 id="myModalLabel">IP Address: <strong><span id="ip_address"></span></strong></h3>
</div>
<div class="modal-body" id="modal-text">
<div class="span6">
<h4>Location Details</h4>
<ul>
<li>Country: <strong><span id="country"></span></strong></li>
<li>City: <strong><span id="city"></span></strong></li>
<li>Region: <strong><span id="region"></span></strong></li>
<li>Timezone: <strong><span id="timezone"></span></strong></li>
<li>Latitude: <strong><span id="lat"></span></strong></li>
<li>Longitude: <strong><span id="lon"></span></strong></li>
</ul>
</div>
<div class="span6">
<h4>Connection Details</h4>
<ul>
<li>ISP: <strong><span id="isp"></span></strong></li>
<li>Organization: <strong><span id="org"></span></strong></li>
<li>AS: <strong><span id="as"></span></strong></li>
</ul>
</div>
</div>
<div class="modal-footer"></div>
</div>
</div>
</div>
</div>
@ -116,8 +144,8 @@
<div class="wellbg">
<div class="wellheader">
<div class="dashboard-wellheader">
<h3>Watching History for <strong>
Username
<h3>Watch History for <strong>
${user}
</strong></h3>
</div>
</div>
@ -143,52 +171,6 @@
</tbody>
</table>
<div id="info-modal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="info-modal" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
<h3 id="myModalLabel">Stream Info: <strong><span id="modal-stream-info"></span></strong></h3>
</div>
<div class="modal-body" id="modal-text">
<div class="span4">
<h4>Stream Details</h4>
<ul>
<h5>Video</h5>
<li>Stream Type: <strong><span id="transcode_video_dec"></span></strong></li>
<li>Video Resolution: <strong><span id="transcode_video_resolution"></span>p</strong></li>
<li>Video Codec: <strong><span id="transcode_video_codec"></span></strong></li>
<li>Video Width: <strong><span id="transcode_video_width"></span></strong></li>
<li>Video Height: <strong><span id="transcode_video_height"></span></strong></li>
</ul>
<ul>
<h5>Audio</h5>
<li>Stream Type: <strong><span id="transcode_audio_dec"></span></strong></li>
<li>Audio Codec: <strong><span id="transcode_audio_codec"></span></strong></li>
<li>Audio Channels: <strong><span id="transcode_audio_channels"></span></strong></li>
</ul>
</div>
<div class="span4">
<h4>Media Source Details</h4>
<li>Container: <strong><span id="media_container"></span></strong></li>
<li>Resolution: <strong><span id="media_resolution"></span>p</strong></li>
<li>Bitrate: <strong><span id="media_bitrate"></span> kbps</strong></li>
</div>
<div class="span4">
<h4>Video Source Details</h4>
<ul>
<li>Width: <strong><span id="video_width"></span></strong></li>
<li>Height: <strong><span id="video_height"></span></strong></li>
<li>Aspect Ratio: <strong><span id="video_aspect"></span></strong></li>
<li>Video Frame Rate: <strong><span id="video_framerate"></span></strong></li>
<li>Video Codec: <strong><span id="video_codec"></span></strong></li>
</ul>
<ul></ul>
<h4>Audio Source Details</h4>
<ul>
<li>Audio Codec: <strong><span id="audio_codec"></span></strong></li>
<li>Audio Channels: <strong><span id="audio_channels"></span></strong></li>
</ul>
</div>
</div>
<div class="modal-footer">
</div>
</div>
</div>
@ -196,29 +178,43 @@
</div>
</div>
</div>
</div>
<footer></footer>
</%def>
<footer></footer>
</%def>
<%def name="javascriptIncludes()">
<%def name="javascriptIncludes()">
<script src="interfaces/default/js/jquery.dataTables.min.js"></script>
<script src="interfaces/default/js/jquery.dataTables.bootstrap.pagination.integration.js"></script>
<script src="interfaces/default/js/moment-with-locale.js"></script>
<script src="interfaces/default/js/tables/history_table.js"></script>
<script src="interfaces/default/js/tables/user_ips.js"></script>
<script>
$(document).ready(function () {
history_table_options.ajax = {
"url": "get_history",
"data": function(d) {
d.user = "drzoidberg33";
d.user = "${user}";
}
}
history_table = $('#history_table').DataTable(history_table_options);
history_table.column(2).visible(false); // Hide the title column
// Hide the title column
// history_table.column(5).visible(false);
user_ip_table_options.ajax = {
"url": "get_user_ips",
"data": function(d) {
d.user = "${user}";
}
}
user_ip_table = $('#user_ip_table').DataTable(user_ip_table_options);
});
</script>
</%def>
</%def>
% else:
<div class="clear"></div>
<div class="container-fluid">
<div class="row-fluid">
<div class="span10 offset1">
<h3>Error retrieving user information. Please see the logs for more details.</h3>
</div>
</div>
</div>
% endif

View file

@ -23,7 +23,7 @@
<div class='row-fluid'>
<div class='span12'>
<div class='wellbg'>
<table id="usersTable" class="display" width="100%">
<table id="users_list_table" class="display" width="100%">
<thead>
<tr>
<th align="right" id="avatar"></th>
@ -48,79 +48,8 @@
<script src="interfaces/default/js/jquery.dataTables.min.js"></script>
<script src="interfaces/default/js/jquery.dataTables.bootstrap.pagination.integration.js"></script>
<script src="interfaces/default/js/moment-with-locale.js"></script>
<script src="interfaces/default/js/tables/users.js"></script>
<script>
$(document).ready(function() {
var oTable = $('#usersTable').dataTable({
"language": {
"search": "Search: ",
"lengthMenu":"Show _MENU_ entries per page",
"info":"Showing _START_ to _END_ of _TOTAL_ active users",
"infoEmpty":"Showing 0 to 0 of 0 entries",
"infoFiltered":"",
"emptyTable": "No data in table",
},
"destroy": true,
"processing": false,
"serverSide": true,
"pageLength": 10,
"order": [ 0, 'asc'],
"ajax": {
"url": "get_user_list"
},
"bLengthChange": true,
"bInfo": true,
"bAutoWidth": true,
"aaSorting": [[ 0, "asc" ]],
"bStateSave": true,
"bSortClasses": true,
"sPaginationType": "bootstrap",
"columnDefs": [
{
"targets": [0],
"data": null,
"createdCell": function (td, cellData, rowData, row, col) {
//if (rowData['user_thumb'] === '') {
$(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"/>');
//}
},
"orderable": false,
"className": "users-poster-face",
"width": "40px"
},
{
"targets": [1],
"data": "user"
},
{
"targets": [2],
"data": "time",
"render": function ( data, type, full ) {
return moment(data, "X").fromNow();
}
},
{
"targets": [3],
"data": "ip_address",
"searchable": false
},
{
"targets": [4],
"data": "plays"
}
],
"drawCallback": function (settings) {
// Jump to top of page
$('html,body').scrollTop(0);
$('#ajaxMsg').addClass('success').fadeOut();
},
"preDrawCallback": function(settings) {
$('#ajaxMsg').html("<div class='msg'><span class='ui-icon ui-icon-check'></span>Fetching rows...</div>");
$('#ajaxMsg').addClass('success').fadeIn();
}
});
});
var users_list_table = $('#users_list_table').DataTable(users_list_table_options);
</script>
</%def>

View file

@ -75,14 +75,14 @@ class DataTables(object):
% (column_data['column_string'], table_name, where,
order, custom_where)
# logger.debug(u"Query string: %s" % query)
filtered = self.ssp_db.select(query)
if search_value == '':
totalcount = len(filtered)
else:
totalcount = self.ssp_db.select('SELECT COUNT(*) from %s' % table_name)[0][0]
# logger.debug(u"Query string: %s" % query)
result = filtered[start:(start + length)]
output = {'result': result,
'filteredCount': len(filtered),

View file

@ -14,8 +14,8 @@
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
from plexpy import logger, helpers, request, datatables, config, db
from xml.dom import minidom
import plexpy
import json
@ -104,6 +104,70 @@ class PlexWatch(object):
return dict
def get_user_unique_ips(self, start='', length='', kwargs=None, custom_where=''):
data_tables = datatables.DataTables()
start = int(start)
length = int(length)
filtered = []
totalcount = 0
search_value = ""
search_regex = ""
order_column = 0
order_dir = "desc"
if 'order[0][dir]' in kwargs:
order_dir = kwargs.get('order[0][dir]', "desc")
if 'order[0][column]' in kwargs:
order_column = kwargs.get('order[0][column]', 1)
if 'search[value]' in kwargs:
search_value = kwargs.get('search[value]', "")
if 'search[regex]' in kwargs:
search_regex = kwargs.get('search[regex]', "")
columns = ['time as last_seen',
'ip_address',
'COUNT(ip_address) as play_count',
'platform',
'user',
'orig_title as last_watched'
]
query = data_tables.ssp_query(table_name=self.get_user_table_name(),
columns=columns,
start=start,
length=length,
order_column=int(order_column),
order_dir=order_dir,
search_value=search_value,
search_regex=search_regex,
custom_where=custom_where,
group_by='ip_address',
kwargs=kwargs)
results = query['result']
rows = []
for item in results:
row = {"last_seen": item['last_seen'],
"ip_address": item['ip_address'],
"play_count": item['play_count'],
"platform": item['platform'],
"last_watched": item['last_watched']
}
rows.append(row)
dict = {'recordsFiltered': query['filteredCount'],
'recordsTotal': query['totalCount'],
'data': rows,
}
return dict
def get_history(self, start='', length='', kwargs=None, custom_where=''):
data_tables = datatables.DataTables()

View file

@ -92,11 +92,11 @@ class WebInterface(object):
return serve_template(templatename="users.html", title="Users")
@cherrypy.expose
def user(self):
return serve_template(templatename="user.html", title="User")
def user(self, user=None):
return serve_template(templatename="user.html", title="User", user=user)
@cherrypy.expose
def get_stream_data(self, row_id=None, user='', **kwargs):
def get_stream_data(self, row_id=None, user=None, **kwargs):
plex_watch = plexwatch.PlexWatch()
stream_data = plex_watch.get_stream_details(row_id)
@ -581,3 +581,16 @@ class WebInterface(object):
return result
else:
logger.warn('Unable to retrieve data.')
@cherrypy.expose
def get_user_ips(self, start=0, length=100, custom_where='', **kwargs):
if 'user' in kwargs:
user = kwargs.get('user', "")
custom_where = 'user = "%s"' % user
plex_watch = plexwatch.PlexWatch()
history = plex_watch.get_user_unique_ips(start, length, kwargs, custom_where)
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps(history)