Add mobile device settings

This commit is contained in:
JonnyWong16 2017-12-08 20:52:55 -08:00
parent 1ca1f9975c
commit 019787b32d
9 changed files with 343 additions and 173 deletions

View file

@ -2769,24 +2769,14 @@ a .home-platforms-list-cover-face:hover
}
.stacked-configs > li.new-notification-agent,
.stacked-configs > li.notification-agent,
.stacked-configs > li.add-notification-agent {
.stacked-configs > li.add-notification-agent,
.stacked-configs > li.mobile-device,
.stacked-configs > li.add-mobile-device {
cursor: pointer;
}
.stacked-configs > li.registered-device > span,
.stacked-configs > li.registered-device > span:hover,
.stacked-configs > li.registered-device > span:focus {
background-color: #282828;
}
.stacked-configs > li.registered-device > span > a.toggle-left,
.stacked-configs > li.registered-device > span > span.toggle-left {
.stacked-configs > li.mobile-device > span > a.toggle-left,
.stacked-configs > li.mobile-device > span > span.toggle-left {
color: #999;
padding-right: 8px;
}
.stacked-configs > li.registered-device > span > span.delete-mobile-device {
cursor: pointer;
}
.stacked-configs > li.registered-device > span > span.delete-mobile-device:hover {
color: #c9302c;
}
.accordion {
width: 100%;
@ -3433,7 +3423,9 @@ a:hover .overlay-refresh-image:hover {
margin-bottom: 0;
}
#plexpy-notifiers-table .friendly_name,
#notifier-config-modal span.notifier_id {
#notifier-config-modal span.notifier_id,
#plexpy-mobile-devices-table .friendly_name,
#mobile-device-config-modal span.notifier_id {
color: #777;
}
#notifier-config-modal .nav-tabs {

View file

@ -0,0 +1,114 @@
% if device:
<div class="modal-dialog" role="document">
<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="mobile-device-config-modal-header">${device['device_name']} Settings &nbsp;<small><span class="device_id">(Device ID: ${device['id']})</span></small></h4>
</div>
<div class="modal-body">
<div class="container-fluid">
<form action="set_mobile_device_config" method="post" class="form" id="set_mobile_device_config" data-parsley-validate>
<div class="row">
<div class="col-md-12">
<input type="hidden" id="mobile_device_id" name="mobile_device_id" value="${device['id']}" />
<div class="form-group">
<label for="friendly_name">Friendly Name</label>
<div class="row">
<div class="col-md-8">
<input type="text" class="form-control" id="friendly_name" name="friendly_name" value="${device['friendly_name'] or ''}" size="30">
</div>
</div>
<p class="help-block">Optional: Enter a friendly name for this device. Leave blank for default.</p>
</div>
</div>
</div>
</form>
</div>
</div>
<div class="modal-footer">
<input type="button" id="delete-mobile-device" class="btn btn-danger btn-edit" style="float:left;" value="Delete">
<input type="button" id="save-mobile-device" class="btn btn-bright" value="Save">
</div>
</div>
</div>
<script>
$('#mobile-device-config-modal').unbind('hidden.bs.modal');
function reloadModal() {
$.ajax({
url: 'get_mobile_device_config_modal',
data: { mobile_device_id: '${device["id"]}' },
cache: false,
async: true,
complete: function (xhr, status) {
$('#mobile-device-config-modal').html(xhr.responseText);
}
});
}
function saveCallback(jqXHR) {
if (jqXHR) {
var result = $.parseJSON(jqXHR.responseText);
var msg = result.message;
if (result.result == 'success') {
showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 5000)
} else {
showMsg('<i class="fa fa-times"></i> ' + msg, false, true, 5000, true)
}
}
getMobileDevicesTable();
}
function deleteCallback() {
$('#mobile-device-config-modal').modal('hide');
getMobileDevicesTable();
}
function saveMobileDevice() {
// Trim all text inputs before saving
$('input[type=text]').val(function(_, value) {
return $.trim(value);
});
// Reload modal to update certain fields
doAjaxCall('set_mobile_device_config', $(this), false, true, true, saveCallback);
}
$('#delete-mobile-device').click(function () {
var msg = 'Are you sure you want to unregister the device <strong>${device["device_name"]}</strong> from PlexPy?';
var url = 'delete_mobile_device';
confirmAjaxCall(url, msg, { mobile_device_id: '${device["id"]}' }, null, deleteCallback);
});
$('#save-mobile-device').click(function () {
saveMobileDevice();
});
// Never send checkbox values directly, always substitute value in hidden input.
$('.checkboxes').click(function () {
var configToggle = $(this).data('id');
if ($(this).is(':checked')) {
$('#'+configToggle).val(1);
} else {
$('#'+configToggle).val(0);
}
});
</script>
% else:
<div class="modal-dialog" role="document">
<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="mobile-device-config-modal-header">Error</h4>
</div>
<div class="modal-body">
<center><strong>
<i class="fa fa-exclamation-circle"></i> Failed to retrieve mobile device configuration. Check the <a href="logs">logs</a> for more info.
</strong></center>
</div>
<div class="modal-footer">
</div>
</div>
</div>
% endif

View file

@ -9,39 +9,126 @@ Version: 0.1
DOCUMENTATION :: END
</%doc>
% if devices_list:
<ul class="stacked-configs list-unstyled">
% for device in sorted(devices_list, key=lambda k: k['device_name']):
<li class="registered-device" data-id="${device['device_id']}" data-name="${device['device_name']}">
<li class="mobile-device" data-id="${device['id']}" data-name="${device['device_name']}">
<span>
<span class="toggle-left mobile-device-tooltip edit-mobile-device" data-toggle="tooltip" data-placement="top" title="Rename Device"><i class="fa fa-lg fa-pencil"></i></span>
<span class="mobile-device-name">${device['friendly_name'] or device['device_name']}</span>
<input type="text" class="edit-mobile-device-name hidden" value="${device['device_name']}" />
<span class="toggle-right delete-mobile-device" data-toggle="tooltip" data-placement="top" title="Remove Device"><i class="fa fa-lg fa-times"></i></span>
<!--<span class="toggle-right mobile-device-tooltip edit-mobile-device" data-toggle="tooltip" data-placement="top" title="Edit Device"><i class="fa fa-lg fa-pencil"></i></span>-->
<span class="toggle-left"><i class="fa fa-lg fa-mobile"></i></span>
${device['friendly_name'] or device['device_name']} &nbsp;<span class="friendly_name">(${device['id']})</span>
<span class="toggle-right"><i class="fa fa-lg fa-cog"></i></span>
<!--<span class="toggle-right delete-mobile-device" data-toggle="tooltip" data-placement="top" title="Remove Device"><i class="fa fa-lg fa-times"></i></span>-->
</span>
</li>
% endfor
</ul>
<script>
$('.mobile-device-tooltip').tooltip();
$('.delete-mobile-device').tooltip();
$('.delete-mobile-device').click(function () {
var device_id = $(this).closest('li').data('id');
var device_name = $(this).closest('li').data('name');
var msg = 'Are you sure you want to unregister the device <strong>' + device_name + '</strong> from PlexPy?';
var url = 'delete_mobile_device';
confirmAjaxCall(url, msg, { device_id: device_id }, null, getMobileDevicesTable);
});
</script>
% else:
<ul class="stacked-configs list-unstyled">
<li class="registered-device">
<li class="add-mobile-device" id="register-mobile-device" data-target="#api-qr-modal" data-toggle="modal">
<span>
<span class="toggle-left"><i class="fa fa-lg fa-mobile"></i></span>
No devices registered
<span class="toggle-left"><i class="fa fa-lg fa-mobile"></i></span> Register a new device
<span class="toggle-right"><i class="fa fa-lg fa-plus"></i></span>
</span>
</li>
</ul>
% endif
<script>
// Load notification agent config modal
$(".mobile-device").click(function () {
var mobile_device_id = $(this).data('id');
loadMobileDeviceConfig(mobile_device_id);
});
getPlexPyURL = function () {
var deferred = $.Deferred();
if (location.hostname !== "localhost" && location.hostname !== "127.0.0.1") {
deferred.resolve(location.href.split('/settings')[0]);
} else {
$.get('get_plexpy_url').then(function (url) {
deferred.resolve(url);
})
}
return deferred;
}
function checkQRAddress(url) {
var parser = document.createElement('a');
parser.href = url;
var hostname = parser.hostname;
var protocol = parser.protocol;
if (hostname === '127.0.0.1' || hostname === 'localhost') {
$('#api_qr_localhost').toggle(true);
$('#api_qr_private').toggle(false);
} else {
$('#api_qr_localhost').toggle(false);
isPrivateIP(hostname).then(function (valid) {
$('#api_qr_private').toggle((valid !== 'n/a'));
}, function () {
$('#api_qr_private').toggle(false);
});
}
$('#api_qr_https').toggle((protocol === 'http:'));
}
var verifiedDevice = false;
$('#register-mobile-device').click(function () {
verifiedDevice = false;
getPlexPyURL().then(function (url) {
checkQRAddress(url)
$.get('generate_api_key', { device: true }).then(function (token) {
$('#api_qr_address').val(url);
$('#api_qr_token').val(token);
$('#api_qr_code').empty().qrcode({
text: url + '|' + token
});
(function poll() {
setTimeout(function () {
$.ajax({
url: 'verify_mobile_device',
type: 'GET',
data: { device_token: token },
success: function (data) {
if (data.result === 'success') {
verifiedDevice = true;
getMobileDevicesTable();
$('#api-qr-modal').modal('hide');
showMsg('<i class="fa fa-check"></i> ' + data.message, false, true, 5000, false);
}
},
complete: function () {
if (!(verifiedDevice)) {
poll();
}
},
timeout: 1000
});
}, 1000);
})();
});
});
});
$('#api_qr_address').change(function () {
var url = $(this).val();
checkQRAddress(url)
$('#api_qr_code').empty().qrcode({
text: url + '|' + $('#api_qr_token').val()
});
});
$('#api-qr-modal').on('hide.bs.modal', function () {
if (!(verifiedDevice)) {
$.ajax({
url: 'verify_mobile_device',
type: 'GET',
data: { cancel: true },
success: function (data) {
showMsg('<i class="fa fa-times"></i> ' + data.message, false, true, 5000, false);
}
});
}
verifiedDevice = true;
})
</script>

View file

@ -23,7 +23,7 @@ DOCUMENTATION :: END
</span>
</li>
% endfor
<li class="add-notification-agent">
<li class="add-notification-agent" id="add-notification-agent" data-target="#add-notifier-modal" data-toggle="modal">
<span>
<span class="toggle-left"><i class="fa fa-lg fa-bell"></i></span> Add a new notification agent
<span class="toggle-right"><i class="fa fa-lg fa-plus"></i></span>
@ -32,11 +32,6 @@ DOCUMENTATION :: END
</ul>
<script>
// Load add notification agent modal
$(".add-notification-agent").click(function () {
$("#add-notifier-modal").modal('show');
});
// Load notification agent config modal
$(".notification-agent").click(function () {
var notifier_id = $(this).data('id');

View file

@ -49,7 +49,7 @@
<li role="presentation"><a href="#tabs-notification_agents" aria-controls="tabs-notification_agents" role="tab" data-toggle="tab">Notification Agents</a></li>
<li role="presentation"><a href="#tabs-extra_settings" aria-controls="tabs-extra_settings" role="tab" data-toggle="tab">Extra Settings</a></li>
<li role="presentation"><a href="#tabs-import_backups" aria-controls="tabs-import_backups" role="tab" data-toggle="tab">Import & Backups</a></li>
<li role="presentation"><a href="#tabs-android_app" aria-controls="tabs-android_app" role="tab" data-toggle="tab">PlexPy Android App</a></li>
<li role="presentation"><a href="#tabs-android_app" aria-controls="tabs-android_app" role="tab" data-toggle="tab">PlexPy Android App <sup><small>Beta</small></sup></a></li>
</ul>
</div>
<div class="col-md-9">
@ -1071,19 +1071,10 @@
</span>
</p>
</div>
<div class="form-group">
<label>Register Your Device</label>
<p class="help-block">Register your device to your PlexPy server by scanning a QR code.</p>
<div class="btn-group" style="display: table-cell; vertical-align: middle;">
<button class="btn btn-form" type="button" id="generate_qr" data-target="#api-qr-modal" data-toggle="modal">Generate QR Code</button>
</div>
<div style="display: table-cell; vertical-align: middle;">
<span style="color: #eb8600; padding-left: 10px;" id="generate_qr_msg">The API must be enabled under <a data-tab-destination="tabs-access_control" style="cursor: pointer;">Access Control</a> to use the app.</span>
</div>
</div>
<div class="form-group">
<p class="form-group">
<label>Registered Devices</label>
<p class="help-block">List of devices currently registered with the PlexPy server.</p>
<p class="help-block">Register a new device, or configure an existing device by clicking the settings icon on the right.</p>
<p id="app_api_msg" style="color: #eb8600;">The API must be enabled under <a data-tab-destination="tabs-access_control" style="cursor: pointer;">Access Control</a> to use the app.</p>
<div class="row">
<div id="plexpy-mobile-devices-table" class="col-md-6">
<div class='text-muted'><i class="fa fa-refresh fa-spin"></i> Loading registered devices...</div>
@ -1465,6 +1456,7 @@
</div>
</div>
</div>
<div id="mobile-device-config-modal" class="modal fade wide" tabindex="-1" role="dialog" aria-labelledby="mobile-device-config-modal"></div>
</%def>
<%def name="javascriptIncludes()">
@ -1539,6 +1531,18 @@
});
}
function loadMobileDeviceConfig(mobile_device_id) {
$.ajax({
url: 'get_mobile_device_config_modal',
data: { mobile_device_id: mobile_device_id },
cache: false,
async: true,
complete: function (xhr, status) {
$("#mobile-device-config-modal").html(xhr.responseText).modal('show');
}
});
}
$(document).ready(function() {
// Javascript to enable link to tab
@ -2075,109 +2079,13 @@ $(document).ready(function() {
function apiEnabled() {
var api_enabled = $('#api_enabled').prop('checked');
$('#generate_qr').prop('disabled', !(api_enabled));
$('#generate_qr_msg').toggle(!(api_enabled));
$('#app_api_msg').toggle(!(api_enabled));
}
apiEnabled();
$('#api_enabled').click(function () {
apiEnabled();
});
getPlexPyURL = function () {
var deferred = $.Deferred();
if (location.hostname !== "localhost" && location.hostname !== "127.0.0.1") {
deferred.resolve(location.href.split('/settings')[0]);
} else {
$.get('get_plexpy_url').then(function (url) {
deferred.resolve(url);
})
}
return deferred;
}
function checkQRAddress(url) {
var parser = document.createElement('a');
parser.href = url;
var hostname = parser.hostname;
var protocol = parser.protocol;
if (hostname === '127.0.0.1' || hostname === 'localhost') {
$('#api_qr_localhost').toggle(true);
$('#api_qr_private').toggle(false);
} else {
$('#api_qr_localhost').toggle(false);
isPrivateIP(hostname).then(function (valid) {
$('#api_qr_private').toggle((valid !== 'n/a'));
}, function () {
$('#api_qr_private').toggle(false);
});
}
$('#api_qr_https').toggle((protocol === 'http:'));
}
var verifiedDevice = false;
$('#generate_qr').click(function () {
getPlexPyURL().then(function (url) {
checkQRAddress(url)
$.get('generate_api_key', { device: true }).then(function (token) {
$('#api_qr_address').val(url);
$('#api_qr_token').val(token);
$('#api_qr_code').empty().qrcode({
text: url + '|' + token
});
(function poll(){
verifiedDevice = false;
setTimeout(function() {
$.ajax({
url: 'verify_mobile_device',
type: 'GET',
data: { device_token: token },
success: function(data) {
if (data.result === 'success') {
verifiedDevice = true;
getMobileDevicesTable();
$('#api-qr-modal').modal('hide');
showMsg('<i class="fa fa-check"></i> ' + data.message, false, true, 5000, false);
}
},
complete: function() {
if (!(verifiedDevice)) {
poll();
}
},
timeout: 1000
});
}, 1000);
})();
});
});
});
$('#api_qr_address').change(function () {
var url = $(this).val();
checkQRAddress(url)
$('#api_qr_code').empty().qrcode({
text: url + '|' + $('#api_qr_token').val()
});
});
$('#api-qr-modal').on('hidden.bs.modal', function () {
if (!(verifiedDevice)) {
$.ajax({
url: 'verify_mobile_device',
type: 'GET',
data: { cancel: true },
success: function(data) {
showMsg('<i class="fa fa-times"></i> ' + data.message, false, true, 5000, false);
}
});
}
verifiedDevice = true;
})
$('body').on('click', 'a[data-tab-destination]', function () {
var tab = $(this).data('tab-destination');
$("a[href=#" + tab + "]").click();

View file

@ -350,7 +350,7 @@ class API2:
return data
def register_device(self, device_id='', device_name='', **kwargs):
def register_device(self, device_id='', device_name='', friendly_name='', **kwargs):
""" Registers the PlexPy Android App for notifications.
```
@ -359,7 +359,7 @@ class API2:
device_id (str): The OneSignal device id of the PlexPy Android App
Optional parameters:
None
friendly_name (str): A friendly name to identify the mobile device
Returns:
None
@ -377,7 +377,8 @@ class API2:
result = mobile_app.add_mobile_device(device_id=device_id,
device_name=device_name,
device_token=self._api_apikey)
device_token=self._api_apikey,
friendly_name=friendly_name)
if result:
self._api_msg = 'Device registration successful.'

View file

@ -49,13 +49,16 @@ def get_mobile_device_by_token(device_token=None):
return get_mobile_devices(device_token=device_token)
def add_mobile_device(device_id=None, device_name=None, device_token=None):
def add_mobile_device(device_id=None, device_name=None, device_token=None, friendly_name=None):
db = database.MonitorDatabase()
keys = {'device_id': device_id}
values = {'device_name': device_name,
'device_token': device_token}
if friendly_name:
values['friendly_name'] = friendly_name
try:
result = db.upsert(table_name='mobile_devices', key_dict=keys, value_dict=values)
except Exception as e:
@ -70,12 +73,49 @@ def add_mobile_device(device_id=None, device_name=None, device_token=None):
return True
def delete_mobile_device(device_id=None):
def get_mobile_device_config(mobile_device_id=None):
if str(mobile_device_id).isdigit():
mobile_device_id = int(mobile_device_id)
else:
logger.error(u"PlexPy MobileApp :: Unable to retrieve mobile device config: invalid mobile_device_id %s." % mobile_device_id)
return None
db = database.MonitorDatabase()
result = db.select_single('SELECT * FROM mobile_devices WHERE id = ?',
args=[mobile_device_id])
return result
def set_mobile_device_config(mobile_device_id=None, **kwargs):
if str(mobile_device_id).isdigit():
mobile_device_id = int(mobile_device_id)
else:
logger.error(u"PlexPy MobileApp :: Unable to set exisiting mobile device: invalid mobile_device_id %s." % mobile_device_id)
return False
keys = {'id': mobile_device_id}
values = {}
if kwargs.get('friendly_name'):
values['friendly_name'] = kwargs['friendly_name']
db = database.MonitorDatabase()
try:
db.upsert(table_name='mobile_devices', key_dict=keys, value_dict=values)
logger.info(u"PlexPy MobileApp :: Updated mobile device agent: mobile_device_id %s." % mobile_device_id)
return True
except Exception as e:
logger.warn(u"PlexPy MobileApp :: Unable to update mobile device: %s." % e)
return False
def delete_mobile_device(mobile_device_id=None):
db = database.MonitorDatabase()
if device_id:
logger.debug(u"PlexPy MobileApp :: Deleting device_id %s from the database." % device_id)
result = db.action('DELETE FROM mobile_devices WHERE device_id = ?', args=[device_id])
if mobile_device_id:
logger.debug(u"PlexPy MobileApp :: Deleting device_id %s from the database." % mobile_device_id)
result = db.action('DELETE FROM mobile_devices WHERE id = ?', args=[mobile_device_id])
return True
else:
return False

View file

@ -3066,9 +3066,9 @@ class WebInterface(object):
result = notifiers.set_notifier_config(notifier_id=notifier_id, agent_id=agent_id, **kwargs)
if result:
return {'result': 'success', 'message': 'Added notification agent.'}
return {'result': 'success', 'message': 'Saved notification agent.'}
else:
return {'result': 'error', 'message': 'Failed to add notification agent.'}
return {'result': 'error', 'message': 'Failed to save notification agent.'}
@cherrypy.expose
@requireAuth(member_of("admin"))
@ -3277,16 +3277,49 @@ class WebInterface(object):
else:
return {'result': 'error', 'message': 'Device not registered.'}
@cherrypy.expose
@requireAuth(member_of("admin"))
def get_mobile_device_config_modal(self, mobile_device_id=None, **kwargs):
result = mobile_app.get_mobile_device_config(mobile_device_id=mobile_device_id)
return serve_template(templatename="mobile_device_config.html", device=result)
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))
@addtoapi()
def delete_mobile_device(self, device_id=None, **kwargs):
def set_mobile_device_config(self, mobile_device_id=None, **kwargs):
""" Configure an exisitng notificaiton agent.
```
Required parameters:
mobile_device_id (int): The mobile device config to update
Optional parameters:
friendly_name (str): A friendly name to identify the mobile device
Returns:
None
```
"""
result = mobile_app.set_mobile_device_config(mobile_device_id=mobile_device_id, **kwargs)
if result:
return {'result': 'success', 'message': 'Saved mobile device.'}
else:
return {'result': 'error', 'message': 'Failed to save mobile device.'}
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))
@addtoapi()
def delete_mobile_device(self, mobile_device_id=None, **kwargs):
""" Remove a mobile device from the database.
```
Required parameters:
device_id (int): The device to delete
mobile_device_id (int): The device id to delete
Optional parameters:
None
@ -3295,9 +3328,9 @@ class WebInterface(object):
None
```
"""
result = mobile_app.delete_mobile_device(device_id=device_id)
result = mobile_app.delete_mobile_device(mobile_device_id=mobile_device_id)
if result:
return {'result': 'success', 'message': 'Device deleted successfully.'}
return {'result': 'success', 'message': 'Deleted mobile device.'}
else:
return {'result': 'error', 'message': 'Failed to delete device.'}