mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-08-22 06:13:25 -07:00
Merge b0f6e62b1c
into a008b09e09
This commit is contained in:
commit
2e8a2ce4c0
5 changed files with 738 additions and 177 deletions
401
data/interfaces/default/css/selectize.bootstrap3.css
Normal file
401
data/interfaces/default/css/selectize.bootstrap3.css
Normal file
|
@ -0,0 +1,401 @@
|
|||
/**
|
||||
* selectize.bootstrap3.css (v0.12.1) - Bootstrap 3 Theme
|
||||
* Copyright (c) 2013–2015 Brian Reavis & contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
|
||||
* file except in compliance with the License. You may obtain a copy of the License at:
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
* ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*
|
||||
* @author Brian Reavis <brian@thirdroute.com>
|
||||
*/
|
||||
.selectize-control.plugin-drag_drop.multi > .selectize-input > div.ui-sortable-placeholder {
|
||||
visibility: visible !important;
|
||||
background: #f2f2f2 !important;
|
||||
background: rgba(0, 0, 0, 0.06) !important;
|
||||
border: 0 none !important;
|
||||
-webkit-box-shadow: inset 0 0 12px 4px #ffffff;
|
||||
box-shadow: inset 0 0 12px 4px #ffffff;
|
||||
}
|
||||
.selectize-control.plugin-drag_drop .ui-sortable-placeholder::after {
|
||||
content: '!';
|
||||
visibility: hidden;
|
||||
}
|
||||
.selectize-control.plugin-drag_drop .ui-sortable-helper {
|
||||
-webkit-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.selectize-dropdown-header {
|
||||
position: relative;
|
||||
padding: 3px 12px;
|
||||
border-bottom: 1px solid #d0d0d0;
|
||||
background: #f8f8f8;
|
||||
-webkit-border-radius: 4px 4px 0 0;
|
||||
-moz-border-radius: 4px 4px 0 0;
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
.selectize-dropdown-header-close {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 50%;
|
||||
color: #333333;
|
||||
opacity: 0.4;
|
||||
margin-top: -12px;
|
||||
line-height: 20px;
|
||||
font-size: 20px !important;
|
||||
}
|
||||
.selectize-dropdown-header-close:hover {
|
||||
color: #000000;
|
||||
}
|
||||
.selectize-dropdown.plugin-optgroup_columns .optgroup {
|
||||
border-right: 1px solid #f2f2f2;
|
||||
border-top: 0 none;
|
||||
float: left;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.selectize-dropdown.plugin-optgroup_columns .optgroup:last-child {
|
||||
border-right: 0 none;
|
||||
}
|
||||
.selectize-dropdown.plugin-optgroup_columns .optgroup:before {
|
||||
display: none;
|
||||
}
|
||||
.selectize-dropdown.plugin-optgroup_columns .optgroup-header {
|
||||
border-top: 0 none;
|
||||
}
|
||||
.selectize-control.plugin-remove_button [data-value] {
|
||||
position: relative;
|
||||
padding-right: 24px !important;
|
||||
}
|
||||
.selectize-control.plugin-remove_button [data-value] .remove {
|
||||
z-index: 1;
|
||||
/* fixes ie bug (see #392) */
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 17px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
padding: 1px 0 0 0;
|
||||
border-left: 1px solid rgba(0, 0, 0, 0);
|
||||
-webkit-border-radius: 0 2px 2px 0;
|
||||
-moz-border-radius: 0 2px 2px 0;
|
||||
border-radius: 0 2px 2px 0;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.selectize-control.plugin-remove_button [data-value] .remove:hover {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.selectize-control.plugin-remove_button [data-value].active .remove {
|
||||
border-left-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
.selectize-control.plugin-remove_button .disabled [data-value] .remove:hover {
|
||||
background: none;
|
||||
}
|
||||
.selectize-control.plugin-remove_button .disabled [data-value] .remove {
|
||||
border-left-color: rgba(77, 77, 77, 0);
|
||||
}
|
||||
.selectize-control {
|
||||
position: relative;
|
||||
}
|
||||
.selectize-dropdown,
|
||||
.selectize-input,
|
||||
.selectize-input input {
|
||||
color: #333333;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: 20px;
|
||||
-webkit-font-smoothing: inherit;
|
||||
}
|
||||
.selectize-input,
|
||||
.selectize-control.single .selectize-input.input-active {
|
||||
background: #ffffff;
|
||||
cursor: text;
|
||||
display: inline-block;
|
||||
}
|
||||
.selectize-input {
|
||||
border: 1px solid #cccccc;
|
||||
padding: 6px 12px;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.selectize-control.multi .selectize-input.has-items {
|
||||
padding: 5px 12px 2px;
|
||||
}
|
||||
.selectize-input.full {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.selectize-input.disabled,
|
||||
.selectize-input.disabled * {
|
||||
cursor: default !important;
|
||||
}
|
||||
.selectize-input.focus {
|
||||
-webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.15);
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
.selectize-input.dropdown-active {
|
||||
-webkit-border-radius: 4px 4px 0 0;
|
||||
-moz-border-radius: 4px 4px 0 0;
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
.selectize-input > * {
|
||||
vertical-align: baseline;
|
||||
display: -moz-inline-stack;
|
||||
display: inline-block;
|
||||
zoom: 1;
|
||||
*display: inline;
|
||||
}
|
||||
.selectize-control.multi .selectize-input > div {
|
||||
cursor: pointer;
|
||||
margin: 0 3px 3px 0;
|
||||
padding: 1px 3px;
|
||||
background: #efefef;
|
||||
color: #333333;
|
||||
border: 0 solid rgba(0, 0, 0, 0);
|
||||
}
|
||||
.selectize-control.multi .selectize-input > div.active {
|
||||
background: #428bca;
|
||||
color: #ffffff;
|
||||
border: 0 solid rgba(0, 0, 0, 0);
|
||||
}
|
||||
.selectize-control.multi .selectize-input.disabled > div,
|
||||
.selectize-control.multi .selectize-input.disabled > div.active {
|
||||
color: #808080;
|
||||
background: #ffffff;
|
||||
border: 0 solid rgba(77, 77, 77, 0);
|
||||
}
|
||||
.selectize-input > input {
|
||||
display: inline-block !important;
|
||||
padding: 0 !important;
|
||||
min-height: 0 !important;
|
||||
max-height: none !important;
|
||||
max-width: 100% !important;
|
||||
margin: 0 !important;
|
||||
text-indent: 0 !important;
|
||||
border: 0 none !important;
|
||||
background: none !important;
|
||||
line-height: inherit !important;
|
||||
-webkit-user-select: auto !important;
|
||||
-webkit-box-shadow: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
.selectize-input > input::-ms-clear {
|
||||
display: none;
|
||||
}
|
||||
.selectize-input > input:focus {
|
||||
outline: none !important;
|
||||
}
|
||||
.selectize-input::after {
|
||||
content: ' ';
|
||||
display: block;
|
||||
clear: left;
|
||||
}
|
||||
.selectize-input.dropdown-active::before {
|
||||
content: ' ';
|
||||
display: block;
|
||||
position: absolute;
|
||||
background: #ffffff;
|
||||
height: 1px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
.selectize-dropdown {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
border: 1px solid #d0d0d0;
|
||||
background: #ffffff;
|
||||
margin: -1px 0 0 0;
|
||||
border-top: 0 none;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
-webkit-border-radius: 0 0 4px 4px;
|
||||
-moz-border-radius: 0 0 4px 4px;
|
||||
border-radius: 0 0 4px 4px;
|
||||
}
|
||||
.selectize-dropdown [data-selectable] {
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
}
|
||||
.selectize-dropdown [data-selectable] .highlight {
|
||||
background: rgba(255, 237, 40, 0.4);
|
||||
-webkit-border-radius: 1px;
|
||||
-moz-border-radius: 1px;
|
||||
border-radius: 1px;
|
||||
}
|
||||
.selectize-dropdown [data-selectable],
|
||||
.selectize-dropdown .optgroup-header {
|
||||
padding: 3px 12px;
|
||||
}
|
||||
.selectize-dropdown .optgroup:first-child .optgroup-header {
|
||||
border-top: 0 none;
|
||||
}
|
||||
.selectize-dropdown .optgroup-header {
|
||||
color: #777777;
|
||||
background: #ffffff;
|
||||
cursor: default;
|
||||
}
|
||||
.selectize-dropdown .active {
|
||||
background-color: #f5f5f5;
|
||||
color: #262626;
|
||||
}
|
||||
.selectize-dropdown .active.create {
|
||||
color: #262626;
|
||||
}
|
||||
.selectize-dropdown .create {
|
||||
color: rgba(51, 51, 51, 0.5);
|
||||
}
|
||||
.selectize-dropdown-content {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
max-height: 200px;
|
||||
}
|
||||
.selectize-control.single .selectize-input,
|
||||
.selectize-control.single .selectize-input input {
|
||||
cursor: pointer;
|
||||
}
|
||||
.selectize-control.single .selectize-input.input-active,
|
||||
.selectize-control.single .selectize-input.input-active input {
|
||||
cursor: text;
|
||||
}
|
||||
.selectize-control.single .selectize-input:after {
|
||||
content: ' ';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 17px;
|
||||
margin-top: -3px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 5px 5px 0 5px;
|
||||
border-color: #333333 transparent transparent transparent;
|
||||
}
|
||||
.selectize-control.single .selectize-input.dropdown-active:after {
|
||||
margin-top: -4px;
|
||||
border-width: 0 5px 5px 5px;
|
||||
border-color: transparent transparent #333333 transparent;
|
||||
}
|
||||
.selectize-control.rtl.single .selectize-input:after {
|
||||
left: 17px;
|
||||
right: auto;
|
||||
}
|
||||
.selectize-control.rtl .selectize-input > input {
|
||||
margin: 0 4px 0 -2px !important;
|
||||
}
|
||||
.selectize-control .selectize-input.disabled {
|
||||
opacity: 0.5;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.selectize-dropdown,
|
||||
.selectize-dropdown.form-control {
|
||||
height: auto;
|
||||
padding: 0;
|
||||
margin: 2px 0 0 0;
|
||||
z-index: 1000;
|
||||
background: #ffffff;
|
||||
border: 1px solid #cccccc;
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
}
|
||||
.selectize-dropdown .optgroup-header {
|
||||
font-size: 12px;
|
||||
line-height: 1.42857143;
|
||||
}
|
||||
.selectize-dropdown .optgroup:first-child:before {
|
||||
display: none;
|
||||
}
|
||||
.selectize-dropdown .optgroup:before {
|
||||
content: ' ';
|
||||
display: block;
|
||||
height: 1px;
|
||||
margin: 9px 0;
|
||||
overflow: hidden;
|
||||
background-color: #e5e5e5;
|
||||
margin-left: -12px;
|
||||
margin-right: -12px;
|
||||
}
|
||||
.selectize-dropdown-content {
|
||||
padding: 5px 0;
|
||||
}
|
||||
.selectize-dropdown-header {
|
||||
padding: 6px 12px;
|
||||
}
|
||||
.selectize-input {
|
||||
min-height: 34px;
|
||||
}
|
||||
.selectize-input.dropdown-active {
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.selectize-input.dropdown-active::before {
|
||||
display: none;
|
||||
}
|
||||
.selectize-input.focus {
|
||||
border-color: #66afe9;
|
||||
outline: 0;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);
|
||||
}
|
||||
.has-error .selectize-input {
|
||||
border-color: #a94442;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
}
|
||||
.has-error .selectize-input:focus {
|
||||
border-color: #843534;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;
|
||||
}
|
||||
.selectize-control.multi .selectize-input.has-items {
|
||||
padding-left: 9px;
|
||||
padding-right: 9px;
|
||||
}
|
||||
.selectize-control.multi .selectize-input > div {
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.form-control.selectize-control {
|
||||
padding: 0;
|
||||
height: auto;
|
||||
border: none;
|
||||
background: none;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
-webkit-border-radius: 0;
|
||||
-moz-border-radius: 0;
|
||||
border-radius: 0;
|
||||
}
|
3
data/interfaces/default/js/selectize.min.js
vendored
Normal file
3
data/interfaces/default/js/selectize.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -15,6 +15,7 @@ from plexpy import common
|
|||
<link href="interfaces/default/css/bootstrap3/bootstrap.css" rel="stylesheet">
|
||||
<link href="interfaces/default/css/bootstrap-wizard.css" rel="stylesheet">
|
||||
<link href="interfaces/default/css/plexpy.css" rel="stylesheet">
|
||||
<link href="interfaces/default/css/selectize.bootstrap3.css" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet" type="text/css">
|
||||
<link href="interfaces/default/css/font-awesome.min.css" rel="stylesheet">
|
||||
<link rel="icon" type="image/x-icon" href="interfaces/default/images/favicon.ico"/>
|
||||
|
@ -43,17 +44,26 @@ from plexpy import common
|
|||
<h3>Plex Media Server</h3>
|
||||
<form>
|
||||
<p class="help-block">Enter your Plex Server details and then click the Verify button to make sure PlexPy can reach the server.</p>
|
||||
|
||||
<div class="wizard-input-section">
|
||||
<label for="pms_ip">Plex IP or Hostname</label>
|
||||
<label for="pms_ip">Choose Server</label>
|
||||
<div class="row">
|
||||
<div class="col-xs-8">
|
||||
<input type="text" class="form-control pms-settings" name="pms_ip" id="pms_ip" placeholder="127.0.0.1" value="${config['pms_ip']}" required>
|
||||
% if not len(servers):
|
||||
<input type="text" class="form-control pms-settings" name="pms_ip" id="pms_ip" placeholder="127.0.0.1" value="${config['pms_ip']}" required>
|
||||
% else:
|
||||
<select id="pms_ip_select">
|
||||
% for server in servers:
|
||||
<option value="${server['server']}" data-port="${server['port']}" data-id="${server['uuid']}">${server['servername']}</option>
|
||||
% endfor
|
||||
</select>
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wizard-input-section">
|
||||
<label for="pms_port">Port Number</label>
|
||||
<div class="row">
|
||||
<div class="row">
|
||||
<div class="col-xs-3">
|
||||
<input type="text" class="form-control pms-settings" name="pms_port" id="pms_port" placeholder="32400" value="${config['pms_port']}" required>
|
||||
</div>
|
||||
|
@ -186,6 +196,7 @@ from plexpy import common
|
|||
<script src="interfaces/default/js/bootstrap3/bootstrap.min.js"></script>
|
||||
<script src="interfaces/default/js/script.js"></script>
|
||||
<script src="interfaces/default/js/bootstrap-wizard.min.js"></script>
|
||||
<script src="interfaces/default/js/selectize.min.js"></script>
|
||||
<script>
|
||||
|
||||
$(document).ready(function() {
|
||||
|
@ -218,8 +229,71 @@ from plexpy import common
|
|||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
$('#pms_ip_select').selectize({
|
||||
create: true,
|
||||
createOnBlur: true,
|
||||
openOnFocus: true,
|
||||
onInitialize: function () {
|
||||
var s = this;
|
||||
this.revertSettings.$children.each(function () {
|
||||
$.extend(s.options[this.value], $(this).data());
|
||||
});
|
||||
// Set it as valid as we only will use selectize if valid servers are found
|
||||
$("#pms_valid").val("valid");
|
||||
$("#pms-verify-status").html('<i class="fa fa-check"></i> Server found!').show();
|
||||
|
||||
},
|
||||
render: {
|
||||
option: function (item, escape) {
|
||||
return '<div data-id="' + item.id + '" data-port="' + item.port + '">' + item.value + '</div>';
|
||||
},
|
||||
item: function (item, escape) {
|
||||
// first item is rendered before initialization bug?
|
||||
if (!item.port) {
|
||||
$.extend(item,
|
||||
$(this.revertSettings.$children)
|
||||
.filter('[value="' + item.value + '"]').data()
|
||||
);
|
||||
// Mark valid servers
|
||||
if (item.id != "undefined") {
|
||||
$("#pms_valid").val("valid");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return '<div data-id="' + item.id + '" data-port="' + item.port + '">' + item.value + '</div>';
|
||||
}
|
||||
},
|
||||
onChange: function(item) {
|
||||
var uuid = $('.selectize-input').find('div').attr('data-id')
|
||||
var port = $('.selectize-input').find('div').attr('data-port')
|
||||
|
||||
$("#pms-verify-status").html("")
|
||||
|
||||
// If a option was added by a user its
|
||||
// data-xxx="undefined"
|
||||
if (uuid != "undefined") {
|
||||
// To allow next step in the guide.
|
||||
// servers with uuid is verified
|
||||
$("#pms_valid").val("valid");
|
||||
$("#pms-verify-status").html('<i class="fa fa-check"></i> Server found!').show();
|
||||
} else {
|
||||
// Self made options must be verified
|
||||
$("#pms_valid").val("");
|
||||
$("#pms-verify-status").html("").hide();
|
||||
|
||||
}
|
||||
|
||||
// If the server is verified set the correct port
|
||||
if (port != "undefined") {
|
||||
$('#pms_port').val(port)
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function validatePMSip(el) {
|
||||
var valid_pms_ip = el.val();
|
||||
|
@ -296,14 +370,22 @@ from plexpy import common
|
|||
var authenticated = false;
|
||||
|
||||
$("#verify-plex-server").click(function() {
|
||||
var pms_ip = $("#pms_ip").val()
|
||||
var pms_port = $("#pms_port").val()
|
||||
if (($("#pms_ip").val() !== '') || ($("#pms_port").val() !== '')) {
|
||||
var ip
|
||||
var pms_port
|
||||
if ($("#pms_ip").val() != "undefined") {
|
||||
ip = $('#pms_ip_select').selectize()[0].selectize.getValue()
|
||||
} else {
|
||||
ip = $("#pms_ip").val()
|
||||
}
|
||||
|
||||
pms_port = $("#pms_port").val()
|
||||
|
||||
if ((ip !== '') || ($("#pms_port").val() !== '')) {
|
||||
$("#pms-verify-status").html('<i class="fa fa-refresh fa-spin"></i> Validating server...');
|
||||
$('#pms-verify-status').fadeIn('fast');
|
||||
$.ajax({
|
||||
url: 'get_server_id',
|
||||
data : { hostname: pms_ip, port: pms_port },
|
||||
data : { hostname: ip, port: pms_port },
|
||||
cache: true,
|
||||
async: true,
|
||||
timeout: 5000,
|
||||
|
@ -402,4 +484,4 @@ from plexpy import common
|
|||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
# This file is part of PlexPy.
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This file is part of PlexPy.
|
||||
#
|
||||
# PlexPy is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -15,7 +18,7 @@
|
|||
|
||||
from plexpy import logger, helpers, users, http_handler
|
||||
from urlparse import urlparse
|
||||
|
||||
import socket
|
||||
import plexpy
|
||||
|
||||
|
||||
|
@ -39,14 +42,14 @@ class PmsConnect(object):
|
|||
port=port,
|
||||
token=plexpy.CONFIG.PMS_TOKEN)
|
||||
|
||||
"""
|
||||
Return current sessions.
|
||||
|
||||
Optional parameters: output_format { dict, json }
|
||||
|
||||
Output: array
|
||||
"""
|
||||
def get_sessions(self, output_format=''):
|
||||
"""
|
||||
Return current sessions.
|
||||
|
||||
Optional parameters: output_format { dict, json }
|
||||
|
||||
Output: array
|
||||
"""
|
||||
uri = '/status/sessions'
|
||||
request = self.request_handler.make_request(uri=uri,
|
||||
proto=self.protocol,
|
||||
|
@ -55,15 +58,15 @@ class PmsConnect(object):
|
|||
|
||||
return request
|
||||
|
||||
"""
|
||||
Return metadata for request item.
|
||||
|
||||
Parameters required: rating_key { Plex ratingKey }
|
||||
Optional parameters: output_format { dict, json }
|
||||
|
||||
Output: array
|
||||
"""
|
||||
def get_metadata(self, rating_key='', output_format=''):
|
||||
"""
|
||||
Return metadata for request item.
|
||||
|
||||
Parameters required: rating_key { Plex ratingKey }
|
||||
Optional parameters: output_format { dict, json }
|
||||
|
||||
Output: array
|
||||
"""
|
||||
uri = '/library/metadata/' + rating_key
|
||||
request = self.request_handler.make_request(uri=uri,
|
||||
proto=self.protocol,
|
||||
|
@ -72,15 +75,15 @@ class PmsConnect(object):
|
|||
|
||||
return request
|
||||
|
||||
"""
|
||||
Return list of recently added items.
|
||||
|
||||
Parameters required: count { number of results to return }
|
||||
Optional parameters: output_format { dict, json }
|
||||
|
||||
Output: array
|
||||
"""
|
||||
def get_recently_added(self, count='0', output_format=''):
|
||||
"""
|
||||
Return list of recently added items.
|
||||
|
||||
Parameters required: count { number of results to return }
|
||||
Optional parameters: output_format { dict, json }
|
||||
|
||||
Output: array
|
||||
"""
|
||||
uri = '/library/recentlyAdded?X-Plex-Container-Start=0&X-Plex-Container-Size=' + count
|
||||
request = self.request_handler.make_request(uri=uri,
|
||||
proto=self.protocol,
|
||||
|
@ -89,31 +92,31 @@ class PmsConnect(object):
|
|||
|
||||
return request
|
||||
|
||||
"""
|
||||
Return list of children in requested library item.
|
||||
|
||||
Parameters required: rating_key { ratingKey of parent }
|
||||
Optional parameters: output_format { dict, json }
|
||||
|
||||
Output: array
|
||||
"""
|
||||
def get_children_list(self, rating_key='', output_format=''):
|
||||
"""
|
||||
Return list of children in requested library item.
|
||||
|
||||
Parameters required: rating_key { ratingKey of parent }
|
||||
Optional parameters: output_format { dict, json }
|
||||
|
||||
Output: array
|
||||
"""
|
||||
uri = '/library/metadata/' + rating_key + '/children'
|
||||
request = self.request_handler.make_request(uri=uri,
|
||||
proto=self.protocol,
|
||||
request_type='GET',
|
||||
output_format=output_format)
|
||||
|
||||
|
||||
return request
|
||||
|
||||
"""
|
||||
Return list of local servers.
|
||||
|
||||
Optional parameters: output_format { dict, json }
|
||||
|
||||
Output: array
|
||||
"""
|
||||
def get_server_list(self, output_format=''):
|
||||
"""
|
||||
Return list of local servers.
|
||||
|
||||
Optional parameters: output_format { dict, json }
|
||||
|
||||
Output: array
|
||||
"""
|
||||
uri = '/servers'
|
||||
request = self.request_handler.make_request(uri=uri,
|
||||
proto=self.protocol,
|
||||
|
@ -122,14 +125,14 @@ class PmsConnect(object):
|
|||
|
||||
return request
|
||||
|
||||
"""
|
||||
Return the local servers preferences.
|
||||
|
||||
Optional parameters: output_format { dict, json }
|
||||
|
||||
Output: array
|
||||
"""
|
||||
def get_server_prefs(self, output_format=''):
|
||||
"""
|
||||
Return the local servers preferences.
|
||||
|
||||
Optional parameters: output_format { dict, json }
|
||||
|
||||
Output: array
|
||||
"""
|
||||
uri = '/:/prefs'
|
||||
request = self.request_handler.make_request(uri=uri,
|
||||
proto=self.protocol,
|
||||
|
@ -138,14 +141,15 @@ class PmsConnect(object):
|
|||
|
||||
return request
|
||||
|
||||
"""
|
||||
Return the local server identity.
|
||||
|
||||
Optional parameters: output_format { dict, json }
|
||||
|
||||
Output: array
|
||||
"""
|
||||
def get_local_server_identity(self, output_format=''):
|
||||
"""
|
||||
Return the local server identity.
|
||||
|
||||
Optional parameters: output_format { dict, json }
|
||||
|
||||
Output: array
|
||||
"""
|
||||
|
||||
uri = '/identity'
|
||||
request = self.request_handler.make_request(uri=uri,
|
||||
proto=self.protocol,
|
||||
|
@ -154,14 +158,14 @@ class PmsConnect(object):
|
|||
|
||||
return request
|
||||
|
||||
"""
|
||||
Return list of libraries on server.
|
||||
|
||||
Optional parameters: output_format { dict, json }
|
||||
|
||||
Output: array
|
||||
"""
|
||||
def get_libraries_list(self, output_format=''):
|
||||
"""
|
||||
Return list of libraries on server.
|
||||
|
||||
Optional parameters: output_format { dict, json }
|
||||
|
||||
Output: array
|
||||
"""
|
||||
uri = '/library/sections'
|
||||
request = self.request_handler.make_request(uri=uri,
|
||||
proto=self.protocol,
|
||||
|
@ -170,15 +174,15 @@ class PmsConnect(object):
|
|||
|
||||
return request
|
||||
|
||||
"""
|
||||
Return list of items in library on server.
|
||||
|
||||
Optional parameters: output_format { dict, json }
|
||||
|
||||
Output: array
|
||||
"""
|
||||
def get_library_list(self, section_key='', list_type='all', count='0', sort_type='', output_format=''):
|
||||
uri = '/library/sections/' + section_key + '/' + list_type +'?X-Plex-Container-Start=0&X-Plex-Container-Size=' + count + sort_type
|
||||
"""
|
||||
Return list of items in library on server.
|
||||
|
||||
Optional parameters: output_format { dict, json }
|
||||
|
||||
Output: array
|
||||
"""
|
||||
uri = '/library/sections/' + section_key + '/' + list_type + '?X-Plex-Container-Start=0&X-Plex-Container-Size=' + count + sort_type
|
||||
request = self.request_handler.make_request(uri=uri,
|
||||
proto=self.protocol,
|
||||
request_type='GET',
|
||||
|
@ -186,15 +190,15 @@ class PmsConnect(object):
|
|||
|
||||
return request
|
||||
|
||||
"""
|
||||
Return sync item details.
|
||||
|
||||
Parameters required: sync_id { unique sync id for item }
|
||||
Optional parameters: output_format { dict, json }
|
||||
|
||||
Output: array
|
||||
"""
|
||||
def get_sync_item(self, sync_id=None, output_format=''):
|
||||
"""
|
||||
Return sync item details.
|
||||
|
||||
Parameters required: sync_id { unique sync id for item }
|
||||
Optional parameters: output_format { dict, json }
|
||||
|
||||
Output: array
|
||||
"""
|
||||
uri = '/sync/items/' + sync_id
|
||||
request = self.request_handler.make_request(uri=uri,
|
||||
proto=self.protocol,
|
||||
|
@ -203,14 +207,14 @@ class PmsConnect(object):
|
|||
|
||||
return request
|
||||
|
||||
"""
|
||||
Return sync transcode queue.
|
||||
|
||||
Optional parameters: output_format { dict, json }
|
||||
|
||||
Output: array
|
||||
"""
|
||||
def get_sync_transcode_queue(self, output_format=''):
|
||||
"""
|
||||
Return sync transcode queue.
|
||||
|
||||
Optional parameters: output_format { dict, json }
|
||||
|
||||
Output: array
|
||||
"""
|
||||
uri = '/sync/transcodeQueue'
|
||||
request = self.request_handler.make_request(uri=uri,
|
||||
proto=self.protocol,
|
||||
|
@ -219,14 +223,14 @@ class PmsConnect(object):
|
|||
|
||||
return request
|
||||
|
||||
"""
|
||||
Return processed and validated list of recently added items.
|
||||
|
||||
Parameters required: count { number of results to return }
|
||||
|
||||
Output: array
|
||||
"""
|
||||
def get_recently_added_details(self, count='0'):
|
||||
"""
|
||||
Return processed and validated list of recently added items.
|
||||
|
||||
Parameters required: count { number of results to return }
|
||||
|
||||
Output: array
|
||||
"""
|
||||
recent = self.get_recently_added(count, output_format='xml')
|
||||
|
||||
try:
|
||||
|
@ -277,14 +281,14 @@ class PmsConnect(object):
|
|||
output = {'recently_added': sorted(recents_list, key=lambda k: k['added_at'], reverse=True)}
|
||||
return output
|
||||
|
||||
"""
|
||||
Return processed and validated metadata list for requested item.
|
||||
|
||||
Parameters required: rating_key { Plex ratingKey }
|
||||
|
||||
Output: array
|
||||
"""
|
||||
def get_metadata_details(self, rating_key=''):
|
||||
"""
|
||||
Return processed and validated metadata list for requested item.
|
||||
|
||||
Parameters required: rating_key { Plex ratingKey }
|
||||
|
||||
Output: array
|
||||
"""
|
||||
metadata = self.get_metadata(str(rating_key), output_format='xml')
|
||||
|
||||
try:
|
||||
|
@ -610,15 +614,15 @@ class PmsConnect(object):
|
|||
|
||||
return output
|
||||
|
||||
"""
|
||||
Return selected data from current sessions.
|
||||
This function processes and validates session data
|
||||
|
||||
Parameters required: stream_type { track or video }
|
||||
session { the session dictionary }
|
||||
Output: dict
|
||||
"""
|
||||
def get_session_each(self, stream_type='', session=None):
|
||||
"""
|
||||
Return selected data from current sessions.
|
||||
This function processes and validates session data
|
||||
|
||||
Parameters required: stream_type { track or video }
|
||||
session { the session dictionary }
|
||||
Output: dict
|
||||
"""
|
||||
session_output = None
|
||||
user_data = users.Users()
|
||||
|
||||
|
@ -1038,12 +1042,12 @@ class PmsConnect(object):
|
|||
|
||||
return session_output
|
||||
|
||||
"""
|
||||
Return processed and validated children list.
|
||||
|
||||
Output: array
|
||||
"""
|
||||
def get_item_children(self, rating_key=''):
|
||||
"""
|
||||
Return processed and validated children list.
|
||||
|
||||
Output: array
|
||||
"""
|
||||
children_data = self.get_children_list(rating_key, output_format='xml')
|
||||
|
||||
try:
|
||||
|
@ -1061,7 +1065,7 @@ class PmsConnect(object):
|
|||
children_list = {'children_count': '0',
|
||||
'children_list': []
|
||||
}
|
||||
return parent_list
|
||||
return parent_list # undef name fixme
|
||||
|
||||
result_data = []
|
||||
|
||||
|
@ -1080,7 +1084,7 @@ class PmsConnect(object):
|
|||
'thumb': helpers.get_xml_attr(result, 'thumb'),
|
||||
'parent_thumb': helpers.get_xml_attr(a, 'thumb'),
|
||||
'duration': helpers.get_xml_attr(result, 'duration')
|
||||
}
|
||||
}
|
||||
children_list.append(children_output)
|
||||
|
||||
output = {'children_count': helpers.get_xml_attr(xml_head[0], 'size'),
|
||||
|
@ -1091,12 +1095,12 @@ class PmsConnect(object):
|
|||
|
||||
return output
|
||||
|
||||
"""
|
||||
Return the list of local servers.
|
||||
|
||||
Output: array
|
||||
"""
|
||||
def get_servers_info(self):
|
||||
"""
|
||||
Return the list of local servers.
|
||||
|
||||
Output: array
|
||||
"""
|
||||
recent = self.get_server_list(output_format='xml')
|
||||
|
||||
try:
|
||||
|
@ -1118,12 +1122,12 @@ class PmsConnect(object):
|
|||
|
||||
return server_info
|
||||
|
||||
"""
|
||||
Return the local machine identity.
|
||||
|
||||
Output: dict
|
||||
"""
|
||||
def get_server_identity(self):
|
||||
"""
|
||||
Return the local machine identity.
|
||||
|
||||
Output: dict
|
||||
"""
|
||||
identity = self.get_local_server_identity(output_format='xml')
|
||||
|
||||
try:
|
||||
|
@ -1140,14 +1144,14 @@ class PmsConnect(object):
|
|||
|
||||
return server_identity
|
||||
|
||||
"""
|
||||
Return a specified server preference.
|
||||
|
||||
Parameters required: pref { name of preference }
|
||||
|
||||
Output: string
|
||||
"""
|
||||
def get_server_pref(self, pref=None):
|
||||
"""
|
||||
Return a specified server preference.
|
||||
|
||||
Parameters required: pref { name of preference }
|
||||
|
||||
Output: string
|
||||
"""
|
||||
if pref:
|
||||
prefs = self.get_server_prefs(output_format='xml')
|
||||
|
||||
|
@ -1168,12 +1172,12 @@ class PmsConnect(object):
|
|||
logger.debug(u"Server preferences queried but no parameter received.")
|
||||
return None
|
||||
|
||||
"""
|
||||
Return processed and validated server libraries list.
|
||||
|
||||
Output: array
|
||||
"""
|
||||
def get_server_children(self):
|
||||
"""
|
||||
Return processed and validated server libraries list.
|
||||
|
||||
Output: array
|
||||
"""
|
||||
libraries_data = self.get_libraries_list(output_format='xml')
|
||||
|
||||
try:
|
||||
|
@ -1207,18 +1211,18 @@ class PmsConnect(object):
|
|||
'title': helpers.get_xml_attr(xml_head[0], 'title1'),
|
||||
'libraries_list': libraries_list
|
||||
}
|
||||
|
||||
|
||||
return output
|
||||
|
||||
"""
|
||||
Return processed and validated server library items list.
|
||||
def get_library_children(self, library_type='', section_key='', list_type='all', sort_type=''):
|
||||
"""
|
||||
Return processed and validated server library items list.
|
||||
|
||||
Parameters required: library_type { movie, show, episode, artist }
|
||||
section_key { unique library key }
|
||||
Parameters required: library_type { movie, show, episode, artist }
|
||||
section_key { unique library key }
|
||||
|
||||
Output: array
|
||||
"""
|
||||
def get_library_children(self, library_type='', section_key='', list_type='all', sort_type = ''):
|
||||
Output: array
|
||||
"""
|
||||
|
||||
# Currently only grab the library with 1 items so 'size' is not 0
|
||||
count = '1'
|
||||
|
@ -1233,7 +1237,7 @@ class PmsConnect(object):
|
|||
list_type = 'albums'
|
||||
|
||||
library_data = self.get_library_list(section_key, list_type, count, sort_type, output_format='xml')
|
||||
|
||||
|
||||
try:
|
||||
xml_head = library_data.getElementsByTagName('MediaContainer')
|
||||
except:
|
||||
|
@ -1268,12 +1272,12 @@ class PmsConnect(object):
|
|||
|
||||
return output
|
||||
|
||||
"""
|
||||
Return processed and validated library statistics.
|
||||
|
||||
Output: array
|
||||
"""
|
||||
def get_library_stats(self, library_cards=''):
|
||||
"""
|
||||
Return processed and validated library statistics.
|
||||
|
||||
Output: array
|
||||
"""
|
||||
server_libraries = self.get_server_children()
|
||||
|
||||
server_library_stats = []
|
||||
|
@ -1315,16 +1319,16 @@ class PmsConnect(object):
|
|||
|
||||
return server_library_stats
|
||||
|
||||
"""
|
||||
Return image data as array.
|
||||
Array contains the image content type and image binary
|
||||
|
||||
Parameters required: img { Plex image location }
|
||||
Optional parameters: width { the image width }
|
||||
height { the image height }
|
||||
Output: array
|
||||
"""
|
||||
def get_image(self, img=None, width=None, height=None):
|
||||
"""
|
||||
Return image data as array.
|
||||
Array contains the image content type and image binary
|
||||
|
||||
Parameters required: img { Plex image location }
|
||||
Optional parameters: width { the image width }
|
||||
height { the image height }
|
||||
Output: array
|
||||
"""
|
||||
if img:
|
||||
if width.isdigit() and height.isdigit():
|
||||
uri = '/photo/:/transcode?url=http://127.0.0.1:32400' + img + '&width=' + width + '&height=' + height
|
||||
|
@ -1340,3 +1344,67 @@ class PmsConnect(object):
|
|||
else:
|
||||
logger.error("Image proxy queries but no input received.")
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def discover():
|
||||
""" Tries to find all pms on a local network """
|
||||
# setup socket for discovery -> multicast message
|
||||
logger.debug("Trying to find all pms on your network")
|
||||
GDM = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
GDM.settimeout(1.0)
|
||||
|
||||
# Set the time-to-live for messages to 1 for local network
|
||||
GDM.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
|
||||
responses = []
|
||||
try:
|
||||
# Send data to the multicast group
|
||||
GDM.sendto('M-SEARCH * HTTP/1.0', ('<broadcast>', 32414))
|
||||
|
||||
while True:
|
||||
try:
|
||||
data, server = GDM.recvfrom(1024)
|
||||
responses.append({'from': server, 'data': data})
|
||||
|
||||
except socket.timeout:
|
||||
break
|
||||
|
||||
finally:
|
||||
GDM.close()
|
||||
|
||||
trash = []
|
||||
servers = []
|
||||
if responses:
|
||||
for response in responses:
|
||||
if '200 OK' in response.get('data'):
|
||||
ip = response.get('from')[0]
|
||||
# Filter out on uuid so we only get unique servers
|
||||
check = response.get('data').split('\r\n')
|
||||
uuid = check[1:-2][4].split(': ')[1]
|
||||
if uuid not in trash:
|
||||
trash.append(uuid)
|
||||
d = {'server': ip}
|
||||
for each in response.get('data').split('\r\n'):
|
||||
if 'Content-Type:' in each:
|
||||
d['content-type'] = each.split(':')[1].strip()
|
||||
elif 'Resource-Identifier:' in each:
|
||||
d['uuid'] = each.split(':')[1].strip()
|
||||
elif 'Name:' in each:
|
||||
d['servername'] = each.split(':')[1].strip().decode('utf-8', 'replace')
|
||||
elif 'Port:' in each:
|
||||
d['port'] = each.split(':')[1].strip()
|
||||
elif 'Updated-At:' in each:
|
||||
d['updated'] = each.split(':')[1].strip()
|
||||
elif 'Version:' in each:
|
||||
d['version'] = each.split(':')[1].strip()
|
||||
elif 'Server-Class:' in each:
|
||||
d['class'] = each.split(':')[1].strip()
|
||||
|
||||
servers.append(d)
|
||||
else:
|
||||
logger.debug("Didn't find any Plex Media Servers :(")
|
||||
|
||||
logger.debug("Found %s servers" % len(servers))
|
||||
logger.debug(servers)
|
||||
|
||||
return servers
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
# This file is part of PlexPy.
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This file is part of PlexPy.
|
||||
#
|
||||
# PlexPy is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -14,7 +17,7 @@
|
|||
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from plexpy import logger, notifiers, plextv, pmsconnect, common, log_reader, datafactory, graphs, users
|
||||
from plexpy.helpers import checked, radio
|
||||
from plexpy.helpers import checked
|
||||
|
||||
from mako.lookup import TemplateLookup
|
||||
from mako import exceptions
|
||||
|
@ -74,6 +77,8 @@ class WebInterface(object):
|
|||
|
||||
@cherrypy.expose
|
||||
def welcome(self, **kwargs):
|
||||
servers = pmsconnect.PmsConnect.discover()
|
||||
|
||||
config = {
|
||||
"launch_browser": checked(plexpy.CONFIG.LAUNCH_BROWSER),
|
||||
"refresh_users_on_startup": checked(plexpy.CONFIG.REFRESH_USERS_ON_STARTUP),
|
||||
|
@ -102,7 +107,7 @@ class WebInterface(object):
|
|||
plexpy.initialize_scheduler()
|
||||
raise cherrypy.HTTPRedirect("home")
|
||||
else:
|
||||
return serve_template(templatename="welcome.html", title="Welcome", config=config)
|
||||
return serve_template(templatename="welcome.html", title="Welcome", config=config, servers=servers)
|
||||
|
||||
@cherrypy.expose
|
||||
def get_date_formats(self):
|
||||
|
@ -557,7 +562,7 @@ class WebInterface(object):
|
|||
@cherrypy.expose
|
||||
def get_history(self, user=None, user_id=None, **kwargs):
|
||||
|
||||
custom_where=[]
|
||||
custom_where = []
|
||||
if user_id:
|
||||
custom_where = [['user_id', user_id]]
|
||||
elif user:
|
||||
|
@ -694,7 +699,7 @@ class WebInterface(object):
|
|||
try:
|
||||
pms_connect = pmsconnect.PmsConnect()
|
||||
result = pms_connect.get_current_activity()
|
||||
except IOError, e:
|
||||
except IOError:
|
||||
return serve_template(templatename="current_activity_header.html", data=None)
|
||||
|
||||
if result:
|
||||
|
@ -709,7 +714,7 @@ class WebInterface(object):
|
|||
try:
|
||||
pms_connect = pmsconnect.PmsConnect()
|
||||
result = pms_connect.get_recently_added_details(count)
|
||||
except IOError, e:
|
||||
except IOError:
|
||||
return serve_template(templatename="recently_added.html", data=None)
|
||||
|
||||
if result:
|
||||
|
@ -871,7 +876,7 @@ class WebInterface(object):
|
|||
@cherrypy.expose
|
||||
def get_user_ips(self, user_id=None, user=None, **kwargs):
|
||||
|
||||
custom_where=[]
|
||||
custom_where = []
|
||||
if user_id:
|
||||
custom_where = [['user_id', user_id]]
|
||||
elif user:
|
||||
|
@ -1105,7 +1110,7 @@ class WebInterface(object):
|
|||
|
||||
pms_connect = pmsconnect.PmsConnect()
|
||||
result = pms_connect.get_server_children()
|
||||
|
||||
|
||||
if result:
|
||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
return json.dumps(result)
|
||||
|
@ -1220,6 +1225,9 @@ class WebInterface(object):
|
|||
def get_server_id(self, hostname=None, port=None, **kwargs):
|
||||
from plexpy import http_handler
|
||||
|
||||
if ':' in hostname:
|
||||
hostname, port = hostname.split(':')
|
||||
|
||||
if hostname and port:
|
||||
request_handler = http_handler.HTTPHandler(host=hostname,
|
||||
port=port,
|
||||
|
@ -1322,4 +1330,3 @@ class WebInterface(object):
|
|||
else:
|
||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
return json.dumps({'message': 'no data received'})
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue