This commit is contained in:
Tim 2015-08-26 12:08:50 +02:00
commit 6d5e7135ae
21 changed files with 778 additions and 150 deletions

View file

@ -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.

View file

@ -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;

View file

@ -74,6 +74,24 @@ DOCUMENTATION :: END
</div>
</div>
</div>
<div id="confirm-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="confirm-modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
<h4 class="modal-title" id="myModalLabel">Confirm Purge</h4>
</div>
<div class="modal-body" style="text-align: center;">
<p>Are you REALLY sure you want to purge all history for this user?</p>
<p>This is permanent and cannot be undone!</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-dark" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger btn-ok" data-dismiss="modal" id="confirm-purge">Purge</button>
</div>
</div>
</div>
</div>
<script>
// Set new friendly name
$("#save_user_name").click(function() {
@ -119,9 +137,9 @@ DOCUMENTATION :: END
% endif
});
$("#delete-all-history").click(function() {
var r = confirm("Are you REALLY REALLY REALLY sure you want to delete all history for this user?");
if (r == true) {
$("#delete-all-history").on('click', function() {
$('#confirm-modal').modal();
$('#confirm-modal').one('click', '#confirm-purge', function () {
$.ajax({
url: 'delete_all_user_history',
data: {user_id: '${data['user_id']}'},
@ -131,7 +149,34 @@ DOCUMENTATION :: END
location.reload();
}
});
}
});
});
$(document).ready(function() {
// Move #confirm-modal to parent container
if(!($('#edit-user-modal').next().is('#confirm-modal'))) {
$('#confirm-modal').appendTo($('#edit-user-modal').parent()); }
$('#edit-user-modal > #confirm-modal').remove();
$('#edit-user-modal').css('z-index', '1050');
$('.modal-backdrop').not('.modal-backdrop-stack').css('z-index', '1049');
$('.modal-backdrop').not('.modal-backdrop-stack').addClass('modal-backdrop-stack');
$('#confirm-modal').on('show.bs.modal', function () {
// Fix position to match parent modal
var currentPadding = parseInt($('body').css('padding-right'));
$(this).children('.modal-dialog').css('left', -currentPadding/2);
$('#edit-user-modal').css('overflow-y', 'hidden');
});
$('#confirm-modal').on('shown.bs.modal', function () {
$(this).css('z-index', '1060');
$('.modal-backdrop').not('.modal-backdrop-stack').css('z-index', '1059');
$('.modal-backdrop').not('.modal-backdrop-stack').addClass('modal-backdrop-stack');
});
$('#confirm-modal').on('hidden.bs.modal', function() {
$('body').addClass('modal-open');
$('#edit-user-modal').css('overflow-y', 'auto');
});
});
</script>

View file

@ -14,8 +14,11 @@
<span><i class="fa fa-history"></i> History</span>
</div>
<div class="button-bar">
<button class="btn btn-danger" data-toggle="button" aria-pressed="false" autocomplete="off" id="row-edit-mode"><i class="fa fa-trash-o"></i> Delete mode</button>&nbsp
<div class="colvis-button-bar hidden-xs"></div>
<button class="btn btn-danger btn-edit" data-toggle="button" aria-pressed="false" autocomplete="off" id="row-edit-mode">
<i class="fa fa-trash-o"></i> Delete mode
</button>
<div class="alert alert-danger alert-edit" role="alert" id="row-edit-mode-alert"><i class="fa fa-exclamation-triangle"></i>&nbspSelect rows to delete. Data is deleted upon exiting delete mode.</div>
</div>
</div>
<div class='table-card-back'>
@ -42,6 +45,24 @@
</div>
<div class="modal fade" id="ip-info-modal" tabindex="-1" role="dialog" aria-labelledby="ip-info-modal">
</div>
<div class="modal fade" id="confirm-modal" tabindex="-1" role="dialog" aria-labelledby="confirm-modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
<h4 class="modal-title" id="myModalLabel">Confirm Delete</h4>
</div>
<div class="modal-body" style="text-align: center;">
<p>Are you REALLY sure you want to delete <strong><span id="deleteCount"></span></strong> history item(s)?</p>
<p>This is permanent and cannot be undone!</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-dark" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger btn-ok" data-dismiss="modal" id="confirm-delete">Delete</button>
</div>
</div>
</div>
</div>
</div>
</div>
</%def>
@ -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');
});
}
});
});
</script>
</%def>

View file

@ -175,7 +175,11 @@ DOCUMENTATION :: END
<span>Watch History for <strong>${data['title']}</strong></span>
</div>
<div class="button-bar">
<button class="btn btn-danger" data-toggle="button" aria-pressed="false" autocomplete="off" id="row-edit-mode"><i class="fa fa-trash-o"></i> Delete mode</button>&nbsp
<span data-toggle="popover" data-placement="left" data-content="Select rows to delete. Data is deleted upon exiting delete mode." id="delete-message">
<button class="btn btn-danger" data-toggle="button" aria-pressed="false" autocomplete="off" id="row-edit-mode">
<i class="fa fa-trash-o"></i> Delete mode
</button>&nbsp
</span>
<div class="colvis-button-bar hidden-xs"></div>
</div>
</div>
@ -204,6 +208,24 @@ DOCUMENTATION :: END
</div>
<div class="modal fade" id="ip-info-modal" tabindex="-1" role="dialog" aria-labelledby="ip-info-modal">
</div>
<div class="modal fade" id="confirm-modal" tabindex="-1" role="dialog" aria-labelledby="confirm-modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
<h4 class="modal-title" id="myModalLabel">Confirm Delete</h4>
</div>
<div class="modal-body" style="text-align: center;">
<p>Are you REALLY sure you want to delete <strong><span id="deleteCount"></span></strong> history item(s)?</p>
<p>This is permanent and cannot be undone!</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-dark" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger btn-ok" data-dismiss="modal" id="confirm-delete">Delete</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@ -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');
});

View file

@ -32,7 +32,7 @@ DOCUMENTATION :: END
<li>
<a href="info?item_id=${a['rating_key']}">
<div class="season-episodes-poster">
<div class="season-episodes-poster-face" style="background-image: url(pms_image_proxy?img=${a['thumb']}&width=410&height=230);">
<div class="season-episodes-poster-face" style="background-image: url(pms_image_proxy?img=${a['thumb']}&width=500&height=280);">
<div class="season-episodes-card-overlay">
<div class="season-episodes-season">
Episode ${a['index']}

View file

@ -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'
}

View file

@ -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('<button class="btn btn-xs btn-danger" data-id="' + rowData['id'] + '"><i class="fa fa-trash-o"></i> Delete</button>');
$(td).html('<button class="btn btn-xs btn-warning" data-id="' + rowData['id'] + '"><i class="fa fa-trash-o fa-fw"></i> Delete</button>');
},
"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 = '<span class="transcode-tooltip" data-toggle="tooltip" title="Transcode"><i class="fa fa-server fa-fw"></i></span>&nbsp';
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Transcode"><i class="fa fa-server fa-fw"></i></span>';
} else if (rowData['video_decision'] === 'copy') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Stream"><i class="fa fa-video-camera fa-fw"></i></span>&nbsp';
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Stream"><i class="fa fa-video-camera fa-fw"></i></span>';
} else if (rowData['video_decision'] === 'direct play' || rowData['video_decision'] === '') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Play"><i class="fa fa-play-circle fa-fw"></i></span>&nbsp';
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Play"><i class="fa fa-play-circle fa-fw"></i></span>';
}
$(td).html('<div><a href="#" data-target="#info-modal" data-toggle="modal"><div style="float: left;">' + transcode_dec + '&nbsp' + cellData + '</div></a></div>');
}
@ -119,16 +120,16 @@ history_table_options = {
var thumb_popover = '';
if (rowData['media_type'] === 'movie') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Movie"><i class="fa fa-film fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=80&height=120&fallback=poster" data-height="120">' + cellData + ' (' + rowData['year'] + ')</span>'
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120">' + cellData + ' (' + rowData['year'] + ')</span>'
$(td).html('<div class="history-title"><a href="info?source=history&item_id=' + rowData['id'] + '"><div style="float: left;">' + media_type + '&nbsp' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'episode') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Episode"><i class="fa fa-television fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=80&height=120&fallback=poster" data-height="120">' + cellData + ' \
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120">' + cellData + ' \
(S' + ('00' + rowData['parent_media_index']).slice(-2) + 'E' + ('00' + rowData['media_index']).slice(-2) + ')</span>'
$(td).html('<div class="history-title"><a href="info?source=history&item_id=' + rowData['id'] + '"><div style="float: left;" >' + media_type + '&nbsp' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'track') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Track"><i class="fa fa-music fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=80&height=80&fallback=poster" data-height="80">' + cellData + ' (' + rowData['parent_title'] + ')</span>'
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=300&fallback=poster" data-height="80">' + cellData + ' (' + rowData['parent_title'] + ')</span>'
$(td).html('<div class="history-title"><div style="float: left;">' + media_type + '&nbsp' + thumb_popover + '</div></div>');
} else {
$(td).html('<a href="info?item_id=' + rowData['id'] + '">' + cellData + '</a>');
@ -225,7 +226,7 @@ history_table_options = {
trigger: 'hover',
placement: 'right',
content: function () {
return '<div style="background-image: url(' + $(this).data('img') + '); width: 80px; height: ' + $(this).data('height') + 'px;" />';
return '<div class="history-thumbnail" style="background-image: url(' + $(this).data('img') + '); height: ' + $(this).data('height') + 'px;" />';
}
});
@ -238,6 +239,11 @@ history_table_options = {
"preDrawCallback": function(settings) {
var msg = "<div class='msg'><i class='fa fa-refresh fa-spin'></i>&nbspFetching rows...</div>";
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('<i class="fa fa-spin fa-refresh"></i> 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');
});

View file

@ -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('<div class="edit-user-toggles"><button class="btn btn-xs btn-warning" data-id="' + rowData['user_id'] + '" data-toggle="button"><i class="fa fa-eraser fa-fw"></i> Purge</button>&nbsp&nbsp&nbsp' +
'<input type="checkbox" id="do_notify-' + rowData['user_id'] + '" name="do_notify" value="1" ' + rowData['do_notify'] + '><label class="edit-tooltip" for="do_notify-' + rowData['user_id'] + '" data-toggle="tooltip" title="Toggle Notifications"><i class="fa fa-bell fa-lg fa-fw"></i></label>&nbsp' +
'<input type="checkbox" id="keep_history-' + rowData['user_id'] + '" name="keep_history" value="1" ' + rowData['keep_history'] + '><label class="edit-tooltip" for="keep_history-' + rowData['user_id'] + '" data-toggle="tooltip" title="Toggle History"><i class="fa fa-history fa-lg fa-fw"></i></label>&nbsp');
// Show/hide user currently doesn't work
//'<input type="checkbox" id="show_hide-' + rowData['user_id'] + '" name="show_hide" value="1" checked><label class="edit-tooltip" for="show_hide-' + rowData['user_id'] + '" data-toggle="tooltip" title="Show/Hide User"><i class="fa fa-eye fa-lg fa-fw"></i></label>');
},
"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('<img src="interfaces/default/images/gravatar-default-80x80.png" alt="User Logo"/>');
$(td).html('<a href="user?user_id=' + rowData['user_id'] + '"><div class="users-poster-face" style="background-image: url(interfaces/default/images/gravatar-default-80x80.png);"></div></a>');
} else {
$(td).html('<img src="' + cellData + '" alt="User Logo"/>');
$(td).html('<a href="user?user_id=' + rowData['user_id'] + '"><div class="users-poster-face" style="background-image: url(' + cellData + ');"></div></a>');
}
},
"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('<a href="user?user_id=' + rowData['user_id'] + '">' + cellData + '</a>');
$(td).html('<div class="edit-user-name" data-id="' + rowData['user_id'] + '"><a href="user?user_id=' + rowData['user_id'] + '">' + cellData + '</a>' +
'<input type="text" class="hidden" value="' + cellData + '"></div>');
} else {
$(td).html('<a href="user?user=' + rowData['user'] + '">' + cellData + '</a>');
$(td).html('<div class="edit-user-name" data-id="' + rowData['user_id'] + '"><a href="user?user=' + rowData['user'] + '">' + cellData + '</a>' +
'<input type="text" class="hidden" value="' + cellData + '"></div>');
}
} 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 = '<span class="transcode-tooltip" data-toggle="tooltip" title="Transcode"><i class="fa fa-server fa-fw"></i></span>&nbsp';
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Transcode"><i class="fa fa-server fa-fw"></i></span>';
} else if (rowData['video_decision'] === 'copy') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Stream"><i class="fa fa-video-camera fa-fw"></i></span>&nbsp';
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Stream"><i class="fa fa-video-camera fa-fw"></i></span>';
} else if (rowData['video_decision'] === 'direct play' || rowData['video_decision'] === '') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Play"><i class="fa fa-play-circle fa-fw"></i></span>&nbsp';
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Play"><i class="fa fa-play-circle fa-fw"></i></span>';
}
$(td).html('<div><a href="#" data-target="#info-modal" data-toggle="modal"><div style="float: left;">' + transcode_dec + '&nbsp' + cellData + '</div></a></div>');
} 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 = "<div class='msg'><i class='fa fa-refresh fa-spin'></i>&nbspFetching rows...</div>";
@ -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');
}
});

View file

@ -83,6 +83,25 @@ available_notification_agents = notifiers.available_notification_agents()
</div>
<p class="help-block">Set your preferred time format. <a href="javascript:void(0)" data-target="#dateTimeOptionsModal" data-toggle="modal">Click here</a> to see the parameter list.</p>
</div>
<div class="padded-header">
<h3>Homepage Statistics</h3>
</div>
<div class="form-group">
<label for="home_stats_length">Time Frame</label>
<div class="row">
<div class="col-md-2">
<input type="text" class="form-control" data-parsley-type="integer" id="home_stats_length" name="home_stats_length" value="${config['home_stats_length']}" size="3" data-parsley-min="0" data-parsley-trigger="change" required>
</div>
</div>
<p class="help-block">Specify the number of days for the statistics on the home page. Default is 30 days.</p>
</div>
<div class="checkbox">
<label>
<input type="checkbox" id="home_stats_type" name="home_stats_type" value="1" ${config['home_stats_type']}> Use play duration
</label>
<p class="help-block">Use play duration instead of play count to generate statistics.</p>
</div>
<p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p>
</div>
<div role="tabpanel" class="tab-pane" id="tabs-2">
@ -221,9 +240,24 @@ available_notification_agents = notifiers.available_notification_agents()
</label>
<p class="help-block">Force PlexPy to connect to your Plex Server via SSL. Your server needs to have remote access enabled.</p>
</div>
<div class="padded-header">
<h3>Plex Logs</h3>
</div>
<div class="form-group">
<label for="pms_logs_folder">Logs Folder</label>
<div class="row">
<div class="col-md-6">
<input type="text" class="form-control" id="pms_logs_folder" name="pms_logs_folder" value="${config['pms_logs_folder']}" size="30" data-parsley-trigger="change">
</div>
</div>
<p class="help-block">Set the folder where your Plex Server logs are. This is required if you enable IP logging.<br /><a href="https://support.plex.tv/hc/en-us/articles/200250417-Plex-Media-Server-Log-Files" target="_blank">Click here</a> for help.</p>
</div>
<input type="hidden" id="pms_identifier" name="pms_identifier" value="${config['pms_identifier']}">
<input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully">
</div>
<div role="tabpanel" class="tab-pane" id="tabs-5">
@ -278,41 +312,6 @@ available_notification_agents = notifiers.available_notification_agents()
<p class="help-block">If you have media indexing enabled on your server, use these on the activity pane.</p>
</div>
<div class="padded-header">
<h3>Homepage Statistics</h3>
</div>
<div class="form-group">
<label for="home_stats_length">Time Frame</label>
<div class="row">
<div class="col-md-2">
<input type="text" class="form-control" data-parsley-type="integer" id="home_stats_length" name="home_stats_length" value="${config['home_stats_length']}" size="3" data-parsley-min="0" data-parsley-trigger="change" required>
</div>
</div>
<p class="help-block">Specify the number of days for the statistics on the home page. Default is 30 days.</p>
</div>
<div class="checkbox">
<label>
<input type="checkbox" id="home_stats_type" name="home_stats_type" value="1" ${config['home_stats_type']}> Use play duration
</label>
<p class="help-block">Use play duration instead of play count to generate statistics.</p>
</div>
<div class="padded-header">
<h3>Plex Logs</h3>
</div>
<div class="form-group">
<label for="pms_logs_folder">Logs Folder</label>
<div class="row">
<div class="col-md-6">
<input type="text" class="form-control" id="pms_logs_folder" name="pms_logs_folder" value="${config['pms_logs_folder']}" size="30" data-parsley-trigger="change">
</div>
</div>
<p class="help-block">Set the folder where your Plex Server logs are. This is required if you enable IP logging.<br/><a href="https://support.plex.tv/hc/en-us/articles/200250417-Plex-Media-Server-Log-Files" target="_blank">Click here</a> for help.</p>
</div>
<div class="padded-header">
<h3>PlexWatch Import Tool</h3>
</div>

View file

@ -40,8 +40,7 @@ from plexpy import helpers
<div class="col-md-12">
<div class="table-card-back">
<div class="user-info-wrapper">
<div class="user-info-poster-face" id="user-gravatar">
<img id="user-profile-thumb" src="${data['thumb']}" height="80px" width="80px">
<div class="user-info-poster-face" id="user-gravatar" style="background-image: url(${data['thumb']});">
</div>
<div class="user-info-username">
<span class="set-username">${data['friendly_name']}</span> <span id="edit-user-tooltip" data-target="tooltip" title="Edit user details"><a href="#" data-toggle="modal" data-target="#edit-user-modal" id="toggle-edit-user-modal"><i class="fa fa-pencil"></i></a></span>
@ -155,7 +154,11 @@ from plexpy import helpers
</strong></span>
</div>
<div class="button-bar">
<button class="btn btn-danger" data-toggle="button" aria-pressed="false" autocomplete="off" id="row-edit-mode"><i class="fa fa-trash-o"></i> Delete Mode</button>&nbsp
<span data-toggle="popover" data-placement="left" data-content="Select rows to delete. Data is deleted upon exiting delete mode." id="delete-message">
<button class="btn btn-danger" data-toggle="button" aria-pressed="false" autocomplete="off" id="row-edit-mode">
<i class="fa fa-trash-o"></i> Delete mode
</button>&nbsp
</span>
<div class="colvis-button-bar hidden-xs" id="button-bar-history">
</div>
</div>
@ -227,6 +230,24 @@ from plexpy import helpers
</div>
<div class="modal fade" id="ip-info-modal" tabindex="-1" role="dialog" aria-labelledby="ip-info-modal">
</div>
<div class="modal fade" id="confirm-modal" tabindex="-1" role="dialog" aria-labelledby="confirm-modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
<h4 class="modal-title" id="myModalLabel">Confirm Delete</h4>
</div>
<div class="modal-body" style="text-align: center;">
<p>Are you REALLY sure you want to delete <strong><span id="deleteCount"></span></strong> history item(s)?</p>
<p>This is permanent and cannot be undone!</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-dark" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger btn-ok" data-dismiss="modal" id="confirm-delete">Delete</button>
</div>
</div>
</div>
</div>
</div>
<footer></footer>
</%def>
@ -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');
});

View file

@ -34,7 +34,7 @@ DOCUMENTATION :: END
</div>
</ul>
<script>
$("#user-platform-image-${a['result_id']}").html("<div class='user-platforms-instance-poster' style='background-image: url(" + getPlatformImagePath('${a['platform_type']}') + ");'>");
$("#user-platform-image-${a['result_id']}").html("<div class='user-platforms-instance-box' style='background-image: url(" + getPlatformImagePath('${a['platform_type']}') + ");'>");
</script>
% endfor
% else:

View file

@ -12,6 +12,11 @@
<span><i class="fa fa-group"></i> All Users</span>
</div>
<div class="button-bar">
<span data-toggle="popover" data-placement="left" data-content="Data purging does not occur until after exiting edit mode." id="purge-message">
<button class="btn btn-dark" data-toggle="button" aria-pressed="false" autocomplete="off" id="row-edit-mode">
<i class="fa fa-pencil"></i> Edit mode
</button>&nbsp
</span>
<button class="btn btn-dark" id="refresh-users-list"><i class="fa fa-refresh"></i> Refresh users</button>
</div>
</div>
@ -19,6 +24,7 @@
<table id="users_list_table" class="display" width="100%">
<thead>
<tr>
<th align="left" id="edit_row">Edit</th>
<th align="right" id="avatar"></th>
<th align="left" id="friendly_name">User</th>
<th align="left" id="last_seen">Last Seen</th>
@ -35,6 +41,25 @@
</div>
<div class="modal fade" id="ip-info-modal" tabindex="-1" role="dialog" aria-labelledby="ip-info-modal">
</div>
<div class="modal fade" id="confirm-modal" tabindex="-1" role="dialog" aria-labelledby="confirm-modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
<h4 class="modal-title" id="myModalLabel">Confirm Purge</h4>
</div>
<div class="modal-body" style="text-align: center;">
<p>Are you REALLY sure you want to purge all history for the following users:</p>
<ul id="users-to-delete" class="list-unstyled"></ul>
<p>This is permanent and cannot be undone!</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-dark" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger btn-ok" data-dismiss="modal" id="confirm-purge">Purge</button>
</div>
</div>
</div>
</div>
</div>
</div>
@ -47,7 +72,7 @@
<script src="interfaces/default/js/moment-with-locale.js"></script>
<script src="interfaces/default/js/tables/users.js"></script>
<script>
$(document).ready(function() {
$(document).ready(function () {
users_list_table_options.ajax = {
"url": "get_user_list",
type: "post",
@ -59,8 +84,72 @@
users_list_table = $('#users_list_table').DataTable(users_list_table_options);
clearSearchButton('users_list_table', users_list_table);
});
var users_to_purge = [];
$('#row-edit-mode').on('click', function () {
$('#purge-message').popover();
if ($(this).hasClass('active')) {
users_to_purge = [];
ul = $('#users-to-delete');
ul.html('');
$('.edit-control button.btn-danger').map(function () {
users_to_purge.push($(this).attr('data-id'));
ul.append('<li>' + $('div[data-id=' + $(this).attr('data-id') + '] > input').val() + '</li>')
});
if (users_to_purge.length > 0) {
$('#users-to-delete').append
$('#confirm-modal').modal();
$('#confirm-modal').one('click', '#confirm-purge', function () {
for (var i = 0; i < users_to_purge.length; i++) {
$.ajax({
url: 'delete_all_user_history',
data: { user_id: users_to_purge[i] },
cache: false,
async: true,
success: function (data) {
var msg = "User history purged";
showMsg(msg, false, true, 2000);
}
});
}
users_list_table.draw();
});
}
$('.edit-control').each(function () {
$(this).find('button.btn-danger').toggleClass('btn-warning').toggleClass('btn-danger');
$(this).addClass('hidden');
});
$('.edit-user-control > .edit-user-name').each(function () {
a = $(this).children('a');
input = $(this).children('input');
a.text(input.val());
a.removeClass('hidden');
input.addClass('hidden');
});
} else {
$('.edit-control').each(function () {
$(this).removeClass('hidden');
});
$('.edit-user-control > .edit-user-name').each(function () {
$(this).children('a').addClass('hidden');
$(this).children('input').removeClass('hidden');
});
}
});
$(window).resize(function () {
if ($('.popover').popover().is(':visible')) {
var popover = $('.popover');
popover.addClass("noTransition");
$('#purge-message').popover('show');
popover.removeClass("noTransition");
}
});
});
$("#refresh-users-list").click(function() {
$.ajax({
@ -76,4 +165,4 @@
});
});
</script>
</%def>
</%def>

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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)

View file

@ -1,2 +1,2 @@
PLEXPY_VERSION = "master"
PLEXPY_RELEASE_VERSION = "1.1.3"
PLEXPY_RELEASE_VERSION = "1.1.4"

View file

@ -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")