diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 3f0adf04..1a0d5658 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -251,7 +251,7 @@ fieldset[disabled] .btn-bright.active { background-color: #222222; } .modal-body table { - color: #999; + color: #fff; } .modal-body li { margin-top: 7px; diff --git a/data/interfaces/default/history.html b/data/interfaces/default/history.html index bb796574..9546b94b 100644 --- a/data/interfaces/default/history.html +++ b/data/interfaces/default/history.html @@ -25,8 +25,8 @@ Delete Time User - Platform IP Address + Platform Title Started Paused diff --git a/data/interfaces/default/js/script.js b/data/interfaces/default/js/script.js index 0a4df6ff..1ad24f57 100644 --- a/data/interfaces/default/js/script.js +++ b/data/interfaces/default/js/script.js @@ -251,6 +251,9 @@ function humanTime(seconds) { } else if (seconds >= 60) { text = '

' + Math.floor(moment.duration(((seconds % 86400) % 3600), 'seconds').asMinutes()) + '

mins

'; return text; + } else { + text = '

0

mins

'; + return text; } } diff --git a/data/interfaces/default/js/tables/history_table.js b/data/interfaces/default/js/tables/history_table.js index a41135f5..65edc7c8 100644 --- a/data/interfaces/default/js/tables/history_table.js +++ b/data/interfaces/default/js/tables/history_table.js @@ -28,6 +28,16 @@ history_table_options = { "order": [ 1, 'desc'], "autoWidth": false, "columnDefs": [ + { + "targets": [0], + "data": null, + "createdCell": function (td, cellData, rowData, row, col) { + $(td).html(''); + }, + "className": "delete-control no-wrap hidden", + "searchable": false, + "orderable": false + }, { "targets": [1], "data":"date", @@ -59,17 +69,7 @@ history_table_options = { }, { "targets": [3], - "data":"player", - "createdCell": function (td, cellData, rowData, row, col) { - if (cellData !== '') { - $(td).html(' '+cellData); - } - }, - "className": "modal-control no-wrap hidden-sm hidden-xs" - }, - { - "targets": [4], - "data":"ip_address", + "data": "ip_address", "createdCell": function (td, cellData, rowData, row, col) { if (cellData) { if (isPrivateIP(cellData)) { @@ -79,13 +79,23 @@ history_table_options = { $(td).html('n/a'); } } else { - $(td).html(' ' + cellData +''); + $(td).html(' ' + cellData + ''); } } else { $(td).html('n/a'); } }, - "className": "no-wrap hidden-xs modal-control-ip" + "className": "no-wrap hidden-md hidden-sm hidden-xs modal-control-ip" + }, + { + "targets": [4], + "data":"player", + "createdCell": function (td, cellData, rowData, row, col) { + if (cellData !== '') { + $(td).html(' ' + cellData + ''); + } + }, + "className": "no-wrap hidden-md hidden-sm hidden-xs modal-control" }, { "targets": [5], @@ -130,7 +140,7 @@ history_table_options = { } }, "searchable": false, - "className": "no-wrap hidden-xs" + "className": "no-wrap hidden-md hidden-sm hidden-xs" }, { "targets": [8], @@ -143,7 +153,7 @@ history_table_options = { } }, "searchable": false, - "className": "no-wrap hidden-md hidden-xs" + "className": "no-wrap hidden-sm hidden-xs" }, { "targets": [9], @@ -172,19 +182,9 @@ history_table_options = { }, "searchable": false, "orderable": false, - "className": "no-wrap hidden-md hidden-xs", + "className": "no-wrap hidden-md hidden-sm hidden-xs", "width": "10px" }, - { - "targets": [0], - "data": null, - "createdCell": function (td, cellData, rowData, row, col) { - $(td).html(''); - }, - "className": "delete-control no-wrap hidden", - "searchable": false, - "orderable": false - }, ], "drawCallback": function (settings) { // Jump to top of page @@ -247,13 +247,13 @@ $('#history_table').on('click', 'td.modal-control-ip', function () { getUserLocation(rowData['ip_address']); }); -$('#history_table').on('click', 'td.delete-control', function () { +$('#history_table').on('click', 'td.delete-control > button', function () { var tr = $(this).parents('tr'); var row = history_table.row( tr ); var rowData = row.data(); - $(this).children("button").prop('disabled', true); - $(this).children("button").html(' Delete'); + $(this).prop('disabled', true); + $(this).html(' Delete'); $.ajax({ url: 'delete_history_rows', diff --git a/data/interfaces/default/js/tables/history_table_modal.js b/data/interfaces/default/js/tables/history_table_modal.js index 60bea99a..1c684907 100644 --- a/data/interfaces/default/js/tables/history_table_modal.js +++ b/data/interfaces/default/js/tables/history_table_modal.js @@ -74,7 +74,7 @@ history_table_modal_options = { { "targets": [3], "data":"player", - "className": "modal-control no-wrap hidden-sm hidden-xs" + "className": "no-wrap hidden-sm hidden-xs modal-control" }, { "targets": [4], diff --git a/data/interfaces/default/js/tables/user_ips.js b/data/interfaces/default/js/tables/user_ips.js index 7321e1b3..9c8cc411 100644 --- a/data/interfaces/default/js/tables/user_ips.js +++ b/data/interfaces/default/js/tables/user_ips.js @@ -24,13 +24,11 @@ user_ip_table_options = { }, "searchable": false, "width": "15%", - "className": "no-wrap" + "className": "no-wrap hidden-xs" }, { "targets": [1], - "data":"ip_address", - "width": "15%", - "className": "modal-control no-wrap", + "data": "ip_address", "createdCell": function (td, cellData, rowData, row, col) { if (cellData) { if (isPrivateIP(cellData)) { @@ -46,31 +44,59 @@ user_ip_table_options = { $(td).html('n/a'); } }, - "width": "15%" + "width": "15%", + "className": "no-wrap modal-control-ip" }, { "targets": [2], - "data":"play_count", - "width": "10%", - "className": "hidden-xs" + "data":"platform", + "createdCell": function (td, cellData, rowData, row, col) { + if (cellData) { + $(td).html(' ' + cellData + ''); + } else { + $(td).html('n/a'); + } + }, + "width": "15%", + "className": "no-wrap hidden-md hidden-sm hidden-xs modal-control" }, { "targets": [3], - "data":"platform", - "width": "15%", - "className": "hidden-xs" + "data":"last_watched", + "createdCell": function (td, cellData, rowData, row, col) { + if (cellData !== '') { + if (rowData['media_type'] === 'movie' || rowData['media_type'] === 'episode') { + var transcode_dec = ''; + if (rowData['video_decision'] === 'transcode') { + transcode_dec = ' '; + } + $(td).html('
' + cellData + '
' + transcode_dec + '
'); + } else if (rowData['media_type'] === 'track') { + $(td).html('
' + cellData + '
'); + } else if (rowData['media_type']) { + $(td).html('' + cellData + ''); + } else { + $(td).html('n/a'); + } + } + }, + "className": "hidden-sm hidden-xs" }, { "targets": [4], - "data":"last_watched", - "width": "30%", - "className": "hidden-sm hidden-xs" - } + "data":"play_count", + "searchable": false, + "width": "10%" + } ], "drawCallback": function (settings) { // Jump to top of page // $('html,body').scrollTop(0); $('#ajaxMsg').fadeOut(); + // Create the tooltips. + $('.info-modal').each(function () { + $(this).tooltip(); + }); }, "preDrawCallback": function(settings) { var msg = "
 Fetching rows...
"; @@ -83,6 +109,25 @@ $('#user_ip_table').on('mouseenter', 'td.modal-control span', function () { }); $('#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 showStreamDetails() { + $.ajax({ + url: 'get_stream_data', + data: { row_id: rowData['id'], user: rowData['friendly_name'] }, + cache: false, + async: true, + complete: function (xhr, status) { + $("#info-modal").html(xhr.responseText); + } + }); + } + showStreamDetails(); +}); + +$('#user_ip_table').on('click', 'td.modal-control-ip', function () { var tr = $(this).parents('tr'); var row = user_ip_table.row( tr ); var rowData = row.data(); diff --git a/data/interfaces/default/js/tables/users.js b/data/interfaces/default/js/tables/users.js index 5dba864b..c3e022e3 100644 --- a/data/interfaces/default/js/tables/users.js +++ b/data/interfaces/default/js/tables/users.js @@ -34,7 +34,7 @@ users_list_table_options = { { "targets": [1], "data": "friendly_name", - "createdCell": function (td, cellData, rowData, row, col) { + "createdCell": function (td, cellData, rowData, row, col) { if (cellData !== '') { if (rowData['user_id'] > 0) { $(td).html('' + cellData + ''); @@ -45,6 +45,7 @@ users_list_table_options = { $(td).html(cellData); } }, + "width": "15%" }, { "targets": [2], @@ -57,34 +58,126 @@ users_list_table_options = { } }, "searchable": false, - "className": "hidden-xs", + "width": "15%", + "className": "no-wrap hidden-xs" }, { "targets": [3], "data": "ip_address", - "render": function ( data, type, full ) { - if (data) { - return data; + "createdCell": function (td, cellData, rowData, row, col) { + if (cellData) { + if (isPrivateIP(cellData)) { + if (cellData != '') { + $(td).html(cellData); + } else { + $(td).html('n/a'); + } + } else { + $(td).html(' ' + cellData + ''); + } } else { - return "n/a"; + $(td).html('n/a'); } }, - "className": "hidden-xs", + "width": "15%", + "className": "no-wrap hidden-md hidden-sm hidden-xs modal-control-ip" }, { "targets": [4], + "data":"platform", + "createdCell": function (td, cellData, rowData, row, col) { + if (cellData) { + $(td).html(' ' + cellData + ''); + } else { + $(td).html('n/a'); + } + }, + "width": "15%", + "className": "no-wrap hidden-md hidden-sm hidden-xs modal-control" + }, + { + "targets": [5], + "data":"last_watched", + "createdCell": function (td, cellData, rowData, row, col) { + if (cellData !== '') { + if (rowData['media_type'] === 'movie' || rowData['media_type'] === 'episode') { + var transcode_dec = ''; + if (rowData['video_decision'] === 'transcode') { + transcode_dec = ' '; + } + $(td).html('
' + cellData + '
' + transcode_dec + '
'); + } else if (rowData['media_type'] === 'track') { + $(td).html('
' + cellData + '
'); + } else if (rowData['media_type']) { + $(td).html('' + cellData + ''); + } else { + $(td).html('n/a'); + } + } + }, + "className": "hidden-sm hidden-xs" + }, + { + "targets": [6], "data": "plays", - "searchable": false - } + "searchable": false, + "width": "10%" + } ], "drawCallback": function (settings) { // Jump to top of page //$('html,body').scrollTop(0); $('#ajaxMsg').fadeOut(); + // Create the tooltips. + $('.info-modal').each(function () { + $(this).tooltip(); + }); }, "preDrawCallback": function(settings) { var msg = "
 Fetching rows...
"; showMsg(msg, false, false, 0) } } + +$('#users_list_table').on('click', 'td.modal-control', function () { + var tr = $(this).parents('tr'); + var row = users_list_table.row(tr); + var rowData = row.data(); + + function showStreamDetails() { + $.ajax({ + url: 'get_stream_data', + data: { row_id: rowData['id'], user: rowData['friendly_name'] }, + cache: false, + async: true, + complete: function (xhr, status) { + $("#info-modal").html(xhr.responseText); + } + }); + } + showStreamDetails(); +}); + +$('#users_list_table').on('click', 'td.modal-control-ip', function () { + var tr = $(this).parents('tr'); + var row = users_list_table.row(tr); + var rowData = row.data(); + + function getUserLocation(ip_address) { + if (isPrivateIP(ip_address)) { + return "n/a" + } else { + $.ajax({ + url: 'get_ip_address_details', + data: { ip_address: ip_address }, + async: true, + complete: function (xhr, status) { + $("#ip-info-modal").html(xhr.responseText); + } + }); + } + } + + getUserLocation(rowData['ip_address']); +}); \ No newline at end of file diff --git a/data/interfaces/default/user.html b/data/interfaces/default/user.html index 366d46dc..4727be64 100644 --- a/data/interfaces/default/user.html +++ b/data/interfaces/default/user.html @@ -131,11 +131,11 @@ from plexpy import helpers - + - - + +
Last seenLast Seen IP AddressPlay CountPlatform (Last Seen)Last Platform Last WatchedPlay Count
@@ -181,7 +181,6 @@ from plexpy import helpers - @@ -224,7 +223,9 @@ from plexpy import helpers - diff --git a/data/interfaces/default/users.html b/data/interfaces/default/users.html index ddb52843..37cf9bc7 100644 --- a/data/interfaces/default/users.html +++ b/data/interfaces/default/users.html @@ -23,12 +23,18 @@ User Last Seen Last Known IP + Last Platform + Last Watched Total Plays + + diff --git a/plexpy/users.py b/plexpy/users.py index fcc9cf94..fb31916c 100644 --- a/plexpy/users.py +++ b/plexpy/users.py @@ -24,13 +24,19 @@ class Users(object): def get_user_list(self, kwargs=None): data_tables = datatables.DataTables() - columns = ['users.user_id as user_id', + columns = ['session_history.id', + 'users.user_id as user_id', 'users.custom_avatar_url as thumb', '(case when users.friendly_name is null then users.username else \ users.friendly_name end) as friendly_name', 'MAX(session_history.started) as last_seen', 'session_history.ip_address as ip_address', 'COUNT(session_history.id) as plays', + 'session_history.player as platform', + 'session_history_metadata.full_title as last_watched', + 'session_history_metadata.media_type', + 'session_history.rating_key as rating_key', + 'session_history_media_info.video_decision', 'users.username as user' ] try: @@ -38,9 +44,15 @@ class Users(object): columns=columns, custom_where=[], group_by=['users.user_id'], - join_types=['LEFT OUTER JOIN'], - join_tables=['session_history'], - join_evals=[['session_history.user_id', 'users.user_id']], + join_types=['LEFT OUTER JOIN', + 'LEFT OUTER JOIN', + 'LEFT OUTER JOIN'], + join_tables=['session_history', + 'session_history_metadata', + 'session_history_media_info'], + join_evals=[['session_history.user_id', 'users.user_id'], + ['session_history.id', 'session_history_metadata.id'], + ['session_history.id', 'session_history_media_info.id']], kwargs=kwargs) except: logger.warn("Unable to execute database query.") @@ -59,10 +71,16 @@ class Users(object): else: user_thumb = item['thumb'] - row = {"plays": item['plays'], + row = {"id": item['id'], + "plays": item['plays'], "last_seen": item['last_seen'], - "friendly_name": item["friendly_name"], - "ip_address": item["ip_address"], + "friendly_name": item['friendly_name'], + "ip_address": item['ip_address'], + "platform": item['platform'], + "last_watched": item['last_watched'], + "media_type": item['media_type'], + "rating_key": item['rating_key'], + "video_decision": item['video_decision'], "thumb": user_thumb, "user": item["user"], "user_id": item['user_id'] @@ -81,13 +99,22 @@ class Users(object): def get_user_unique_ips(self, kwargs=None, custom_where=None): data_tables = datatables.DataTables() - columns = ['session_history.started as last_seen', + # Change custom_where column name due to ambiguous column name after JOIN + custom_where[0][0] = 'custom_user_id' if custom_where[0][0] == 'user_id' else custom_where[0][0] + + columns = ['session_history.id', + 'session_history.started as last_seen', 'session_history.ip_address as ip_address', 'COUNT(session_history.id) as play_count', 'session_history.player as platform', 'session_history_metadata.full_title as last_watched', + 'session_history_metadata.media_type', + 'session_history.rating_key as rating_key', + 'session_history_media_info.video_decision', 'session_history.user as user', - 'session_history.user_id as user_id' + 'session_history.user_id as custom_user_id', + '(case when users.friendly_name is null then users.username else \ + users.friendly_name end) as friendly_name' ] try: @@ -95,9 +122,15 @@ class Users(object): columns=columns, custom_where=custom_where, group_by=['ip_address'], - join_types=['JOIN'], - join_tables=['session_history_metadata'], - join_evals=[['session_history.id', 'session_history_metadata.id']], + join_types=['JOIN', + 'JOIN', + 'JOIN'], + join_tables=['users', + 'session_history_metadata', + 'session_history_media_info'], + join_evals=[['session_history.user_id', 'users.user_id'], + ['session_history.id', 'session_history_metadata.id'], + ['session_history.id', 'session_history_media_info.id']], kwargs=kwargs) except: logger.warn("Unable to execute database query.") @@ -111,11 +144,16 @@ class Users(object): rows = [] for item in results: - row = {"last_seen": item['last_seen'], + row = {"id": item['id'], + "last_seen": item['last_seen'], "ip_address": item['ip_address'], "play_count": item['play_count'], "platform": item['platform'], - "last_watched": item['last_watched'] + "last_watched": item['last_watched'], + "media_type": item['media_type'], + "rating_key": item['rating_key'], + "video_decision": item['video_decision'], + "friendly_name": item['friendly_name'] } rows.append(row)