diff --git a/CHANGELOG.md b/CHANGELOG.md index 22afc3dc..242ae2e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,21 +1,33 @@ # Changelog +## v1.1.4 (2015-08-26) + +* User info is now editable from the users table. Thanks @JonnyWong. +* Improved delete mode for history pages - able to multi-select now. Thanks @JonnyWong. +* Improved image quality on tooltip images. +* More styling improvements and fixes on user and info pages. Thanks @JonnyWong. +* Added some user submitted systemd init scripts. Thanks @malle-pietje and @artbird309. +* Fixed some background operations when saving settings. +* Fix max width restricting home stats to 1600px. +* Fix stream duration parameter for notifications when paused counter is null. + + ## v1.1.3 (2015-08-22) * Show human readable version info and this cool changelog in Settings -> General. * Add a "delete" mode to the history tables. Toggle it to show a delete button next to each history item. -* Two digit season and episode numbers for custom notification messages. Thanks @JohnnyWong. -* New FreeNAS init script. Thanks @JohnnyWong. -* Lots of styling improvements! Thanks @JohnnyWong. -* Graph page remembers last selected options. Thanks @JohnnyWong. -* New Popular movie homepage stats. Thanks @JohnnyWong. -* Add option for duration vs play count on home stats. (Settings -> Extra Settings). Thanks @JohnnyWong. -* Clean up media info pages. Don't show metadata that is missing. Thanks @JohnnyWong. -* Add clear button to search inputs. Thanks @JohnnyWong. -* New columns on Users list. Thanks @JohnnyWong. -* New stream duration option for custom notification messages. Thanks @JohnnyWong. -* Rad new tooltips on the history pages. Thanks @JohnnyWong. -* And a lot of small visual changes and fixes. Thanks @JohnnyWong. +* Two digit season and episode numbers for custom notification messages. Thanks @JonnyWong. +* New FreeNAS init script. Thanks @JonnyWong. +* Lots of styling improvements! Thanks @JonnyWong. +* Graph page remembers last selected options. Thanks @JonnyWong. +* New Popular movie homepage stats. Thanks @JonnyWong. +* Add option for duration vs play count on home stats. (Settings -> Extra Settings). Thanks @JonnyWong. +* Clean up media info pages. Don't show metadata that is missing. Thanks @JonnyWong. +* Add clear button to search inputs. Thanks @JonnyWong. +* New columns on Users list. Thanks @JonnyWong. +* New stream duration option for custom notification messages. Thanks @JonnyWong. +* Rad new tooltips on the history pages. Thanks @JonnyWong. +* And a lot of small visual changes and fixes. Thanks @JonnyWong. * Fixed IP address modal on user history page. * Fixed "invalid date" showing on monthly plays graph. diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 7d715a3c..587bee7b 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -23,7 +23,7 @@ select { outline: none; } select.input-sm { - margin: 5px 0 5px 0; + margin: 5px 0 5px 0; border: 2px solid #444; background: #333; -webkit-border-radius: 2px; @@ -34,8 +34,8 @@ select.input-sm { } img { -webkit-box-sizing: content-box; - -moz-box-sizing: content-box; - box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; } .navbar { background: #000; @@ -237,6 +237,35 @@ fieldset[disabled] .btn-bright.active { color: #fff; background-color: #eb8600; } +.btn-danger.btn-edit { + color: #d7d7d7; + background-color: #3B3B3B; + border-color: transparent; + float: right; + margin-right: 5px; +} +.btn-danger.btn-edit:hover { + color: #fff; + background-color: #c9302c; + border-color: #ac2925; +} +.btn-danger.btn-edit.active { + color: #fff; + background-color: #c9302c; + border-color: #ac2925; +} +.btn-danger.btn-edit.active:hover { + color: #fff; + background-color: #ac2925; + border-color: #761c19; +} +.alert-edit { + display: none; + float: right; + margin-bottom: 0; + margin-right: 5px; + padding: 6px 15px; +} .modal-header { padding: 15px 20px; background-color: #323232; @@ -418,6 +447,12 @@ input[type="color"], webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); -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); + overflow: hidden; +} +a .poster-face:hover { + webkit-box-shadow: inset 0 0 0 2px #e9a049; + -moz-box-shadow: inset 0 0 0 2px #e9a049; + box-shadow: inset 0 0 0 2px #e9a049; } .cover-face { background-position: center; @@ -430,6 +465,29 @@ input[type="color"], -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); } +a .cover-face:hover { + webkit-box-shadow: inset 0 0 0 2px #e9a049; + -moz-box-shadow: inset 0 0 0 2px #e9a049; + box-shadow: inset 0 0 0 2px #e9a049; +} +a .users-poster-face:hover { + webkit-box-shadow: inset 0 0 0 2px #e9a049; + -moz-box-shadow: inset 0 0 0 2px #e9a049; + box-shadow: inset 0 0 0 2px #e9a049; +} +.users-poster-face { + overflow: hidden; + float: left; + background-size: contain; + height: 40px; + width: 40px; + -webkit-border-radius: 50%; + -moz-border-radius: 50%; + border-radius: 50%; + webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -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); +} .users-poster-face img { bottom: 0; overflow: hidden; @@ -619,11 +677,6 @@ input[type="color"], } .dashboard-recent-media-instance { } -.dashboard-recent-media-instance a:hover .poster-face { - webkit-box-shadow: inset 0 0 0 2px #e9a049; - -moz-box-shadow: inset 0 0 0 2px #e9a049; - box-shadow: inset 0 0 0 2px #e9a049; -} .dashboard-recent-media li { margin-right: 27px; position: relative; @@ -788,9 +841,9 @@ input[type="color"], position: relative; top: -10px; float: left; - margin-left: 20px; - width: 174px; - height: 260px; + margin-left: 25px; + width: 150px; + height: 225px; } .summary-content-poster img { bottom: 0; @@ -821,9 +874,6 @@ input[type="color"], -ms-backface-visibility: hidden; -o-backface-visibility: hidden; backface-visibility: hidden; - width: auto; - height: 260px; - border: 1px solid rgba(128, 128, 128, 0.3); } .summary-content { position: relative; @@ -843,6 +893,13 @@ input[type="color"], line-height: 32px; float: left; } +.summary-content-title h1 a{ + color: #F9AA03; +} +.summary-content-title h1 a:hover{ + color: #F9AA03; + text-decoration: underline; +} .summary-content-details-wrapper { width: 100%; padding-bottom: 15px; @@ -909,6 +966,8 @@ input[type="color"], } .summary-content-people-wrapper { margin-top: 25px; + margin-right: 25px; + float: left; } .summary-content-people-wrapper hidden-phone hidden-tablet { overflow: hidden; @@ -1011,7 +1070,7 @@ input[type="color"], left: 0px; margin-right: 25px; } -.season-episodes-instance a:hover .season-episodes-card-overlay { +a .season-episodes-card-overlay:hover { webkit-box-shadow: inset 0 0 0 2px #e9a049; -moz-box-shadow: inset 0 0 0 2px #e9a049; box-shadow: inset 0 0 0 2px #e9a049; @@ -1034,10 +1093,10 @@ input[type="color"], .season-episodes-poster-face img { bottom: 0; overflow: hidden; - width: 205px; - height: 115px; + width: 250px; + height: 140px; } -.season-episodes-poster-face img:hover { +.season-episodes-poster-face img:hover { webkit-box-shadow: 0 0 0 2px #F9AA03; -moz-box-shadow: 0 0 0 2px #F9AA03; box-shadow: 0 0 0 2px #F9AA03; @@ -1114,6 +1173,16 @@ input[type="color"], .user-info-poster-face { float: left; margin-top: 15px; + margin-right: 15px; + background-size: contain; + height: 80px; + width: 80px; + -webkit-border-radius: 50%; + -moz-border-radius: 50%; + border-radius: 50%; + webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -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); } .user-info-poster-face img { bottom: 0; @@ -1232,13 +1301,13 @@ input[type="color"], } .user-platforms-instance li { } -.user-platforms-instance-poster { +.user-platforms-instance-box { float: left; width: 75px; border-radius: 3px; - -webkit-box-shadow: 0 0 5px rgba(0,0,0,0.5); - -moz-box-shadow: 0 0 5px rgba(0,0,0,0.5); - box-shadow: 0 0 5px rgba(0,0,0,0.5); + webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -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); background-size: contain; position: relative; height: 80px; @@ -1276,7 +1345,6 @@ input[type="color"], left: 0px; } .home-platforms { - max-width: 1600px; } .home-platforms ul { list-style: none; @@ -1296,7 +1364,12 @@ input[type="color"], bottom: 35px; height: 80px; width: 80px; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; border-radius: 3px; + webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -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-oval { background-size: contain; @@ -1308,6 +1381,9 @@ input[type="color"], -webkit-border-radius: 50%; -moz-border-radius: 50%; border-radius: 50%; + webkit-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); + -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-name { float: left; @@ -1368,7 +1444,12 @@ input[type="color"], webkit-box-sizing: content-box; box-sizing: content-box; } -.home-platforms-instance a:hover .poster-face { +a .home-platforms-instance-oval:hover { + webkit-box-shadow: inset 0 0 0 2px #e9a049; + -moz-box-shadow: inset 0 0 0 2px #e9a049; + box-shadow: inset 0 0 0 2px #e9a049; +} +a .home-platforms-instance-box:hover { webkit-box-shadow: inset 0 0 0 2px #e9a049; -moz-box-shadow: inset 0 0 0 2px #e9a049; box-shadow: inset 0 0 0 2px #e9a049; @@ -1670,6 +1751,41 @@ input[type="color"], .history-title .popover.right .popover-content { padding: 5px 8px; } +.history-thumbnail { + background-position: center; + background-size: cover; + width: 80px; +} +.edit-user-toggles { + padding-right: 10px; +} +.edit-user-toggles > input[type='checkbox'] { + display: none; +} +.edit-user-toggles > input[type='checkbox'] + label { + color: #444; + cursor: pointer; +} +.edit-user-toggles > input[type='checkbox']:checked + label { + color: #fff; + cursor: pointer; +} +.popover { + z-index: 2; +} +.popover .popover-content { + color: #000; +} +.noTransition +{ + -moz-transition: none !important; + -webkit-transition: none !important; + -o-transition: none !important; + transition: none !important; +} +#users-to-delete > li { + color: #e9a049; +} #updatebar { background-color: #444; color: #999999; diff --git a/data/interfaces/default/edit_user.html b/data/interfaces/default/edit_user.html index cb1a5775..1e19b66e 100644 --- a/data/interfaces/default/edit_user.html +++ b/data/interfaces/default/edit_user.html @@ -74,6 +74,24 @@ DOCUMENTATION :: END + diff --git a/data/interfaces/default/history.html b/data/interfaces/default/history.html index 9e1ec5f6..c7960d20 100644 --- a/data/interfaces/default/history.html +++ b/data/interfaces/default/history.html @@ -14,8 +14,11 @@ History
-   + +
@@ -42,6 +45,24 @@
+ @@ -68,18 +89,42 @@ clearSearchButton('history_table', history_table); - $('#row-edit-mode').click(function() { + $('#row-edit-mode').on('click', function() { + $('#row-edit-mode-alert').fadeIn(200); + if ($(this).hasClass('active')) { - $('.delete-control').each(function() { + if (history_to_delete.length > 0) { + $('#deleteCount').text(history_to_delete.length); + $('#confirm-modal').modal(); + $('#confirm-modal').one('click', '#confirm-delete', function () { + for (var i = 0; i < history_to_delete.length; i++) { + $.ajax({ + url: 'delete_history_rows', + data: { row_id: history_to_delete[i] }, + async: true, + success: function (data) { + var msg = "User history purged"; + showMsg(msg, false, true, 2000); + } + }); + } + history_table.draw(); + }); + } + + $('.delete-control').each(function () { + $(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger'); $(this).addClass('hidden'); + $('#row-edit-mode-alert').fadeOut(200); }); + } else { + history_to_delete = []; $('.delete-control').each(function() { $(this).removeClass('hidden'); }); } }); }); - diff --git a/data/interfaces/default/info.html b/data/interfaces/default/info.html index b2ba2b98..d52d19aa 100644 --- a/data/interfaces/default/info.html +++ b/data/interfaces/default/info.html @@ -175,7 +175,11 @@ DOCUMENTATION :: END Watch History for ${data['title']}
-   + +   +
@@ -204,6 +208,24 @@ DOCUMENTATION :: END + @@ -271,12 +293,36 @@ DOCUMENTATION :: END clearSearchButton('history_table', history_table); - $('#row-edit-mode').click(function() { + $('#row-edit-mode').on('click', function() { + $('#delete-message').popover(); + if ($(this).hasClass('active')) { - $('.delete-control').each(function() { + if (history_to_delete.length > 0) { + $('#deleteCount').text(history_to_delete.length); + $('#confirm-modal').modal(); + $('#confirm-modal').one('click', '#confirm-delete', function () { + for (var i = 0; i < history_to_delete.length; i++) { + $.ajax({ + url: 'delete_history_rows', + data: { row_id: history_to_delete[i] }, + async: true, + success: function (data) { + var msg = "User history purged"; + showMsg(msg, false, true, 2000); + } + }); + } + history_table.draw(); + }); + } + + $('.delete-control').each(function () { + $(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger'); $(this).addClass('hidden'); }); + } else { + history_to_delete = []; $('.delete-control').each(function() { $(this).removeClass('hidden'); }); diff --git a/data/interfaces/default/info_episode_list.html b/data/interfaces/default/info_episode_list.html index 56700ac5..ef02c42c 100644 --- a/data/interfaces/default/info_episode_list.html +++ b/data/interfaces/default/info_episode_list.html @@ -32,7 +32,7 @@ DOCUMENTATION :: END
  • -
    +
    Episode ${a['index']} diff --git a/data/interfaces/default/js/graphs/plays_by_month.js b/data/interfaces/default/js/graphs/plays_by_month.js index febe8bbe..d8cf6ebd 100644 --- a/data/interfaces/default/js/graphs/plays_by_month.js +++ b/data/interfaces/default/js/graphs/plays_by_month.js @@ -25,11 +25,7 @@ var hc_plays_by_month_options = { }, colors: ['#F9AA03', '#FFFFFF'], xAxis: { - type: 'datetime', labels: { - formatter: function() { - return moment(this.value).format("MMM YYYY"); - }, style: { color: '#aaa' } diff --git a/data/interfaces/default/js/tables/history_table.js b/data/interfaces/default/js/tables/history_table.js index 9898cc1b..3e4b9d43 100644 --- a/data/interfaces/default/js/tables/history_table.js +++ b/data/interfaces/default/js/tables/history_table.js @@ -1,5 +1,6 @@ var date_format = 'YYYY-MM-DD'; var time_format = 'hh:mm a'; +var history_to_delete = []; $.ajax({ url: 'get_date_formats', @@ -18,7 +19,7 @@ history_table_options = { "info":"Showing _START_ to _END_ of _TOTAL_ history items", "infoEmpty":"Showing 0 to 0 of 0 entries", "infoFiltered":"(filtered from _MAX_ total entries)", - "emptyTable": "No data in table", + "emptyTable": "No data in table" }, "pagingType": "bootstrap", "stateSave": true, @@ -32,7 +33,7 @@ history_table_options = { "targets": [0], "data": null, "createdCell": function (td, cellData, rowData, row, col) { - $(td).html(''); + $(td).html(''); }, "width": "5%", "className": "delete-control no-wrap hidden", @@ -98,11 +99,11 @@ history_table_options = { if (cellData !== '') { var transcode_dec = ''; if (rowData['video_decision'] === 'transcode') { - transcode_dec = ' '; + transcode_dec = ''; } else if (rowData['video_decision'] === 'copy') { - transcode_dec = ' '; + transcode_dec = ''; } else if (rowData['video_decision'] === 'direct play' || rowData['video_decision'] === '') { - transcode_dec = ' '; + transcode_dec = ''; } $(td).html(''); } @@ -119,16 +120,16 @@ history_table_options = { var thumb_popover = ''; if (rowData['media_type'] === 'movie') { media_type = ''; - thumb_popover = '' + cellData + ' (' + rowData['year'] + ')' + thumb_popover = '' + cellData + ' (' + rowData['year'] + ')' $(td).html(''); } else if (rowData['media_type'] === 'episode') { media_type = ''; - thumb_popover = '' + cellData + ' \ + thumb_popover = '' + cellData + ' \ (S' + ('00' + rowData['parent_media_index']).slice(-2) + 'E' + ('00' + rowData['media_index']).slice(-2) + ')' $(td).html(''); } else if (rowData['media_type'] === 'track') { media_type = ''; - thumb_popover = '' + cellData + ' (' + rowData['parent_title'] + ')' + thumb_popover = '' + cellData + ' (' + rowData['parent_title'] + ')' $(td).html('
    ' + media_type + ' ' + thumb_popover + '
    '); } else { $(td).html('' + cellData + ''); @@ -225,7 +226,7 @@ history_table_options = { trigger: 'hover', placement: 'right', content: function () { - return '
    '; + return '
    '; } }); @@ -238,6 +239,11 @@ history_table_options = { "preDrawCallback": function(settings) { var msg = "
     Fetching rows...
    "; showMsg(msg, false, false, 0) + }, + "rowCallback": function (row, rowData) { + if ($.inArray(rowData['id'], history_to_delete) !== -1) { + $(row).find('button[data-id="' + rowData['id'] + '"]').toggleClass('btn-warning').toggleClass('btn-danger'); + } } } @@ -287,16 +293,11 @@ $('#history_table').on('click', 'td.delete-control > button', function () { var row = history_table.row( tr ); var rowData = row.data(); - $(this).prop('disabled', true); - $(this).html(' Delete'); - - $.ajax({ - url: 'delete_history_rows', - data: {row_id: rowData['id']}, - async: true, - success: function(data) { - history_table.ajax.reload(null, false); - } - }); - + var index = $.inArray(rowData['id'], history_to_delete); + if (index === -1) { + history_to_delete.push(rowData['id']); + } else { + history_to_delete.splice(index, 1); + } + $(this).toggleClass('btn-warning').toggleClass('btn-danger'); }); \ No newline at end of file diff --git a/data/interfaces/default/js/tables/users.js b/data/interfaces/default/js/tables/users.js index c3d1ae1e..8a5154df 100644 --- a/data/interfaces/default/js/tables/users.js +++ b/data/interfaces/default/js/tables/users.js @@ -11,44 +11,62 @@ users_list_table_options = { "processing": false, "serverSide": true, "pageLength": 10, - "order": [ 1, 'asc'], + "order": [ 2, 'asc'], "autoWidth": true, "stateSave": true, "pagingType": "bootstrap", "columnDefs": [ { "targets": [0], + "data": null, + "createdCell": function (td, cellData, rowData, row, col) { + $(td).html('
       ' + + ' ' + + ' '); + // Show/hide user currently doesn't work + //''); + }, + "width": "7%", + "className": "edit-control no-wrap hidden", + "searchable": false, + "orderable": false + }, + { + "targets": [1], "data": "user_thumb", "createdCell": function (td, cellData, rowData, row, col) { if (cellData === '') { - $(td).html('User Logo'); + $(td).html('
    '); } else { - $(td).html('User Logo'); + $(td).html('
    '); } }, "orderable": false, "searchable": false, "width": "5%", - "className": "users-poster-face" + "className": "users-thumbs" }, { - "targets": [1], + "targets": [2], "data": "friendly_name", "createdCell": function (td, cellData, rowData, row, col) { if (cellData !== '') { if (rowData['user_id'] > 0) { - $(td).html('' + cellData + ''); + $(td).html(''); } else { - $(td).html('' + cellData + ''); + $(td).html(''); } } else { $(td).html(cellData); } }, - "width": "15%" + "width": "12%", + "className": "edit-user-control no-wrap" }, { - "targets": [2], + "targets": [3], "data": "last_seen", "render": function ( data, type, full ) { if (data) { @@ -58,11 +76,11 @@ users_list_table_options = { } }, "searchable": false, - "width": "15%", + "width": "12%", "className": "no-wrap hidden-xs" }, { - "targets": [3], + "targets": [4], "data": "ip_address", "createdCell": function (td, cellData, rowData, row, col) { if (cellData) { @@ -79,32 +97,32 @@ users_list_table_options = { $(td).html('n/a'); } }, - "width": "15%", + "width": "12%", "className": "no-wrap hidden-md hidden-sm hidden-xs modal-control-ip" }, { - "targets": [4], + "targets": [5], "data":"platform", "createdCell": function (td, cellData, rowData, row, col) { if (cellData) { var transcode_dec = ''; if (rowData['video_decision'] === 'transcode') { - transcode_dec = ' '; + transcode_dec = ''; } else if (rowData['video_decision'] === 'copy') { - transcode_dec = ' '; + transcode_dec = ''; } else if (rowData['video_decision'] === 'direct play' || rowData['video_decision'] === '') { - transcode_dec = ' '; + transcode_dec = ''; } $(td).html(''); } else { $(td).html('n/a'); } }, - "width": "15%", + "width": "12%", "className": "no-wrap hidden-md hidden-sm hidden-xs modal-control" }, { - "targets": [5], + "targets": [6], "data":"last_watched", "createdCell": function (td, cellData, rowData, row, col) { if (cellData !== '') { @@ -129,14 +147,15 @@ users_list_table_options = { } } }, + "width": "30%", "className": "hidden-sm hidden-xs" }, { - "targets": [6], + "targets": [7], "data": "plays", "searchable": false, "width": "10%" - } + } ], "drawCallback": function (settings) { @@ -145,6 +164,8 @@ users_list_table_options = { $('#ajaxMsg').fadeOut(); // Create the tooltips. + $('.purge-tooltip').tooltip(); + $('.edit-tooltip').tooltip(); $('.transcode-tooltip').tooltip(); $('.media-type-tooltip').tooltip(); $('.watched-tooltip').tooltip(); @@ -157,6 +178,11 @@ users_list_table_options = { } }); + if ($('#row-edit-mode').hasClass('active')) { + $('.edit-control').each(function () { + $(this).removeClass('hidden'); + }); + } }, "preDrawCallback": function(settings) { var msg = "
     Fetching rows...
    "; @@ -204,4 +230,50 @@ $('#users_list_table').on('click', 'td.modal-control-ip', function () { } getUserLocation(rowData['ip_address']); +}); + +$('#users_list_table').on('change', 'td.edit-control > .edit-user-toggles > input, td.edit-user-control > .edit-user-name > input', function () { + var tr = $(this).parents('tr'); + var row = users_list_table.row(tr); + var rowData = row.data(); + + var do_notify = 0; + var keep_history = 0; + if ($('#do_notify-' + rowData['user_id']).is(':checked')) { + do_notify = 1; + } + if ($('#keep_history-' + rowData['user_id']).is(':checked')) { + keep_history = 1; + } + + friendly_name = tr.find('td.edit-user-control > .edit-user-name > input').val(); + + $.ajax({ + url: 'edit_user', + data: { + user_id: rowData['user_id'], + friendly_name: friendly_name, + do_notify: do_notify, + keep_history: keep_history, + thumb: rowData['user_thumb'] + }, + cache: false, + async: true, + success: function (data) { + var msg = "User updated"; + showMsg(msg, false, true, 2000); + } + }); +}); + +$('#users_list_table').on('click', 'td.edit-control > .edit-user-toggles > button', function () { + var tr = $(this).parents('tr'); + var row = users_list_table.row(tr); + var rowData = row.data(); + + if ($(this).hasClass('active')) { + $(this).toggleClass('btn-warning').toggleClass('btn-danger'); + } else { + $(this).toggleClass('btn-danger').toggleClass('btn-warning'); + } }); \ No newline at end of file diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index dcc8e164..3145c4ae 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -83,6 +83,25 @@ available_notification_agents = notifiers.available_notification_agents()

    Set your preferred time format. Click here to see the parameter list.

    +
    +

    Homepage Statistics

    +
    + +
    + +
    +
    + +
    +
    +

    Specify the number of days for the statistics on the home page. Default is 30 days.

    +
    +
    + +

    Use play duration instead of play count to generate statistics.

    +

    @@ -221,9 +240,24 @@ available_notification_agents = notifiers.available_notification_agents()

    Force PlexPy to connect to your Plex Server via SSL. Your server needs to have remote access enabled.

    +
    +

    Plex Logs

    +
    + +
    + +
    +
    + +
    +
    +

    Set the folder where your Plex Server logs are. This is required if you enable IP logging.
    Click here for help.

    +
    + +
    @@ -278,41 +312,6 @@ available_notification_agents = notifiers.available_notification_agents()

    If you have media indexing enabled on your server, use these on the activity pane.

    -
    -

    Homepage Statistics

    -
    - -
    - -
    -
    - -
    -
    -

    Specify the number of days for the statistics on the home page. Default is 30 days.

    -
    -
    - -

    Use play duration instead of play count to generate statistics.

    -
    - -
    -

    Plex Logs

    -
    - -
    - -
    -
    - -
    -
    -

    Set the folder where your Plex Server logs are. This is required if you enable IP logging.
    Click here for help.

    -
    - -

    PlexWatch Import Tool

    diff --git a/data/interfaces/default/user.html b/data/interfaces/default/user.html index 4727be64..c2176a10 100644 --- a/data/interfaces/default/user.html +++ b/data/interfaces/default/user.html @@ -40,8 +40,7 @@ from plexpy import helpers
    @@ -368,13 +389,36 @@ from plexpy import helpers }); }); - // Delete mode button - $('#row-edit-mode').click(function() { + $('#row-edit-mode').on('click', function() { + $('#delete-message').popover(); + if ($(this).hasClass('active')) { - $('.delete-control').each(function() { + if (history_to_delete.length > 0) { + $('#deleteCount').text(history_to_delete.length); + $('#confirm-modal').modal(); + $('#confirm-modal').one('click', '#confirm-delete', function () { + for (var i = 0; i < history_to_delete.length; i++) { + $.ajax({ + url: 'delete_history_rows', + data: { row_id: history_to_delete[i] }, + async: true, + success: function (data) { + var msg = "User history purged"; + showMsg(msg, false, true, 2000); + } + }); + } + history_table.draw(); + }); + } + + $('.delete-control').each(function () { + $(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger'); $(this).addClass('hidden'); }); + } else { + history_to_delete = []; $('.delete-control').each(function() { $(this).removeClass('hidden'); }); diff --git a/data/interfaces/default/user_platform_stats.html b/data/interfaces/default/user_platform_stats.html index c281b48b..afd1d6b3 100644 --- a/data/interfaces/default/user_platform_stats.html +++ b/data/interfaces/default/user_platform_stats.html @@ -34,7 +34,7 @@ DOCUMENTATION :: END
    % endfor % else: diff --git a/data/interfaces/default/users.html b/data/interfaces/default/users.html index 37cf9bc7..df36a858 100644 --- a/data/interfaces/default/users.html +++ b/data/interfaces/default/users.html @@ -12,6 +12,11 @@ All Users
    + +   +
    @@ -19,6 +24,7 @@ + @@ -35,6 +41,25 @@ + @@ -47,7 +72,7 @@ - + \ No newline at end of file diff --git a/init-scripts/init.fedora.centos.systemd b/init-scripts/init.fedora.centos.systemd index 708ddefa..5cb70ce8 100755 --- a/init-scripts/init.fedora.centos.systemd +++ b/init-scripts/init.fedora.centos.systemd @@ -1,4 +1,4 @@ -# PlexPy +# PlexPy - Stats for Plex Media Server usage # # Service Unit file for systemd system manager # @@ -53,7 +53,7 @@ # graphical.target equates to runlevel 5 (multi-user X11 graphical mode) [Unit] -Description=PlexPy +Description=PlexPy - Stats for Plex Media Server usage [Service] ExecStart=/home/sabnzbd/plexpy/PlexPy.py --daemon --config /etc/plexpy/plexpy.ini --datadir /home/sabnzbd/.plexpy --nolaunch --quiet diff --git a/init-scripts/init.opensuse.systemd b/init-scripts/init.opensuse.systemd new file mode 100644 index 00000000..7dbdfc3e --- /dev/null +++ b/init-scripts/init.opensuse.systemd @@ -0,0 +1,67 @@ +# PlexPy - Stats for Plex Media Server usage +# +# Service Unit file for systemd system manager +# +# INSTALLATION NOTES +# +# 1. Rename this file as you want, ensuring that it ends in .service +# e.g. 'plexpy.service' +# +# 2. Adjust configuration settings as required. More details in the +# "CONFIGURATION NOTES" section shown below. +# +# 3. Copy this file into your systemd service unit directory, which is +# often '/lib/systemd/system'. +# +# 4. Create any files/directories that you specified back in step #2. +# e.g. '/opt/plexpy.ini' +# '/opt/plexpy' +# +# 5. Enable boot-time autostart with the following commands: +# systemctl daemon-reload +# systemctl enable plexpy.service +# +# 6. Start now with the following command: +# systemctl start plexpy.service +# +# 7. If troubleshooting startup-errors, start by checking permissions +# and ownership on the files/directories that you created in step #4. +# +# +# CONFIGURATION NOTES +# +# - The example settings in this file assume that: +# 1. You will run PlexPy as user/group: plex.users +# 2. You will either have PlexPy installed as a subdirectory +# under '/opt', or that you will have a symlink under +# '/opt' pointing to your PlexPy install dir. +# 3. Your PlexPy data directory and configuration file can be +# in separate locations from your PlexPy install dir, to +# simplify updates. However, in the example below they are in the +# PlexPy install dir. +# +# - Option names (e.g. ExecStart=, Type=) appear to be case-sensitive) +# +# - Adjust ExecStart= to point to: +# 1. Your PlexPy executable, +# 2. Your config file (recommended is to put it somewhere in /etc) +# 3. Your datadir (recommended is to NOT put it in your PlexPy exec dir) +# +# - Adjust User= and Group= to the user/group you want PlexPy to run as. +# +# - WantedBy= specifies which target (i.e. runlevel) to start PlexPy for. +# multi-user.target equates to runlevel 3 (multi-user text mode) +# graphical.target equates to runlevel 5 (multi-user X11 graphical mode) + +[Unit] +Description=PlexPy - Stats for Plex Media Server usage + +[Service] +ExecStart=/opt/plexpy/PlexPy.py --daemon --config /opt/plexpy/config.ini --datadir /opt/plexpy --nolaunch --quiet +GuessMainPID=no +Type=forking +User=plex +Group=users + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/init-scripts/init.ubuntu.systemd b/init-scripts/init.ubuntu.systemd new file mode 100644 index 00000000..fd4d54bc --- /dev/null +++ b/init-scripts/init.ubuntu.systemd @@ -0,0 +1,66 @@ +# PlexPy - Stats for Plex Media Server usage +# +# Service Unit file for systemd system manager +# +# INSTALLATION NOTES +# +# 1. Rename this file as you want, ensuring that it ends in .service +# e.g. 'plexpy.service' +# +# 2. Adjust configuration settings as required. More details in the +# "CONFIGURATION NOTES" section shown below. +# +# 3. Copy this file into your systemd service unit directory, which is +# often '/lib/systemd/system'. +# +# 4. Create any files/directories that you specified back in step #2. +# e.g. '/etc/plexpy/plexpy.ini' +# '/home/sabnzbd/.plexpy' +# +# 5. Enable boot-time autostart with the following commands: +# systemctl daemon-reload +# systemctl enable plexpy.service +# +# 6. Start now with the following command: +# systemctl start plexpy.service +# +# 7. If troubleshooting startup-errors, start by checking permissions +# and ownership on the files/directories that you created in step #4. +# +# +# CONFIGURATION NOTES +# +# - The example settings in this file assume that: +# 1. You will run PlexPy as user/group: sabnzbd.sabnzbd +# 2. You will either have PlexPy installed as a subdirectory +# under '~sabnzbd', or that you will have a symlink under +# '~/sabnzbd' pointing to your PlexPy install dir. +# 3. Your PlexPy data directory and configuration file will be +# in separate locations from your PlexPy install dir, to +# simplify updates. +# +# - Option names (e.g. ExecStart=, Type=) appear to be case-sensitive) +# +# - Adjust ExecStart= to point to: +# 1. Your PlexPy executable, +# 2. Your config file (recommended is to put it somewhere in /etc) +# 3. Your datadir (recommended is to NOT put it in your PlexPy exec dir) +# +# - Adjust User= and Group= to the user/group you want PlexPy to run as. +# +# - WantedBy= specifies which target (i.e. runlevel) to start PlexPy for. +# multi-user.target equates to runlevel 3 (multi-user text mode) +# graphical.target equates to runlevel 5 (multi-user X11 graphical mode) + +[Unit] +Description=PlexPy - Stats for Plex Media Server usage + +[Service] +ExecStart=/opt/plexpy/PlexPy.py --quiet --daemon --nolaunch --config /opt/plexpy/config.ini --datadir /opt/plexpy +GuessMainPID=no +Type=forking +User=plexpy +Group=nogroup + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/plexpy/graphs.py b/plexpy/graphs.py index d3590d36..8b139d82 100644 --- a/plexpy/graphs.py +++ b/plexpy/graphs.py @@ -259,7 +259,7 @@ class Graphs(object): dt = datetime.datetime(*month_item[:6]) date_string = dt.strftime('%Y-%m') - categories.append(date_string) + categories.append(dt.strftime('%b %Y')) series_1_value = 0 series_2_value = 0 for item in result: diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py index 0429bb30..5330b7e5 100644 --- a/plexpy/notification_handler.py +++ b/plexpy/notification_handler.py @@ -162,6 +162,7 @@ def notify(stream_data=None, notify_action=None): else: logger.debug(u"PlexPy Notifier :: Notify called but incomplete data received.") + def get_notify_state(session): monitor_db = database.MonitorDatabase() result = monitor_db.select('SELECT on_play, on_stop, on_pause, on_resume, on_buffer, on_watched, agent_id ' @@ -184,6 +185,7 @@ def get_notify_state(session): return notify_states + def set_notify_state(session, state, agent_info): if session and state and agent_info: @@ -215,6 +217,7 @@ def set_notify_state(session, state, agent_info): else: logger.error('PlexPy Notifier :: Unable to set notify state.') + def build_notify_text(session, state): from plexpy import pmsconnect, helpers import re @@ -300,7 +303,13 @@ def build_notify_text(session, state): duration = helpers.convert_milliseconds_to_minutes(item_metadata['duration']) view_offset = helpers.convert_milliseconds_to_minutes(session['view_offset']) - stream_duration = 0 if state == 'play' else int((time.time() - helpers.cast_to_float(session['started']) - helpers.cast_to_float(session['paused_counter'])) / 60) + stream_duration = 0 + if state != 'play': + if session['paused_counter']: + stream_duration = int((time.time() - helpers.cast_to_float(session['started']) - + helpers.cast_to_float(session['paused_counter'])) / 60) + else: + stream_duration = int((time.time() - helpers.cast_to_float(session['started'])) / 60) progress_percent = helpers.get_percent(view_offset, duration) @@ -479,6 +488,7 @@ def build_notify_text(session, state): else: return None + def strip_tag(data): import re diff --git a/plexpy/users.py b/plexpy/users.py index 8a24b38b..26f46abf 100644 --- a/plexpy/users.py +++ b/plexpy/users.py @@ -40,7 +40,9 @@ class Users(object): 'session_history_metadata.media_type', 'session_history.rating_key as rating_key', 'session_history_media_info.video_decision', - 'users.username as user' + 'users.username as user', + 'users.do_notify as do_notify', + 'users.keep_history as keep_history' ] try: query = data_tables.ssp_query(table_name='users', @@ -94,7 +96,9 @@ class Users(object): "video_decision": item['video_decision'], "user_thumb": user_thumb, "user": item["user"], - "user_id": item['user_id'] + "user_id": item['user_id'], + "do_notify": helpers.checked(item['do_notify']), + "keep_history": helpers.checked(item['keep_history']) } rows.append(row) diff --git a/plexpy/version.py b/plexpy/version.py index c0740a4d..32a5b3d6 100644 --- a/plexpy/version.py +++ b/plexpy/version.py @@ -1,2 +1,2 @@ PLEXPY_VERSION = "master" -PLEXPY_RELEASE_VERSION = "1.1.3" +PLEXPY_RELEASE_VERSION = "1.1.4" diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 8b7e3c6d..93092587 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -492,18 +492,34 @@ class WebInterface(object): kwargs[plain_config] = kwargs[use_config] del kwargs[use_config] + # Check if we should refresh our data + refresh_users = False + reschedule = False + + if 'monitoring_interval' in kwargs and 'refresh_users_interval' in kwargs: + if (kwargs['monitoring_interval'] != str(plexpy.CONFIG.MONITORING_INTERVAL)) or \ + (kwargs['refresh_users_interval'] != str(plexpy.CONFIG.REFRESH_USERS_INTERVAL)): + reschedule = True + + if 'pms_ip' in kwargs: + if kwargs['pms_ip'] != plexpy.CONFIG.PMS_IP: + refresh_users = True + plexpy.CONFIG.process_kwargs(kwargs) # Write the config plexpy.CONFIG.write() - # Reconfigure scheduler - plexpy.initialize_scheduler() - + # Get new server URLs for SSL communications. plextv.get_real_pms_url() - # Refresh users table. Probably shouldn't do this on every config save, will improve this later. - threading.Thread(target=plextv.refresh_users).start() + # Reconfigure scheduler if intervals changed + if reschedule: + plexpy.initialize_scheduler() + + # Refresh users table if our server IP changes. + if refresh_users: + threading.Thread(target=plextv.refresh_users).start() raise cherrypy.HTTPRedirect("settings")
    Edit User Last Seen