diff --git a/data/interfaces/default/current_activity.html b/data/interfaces/default/current_activity.html index 5241d10e..4cea9945 100644 --- a/data/interfaces/default/current_activity.html +++ b/data/interfaces/default/current_activity.html @@ -25,7 +25,7 @@
- ${a['user']} is ${a['state']} + ${a['user']} is ${a['state']}
% if a['type'] == 'episode': diff --git a/data/interfaces/default/js/script.js b/data/interfaces/default/js/script.js index 98b34a40..6237e963 100644 --- a/data/interfaces/default/js/script.js +++ b/data/interfaces/default/js/script.js @@ -207,4 +207,14 @@ function getPlatformImagePath(platformName) { } else { 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; } \ No newline at end of file diff --git a/data/interfaces/default/js/tables/history_table.js b/data/interfaces/default/js/tables/history_table.js index fc02822a..634d4bf5 100644 --- a/data/interfaces/default/js/tables/history_table.js +++ b/data/interfaces/default/js/tables/history_table.js @@ -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('' + cellData + ''); + } else { + $(td).html(cellData); + } + }, }, { "targets": [3], diff --git a/data/interfaces/default/js/tables/logs.js b/data/interfaces/default/js/tables/logs.js new file mode 100644 index 00000000..5fa6e8df --- /dev/null +++ b/data/interfaces/default/js/tables/logs.js @@ -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("
Fetching rows...
"); + $('#ajaxMsg').addClass('success').fadeIn(); + } +}); diff --git a/data/interfaces/default/js/tables/user_ips.js b/data/interfaces/default/js/tables/user_ips.js new file mode 100644 index 00000000..99314278 --- /dev/null +++ b/data/interfaces/default/js/tables/user_ips.js @@ -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(' ' + cellData +''); + } + }, + "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("
Fetching rows...
"); + $('#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']); +}); \ No newline at end of file diff --git a/data/interfaces/default/js/tables/users.js b/data/interfaces/default/js/tables/users.js new file mode 100644 index 00000000..cbde3b2e --- /dev/null +++ b/data/interfaces/default/js/tables/users.js @@ -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('User Logo'); + //} else { + // $(td).html('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('' + cellData + ''); + } 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("
Fetching rows...
"); + $('#ajaxMsg').addClass('success').fadeIn(); + } +} diff --git a/data/interfaces/default/logs.html b/data/interfaces/default/logs.html index fb8c866a..c153abf5 100644 --- a/data/interfaces/default/logs.html +++ b/data/interfaces/default/logs.html @@ -24,7 +24,12 @@
-

Logs

+

Logs

+
+ +
@@ -68,69 +73,23 @@ <%def name="javascriptIncludes()"> - + - diff --git a/data/interfaces/default/user.html b/data/interfaces/default/user.html index 46d36fdf..aeebf954 100644 --- a/data/interfaces/default/user.html +++ b/data/interfaces/default/user.html @@ -7,218 +7,214 @@ -<%def name="body()"> -
-
-
- -
-
-
-
-
-
-
-
-
-
-
-

Global Stats

-
-
-
-
-
+% if user != None: + <%def name="body()"> +
+
+
+ -
-
-
-
-
-
-
-
-

Platform Stats

-
-
-
-
-
+ -
-
-
-
-
-
-
-
-
-

Recently watched

-
-
-
-
-
+
-
-
-
-
-
-
-
-

Public IP Addresses for - Username -

+
+
+
+
+
+
+
+
+

Global Stats

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

Platform Stats

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

Recently watched

+
+
+
+
- - - - - - - - - - - -
Last seen IP Address Play Count Platform (Last Seen) Location Location
-
-
-
-
-
-
-
-
-

Watching History for - Username -

+
+
+
+
+
+
+
+

Public IP Addresses for + ${user} +

+
-
- - - - - - - - - - - - - - - - - - - - -
ID Time User Platform IP Address Title Started Paused Stopped Duration Completed RatingKey
- +
+
+
+
+
+
+
+
+

Watch History for + ${user} +

-
-

Video Source Details

-
    -
  • Width:
  • -
  • Height:
  • -
  • Aspect Ratio:
  • -
  • Video Frame Rate:
  • -
  • Video Codec:
  • -
-
    -

    Audio Source Details

    -
      -
    • Audio Codec:
    • -
    • Audio Channels:
    • -
    -
    -
    - -
    +
    + + + + + + + + + + + + + + + + + + + + +
    ID Time User Platform IP Address Title Started Paused Stopped Duration Completed RatingKey
    + +
    -
    -
    - +
    + -<%def name="javascriptIncludes()"> - - - - - - + + + + + - \ No newline at end of file + 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); + }); + + +% else: +
    +
    +
    +
    +

    Error retrieving user information. Please see the logs for more details.

    +
    +
    +
    +% endif \ No newline at end of file diff --git a/data/interfaces/default/users.html b/data/interfaces/default/users.html index 576dc220..d3925d65 100644 --- a/data/interfaces/default/users.html +++ b/data/interfaces/default/users.html @@ -23,7 +23,7 @@
    - +
    @@ -48,79 +48,8 @@ - + + var users_list_table = $('#users_list_table').DataTable(users_list_table_options); + diff --git a/plexpy/datatables.py b/plexpy/datatables.py index 4d87ee32..1df9df89 100644 --- a/plexpy/datatables.py +++ b/plexpy/datatables.py @@ -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), diff --git a/plexpy/plexwatch.py b/plexpy/plexwatch.py index 3f2d8aea..f313cb74 100644 --- a/plexpy/plexwatch.py +++ b/plexpy/plexwatch.py @@ -14,8 +14,8 @@ # along with PlexPy. If not, see . 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() diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 37fb5c84..1ea1c695 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -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) @@ -580,4 +580,17 @@ class WebInterface(object): cherrypy.response.headers['Content-type'] = 'application/json' return result else: - logger.warn('Unable to retrieve data.') \ No newline at end of file + 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) \ No newline at end of file