Template the stream modal

Template the history table
Add history history to metadata info screens
Fix some more datatables stuff
More webserve housekeeping
This commit is contained in:
Tim 2015-06-19 22:27:50 +02:00
parent 58a1442fe9
commit c4504d8be0
8 changed files with 564 additions and 301 deletions

View file

@ -46,53 +46,6 @@
</tbody> </tbody>
</table> </table>
<div id="info-modal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="info-modal" aria-hidden="true"> <div id="info-modal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="info-modal" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
<h3 id="myModalLabel">Stream Info: <strong><span id="modal-stream-info"></span></strong></h3>
</div>
<div class="modal-body" id="modal-text">
<div class="span4">
<h4>Stream Details</h4>
<ul>
<h5>Video</h5>
<li>Stream Type: <strong><span id="transcode_video_dec"></span></strong></li>
<li>Video Resolution: <strong><span id="transcode_video_resolution"></span>p</strong></li>
<li>Video Codec: <strong><span id="transcode_video_codec"></span></strong></li>
<li>Video Width: <strong><span id="transcode_video_width"></span></strong></li>
<li>Video Height: <strong><span id="transcode_video_height"></span></strong></li>
</ul>
<ul>
<h5>Audio</h5>
<li>Stream Type: <strong><span id="transcode_audio_dec"></span></strong></li>
<li>Audio Codec: <strong><span id="transcode_audio_codec"></span></strong></li>
<li>Audio Channels: <strong><span id="transcode_audio_channels"></span></strong></li>
</ul>
</div>
<div class="span4">
<h4>Media Source Details</h4>
<li>Container: <strong><span id="media_container"></span></strong></li>
<li>Resolution: <strong><span id="media_resolution"></span>p</strong></li>
<li>Bitrate: <strong><span id="media_bitrate"></span> kbps</strong></li>
</div>
<div class="span4">
<h4>Video Source Details</h4>
<ul>
<li>Width: <strong><span id="video_width"></span></strong></li>
<li>Height: <strong><span id="video_height"></span></strong></li>
<li>Aspect Ratio: <strong><span id="video_aspect"></span></strong></li>
<li>Video Frame Rate: <strong><span id="video_framerate"></span></strong></li>
<li>Video Codec: <strong><span id="video_codec"></span></strong></li>
</ul>
<ul></ul>
<h4>Audio Source Details</h4>
<ul>
<li>Audio Codec: <strong><span id="audio_codec"></span></strong></li>
<li>Audio Channels: <strong><span id="audio_channels"></span></strong></li>
</ul>
</div>
</div>
<div class="modal-footer">
</div>
</div> </div>
</div> </div>
</div> </div>
@ -107,213 +60,15 @@
<script src="interfaces/default/js/jquery.dataTables.min.js"></script> <script src="interfaces/default/js/jquery.dataTables.min.js"></script>
<script src="interfaces/default/js/jquery.dataTables.bootstrap.pagination.integration.js"></script> <script src="interfaces/default/js/jquery.dataTables.bootstrap.pagination.integration.js"></script>
<script src="interfaces/default/js/moment-with-locale.js"></script> <script src="interfaces/default/js/moment-with-locale.js"></script>
<script src="interfaces/default/js/tables/history_table.js"></script>
<script> <script>
$(document).ready(function() { $(document).ready(function() {
history_table_options.ajax = {
"url": "get_history"
}
var history_table; history_table = $('#history_table').DataTable(history_table_options);
});
history_table = $('#history_table').DataTable({ </script>
"destroy": true,
"language": {
"search": "Search: ",
"lengthMenu":"Show _MENU_ entries per page",
"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",
},
"stateSave": true,
"sPaginationType": "bootstrap",
"processing": false,
"serverSide": true,
"pageLength": 10,
"order": [ 1, 'desc'],
"ajax": {
"url": "getHistory.json"
},
"columnDefs": [
{
"targets": [0],
"data":"id",
"visible": false,
"searchable": false
},
{
"targets": [1],
"data":"date",
"createdCell": function (td, cellData, rowData, row, col) {
if (rowData['stopped'] === null) {
$(td).addClass('currentlyWatching');
$(td).html('Currently watching...');
} else {
$(td).html(moment(cellData,"X").format("${date_format}"));
}
},
"searchable": false
},
{
"targets": [2],
"data":"user"
},
{
"targets": [3],
"data":"platform",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
$(td).html('<a href="#info-modal" data-toggle="modal"><span data-toggle="tooltip" data-placement="left" title="Stream Info" id="stream-info" class="badge badge-inverse"><i class="fa fa-info"></i></span></a>&nbsp'+cellData);
}
},
"className": "modal-control"
},
{
"targets": [4],
"data":"ip_address",
"createdCell": function (td, cellData, rowData, row, col) {
if ((cellData == '') || (cellData == '0')) {
$(td).html('n/a');
}
}
},
{
"targets": [5],
"data":"title",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
$(td).html('<a href="info?rating_key=' + rowData['rating_key'] + '">' + cellData + '</a>');
}
}
},
{
"targets": [6],
"data":"started",
"render": function ( data, type, full ) {
return moment(data, "X").format("${time_format}");
},
"searchable": false
},
{
"targets": [7],
"data":"paused_counter",
"render": function ( data, type, full ) {
return Math.round(moment.duration(data, 'seconds').as('minutes')) + ' mins';
},
"searchable": false
},
{
"targets": [8],
"data":"stopped",
"render": function ( data, type, full ) {
if (data !== null) {
return moment(data, "X").format("${time_format}");
} else {
return data;
}
},
"searchable": false
},
{
"targets": [9],
"data":"duration",
"render": function ( data, type, full ) {
if (data !== null) {
return Math.round(moment.duration(data, 'seconds').as('minutes')) + ' mins';
} else {
return data;
}
},
"searchable": false
},
{
"targets": [10],
"data":"percent_complete",
"orderable": false,
"render": function ( data, type, full ) {
if (data < 95) {
return '<span class="badge">'+Math.round(data)+'%</span>';
} else {
return '<span class="badge">100%</span>';
}
},
"searchable": false
},
{
"targets": [11],
"data":"rating_key",
"visible": false,
"searchable": false
},
{
"targets": [12],
"data":"xml",
"searchable":false,
"visible":false
}
],
"drawCallback": function (settings) {
// Jump to top of page
$('html,body').scrollTop(0);
$('#ajaxMsg').addClass('success').fadeOut();
},
"preDrawCallback": function(settings) {
$('#ajaxMsg').html("<div class='msg'><span class='ui-icon ui-icon-check'></span>Fetching rows...</div>");
$('#ajaxMsg').addClass('success').fadeIn();
}
});
$('#history_table').on('mouseenter', 'td.modal-control span', function () {
$(this).tooltip();
} );
$('#history_table').on('click', 'td.modal-control', function () {
var tr = $(this).parents('tr');
var row = history_table.row( tr );
var rowData = row.data();
$.ajax({
url: 'getStreamDetails',
type: 'GET',
data: {id: rowData['id']},
success: function(data) {
$('#modal-stream-info').html(rowData['title']+' ('+rowData['user']+')');
$('#media_container').html(data.data.opt.Media['@container']);
$('#media_resolution').html(data.data.opt.Media['@videoResolution']);
$('#media_bitrate').html(data.data.opt.Media['@bitrate']);
if (data.data.opt.TranscodeSession) {
$('#transcode_video_dec').html(data.data.opt.TranscodeSession['@videoDecision']);
$('#transcode_video_resolution').html(data.data.opt.TranscodeSession['@height']);
$('#transcode_video_codec').html(data.data.opt.TranscodeSession['@videoCodec']);
$('#transcode_video_width').html(data.data.opt.TranscodeSession['@width']);
$('#transcode_video_height').html(data.data.opt.TranscodeSession['@height']);
$('#transcode_audio_dec').html(data.data.opt.TranscodeSession['@audioDecision']);
$('#transcode_audio_codec').html(data.data.opt.TranscodeSession['@audioCodec']);
$('#transcode_audio_channels').html(data.data.opt.TranscodeSession['@audioChannels']);
} else {
$('#transcode_video_dec').html('Direct Play');
$('#transcode_video_resolution').html(data.data.opt.Media['@videoResolution']);
$('#transcode_video_codec').html(data.data.opt.Media.Part.Stream[0]['@codec']);
$('#transcode_video_width').html(data.data.opt.Media['@width']);
$('#transcode_video_height').html(data.data.opt.Media['@height']);
$('#transcode_audio_dec').html('Direct Play');
$('#transcode_audio_codec').html(data.data.opt.Media['@audioCodec']);
$('#transcode_audio_channels').html(data.data.opt.Media['@audioChannels']);
}
$('#video_width').html(data.data.opt.Media['@width']);
$('#video_height').html(data.data.opt.Media['@height']);
$('#video_aspect').html(data.data.opt.Media['@aspectRatio']);
$('#video_framerate').html(data.data.opt.Media['@videoFrameRate']);
$('#video_codec').html(data.data.opt.Media['@videoCodec']);
$('#audio_codec').html(data.data.opt.Media['@audioCodec']);
$('#audio_channels').html(data.data.opt.Media['@audioChannels']);
}
});
});
});
</script>
</%def> </%def>

View file

@ -4,7 +4,7 @@
%> %>
<%def name="headIncludes()"> <%def name="headIncludes()">
<link rel="stylesheet" href="interfaces/default/css/plexwatch-tables.css">
</%def> </%def>
<%def name="body()"> <%def name="body()">
@ -52,12 +52,12 @@
<div class="summary-content-director"> <div class="summary-content-director">
% if metadata['type'] == 'episode' or metadata['type'] == 'movie': % if metadata['type'] == 'episode' or metadata['type'] == 'movie':
% if metadata['directors']: % if metadata['directors']:
Directed by <strong> ${metadata['directors'][0]} Directed by <strong> ${metadata['directors'][0]}</strong>
% else: % else:
Directed by <strong> unknown Directed by <strong> unknown</strong>
% endif % endif
% elif metadata['type'] == 'show': % elif metadata['type'] == 'show':
Studio <strong> ${metadata['studio']} Studio <strong> ${metadata['studio']}</strong>
% endif % endif
</div> </div>
<div class="summary-content-duration"> <div class="summary-content-duration">
@ -138,6 +138,44 @@
</div> </div>
<!--}--> <!--}-->
</div> </div>
<div class='container-fluid'>
<div class='row-fluid'>
<div class='span12'>
<div class='wellbg'>
% if metadata['type'] == 'movie' or metadata['type'] == 'episode':
<div class="wellheader">
<div class="dashboard-wellheader">
<h3>Watch history for <strong>${metadata['title']}</strong></h3>
</div>
</div>
<table class="display" id="history_table" width="100%">
<thead>
<tr>
<th align='left' id="id"><i class='fa fa-sort'></i> ID</th>
<th align='left' id="date"><i class='fa fa-sort'></i> Time</th>
<th align='left' id="user"><i class='fa fa-sort'></i> User</th>
<th align='left' id="platform"><i class='fa fa-sort'></i> Platform</th>
<th align='left' id="ip_address"><i class='fa fa-sort'></i> IP Address</th>
<th align='left' id="title"><i class='fa fa-sort'></i> Title</th>
<th align='left' id="started"><i class='fa fa-sort'></i> Started</th>
<th align='left' id="paused_counter"><i class='fa fa-sort'></i> Paused</th>
<th align='left' id="stopped"><i class='fa fa-sort'></i> Stopped</th>
<th align='left' id="duration"><i class='fa fa-sort'></i> Duration</th>
<th align='left' id="percent_complete"> Completed</th>
<th align='left' id="rating_key"> RatingKey</th>
<th align='left' id="xml"></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div id="info-modal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="info-modal" aria-hidden="true">
</div>
% endif
</div>
</div>
</div>
</div>
% else: % else:
<div class="clear"></div> <div class="clear"></div>
<div class="container-fluid"> <div class="container-fluid">
@ -152,6 +190,10 @@
<%def name="javascriptIncludes()"> <%def name="javascriptIncludes()">
<script src="interfaces/default/js/jquery.rateit.min.js"></script> <script src="interfaces/default/js/jquery.rateit.min.js"></script>
<script src="interfaces/default/js/jquery.dataTables.min.js"></script>
<script src="interfaces/default/js/jquery.dataTables.bootstrap.pagination.integration.js"></script>
<script src="interfaces/default/js/moment-with-locale.js"></script>
% if metadata: % if metadata:
% if metadata['type'] == 'movie': % if metadata['type'] == 'movie':
<script> <script>
@ -160,5 +202,23 @@
$('#stars').attr('data-rateit-value', starRating) $('#stars').attr('data-rateit-value', starRating)
</script> </script>
% endif % endif
% if metadata['type'] == 'movie' or metadata['type'] == 'episode':
<script src="interfaces/default/js/tables/history_table.js"></script>
<script>
$(document).ready(function () {
history_table_options.ajax = {
"url": "get_history",
"data": function(d) {
d.rating_key = ${metadata['ratingKey']};
}
}
history_table = $('#history_table').DataTable(history_table_options);
// Hide the title column
history_table.column(5).visible(false);
});
</script>
%endif
% endif % endif
</%def> </%def>

View file

@ -0,0 +1,180 @@
var date_format = 'YYYY-MM-DD';
var time_format = 'hh:mm a';
$.ajax({
url: 'get_date_formats',
type: 'GET',
success: function(data) {
date_format = data.date_format;
time_format = data.time_format;
}
});
history_table_options = {
"destroy": true,
"language": {
"search": "Search: ",
"lengthMenu":"Show _MENU_ entries per page",
"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",
},
"stateSave": true,
"sPaginationType": "bootstrap",
"processing": false,
"serverSide": true,
"pageLength": 10,
"order": [ 1, 'desc'],
"columnDefs": [
{
"targets": [0],
"data":"id",
"visible": false,
"searchable": false
},
{
"targets": [1],
"data":"date",
"createdCell": function (td, cellData, rowData, row, col) {
if (rowData['stopped'] === null) {
$(td).addClass('currentlyWatching');
$(td).html('Currently watching...');
} else {
$(td).html(moment(cellData,"X").format(date_format));
}
},
"searchable": false
},
{
"targets": [2],
"data":"user"
},
{
"targets": [3],
"data":"platform",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
$(td).html('<a href="#info-modal" data-toggle="modal"><span data-toggle="tooltip" data-placement="left" title="Stream Info" id="stream-info" class="badge badge-inverse"><i class="fa fa-info"></i></span></a>&nbsp'+cellData);
}
},
"className": "modal-control"
},
{
"targets": [4],
"data":"ip_address",
"createdCell": function (td, cellData, rowData, row, col) {
if ((cellData == '') || (cellData == '0')) {
$(td).html('n/a');
}
}
},
{
"targets": [5],
"data":"title",
"name":"title",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
$(td).html('<a href="info?rating_key=' + rowData['rating_key'] + '">' + cellData + '</a>');
}
}
},
{
"targets": [6],
"data":"started",
"render": function ( data, type, full ) {
return moment(data, "X").format(time_format);
},
"searchable": false
},
{
"targets": [7],
"data":"paused_counter",
"render": function ( data, type, full ) {
return Math.round(moment.duration(data, 'seconds').as('minutes')) + ' mins';
},
"searchable": false
},
{
"targets": [8],
"data":"stopped",
"render": function ( data, type, full ) {
if (data !== null) {
return moment(data, "X").format(time_format);
} else {
return data;
}
},
"searchable": false
},
{
"targets": [9],
"data":"duration",
"render": function ( data, type, full ) {
if (data !== null) {
return Math.round(moment.duration(data, 'seconds').as('minutes')) + ' mins';
} else {
return data;
}
},
"searchable": false
},
{
"targets": [10],
"data":"percent_complete",
"orderable": false,
"render": function ( data, type, full ) {
if (data < 95) {
return '<span class="badge">'+Math.round(data)+'%</span>';
} else {
return '<span class="badge">100%</span>';
}
},
"searchable": false
},
{
"targets": [11],
"data":"rating_key",
"visible": false,
"searchable": false
},
{
"targets": [12],
"data":"xml",
"searchable":false,
"visible":false
}
],
"drawCallback": function (settings) {
// Jump to top of page
// $('html,body').scrollTop(0);
$('#ajaxMsg').addClass('success').fadeOut();
},
"preDrawCallback": function(settings) {
$('#ajaxMsg').html("<div class='msg'><span class='ui-icon ui-icon-check'></span>Fetching rows...</div>");
$('#ajaxMsg').addClass('success').fadeIn();
}
}
$('#history_table').on('mouseenter', 'td.modal-control span', function () {
$(this).tooltip();
});
$('#history_table').on('click', 'td.modal-control', function () {
var tr = $(this).parents('tr');
var row = history_table.row( tr );
var rowData = row.data();
function showStreamDetails() {
$.ajax({
url: 'get_stream_data',
data: {row_id: rowData['id'], user: rowData['user']},
cache: false,
async: true,
complete: function(xhr, status) {
$("#info-modal").html(xhr.responseText);
}
});
}
showStreamDetails();
});

View file

@ -0,0 +1,52 @@
% if data is not None:
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
% if data['media_type'] == 'episode':
<h3 id="myModalLabel">Stream Info: <strong>${data['grandparent_title']} - ${data['title']} (${user})</strong></h3>
% else:
<h3 id="myModalLabel">Stream Info: <strong>${data['title']} (${user})</strong></h3>
% endif
</div>
<div class="modal-body" id="modal-text">
<div class="span4">
<h4>Stream Details</h4>
<ul>
<h5>Video</h5>
<li>Stream Type: <strong>${data['transcode_video_dec']}</strong></li>
<li>Video Resolution: <strong>${data['transcode_height']}p</strong></li>
<li>Video Codec: <strong>${data['transcode_video_codec']}</strong></li>
<li>Video Width: <strong>${data['transcode_width']}</strong></li>
<li>Video Height: <strong>${data['transcode_height']}</span></strong></li>
</ul>
<ul>
<h5>Audio</h5>
<li>Stream Type: <strong>${data['transcode_audio_dec']}</strong></li>
<li>Audio Codec: <strong>${data['transcode_audio_codec']}</strong></li>
<li>Audio Channels: <strong>${data['transcode_audio_channels']}</strong></li>
</ul>
</div>
<div class="span4">
<h4>Media Source Details</h4>
<li>Container: <strong>${data['container']}</strong></li>
<li>Resolution: <strong>${data['height']}p</strong></li>
<li>Bitrate: <strong>${data['bitrate']} kbps</strong></li>
</div>
<div class="span4">
<h4>Video Source Details</h4>
<ul>
<li>Width: <strong>${data['width']}</strong></li>
<li>Height: <strong>${data['height']}</strong></li>
<li>Aspect Ratio: <strong>${data['aspect_ratio']}</strong></li>
<li>Video Frame Rate: <strong>${data['video_framerate']}</span></strong></li>
<li>Video Codec: <strong>${data['video_codec']}</strong></li>
</ul>
<ul></ul>
<h4>Audio Source Details</h4>
<ul>
<li>Audio Codec: <strong>${data['audio_codec']}</strong></li>
<li>Audio Channels: <strong>${data['audio_channels']}</strong></li>
</ul>
</div>
</div>
<div class="modal-footer"></div>
% endif

View file

@ -121,17 +121,76 @@
</strong></h3> </strong></h3>
</div> </div>
</div> </div>
Table <table class="display" id="history_table" width="100%">
<div id="info-modal" class="modal hide fade" tabindex="-1" <thead>
role="dialog" aria-labelledby="info-modal" aria-hidden="true"> <tr>
<div class="modal-header"> <th align='left' id="id"><i class='fa fa-sort'></i> ID</th>
<button type="button" class="close" data-dismiss="modal" <th align='left' id="date"><i class='fa fa-sort'></i> Time</th>
aria-hidden="true"><i class="fa fa-remove"></i></button> <th align='left' id="user"><i class='fa fa-sort'></i> User</th>
<h3 id="myModalLabel">Stream Info: <span id="modal-stream-info"></span></h3> <th align='left' id="platform"><i class='fa fa-sort'></i> Platform</th>
</div> <th align='left' id="ip_address"><i class='fa fa-sort'></i> IP Address</th>
<div class="modal-body" id="modal-text"></div> <th align='left' id="title"><i class='fa fa-sort'></i> Title</th>
<div class="modal-footer"></div> <th align='left' id="started"><i class='fa fa-sort'></i> Started</th>
</div> <th align='left' id="paused_counter"><i class='fa fa-sort'></i> Paused</th>
<th align='left' id="stopped"><i class='fa fa-sort'></i> Stopped</th>
<th align='left' id="duration"><i class='fa fa-sort'></i> Duration</th>
<th align='left' id="percent_complete"> Completed</th>
<th align='left' id="rating_key"> RatingKey</th>
<th align='left' id="xml"></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div id="info-modal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="info-modal" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
<h3 id="myModalLabel">Stream Info: <strong><span id="modal-stream-info"></span></strong></h3>
</div>
<div class="modal-body" id="modal-text">
<div class="span4">
<h4>Stream Details</h4>
<ul>
<h5>Video</h5>
<li>Stream Type: <strong><span id="transcode_video_dec"></span></strong></li>
<li>Video Resolution: <strong><span id="transcode_video_resolution"></span>p</strong></li>
<li>Video Codec: <strong><span id="transcode_video_codec"></span></strong></li>
<li>Video Width: <strong><span id="transcode_video_width"></span></strong></li>
<li>Video Height: <strong><span id="transcode_video_height"></span></strong></li>
</ul>
<ul>
<h5>Audio</h5>
<li>Stream Type: <strong><span id="transcode_audio_dec"></span></strong></li>
<li>Audio Codec: <strong><span id="transcode_audio_codec"></span></strong></li>
<li>Audio Channels: <strong><span id="transcode_audio_channels"></span></strong></li>
</ul>
</div>
<div class="span4">
<h4>Media Source Details</h4>
<li>Container: <strong><span id="media_container"></span></strong></li>
<li>Resolution: <strong><span id="media_resolution"></span>p</strong></li>
<li>Bitrate: <strong><span id="media_bitrate"></span> kbps</strong></li>
</div>
<div class="span4">
<h4>Video Source Details</h4>
<ul>
<li>Width: <strong><span id="video_width"></span></strong></li>
<li>Height: <strong><span id="video_height"></span></strong></li>
<li>Aspect Ratio: <strong><span id="video_aspect"></span></strong></li>
<li>Video Frame Rate: <strong><span id="video_framerate"></span></strong></li>
<li>Video Codec: <strong><span id="video_codec"></span></strong></li>
</ul>
<ul></ul>
<h4>Audio Source Details</h4>
<ul>
<li>Audio Codec: <strong><span id="audio_codec"></span></strong></li>
<li>Audio Channels: <strong><span id="audio_channels"></span></strong></li>
</ul>
</div>
</div>
<div class="modal-footer">
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -146,7 +205,20 @@
<script src="interfaces/default/js/jquery.dataTables.bootstrap.pagination.integration.js"></script> <script src="interfaces/default/js/jquery.dataTables.bootstrap.pagination.integration.js"></script>
<script src="interfaces/default/js/moment-with-locale.js"></script> <script src="interfaces/default/js/moment-with-locale.js"></script>
<script src="interfaces/default/js/tables/history_table.js"></script>
<script> <script>
$(document).ready(function () {
history_table_options.ajax = {
"url": "get_history",
"data": function(d) {
d.user = "drzoidberg33";
}
}
history_table = $('#history_table').DataTable(history_table_options);
// Hide the title column
// history_table.column(5).visible(false);
});
</script> </script>
</%def> </%def>

View file

@ -28,6 +28,7 @@ class DataTables(object):
def __init__(self): def __init__(self):
self.ssp_db = db.DBConnection() self.ssp_db = db.DBConnection()
# TODO: Pass all parameters via kwargs
def ssp_query(self, table_name, def ssp_query(self, table_name,
columns=[], columns=[],
start=0, start=0,
@ -53,16 +54,26 @@ class DataTables(object):
# TODO: custom_where is ugly and causes issues with reported total results # TODO: custom_where is ugly and causes issues with reported total results
if custom_where != '': if custom_where != '':
where += 'AND (' + custom_where + ')' custom_where = 'WHERE (' + custom_where + ')'
if grouping: if grouping:
query = 'SELECT * FROM (SELECT %s FROM %s GROUP BY %s) %s %s' \ if custom_where == '':
% (column_data['column_string'], table_name, group_by, query = 'SELECT * FROM (SELECT %s FROM %s GROUP BY %s) %s %s' \
where, order) % (column_data['column_string'], table_name, group_by,
where, order)
else:
query = 'SELECT * FROM (SELECT * FROM (SELECT %s FROM %s GROUP BY %s) %s %s) %s' \
% (column_data['column_string'], table_name, group_by,
where, order, custom_where)
else: else:
query = 'SELECT %s FROM %s %s %s' \ if custom_where == '':
% (column_data['column_string'], table_name, where, query = 'SELECT %s FROM %s %s %s' \
order) % (column_data['column_string'], table_name, where,
order)
else:
query = 'SELECT * FROM (SELECT %s FROM %s %s %s) %s' \
% (column_data['column_string'], table_name, where,
order, custom_where)
filtered = self.ssp_db.select(query) filtered = self.ssp_db.select(query)
if search_value == '': if search_value == '':
@ -122,7 +133,9 @@ class DataTables(object):
return where return where
else: else:
return '' where = ''
return where
@staticmethod @staticmethod
def extract_columns(columns=[]): def extract_columns(columns=[]):

View file

@ -13,7 +13,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>. # along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
from plexpy import logger, helpers, request, datatables, config from plexpy import logger, helpers, request, datatables, config, db
from xml.dom import minidom from xml.dom import minidom
import plexpy import plexpy
@ -104,7 +104,7 @@ class PlexWatch(object):
return dict return dict
def get_history(self, start='', length='', kwargs=None): def get_history(self, start='', length='', kwargs=None, custom_where=''):
data_tables = datatables.DataTables() data_tables = datatables.DataTables()
start = int(start) start = int(start)
@ -152,7 +152,7 @@ class PlexWatch(object):
order_dir=order_dir, order_dir=order_dir,
search_value=search_value, search_value=search_value,
search_regex=search_regex, search_regex=search_regex,
custom_where='', custom_where=custom_where,
group_by='', group_by='',
kwargs=kwargs) kwargs=kwargs)
@ -216,3 +216,112 @@ class PlexWatch(object):
} }
return dict return dict
def get_stream_details(self, id=0):
myDB = db.DBConnection()
query = 'SELECT xml from %s where id = %s' % (self.get_history_table_name(), id)
xml = myDB.select_single(query)
try:
dict_data = helpers.convert_xml_to_dict(helpers.latinToAscii(xml))
except IOError, e:
logger.warn("Error parsing XML in PlexWatch db: %s" % e)
dict = {'id': id,
'data': dict_data}
return dict
"""
Validate xml keys to make sure they exist and return their attribute value, return blank value is none found
"""
@staticmethod
def get_xml_attr(xml_key, attribute, return_bool=False, default_return=''):
if xml_key.getAttribute(attribute):
if return_bool:
return True
else:
return xml_key.getAttribute(attribute)
else:
if return_bool:
return False
else:
return default_return
def get_stream_details(self, row_id=None):
myDB = db.DBConnection()
if row_id:
query = 'SELECT xml from %s where id = %s' % (self.get_history_table_name(), row_id)
xml = myDB.select_single(query)
xml_data = helpers.latinToAscii(xml)
else:
return None
try:
xml_parse = minidom.parseString(xml_data)
except:
logger.warn("Error parsing XML for Plex stream data.")
return None
xml_head = xml_parse.getElementsByTagName('opt')
if not xml_head:
logger.warn("Error parsing XML for Plex stream data.")
return None
stream_output = {}
for a in xml_head:
media_type = self.get_xml_attr(a, 'type')
title = self.get_xml_attr(a, 'title')
grandparent_title = self.get_xml_attr(a, 'grandparentTitle')
if a.getElementsByTagName('TranscodeSession'):
transcode_data = a.getElementsByTagName('TranscodeSession')
for transcode_session in transcode_data:
transcode_video_dec = self.get_xml_attr(transcode_session, 'videoDecision')
transcode_video_codec = self.get_xml_attr(transcode_session, 'videoCodec')
transcode_height = self.get_xml_attr(transcode_session, 'height')
transcode_width = self.get_xml_attr(transcode_session, 'width')
transcode_audio_dec = self.get_xml_attr(transcode_session, 'audioDecision')
transcode_audio_codec = self.get_xml_attr(transcode_session, 'audioCodec')
transcode_audio_channels = self.get_xml_attr(transcode_session, 'audioChannels')
else:
transcode_data = a.getElementsByTagName('Media')
for transcode_session in transcode_data:
transcode_video_dec = 'direct play'
transcode_video_codec = self.get_xml_attr(transcode_session, 'videoCodec')
transcode_height = self.get_xml_attr(transcode_session, 'height')
transcode_width = self.get_xml_attr(transcode_session, 'width')
transcode_audio_dec = 'direct play'
transcode_audio_codec = self.get_xml_attr(transcode_session, 'audioCodec')
transcode_audio_channels = self.get_xml_attr(transcode_session, 'audioChannels')
if a.getElementsByTagName('Media'):
stream_data = a.getElementsByTagName('Media')
for stream_item in stream_data:
stream_output = {'container': self.get_xml_attr(stream_item, 'container'),
'bitrate': self.get_xml_attr(stream_item, 'bitrate'),
'video_resolution': self.get_xml_attr(stream_item, 'videoResolution'),
'width': self.get_xml_attr(stream_item, 'width'),
'height': self.get_xml_attr(stream_item, 'height'),
'aspect_ratio': self.get_xml_attr(stream_item, 'aspectRatio'),
'video_framerate': self.get_xml_attr(stream_item, 'videoFrameRate'),
'video_codec': self.get_xml_attr(stream_item, 'videoCodec'),
'audio_codec': self.get_xml_attr(stream_item, 'audioCodec'),
'audio_channels': self.get_xml_attr(stream_item, 'audioChannels'),
'transcode_video_dec': transcode_video_dec,
'transcode_video_codec': transcode_video_codec,
'transcode_height': transcode_height,
'transcode_width': transcode_width,
'transcode_audio_dec': transcode_audio_dec,
'transcode_audio_codec': transcode_audio_codec,
'transcode_audio_channels': transcode_audio_channels,
'media_type': media_type,
'title': title,
'grandparent_title': grandparent_title
}
return stream_output

View file

@ -13,7 +13,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>. # along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
from plexpy import logger, db, helpers, notifiers, plextv, pmsconnect, plexwatch from plexpy import logger, helpers, notifiers, plextv, pmsconnect, plexwatch
from plexpy.helpers import checked, radio, today, cleanName from plexpy.helpers import checked, radio, today, cleanName
from xml.dom import minidom from xml.dom import minidom
@ -67,7 +67,7 @@ class WebInterface(object):
return serve_template(templatename="index.html", title="Home") return serve_template(templatename="index.html", title="Home")
@cherrypy.expose @cherrypy.expose
def history(self): def get_date_formats(self):
if plexpy.CONFIG.DATE_FORMAT: if plexpy.CONFIG.DATE_FORMAT:
date_format = plexpy.CONFIG.DATE_FORMAT date_format = plexpy.CONFIG.DATE_FORMAT
else: else:
@ -77,8 +77,15 @@ class WebInterface(object):
else: else:
time_format = 'HH:mm' time_format = 'HH:mm'
return serve_template(templatename="history.html", title="History", date_format=date_format, formats = {'date_format': date_format,
time_format=time_format) 'time_format': time_format}
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps(formats)
@cherrypy.expose
def history(self):
return serve_template(templatename="history.html", title="History")
@cherrypy.expose @cherrypy.expose
def users(self): def users(self):
@ -88,6 +95,14 @@ class WebInterface(object):
def user(self): def user(self):
return serve_template(templatename="user.html", title="User") return serve_template(templatename="user.html", title="User")
@cherrypy.expose
def get_stream_data(self, row_id=None, user='', **kwargs):
plex_watch = plexwatch.PlexWatch()
stream_data = plex_watch.get_stream_details(row_id)
return serve_template(templatename="stream_data.html", title="Stream Data", data=stream_data, user=user)
@cherrypy.expose @cherrypy.expose
def get_user_list(self, start=0, length=100, **kwargs): def get_user_list(self, start=0, length=100, **kwargs):
@ -332,34 +347,29 @@ class WebInterface(object):
message=message, timer=timer) message=message, timer=timer)
@cherrypy.expose @cherrypy.expose
def getHistory_json(self, start=0, length=100, **kwargs): def get_history(self, start=0, length=100, custom_where='', **kwargs):
if 'user' in kwargs:
user = kwargs.get('user', "")
custom_where = 'user = "%s"' % user
if 'rating_key' in kwargs:
rating_key = kwargs.get('rating_key', "")
custom_where = 'rating_key = %s' % rating_key
plex_watch = plexwatch.PlexWatch() plex_watch = plexwatch.PlexWatch()
history = plex_watch.get_history(start, length, kwargs) history = plex_watch.get_history(start, length, kwargs, custom_where)
cherrypy.response.headers['Content-type'] = 'application/json' cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps(history) return json.dumps(history)
@cherrypy.expose @cherrypy.expose
def getStreamDetails(self, id=0, **kwargs): def get_stream_details(self, rating_key=0, **kwargs):
myDB = db.DBConnection() plex_watch = plexwatch.PlexWatch()
db_table = db.DBConnection().get_history_table_name() stream_details = plex_watch.get_stream_details(rating_key)
query = 'SELECT xml from %s where id = %s' % (db_table, id)
xml = myDB.select_single(query)
try:
dict_data = helpers.convert_xml_to_dict(helpers.latinToAscii(xml))
except IOError, e:
logger.warn("Error parsing XML in PlexWatch db: %s" % e)
dict = {'id': id,
'data': dict_data}
s = json.dumps(dict)
cherrypy.response.headers['Content-type'] = 'application/json' cherrypy.response.headers['Content-type'] = 'application/json'
return s return json.dumps(stream_details)
@cherrypy.expose @cherrypy.expose
@ -554,6 +564,18 @@ class WebInterface(object):
pms_connect = pmsconnect.PmsConnect() pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_recently_added(count, 'json') result = pms_connect.get_recently_added(count, 'json')
if result:
cherrypy.response.headers['Content-type'] = 'application/json'
return result
else:
logger.warn('Unable to retrieve data.')
@cherrypy.expose
def get_stream(self, row_id='', **kwargs):
plex_watch = plexwatch.PlexWatch()
result = plex_watch.get_stream_details('122')
if result: if result:
cherrypy.response.headers['Content-type'] = 'application/json' cherrypy.response.headers['Content-type'] = 'application/json'
return result return result