Merge branch 'v2.5-monitor-remote-access' into nightly

This commit is contained in:
JonnyWong16 2020-07-10 17:09:58 -07:00
commit b4a10adec2
No known key found for this signature in database
GPG key ID: B1F1F9807184697A
9 changed files with 92 additions and 163 deletions

View file

@ -28,7 +28,7 @@ DOCUMENTATION :: END
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
% for job in common.SCHEDULER_LIST: % for job, job_type in common.SCHEDULER_LIST.items():
% if job in scheduled_jobs: % if job in scheduled_jobs:
<% <%
sched_job = plexpy.SCHED.get_job(job) sched_job = plexpy.SCHED.get_job(job)
@ -41,7 +41,7 @@ DOCUMENTATION :: END
<td>${helpers.format_timedelta_Hms(sched_job.next_run_time - now)}</td> <td>${helpers.format_timedelta_Hms(sched_job.next_run_time - now)}</td>
<td>${sched_job.next_run_time.astimezone(plexpy.SYS_TIMEZONE).strftime('%Y-%m-%d %H:%M:%S')}</td> <td>${sched_job.next_run_time.astimezone(plexpy.SYS_TIMEZONE).strftime('%Y-%m-%d %H:%M:%S')}</td>
</tr> </tr>
% elif job in ('Check for server response', 'Check for active sessions', 'Check for recently added items') and plexpy.WS_CONNECTED: % elif job_type == 'websocket' and plexpy.WS_CONNECTED:
<tr> <tr>
% if job == 'Check for active sessions': % if job == 'Check for active sessions':
<td><a class="queue-modal-link" href="#" data-queue="active sessions">${job}</a></td> <td><a class="queue-modal-link" href="#" data-queue="active sessions">${job}</a></td>

View file

@ -775,7 +775,6 @@
<label> <label>
<input type="checkbox" class="pms-settings" id="pms_url_manual" name="pms_url_manual" value="1" ${config['pms_url_manual']}> Manual Connection <input type="checkbox" class="pms-settings" id="pms_url_manual" name="pms_url_manual" value="1" ${config['pms_url_manual']}> Manual Connection
</label> </label>
<span id="cloudManualConnection" style="display: none; color: #eb8600; padding-left: 10px;"> Not available for Plex Cloud servers.</span>
<p class="help-block">Use the user defined connection details. Do not retrieve the server connection URL automatically.</p> <p class="help-block">Use the user defined connection details. Do not retrieve the server connection URL automatically.</p>
</div> </div>
<div class="form-group advanced-setting"> <div class="form-group advanced-setting">
@ -832,7 +831,6 @@
<label> <label>
<input type="checkbox" id="monitor_pms_updates" name="monitor_pms_updates" value="1" ${config['monitor_pms_updates']}> Monitor Plex Updates <input type="checkbox" id="monitor_pms_updates" name="monitor_pms_updates" value="1" ${config['monitor_pms_updates']}> Monitor Plex Updates
</label> </label>
<span id="cloudMonitorUpdates" style="display: none; color: #eb8600; padding-left: 10px;"> Not available for Plex Cloud servers.</span>
<p class="help-block">Enable to have Tautulli check if updates are available for the Plex Media Server.</p> <p class="help-block">Enable to have Tautulli check if updates are available for the Plex Media Server.</p>
</div> </div>
<div id="pms_update_options"> <div id="pms_update_options">
@ -866,36 +864,6 @@
</p> </p>
</div> </div>
</div> </div>
<div class="checkbox">
<label>
<input type="checkbox" id="monitor_remote_access" name="monitor_remote_access" value="1" ${config['monitor_remote_access']}> Monitor Plex Remote Access
</label>
<span id="cloudMonitorRemoteAccess" style="display: none; color: #eb8600; padding-left: 10px;"> Not available for Plex Cloud servers.</span>
<span id="remoteAccessCheck" class="settings-warning"></span>
<p class="help-block">Enable to have Tautulli check if remote access to the Plex Media Server goes down.</p>
</div>
<div id="monitor_remote_access_options">
<div class="form-group advanced-setting">
<label for="remote_access_ping_interval">Remote Access Ping Interval</label>
<div class="row">
<div class="col-md-2">
<input type="text" class="form-control" data-parsley-type="integer" id="remote_access_ping_interval" name="remote_access_ping_interval" value="${config['remote_access_ping_interval']}" size="5" data-parsley-min="60" data-parsley-trigger="change" data-parsley-errors-container="#remote_access_ping_interval_error" required>
</div>
<div id="remote_access_ping_interval_error" class="alert alert-danger settings-alert" role="alert"></div>
</div>
<p class="help-block">The interval (in seconds) Tautulli will ping the Plex Media Server for the remote access status. Minimum 60.</p>
</div>
<div class="form-group advanced-setting">
<label for="remote_access_ping_threshold">Remote Access Ping Threshold</label>
<div class="row">
<div class="col-md-2">
<input type="text" class="form-control" data-parsley-type="integer" id="remote_access_ping_threshold" name="remote_access_ping_threshold" value="${config['remote_access_ping_threshold']}" size="5" data-parsley-min="1" data-parsley-trigger="change" data-parsley-errors-container="#remote_access_ping_threshold_error" required>
</div>
<div id="remote_access_ping_threshold_error" class="alert alert-danger settings-alert" role="alert"></div>
</div>
<p class="help-block">The number of consecutive remote access status failures to consider remote access as down. Minimum 1.</p>
</div>
</div>
<div class="form-group advanced-setting"> <div class="form-group advanced-setting">
<label for="refresh_users_interval">Users List Refresh Interval</label> <label for="refresh_users_interval">Users List Refresh Interval</label>
@ -2158,7 +2126,6 @@ $(document).ready(function() {
initConfigCheckbox('#https_create_cert'); initConfigCheckbox('#https_create_cert');
initConfigCheckbox('#check_github'); initConfigCheckbox('#check_github');
initConfigCheckbox('#monitor_pms_updates'); initConfigCheckbox('#monitor_pms_updates');
initConfigCheckbox('#monitor_remote_access');
initConfigCheckbox('#newsletter_self_hosted'); initConfigCheckbox('#newsletter_self_hosted');
$('#menu_link_shutdown').click(function() { $('#menu_link_shutdown').click(function() {
@ -2404,7 +2371,6 @@ $(document).ready(function() {
$('#pms_is_cloud').val(is_cloud !== 'undefined' && is_cloud === true ? 1 : 0); $('#pms_is_cloud').val(is_cloud !== 'undefined' && is_cloud === true ? 1 : 0);
$('#pms_url_manual').prop('checked', false); $('#pms_url_manual').prop('checked', false);
$('#pms_url').val('Please verify your server above to retrieve the URL'); $('#pms_url').val('Please verify your server above to retrieve the URL');
PMSCloudCheck();
}, },
onDropdownOpen: function() { onDropdownOpen: function() {
this.clear(); this.clear();
@ -2435,38 +2401,6 @@ $(document).ready(function() {
} }
getServerOptions(); getServerOptions();
function PMSCloudCheck() {
if ($('#pms_is_cloud').val() === "1") {
$('#pms_port').val(443).prop('readonly', true);
$('#pms_is_remote_checkbox').prop('checked', true).prop('disabled', true);
$('#pms_is_remote').val(1);
$('#pms_ssl_checkbox').prop('checked', true).prop('disabled', true);
$('#pms_ssl').val(1);
$('#pms_url_manual').prop('checked', false).prop('disabled', true);
$('#monitor_pms_updates').prop('checked', false).prop('disabled', true);
$('#pms_update_options').hide();
$('#monitor_remote_access').prop('checked', false).prop('disabled', true);
$('#cloudManualConnection').show();
$('#cloudMonitorUpdates').show();
$('#cloudMonitorRemoteAccess').show();
$('#remoteAccessCheck').hide();
} else {
$('#pms_port').prop('readonly', false);
$('#pms_is_remote_checkbox').prop('disabled', false);
$('#pms_is_remote').val($('#pms_is_remote_checkbox').is(':checked') ? 1 : 0);
$('#pms_ssl_checkbox').prop('disabled', false);
$('#pms_ssl').val($('#pms_ssl_checkbox').is(':checked') ? 1 : 0);
$('#pms_url_manual').prop('disabled', false);
$('#monitor_pms_updates').prop('disabled', false);
$('#monitor_remote_access').prop('disabled', false);
$('#cloudManualConnection').hide();
$('#cloudMonitorUpdates').hide();
$('#cloudMonitorRemoteAccess').hide();
remoteAccessEnabledCheck()
}
}
PMSCloudCheck();
function verifyServer(_callback) { function verifyServer(_callback) {
var pms_ip = $("#pms_ip").val(); var pms_ip = $("#pms_ip").val();
var pms_port = $("#pms_port").val(); var pms_port = $("#pms_port").val();
@ -2583,21 +2517,6 @@ $(document).ready(function() {
pms_logs_debug = false; pms_logs_debug = false;
pms_logs = false; pms_logs = false;
function remoteAccessEnabledCheck() {
$.ajax({
url: 'get_server_pref',
data: { pref: 'PublishServerOnPlexOnlineKey' },
async: true,
success: function(data) {
if (data === 'false' || data === '0') {
$("#remoteAccessCheck").html("Remote access must be enabled on your Plex Server. <a target='_blank' href='${anon_url('https://support.plex.tv/hc/en-us/articles/200484543-Enabling-Remote-Access-for-a-Server')}'>Click here</a> for help.");
$("#monitor_remote_access").attr("checked", false).attr("disabled", true);
}
}
});
}
remoteAccessEnabledCheck();
// Sortable home_sections // Sortable home_sections
function set_home_sections() { function set_home_sections() {
var home_sections = []; var home_sections = [];

View file

@ -136,6 +136,7 @@ DEV = False
WEBSOCKET = None WEBSOCKET = None
WS_CONNECTED = False WS_CONNECTED = False
PLEX_SERVER_UP = None PLEX_SERVER_UP = None
PLEX_REMOTE_ACCESS_UP = None
TRACKER = None TRACKER = None
@ -443,10 +444,6 @@ def initialize_scheduler():
schedule_job(plextv.get_server_resources, 'Refresh Plex server URLs', schedule_job(plextv.get_server_resources, 'Refresh Plex server URLs',
hours=12 * (not bool(CONFIG.PMS_URL_MANUAL)), minutes=0, seconds=0) hours=12 * (not bool(CONFIG.PMS_URL_MANUAL)), minutes=0, seconds=0)
pms_remote_access_seconds = CONFIG.REMOTE_ACCESS_PING_INTERVAL if 60 <= CONFIG.REMOTE_ACCESS_PING_INTERVAL else 60
schedule_job(activity_pinger.check_server_access, 'Check for Plex remote access',
hours=0, minutes=0, seconds=pms_remote_access_seconds * bool(CONFIG.MONITOR_REMOTE_ACCESS))
schedule_job(activity_pinger.check_server_updates, 'Check for Plex updates', schedule_job(activity_pinger.check_server_updates, 'Check for Plex updates',
hours=pms_update_check_hours * bool(CONFIG.MONITOR_PMS_UPDATES), minutes=0, seconds=0) hours=pms_update_check_hours * bool(CONFIG.MONITOR_PMS_UPDATES), minutes=0, seconds=0)
@ -469,8 +466,6 @@ def initialize_scheduler():
schedule_job(plextv.get_server_resources, 'Refresh Plex server URLs', schedule_job(plextv.get_server_resources, 'Refresh Plex server URLs',
hours=0, minutes=0, seconds=0) hours=0, minutes=0, seconds=0)
schedule_job(activity_pinger.check_server_access, 'Check for Plex remote access',
hours=0, minutes=0, seconds=0)
schedule_job(activity_pinger.check_server_updates, 'Check for Plex updates', schedule_job(activity_pinger.check_server_updates, 'Check for Plex updates',
hours=0, minutes=0, seconds=0) hours=0, minutes=0, seconds=0)

View file

@ -505,6 +505,55 @@ class TimelineHandler(object):
schedule_callback('rating_key-{}'.format(rating_key), remove_job=True) schedule_callback('rating_key-{}'.format(rating_key), remove_job=True)
class ReachabilityHandler(object):
def __init__(self, data):
self.data = data
def is_reachable(self):
if 'reachability' in self.data:
return self.data['reachability']
return False
def remote_access_enabled(self):
pms_connect = pmsconnect.PmsConnect()
pref = pms_connect.get_server_pref(pref='PublishServerOnPlexOnlineKey')
return helpers.bool_true(pref)
def process(self):
# Check if remote access is enabled
if not self.remote_access_enabled():
return
# Do nothing if remote access is still up and hasn't changed
if self.is_reachable() and plexpy.PLEX_REMOTE_ACCESS_UP:
return
pms_connect = pmsconnect.PmsConnect()
server_response = pms_connect.get_server_response()
if server_response:
# Waiting for port mapping
if server_response['mapping_state'] == 'waiting':
logger.warn("Tautulli Monitor :: Remote access waiting for port mapping.")
elif plexpy.PLEX_REMOTE_ACCESS_UP is not False and server_response['reason']:
logger.warn("Tautulli Monitor :: Remote access failed: %s" % server_response['reason'])
logger.info("Tautulli Monitor :: Plex remote access is down.")
plexpy.PLEX_REMOTE_ACCESS_UP = False
plexpy.NOTIFY_QUEUE.put({'notify_action': 'on_extdown', 'remote_access_info': server_response})
elif plexpy.PLEX_REMOTE_ACCESS_UP is False and not server_response['reason']:
logger.info("Tautulli Monitor :: Plex remote access is back up.")
plexpy.PLEX_REMOTE_ACCESS_UP = True
plexpy.NOTIFY_QUEUE.put({'notify_action': 'on_extup', 'remote_access_info': server_response})
elif plexpy.PLEX_REMOTE_ACCESS_UP is None:
plexpy.PLEX_REMOTE_ACCESS_UP = self.is_reachable()
def del_keys(key): def del_keys(key):
if isinstance(key, set): if isinstance(key, set):
for child_key in key: for child_key in key:

View file

@ -318,47 +318,6 @@ def connect_server(log=True, startup=False):
logger.error("Websocket :: Unable to open connection: %s." % e) logger.error("Websocket :: Unable to open connection: %s." % e)
def check_server_access():
with monitor_lock:
pms_connect = pmsconnect.PmsConnect()
server_response = pms_connect.get_server_response()
global ext_ping_count
global ext_ping_error
# Check for remote access
if server_response:
log = (server_response['mapping_error'] != ext_ping_error)
if server_response['reason']:
ext_ping_count += 1
ext_ping_error = server_response['mapping_error']
if log:
logger.warn("Tautulli Monitor :: Remote access failed: %s, ping attempt %s."
% (server_response['reason'], str(ext_ping_count)))
# Waiting for port mapping
elif server_response['mapping_state'] == 'waiting':
ext_ping_error = server_response['mapping_error']
if log:
logger.warn("Tautulli Monitor :: Remote access waiting for port mapping, ping attempt %s."
% str(ext_ping_count))
# Reset external ping counter
else:
if ext_ping_count >= plexpy.CONFIG.REMOTE_ACCESS_PING_THRESHOLD:
logger.info("Tautulli Monitor :: Plex remote access is back up.")
plexpy.NOTIFY_QUEUE.put({'notify_action': 'on_extup', 'remote_access_info': server_response})
ext_ping_count = 0
ext_ping_error = None
if ext_ping_count == plexpy.CONFIG.REMOTE_ACCESS_PING_THRESHOLD:
logger.info("Tautulli Monitor: Plex remote access is down.")
plexpy.NOTIFY_QUEUE.put({'notify_action': 'on_extdown', 'remote_access_info': server_response})
def check_server_updates(): def check_server_updates():
with monitor_lock: with monitor_lock:

View file

@ -217,18 +217,19 @@ EXTRA_TYPES = {
} }
SCHEDULER_LIST = [ SCHEDULER_LIST = [
'Check GitHub for updates', ('Check GitHub for updates', 'websocket'),
'Check for server response', ('Check for server response', 'websocket'),
'Check for active sessions', ('Check for active sessions', 'websocket'),
'Check for recently added items', ('Check for recently added items', 'websocket'),
'Check for Plex updates', ('Check for server remote access', 'websocket'),
'Check for Plex remote access', ('Check for Plex updates', 'scheduled'),
'Refresh users list', ('Refresh users list', 'scheduled'),
'Refresh libraries list', ('Refresh libraries list', 'scheduled'),
'Refresh Plex server URLs', ('Refresh Plex server URLs', 'scheduled'),
'Backup Tautulli database', ('Backup Tautulli database', 'scheduled'),
'Backup Tautulli config' ('Backup Tautulli config', 'scheduled')
] ]
SCHEDULER_LIST = OrderedDict(SCHEDULER_LIST)
DATE_TIME_FORMATS = [ DATE_TIME_FORMATS = [
{ {

View file

@ -2971,8 +2971,6 @@ class PmsConnect(object):
return key_list return key_list
def get_server_response(self): def get_server_response(self):
# Refresh Plex remote access port mapping first
self.put_refresh_reachability()
account_data = self.get_account(output_format='xml') account_data = self.get_account(output_format='xml')
try: try:

View file

@ -255,42 +255,55 @@ def process(opcode, data):
try: try:
data = data.decode('utf-8') data = data.decode('utf-8')
logger.websocket_debug(data) logger.websocket_debug(data)
info = json.loads(data) event = json.loads(data)
except Exception as e: except Exception as e:
logger.warn("Tautulli WebSocket :: Error decoding message from websocket: %s" % e) logger.warn("Tautulli WebSocket :: Error decoding message from websocket: %s" % e)
logger.websocket_error(data) logger.websocket_error(data)
return False return False
info = info.get('NotificationContainer', info) event = event.get('NotificationContainer', event)
info_type = info.get('type') event_type = event.get('type')
if not info_type: if not event_type:
return False return False
if info_type == 'playing': if event_type == 'playing':
time_line = info.get('PlaySessionStateNotification', info.get('_children', {})) event_data = event.get('PlaySessionStateNotification', event.get('_children', {}))
if not time_line: if not event_data:
logger.debug("Tautulli WebSocket :: Session found but unable to get timeline data.") logger.debug("Tautulli WebSocket :: Session event found but unable to get websocket data.")
return False return False
try: try:
activity = activity_handler.ActivityHandler(timeline=time_line[0]) activity = activity_handler.ActivityHandler(timeline=event_data[0])
activity.process() activity.process()
except Exception as e: except Exception as e:
logger.exception("Tautulli WebSocket :: Failed to process session data: %s." % e) logger.exception("Tautulli WebSocket :: Failed to process session data: %s." % e)
if info_type == 'timeline': if event_type == 'timeline':
time_line = info.get('TimelineEntry', info.get('_children', {})) event_data = event.get('TimelineEntry', event.get('_children', {}))
if not time_line: if not event_data:
logger.debug("Tautulli WebSocket :: Timeline event found but unable to get timeline data.") logger.debug("Tautulli WebSocket :: Timeline event found but unable to get websocket data.")
return False return False
try: try:
activity = activity_handler.TimelineHandler(timeline=time_line[0]) activity = activity_handler.TimelineHandler(timeline=event_data[0])
activity.process() activity.process()
except Exception as e: except Exception as e:
logger.exception("Tautulli WebSocket :: Failed to process timeline data: %s." % e) logger.exception("Tautulli WebSocket :: Failed to process timeline data: %s." % e)
if event_type == 'reachability':
event_data = event.get('ReachabilityNotification', event.get('_children', {}))
if not event_data:
logger.debug("Tautulli WebSocket :: Reachability event found but unable to get websocket data.")
return False
try:
activity = activity_handler.ReachabilityHandler(data=event_data[0])
activity.process()
except Exception as e:
logger.exception("Tautulli WebSocket :: Failed to process reachability data: %s." % e)
return True return True

View file

@ -2997,9 +2997,6 @@ class WebInterface(object):
"grouping_user_history": checked(plexpy.CONFIG.GROUPING_USER_HISTORY), "grouping_user_history": checked(plexpy.CONFIG.GROUPING_USER_HISTORY),
"grouping_charts": checked(plexpy.CONFIG.GROUPING_CHARTS), "grouping_charts": checked(plexpy.CONFIG.GROUPING_CHARTS),
"monitor_pms_updates": checked(plexpy.CONFIG.MONITOR_PMS_UPDATES), "monitor_pms_updates": checked(plexpy.CONFIG.MONITOR_PMS_UPDATES),
"monitor_remote_access": checked(plexpy.CONFIG.MONITOR_REMOTE_ACCESS),
"remote_access_ping_interval": plexpy.CONFIG.REMOTE_ACCESS_PING_INTERVAL,
"remote_access_ping_threshold": plexpy.CONFIG.REMOTE_ACCESS_PING_THRESHOLD,
"refresh_libraries_interval": plexpy.CONFIG.REFRESH_LIBRARIES_INTERVAL, "refresh_libraries_interval": plexpy.CONFIG.REFRESH_LIBRARIES_INTERVAL,
"refresh_libraries_on_startup": checked(plexpy.CONFIG.REFRESH_LIBRARIES_ON_STARTUP), "refresh_libraries_on_startup": checked(plexpy.CONFIG.REFRESH_LIBRARIES_ON_STARTUP),
"refresh_users_interval": plexpy.CONFIG.REFRESH_USERS_INTERVAL, "refresh_users_interval": plexpy.CONFIG.REFRESH_USERS_INTERVAL,
@ -3077,7 +3074,7 @@ class WebInterface(object):
"refresh_libraries_on_startup", "refresh_users_on_startup", "refresh_libraries_on_startup", "refresh_users_on_startup",
"notify_consecutive", "notify_recently_added_upgrade", "notify_consecutive", "notify_recently_added_upgrade",
"notify_group_recently_added_grandparent", "notify_group_recently_added_parent", "notify_group_recently_added_grandparent", "notify_group_recently_added_parent",
"monitor_pms_updates", "monitor_remote_access", "get_file_sizes", "log_blacklist", "http_hash_password", "monitor_pms_updates", "get_file_sizes", "log_blacklist", "http_hash_password",
"allow_guest_access", "cache_images", "http_proxy", "http_basic_auth", "notify_concurrent_by_ip", "allow_guest_access", "cache_images", "http_proxy", "http_basic_auth", "notify_concurrent_by_ip",
"history_table_activity", "plexpy_auto_update", "history_table_activity", "plexpy_auto_update",
"themoviedb_lookup", "tvmaze_lookup", "musicbrainz_lookup", "http_plex_admin", "themoviedb_lookup", "tvmaze_lookup", "musicbrainz_lookup", "http_plex_admin",
@ -3130,8 +3127,6 @@ class WebInterface(object):
kwargs.get('refresh_users_interval') != str(plexpy.CONFIG.REFRESH_USERS_INTERVAL) or \ kwargs.get('refresh_users_interval') != str(plexpy.CONFIG.REFRESH_USERS_INTERVAL) or \
kwargs.get('pms_update_check_interval') != str(plexpy.CONFIG.PMS_UPDATE_CHECK_INTERVAL) or \ kwargs.get('pms_update_check_interval') != str(plexpy.CONFIG.PMS_UPDATE_CHECK_INTERVAL) or \
kwargs.get('monitor_pms_updates') != plexpy.CONFIG.MONITOR_PMS_UPDATES or \ kwargs.get('monitor_pms_updates') != plexpy.CONFIG.MONITOR_PMS_UPDATES or \
kwargs.get('monitor_remote_access') != plexpy.CONFIG.MONITOR_REMOTE_ACCESS or \
kwargs.get('remote_access_ping_interval') != str(plexpy.CONFIG.REMOTE_ACCESS_PING_INTERVAL) or \
kwargs.get('pms_url_manual') != plexpy.CONFIG.PMS_URL_MANUAL: kwargs.get('pms_url_manual') != plexpy.CONFIG.PMS_URL_MANUAL:
reschedule = True reschedule = True