Compare commits
34 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a7a9ed8628 | ||
![]() |
6af96332fa | ||
![]() |
e334a0fc8b | ||
![]() |
54bbbb36a6 | ||
![]() |
b8ef56574a | ||
![]() |
bc491628d4 | ||
![]() |
629800c239 | ||
![]() |
8bf876f88c | ||
![]() |
a81ad6d73e | ||
![]() |
02220209e3 | ||
![]() |
9450a1434d | ||
![]() |
c358693fb2 | ||
![]() |
e7b3d768ce | ||
![]() |
ee91da2ff1 | ||
![]() |
0428df8e3f | ||
![]() |
d4fee1d701 | ||
![]() |
3b44a3afd2 | ||
![]() |
7ee1c51810 | ||
![]() |
f958de2de6 | ||
![]() |
b83eb2e763 | ||
![]() |
41c9369b43 | ||
![]() |
554be92c39 | ||
![]() |
7486a50c33 | ||
![]() |
1e777b1a1b | ||
![]() |
b7bb159630 | ||
![]() |
6e0a0d51b5 | ||
![]() |
55aad4e6ee | ||
![]() |
d1e401cb0c | ||
![]() |
018479fae9 | ||
![]() |
1c18e72539 | ||
![]() |
779e710045 | ||
![]() |
089a981f6e | ||
![]() |
3b24bbee5f | ||
![]() |
f9a597bed9 |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,3 +1,3 @@
|
||||
github: JonnyWong16
|
||||
patreon: Tautulli
|
||||
custom: ["https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=6XPPKTDSX9QFL&lc=US&item_name=Tautulli¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_LG%2egif%3aNonHosted"]
|
||||
custom: ["https://bit.ly/2InPp15"]
|
28
CHANGELOG.md
28
CHANGELOG.md
@@ -1,5 +1,33 @@
|
||||
# Changelog
|
||||
|
||||
## v2.1.41 (2019-12-30)
|
||||
|
||||
* Other:
|
||||
* Fix: Failing to extract the GeoLite2 database on Windows.
|
||||
|
||||
|
||||
## v2.1.40 (2019-12-30)
|
||||
|
||||
* UI:
|
||||
* Change: Moved 3rd Party API settings to new tab in the settings.
|
||||
* Graphs:
|
||||
* Change: Improve calculating month ranges for Play Totals graphs.
|
||||
* Other:
|
||||
* Fix: Failing to verify a Plex Media Server using a hostname.
|
||||
* Change: A license key is now required to install the MaxMind GeoLite2 database for IP geolocation. Please follow the guide in the wiki to reinstall the GeoLite2 database.
|
||||
* Change: The GeoLite2 database will now automatically update periodically if installed.
|
||||
|
||||
|
||||
## v2.1.39 (2019-12-08)
|
||||
|
||||
* UI:
|
||||
* New: Added creating admin username and password to setup wizard.
|
||||
* API:
|
||||
* Change: Remove default notification subject and body for notify API command.
|
||||
* Other:
|
||||
* Change: Check for database corruption when making backup.
|
||||
|
||||
|
||||
## v2.1.38 (2019-11-17)
|
||||
|
||||
* Notifications:
|
||||
|
26
Tautulli.py
26
Tautulli.py
@@ -234,31 +234,19 @@ def main():
|
||||
plexpy.CONFIG.ENABLE_HTTPS = False
|
||||
|
||||
# Try to start the server. Will exit here is address is already in use.
|
||||
web_config = {
|
||||
'http_port': plexpy.HTTP_PORT,
|
||||
'http_host': plexpy.CONFIG.HTTP_HOST,
|
||||
'http_root': plexpy.CONFIG.HTTP_ROOT,
|
||||
'http_environment': plexpy.CONFIG.HTTP_ENVIRONMENT,
|
||||
'http_proxy': plexpy.CONFIG.HTTP_PROXY,
|
||||
'enable_https': plexpy.CONFIG.ENABLE_HTTPS,
|
||||
'https_cert': plexpy.CONFIG.HTTPS_CERT,
|
||||
'https_cert_chain': plexpy.CONFIG.HTTPS_CERT_CHAIN,
|
||||
'https_key': plexpy.CONFIG.HTTPS_KEY,
|
||||
'http_username': plexpy.CONFIG.HTTP_USERNAME,
|
||||
'http_password': plexpy.CONFIG.HTTP_PASSWORD,
|
||||
'http_basic_auth': plexpy.CONFIG.HTTP_BASIC_AUTH
|
||||
}
|
||||
webstart.initialize(web_config)
|
||||
webstart.start()
|
||||
|
||||
# Windows system tray icon
|
||||
if os.name == 'nt' and plexpy.CONFIG.WIN_SYS_TRAY:
|
||||
plexpy.win_system_tray()
|
||||
|
||||
logger.info("Tautulli is ready!")
|
||||
|
||||
# Open webbrowser
|
||||
if plexpy.CONFIG.LAUNCH_BROWSER and not args.nolaunch and not plexpy.DEV:
|
||||
plexpy.launch_browser(plexpy.CONFIG.HTTP_HOST, plexpy.HTTP_PORT,
|
||||
plexpy.HTTP_ROOT)
|
||||
|
||||
# Windows system tray icon
|
||||
if os.name == 'nt' and plexpy.CONFIG.WIN_SYS_TRAY:
|
||||
plexpy.win_system_tray()
|
||||
|
||||
# Wait endlessy for a signal to happen
|
||||
while True:
|
||||
if not plexpy.SIGNAL:
|
||||
|
@@ -53,14 +53,6 @@ DOCUMENTATION :: END
|
||||
<td>Newsletter Directory:</td>
|
||||
<td>${plexpy.CONFIG.NEWSLETTER_DIR}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>GeoLite2 Database:</td>
|
||||
% if plexpy.CONFIG.GEOIP_DB:
|
||||
<td>${plexpy.CONFIG.GEOIP_DB} | <a class="no-highlight" href="#" id="reinstall_geoip_db">Reinstall / Update</a> | <a class="no-highlight" href="#" id="uninstall_geoip_db">Uninstall</a></td>
|
||||
% else:
|
||||
<td><a class="no-highlight" href="#" id="install_geoip_db">Click here to install the GeoLite2 database.</a></td>
|
||||
% endif
|
||||
</tr>
|
||||
% if plexpy.ARGS:
|
||||
<tr>
|
||||
<td>Arguments:</td>
|
||||
@@ -102,22 +94,6 @@ DOCUMENTATION :: END
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$("#install_geoip_db, #reinstall_geoip_db").click(function () {
|
||||
var msg = 'Are you sure you want to install the GeoLite2 database?<br /><br />' +
|
||||
'The database is used to lookup IP address geolocation info.<br />' +
|
||||
'The database will be downloaded from <a href="${anon_url("https://dev.maxmind.com/geoip/geoip2/geolite2/")}" target="_blank">MaxMind</a>, <br />' +
|
||||
'and requires <strong>100MB</strong> of free space to install in your Tautulli directory.<br />'
|
||||
var url = 'install_geoip_db';
|
||||
confirmAjaxCall(url, msg, null, 'Installing GeoLite2 database.', getConfigurationTable);
|
||||
});
|
||||
|
||||
$("#uninstall_geoip_db").click(function () {
|
||||
var msg = 'Are you sure you want to uninstall the GeoLite2 database?<br /><br />' +
|
||||
'You will not be able to lookup IP address geolocation info.';
|
||||
var url = 'uninstall_geoip_db';
|
||||
confirmAjaxCall(url, msg, null, 'Uninstalling GeoLite2 database.', getConfigurationTable);
|
||||
});
|
||||
|
||||
$('.guidelines-modal-link').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
$('#guidelines-type').text($(this).data('id'))
|
||||
|
@@ -271,7 +271,7 @@
|
||||
</div>
|
||||
<p class="help-block">
|
||||
Select an existing notification agent where the subject and body text will be sent.<br>
|
||||
Note: Self-hosted newsletters must be enabled under <a data-tab-destination="tabs-notifications" data-dismiss="modal" data-target="#newsletter_self_hosted">Newsletters</a> to include a link to the newsletter.
|
||||
Note: Self-hosted newsletters must be enabled under <a data-tab-destination="notifications" data-dismiss="modal" data-target="newsletter_self_hosted">Newsletters</a> to include a link to the newsletter.
|
||||
</p>
|
||||
</div>
|
||||
<div id="newsletter-email-config">
|
||||
|
@@ -485,7 +485,7 @@
|
||||
'<div class="form-group">' +
|
||||
'<label>Warning</label>' +
|
||||
'<p class="help-block" style="color: #eb8600;">Facebook requires HTTPS for authorization. ' +
|
||||
'Please enable HTTPS for Tautulli under <a data-tab-destination="tabs-web_interface" data-dismiss="modal" data-target="#enable_https">Web Interface</a>.</p>' +
|
||||
'Please enable HTTPS for Tautulli under <a data-tab-destination="web_interface" data-dismiss="modal" data-target="enable_https">Web Interface</a>.</p>' +
|
||||
'</div>'
|
||||
);
|
||||
$('#facebook_redirect_uri').val('HTTPS not enabled');
|
||||
|
@@ -56,6 +56,7 @@
|
||||
<li role="presentation"><a href="#tabs-notifications" aria-controls="tabs-notifications" role="tab" data-toggle="tab">Notifications & Newsletters</a></li>
|
||||
<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-newsletter_agents" aria-controls="tabs-newsletter_agents" role="tab" data-toggle="tab">Newsletter Agents</a></li>
|
||||
<li role="presentation"><a href="#tabs-3rd_party_apis" aria-controls="tabs-3rd_party_apis" role="tab" data-toggle="tab">3rd Party APIs</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">Tautulli Remote Android App <sup><small>beta</small></sup></a></li>
|
||||
</ul>
|
||||
@@ -284,7 +285,7 @@
|
||||
</div>
|
||||
<div id="home_refresh_interval_error" class="alert alert-danger settings-alert" role="alert"></div>
|
||||
</div>
|
||||
<p class="help-block">Set the interval (in seconds) to refresh the current activity on the homepage. Minimum 2.</p>
|
||||
<p class="help-block">Set the interval (in seconds) to refresh the current activity on the homepage. Minimum 2, default 10.</p>
|
||||
</div>
|
||||
|
||||
<div class="padded-header">
|
||||
@@ -1008,7 +1009,7 @@
|
||||
<p class="help-block" id="self_host_newsletter_message">
|
||||
Note: The <span class="inline-pre">${http_root}newsletter</span> endpoint on your domain must be publicly accessible from the internet.
|
||||
</p>
|
||||
<p class="help-block settings-warning base-url-warning">Warning: Public Tautulli domain not set under <a data-tab-destination="tabs-web_interface" data-target="#http_base_url">Web Interface</a>.</p>
|
||||
<p class="help-block settings-warning base-url-warning">Warning: Public Tautulli domain not set under <a data-tab-destination="web_interface" data-target="http_base_url">Web Interface</a>.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="newsletter_auth">Newsletter Authentication</label>
|
||||
@@ -1022,7 +1023,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">Select the authentication method to use for self-hosted newsletters.</p>
|
||||
<p class="help-block settings-warning newsletter-guest-access-warning">Warning: Guest Access is not enabled under <a data-tab-destination="tabs-web_interface" data-target="#allow_guest_access">Web Interface</a>.</p>
|
||||
<p class="help-block settings-warning newsletter-guest-access-warning">Warning: Guest Access is not enabled under <a data-tab-destination="web_interface" data-target="allow_guest_access">Web Interface</a>.</p>
|
||||
</div>
|
||||
<div class="form-group" id="newsletter_password_option">
|
||||
<label for="newsletter_password">Newsletter Password</label>
|
||||
@@ -1063,12 +1064,60 @@
|
||||
<p class="help-block">Enter the full path to where newsletter files will be saved.</p>
|
||||
</div>
|
||||
|
||||
<p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p>
|
||||
|
||||
</div>
|
||||
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-notification_agents">
|
||||
|
||||
<div class="padded-header">
|
||||
<h3>3rd Party APIs</h3>
|
||||
<h3>Notification Agents</h3>
|
||||
</div>
|
||||
|
||||
<p class="help-block">
|
||||
Add a new notification agent, or configure an existing notification agent by clicking the settings icon on the right.
|
||||
</p>
|
||||
<p class="help-block">
|
||||
Please see the <a target='_blank' href='${anon_url('https://github.com/%s/%s-Wiki/wiki/Notification-Agents-Guide' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}'>Notification Agents Guide</a> for instructions on setting up each notification agent.
|
||||
</p>
|
||||
<br />
|
||||
<div id="plexpy-notifiers-table">
|
||||
<div class='text-muted'><i class="fa fa-refresh fa-spin"></i> Loading notification agents...</div>
|
||||
<br>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-newsletter_agents">
|
||||
|
||||
<div class="padded-header">
|
||||
<h3>Newsletter Agents</h3>
|
||||
</div>
|
||||
|
||||
<p class="help-block">
|
||||
Add a new newsletter agent, or configure an existing newsletter agent by clicking the settings icon on the right.
|
||||
</p>
|
||||
<p class="help-block settings-warning" id="newsletter_upload_warning">
|
||||
Warning: The <a data-tab-destination="3rd_party_apis" data-target="notify_upload_posters">Image Hosting</a> setting must be enabled for images to display on the newsletter.</span>
|
||||
</p>
|
||||
<br/>
|
||||
<div id="plexpy-newsletters-table">
|
||||
<div class='text-muted'><i class="fa fa-refresh fa-spin"></i> Loading newsletter agents...</div>
|
||||
<br>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-3rd_party_apis">
|
||||
|
||||
<div class="padded-header">
|
||||
<h3>Image Hosting</h3>
|
||||
</div>
|
||||
|
||||
<p class="help-block">Image hosting is used to provide posters and artwork for some notification agents and newsletters.</p>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="notify_upload_posters">Image Hosting</label>
|
||||
<label for="notify_upload_posters">Image Host</label>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="${'input-group' if config['notify_upload_posters'] in (1, 3) else ''}">
|
||||
@@ -1090,8 +1139,8 @@
|
||||
</div>
|
||||
<div id="imgur_upload_options" style="overlfow: hidden; display: ${'none' if config['notify_upload_posters'] != 1 else 'block'}">
|
||||
<div class="form-group">
|
||||
<p class="help-block" id="imgur_upload_message">
|
||||
You can register a new Imgur application <a href="${anon_url('https://api.imgur.com/oauth2/addclient')}" target="_blank">here</a>.<br>
|
||||
<p class="help-block">
|
||||
Please see the <a target='_blank' href='${anon_url('https://github.com/%s/%s-Wiki/wiki/3rd-Party-APIs-Guide' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}'>3rd Party APIs Guide</a> for instructions on setting up Imgur.<br>
|
||||
Warning: Imgur uploads are rate-limited and newsletters may exceed the limit. Please use Cloudinary for newsletters instead.
|
||||
</p>
|
||||
</div>
|
||||
@@ -1108,13 +1157,13 @@
|
||||
<div id="self_host_image_options" style="overlfow: hidden; display: ${'none' if config['notify_upload_posters'] != 2 else 'block'}">
|
||||
<div class="form-group">
|
||||
<p class="help-block" id="self_host_image_message">Note: The <span class="inline-pre">${http_root}image</span> endpoint on your domain must be publicly accessible from the internet.</p>
|
||||
<p class="help-block settings-warning base-url-warning">Warning: Public Tautulli domain not set under <a data-tab-destination="tabs-web_interface" data-target="#http_base_url">Web Interface</a>.</p>
|
||||
<p class="help-block settings-warning base-url-warning">Warning: Public Tautulli domain not set under <a data-tab-destination="web_interface" data-target="http_base_url">Web Interface</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="cloudinary_upload_options" style="overlfow: hidden; display: ${'none' if config['notify_upload_posters'] != 3 else 'block'}">
|
||||
<div class="form-group">
|
||||
<p class="help-block" id="imgur_upload_message">
|
||||
You can sign up for Cloudinary <a href="${anon_url('https://cloudinary.com')}" target="_blank">here</a>.<br>
|
||||
<p class="help-block">
|
||||
Please see the <a target='_blank' href='${anon_url('https://github.com/%s/%s-Wiki/wiki/3rd-Party-APIs-Guide' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}'>3rd Party APIs Guide</a> for instructions on setting up Cloudinary.
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@@ -1151,6 +1200,13 @@
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="padded-header">
|
||||
<h3>Metadata Lookups</h3>
|
||||
</div>
|
||||
|
||||
<p class="help-block">Metadata lookups are used to provide additional links for notifications when available.</p>
|
||||
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="themoviedb_lookup" id="themoviedb_lookup" value="1" ${config['themoviedb_lookup']}> Lookup TheMovieDB Links
|
||||
@@ -1170,50 +1226,57 @@
|
||||
<p class="help-block">Enable to lookup links to MusicBrainz for music when available.</p>
|
||||
</div>
|
||||
|
||||
<div class="padded-header">
|
||||
<h3>Geolocation Database</h3>
|
||||
</div>
|
||||
|
||||
<p class="help-block">The GeoLite2 database is used to geolocate IP addresses.</p>
|
||||
<p class="help-block">
|
||||
Please see the <a target='_blank' href='${anon_url('https://github.com/%s/%s-Wiki/wiki/3rd-Party-APIs-Guide' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}'>3rd Party APIs Guide</a> for instructions on setting up MaxMind.<br>
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<label for="maxmind_license_key">MaxMind License Key</label>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<input type="text" class="form-control" id="maxmind_license_key" name="maxmind_license_key" value="${config['maxmind_license_key']}" data-parsley-trigger="change">
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">
|
||||
Enter and save your MaxMind License Key to install the GeoLite2 database.
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="geoip_db">GeoLite2 Database File</label> ${docker_msg | n}
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="geoip_db" name="geoip_db" value="${config['geoip_db']}" ${docker_setting}>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-form" type="button" id="install_geoip_db" disabled>${'Update' if config["geoip_db_installed"] else 'Install'}</button>
|
||||
<button class="btn btn-form" type="button" id="uninstall_geoip_db">Uninstall</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">
|
||||
GeoLite2 Database last updated <strong><span id="geoip_db_updated">never</span></strong>.
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group advanced-setting">
|
||||
<label for="geoip_db_update_days">GeoLite2 Database Update Interval</label>
|
||||
<div class="row">
|
||||
<div class="col-md-2">
|
||||
<input type="text" class="form-control" data-parsley-type="integer" id="geoip_db_update_days" name="geoip_db_update_days" value="${config['geoip_db_update_days']}" size="5" data-parsley-range="[7, 30]" data-parsley-trigger="change" data-parsley-errors-container="#geoip_db_update_days_error" required>
|
||||
</div>
|
||||
<div id="geoip_db_update_days_error" class="alert alert-danger settings-alert" role="alert"></div>
|
||||
</div>
|
||||
<p class="help-block">The interval (in days) Tautulli will automatically update the GeoLite2 database. Minimum 7, maximum 30, default 30.</p>
|
||||
</div>
|
||||
|
||||
<p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p>
|
||||
|
||||
</div>
|
||||
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-notification_agents">
|
||||
|
||||
<div class="padded-header">
|
||||
<h3>Notification Agents</h3>
|
||||
</div>
|
||||
|
||||
<p class="help-block">
|
||||
Add a new notification agent, or configure an existing notification agent by clicking the settings icon on the right.
|
||||
</p>
|
||||
<p class="help-block">
|
||||
Please see the <a target='_blank' href='${anon_url('https://github.com/%s/%s-Wiki/wiki/Notification-Agents-Guide' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}'>Notification Agents Guide</a> for instructions on setting up each notification agent.
|
||||
</p>
|
||||
<br />
|
||||
<div id="plexpy-notifiers-table">
|
||||
<div class='text-muted'><i class="fa fa-refresh fa-spin"></i> Loading notification agents...</div>
|
||||
<br>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-newsletter_agents">
|
||||
|
||||
<div class="padded-header">
|
||||
<h3>Newsletter Agents</h3>
|
||||
</div>
|
||||
|
||||
<p class="help-block">
|
||||
Add a new newsletter agent, or configure an existing newsletter agent by clicking the settings icon on the right.
|
||||
</p>
|
||||
<p class="help-block settings-warning" id="newsletter_upload_warning">
|
||||
Warning: The <a data-tab-destination="tabs-notifications" data-target="#notify_upload_posters">Image Hosting</a> setting must be enabled for images to display on the newsletter.</span>
|
||||
</p>
|
||||
<br/>
|
||||
<div id="plexpy-newsletters-table">
|
||||
<div class='text-muted'><i class="fa fa-refresh fa-spin"></i> Loading newsletter agents...</div>
|
||||
<br>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-import_backups">
|
||||
|
||||
<div class="padded-header">
|
||||
@@ -1316,7 +1379,7 @@
|
||||
<div class="form-group">
|
||||
<label>Registered Devices</label>
|
||||
<p class="help-block">Register a new device using a QR code, 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-web_interface" data-target="#api_enabled">Web Interface</a> to use the app.</p>
|
||||
<p id="app_api_msg" style="color: #eb8600;">Warning: The API must be enabled under <a data-tab-destination="web_interface" data-target="api_enabled">Web Interface</a> to use the app.</p>
|
||||
<div class="row">
|
||||
<div id="plexpy-mobile-devices-table" class="col-md-12">
|
||||
<div class='text-muted'><i class="fa fa-refresh fa-spin"></i> Loading registered devices...</div>
|
||||
@@ -1796,11 +1859,6 @@ Rating: {rating}/10 --> Rating: /10
|
||||
async: true,
|
||||
complete: function(xhr, status) {
|
||||
$("#plexpy-configuration-table").html(xhr.responseText);
|
||||
if ("${kwargs.get('install_geoip')}" == 'true') {
|
||||
$('#install_geoip_db').removeClass('no-highlight').css('color','#e9a049');
|
||||
} else if ("${kwargs.get('reinstall_geoip')}" == 'true') {
|
||||
$('#reinstall_geoip_db').removeClass('no-highlight').css('color','#e9a049');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1943,6 +2001,7 @@ $(document).ready(function() {
|
||||
getNewslettersTable();
|
||||
getMobileDevicesTable();
|
||||
loadUpdateDistros();
|
||||
enableGeoLiteInstallButton();
|
||||
settingsChanged = false;
|
||||
}
|
||||
|
||||
@@ -2773,25 +2832,75 @@ $(document).ready(function() {
|
||||
|
||||
$('#allow_guest_access').click(function () {
|
||||
newsletterPasswordEnabled();
|
||||
})
|
||||
});
|
||||
|
||||
function gotoSetting(tab, setting){
|
||||
$("a[href=#tabs-" + tab + "]").click();
|
||||
if (setting) {
|
||||
_setting = '#' + setting;
|
||||
if ($(_setting).closest('.advanced-setting').length && !$('#menu_link_show_advanced_settings').hasClass('active')) {
|
||||
$('#menu_link_show_advanced_settings').click()
|
||||
}
|
||||
var body_container = $('.body-container');
|
||||
var scroll_pos = setting ? body_container.scrollTop() + $(_setting).offset().top - 100 : 0;
|
||||
body_container.animate({scrollTop: scroll_pos});
|
||||
$(_setting).closest('.form-group, .checkbox').delay(500).fadeOut().fadeIn('slow').fadeOut().fadeIn('slow');
|
||||
}
|
||||
}
|
||||
|
||||
$('body').on('click', 'a[data-tab-destination]', function () {
|
||||
var tab = $(this).data('tab-destination');
|
||||
$("a[href=#" + tab + "]").click();
|
||||
var scroll_destination = $(this).data('target');
|
||||
if (scroll_destination) {
|
||||
if ($(scroll_destination).closest('.advanced-setting').length && !$('#menu_link_show_advanced_settings').hasClass('active')) {
|
||||
$('#menu_link_show_advanced_settings').click()
|
||||
}
|
||||
var body_container = $('.body-container')
|
||||
var scroll_pos = scroll_destination ? body_container.scrollTop() + $(scroll_destination).offset().top - 100 : 0;
|
||||
body_container.animate({scrollTop: scroll_pos});
|
||||
}
|
||||
var setting = $(this).data('target');
|
||||
gotoSetting(tab, setting)
|
||||
});
|
||||
|
||||
$('#resources-xml').on('tripleclick', function () {
|
||||
openPlexXML('/api/resources', true, {includeHttps: 1});
|
||||
});
|
||||
|
||||
if ("${kwargs.get('install_geoip')}" === 'true') {
|
||||
gotoSetting('3rd_party_apis', 'geoip_db')
|
||||
}
|
||||
|
||||
function enableGeoLiteInstallButton() {
|
||||
$('#install_geoip_db').prop('disabled', !(Boolean($("#maxmind_license_key").val())));
|
||||
}
|
||||
enableGeoLiteInstallButton();
|
||||
|
||||
if ("${config['geoip_db_installed']}" > "0") {
|
||||
$("#geoip_db_updated").text(moment("${config['geoip_db_installed']}", "X").fromNow());
|
||||
}
|
||||
|
||||
$("#install_geoip_db").click(function () {
|
||||
var msg = 'Are you sure you want to install the GeoLite2 database?<br /><br />' +
|
||||
'The database is used to lookup IP address geolocation info.<br />' +
|
||||
'The database will be downloaded from <a href="${anon_url("https://dev.maxmind.com/geoip/geoip2/geolite2/")}" target="_blank">MaxMind</a>, <br />' +
|
||||
'and requires <strong>100MB</strong> of free space to install in your Tautulli directory.<br />';
|
||||
var url = 'install_geoip_db';
|
||||
if ($(this).text() === 'Update') {
|
||||
url += '?update=true';
|
||||
}
|
||||
confirmAjaxCall(url, msg, null, 'Installing GeoLite2 database.', function (result) {
|
||||
if (result.result === "success") {
|
||||
$('#install_geoip_db').text('Update');
|
||||
$('#geoip_db_updated').text(moment(result.updated, "X").fromNow());
|
||||
}
|
||||
getSchedulerTable();
|
||||
});
|
||||
});
|
||||
|
||||
$("#uninstall_geoip_db").click(function () {
|
||||
var msg = 'Are you sure you want to uninstall the GeoLite2 database?<br /><br />' +
|
||||
'You will not be able to lookup IP address geolocation info.';
|
||||
var url = 'uninstall_geoip_db';
|
||||
confirmAjaxCall(url, msg, null, 'Uninstalling GeoLite2 database.', function (result) {
|
||||
if (result.result === "success") {
|
||||
$('#install_geoip_db').text('Install');
|
||||
$('#geoip_db_updated').text('never');
|
||||
}
|
||||
getSchedulerTable();
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</%def>
|
||||
|
@@ -21,28 +21,21 @@
|
||||
<link href="${http_root}css/font-awesome.v4-shims.min.css" rel="stylesheet">
|
||||
|
||||
<!-- Favicons -->
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="${http_root}images/favicon/favicon-32x32.png?v=2.0.0">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="${http_root}images/favicon/favicon-16x16.png?v=2.0.0">
|
||||
<link rel="shortcut icon" href="${http_root}images/favicon/favicon.ico?v=2.0.0">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="${http_root}images/favicon/favicon-32x32.png?v=2.0.5">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="${http_root}images/favicon/favicon-16x16.png?v=2.0.5">
|
||||
<link rel="shortcut icon" href="${http_root}images/favicon/favicon.ico?v=2.0.5">
|
||||
|
||||
<!-- ICONS -->
|
||||
<!-- Android >M39 icon -->
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="${http_root}images/favicon/android-chrome-192x192.png?v=2.0.0">
|
||||
<link rel="manifest" href="${http_root}json/Android-manifest.json?v=2.0.0">
|
||||
<meta name="theme-color" content="#1f1f1f">
|
||||
<!-- Android -->
|
||||
<link rel="manifest" href="${http_root}images/favicon/manifest.json?v=2.0.5">
|
||||
<meta name="theme-color" content="#282a2d">
|
||||
<!-- Apple -->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="${http_root}images/favicon/apple-touch-icon.png?v=2.0.0">
|
||||
<link rel="mask-icon" href="${http_root}images/favicon/safari-pinned-tab.svg?v=2.0.0" color="#1f1f1f">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="${http_root}images/favicon/apple-touch-icon.png?v=2.0.5">
|
||||
<link rel="mask-icon" href="${http_root}images/favicon/safari-pinned-tab.svg?v=2.0.5" color="#282a2d">
|
||||
<meta name="apple-mobile-web-app-title" content="Tautulli">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="viewport" content="initial-scale=1">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<!-- IE10 icon -->
|
||||
<!-- Microsoft -->
|
||||
<meta name="application-name" content="Tautulli">
|
||||
<meta name="msapplication-TileColor" content="#1f1f1f">
|
||||
<meta name="msapplication-TileImage" content="${http_root}images/favicon/mstile-144x144.png?v=2.0.0">
|
||||
<meta name="msapplication-config" content="${http_root}xml/IEconfig.xml?v=2.0.0" />
|
||||
<meta name="msapplication-config" content="${http_root}images/favicon/browserconfig.xml?v=2.0.5">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -67,8 +60,38 @@
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wizard-card" data-cardname="card2">
|
||||
<h3>Plex Authentication</h3>
|
||||
<h3>Authentication</h3>
|
||||
<div class="wizard-input-section">
|
||||
<p class="help-block">
|
||||
Please setup an admin username and password for Tautulli.
|
||||
</p>
|
||||
</div>
|
||||
<div class="wizard-input-section">
|
||||
<label for="http_username">HTTP Username</label>
|
||||
<div class="row">
|
||||
<div class="col-xs-8">
|
||||
<input type="text" class="form-control auth-settings" id="http_username" name="http_username" value="" size="30">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wizard-input-section">
|
||||
<label for="http_password">HTTP Password</label>
|
||||
<div class="row">
|
||||
<div class="col-xs-8">
|
||||
<input type="password" class="form-control auth-settings" id="http_password" name="http_password" value="" size="30" autocomplete="new-password">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" class="form-control" name="http_hash_password" id="http_hash_password" value="1">
|
||||
<input type="hidden" class="form-control" name="http_plex_admin" id="http_plex_admin" value="1">
|
||||
<input type="hidden" id="authentication_valid" data-validate="validateAuthentication" value="">
|
||||
<span style="display: none;" id="authentication-status"></span>
|
||||
</div>
|
||||
|
||||
<div class="wizard-card" data-cardname="card3">
|
||||
<h3>Plex Account</h3>
|
||||
<div class="wizard-input-section">
|
||||
<p class="help-block">
|
||||
Tautulli requires a Plex.tv account. Click the button below to sign in on Plex.tv. You may need to allow popups in your browser.
|
||||
@@ -78,7 +101,8 @@
|
||||
<a class="btn btn-dark" id="sign-in-plex" href="#" role="button">Sign In with Plex</a>
|
||||
<span style="margin-left: 10px; display: none;" id="pms-token-status"></span>
|
||||
</div>
|
||||
<div class="wizard-card" data-cardname="card3">
|
||||
|
||||
<div class="wizard-card" data-cardname="card4">
|
||||
<h3>Plex Media Server</h3>
|
||||
<div class="wizard-input-section">
|
||||
<p class="help-block">
|
||||
@@ -137,7 +161,7 @@
|
||||
<span style="margin-left: 10px; display: none;" id="pms-verify-status"></span>
|
||||
</div>
|
||||
|
||||
<div class="wizard-card" data-cardname="card4">
|
||||
<div class="wizard-card" data-cardname="card5">
|
||||
<h3>Activity Logging</h3>
|
||||
<div class="wizard-input-section">
|
||||
<p class="help-block">
|
||||
@@ -162,7 +186,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wizard-card" data-cardname="card4">
|
||||
<div class="wizard-card" data-cardname="card6">
|
||||
<h3>Notifications</h3>
|
||||
<div class="wizard-input-section">
|
||||
<p class="help-block">
|
||||
@@ -175,7 +199,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wizard-card" data-cardname="card5">
|
||||
<div class="wizard-card" data-cardname="card7">
|
||||
<h3>Database Import</h3>
|
||||
<div class="wizard-input-section">
|
||||
<p class="help-block">
|
||||
@@ -227,11 +251,29 @@
|
||||
<script src="${http_root}js/script.js${cache_param}"></script>
|
||||
<script src="${http_root}js/bootstrap-wizard.min.js"></script>
|
||||
<script>
|
||||
function validateAuthentication(el) {
|
||||
var http_username = $("#http_username").val();
|
||||
var http_password = $("#http_password").val();
|
||||
var valid_authentication = el.val();
|
||||
var retValue = {};
|
||||
|
||||
if (http_username === "" || http_password === "") {
|
||||
retValue.status = false;
|
||||
retValue.msg = "Please enter a username and password.";
|
||||
$("#authentication-status").html('<i class="fa fa-exclamation-circle"></i> Please enter a username and password.');
|
||||
$('#authentication-status').fadeIn('fast').delay(2000).fadeOut('fast');
|
||||
} else {
|
||||
retValue.status = true;
|
||||
}
|
||||
|
||||
return retValue;
|
||||
}
|
||||
|
||||
function validatePMSip(el) {
|
||||
var valid_pms_ip = el.val();
|
||||
var retValue = {};
|
||||
|
||||
if (valid_pms_ip == "") {
|
||||
if (valid_pms_ip === "") {
|
||||
retValue.status = false;
|
||||
retValue.msg = "Please verify your server.";
|
||||
$("#pms-verify-status").html('<i class="fa fa-exclamation-circle"></i> Please verify your server.');
|
||||
@@ -247,7 +289,7 @@
|
||||
var valid_pms_token = el.val();
|
||||
var retValue = {};
|
||||
|
||||
if (valid_pms_token == "") {
|
||||
if (valid_pms_token === "") {
|
||||
retValue.status = false;
|
||||
retValue.msg = "Please authenticate.";
|
||||
$("#pms-token-status").html('<i class="fa fa-exclamation-circle"></i> Please authenticate.');
|
||||
@@ -284,7 +326,7 @@ $(document).ready(function() {
|
||||
$.fn.wizard.logging = false;
|
||||
var options = {
|
||||
keyboard : false,
|
||||
contentHeight : 400,
|
||||
contentHeight : 450,
|
||||
contentWidth : 700,
|
||||
backdrop: 'static',
|
||||
buttons: {submitText: 'Finish'},
|
||||
|
@@ -29,7 +29,6 @@ try:
|
||||
except ImportError:
|
||||
no_browser = True
|
||||
|
||||
import cherrypy
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from apscheduler.triggers.interval import IntervalTrigger
|
||||
from UniversalAnalytics import Tracker
|
||||
@@ -40,6 +39,7 @@ import activity_pinger
|
||||
import common
|
||||
import database
|
||||
import datafactory
|
||||
import helpers
|
||||
import libraries
|
||||
import logger
|
||||
import mobile_app
|
||||
@@ -51,6 +51,7 @@ import plextv
|
||||
import users
|
||||
import versioncheck
|
||||
import web_socket
|
||||
import webstart
|
||||
import plexpy.config
|
||||
|
||||
PROG_DIR = None
|
||||
@@ -166,7 +167,7 @@ def initialize(config_file):
|
||||
plexpy.SYS_TIMEZONE.zone, plexpy.SYS_UTC_OFFSET
|
||||
))
|
||||
logger.info(u"Python {}".format(
|
||||
sys.version
|
||||
sys.version.replace('\n', '')
|
||||
))
|
||||
logger.info(u"Program Dir: {}".format(
|
||||
PROG_DIR
|
||||
@@ -444,6 +445,8 @@ def initialize_scheduler():
|
||||
hours=backup_hours, minutes=0, seconds=0, args=(True, True))
|
||||
schedule_job(config.make_backup, 'Backup Tautulli config',
|
||||
hours=backup_hours, minutes=0, seconds=0, args=(True, True))
|
||||
schedule_job(helpers.update_geoip_db, 'Update GeoLite2 database',
|
||||
hours=12 * bool(CONFIG.GEOIP_DB_INSTALLED), minutes=0, seconds=0)
|
||||
|
||||
if WS_CONNECTED and CONFIG.PMS_IP and CONFIG.PMS_TOKEN:
|
||||
schedule_job(plextv.get_server_resources, 'Refresh Plex server URLs',
|
||||
@@ -1972,8 +1975,7 @@ def upgrade():
|
||||
|
||||
|
||||
def shutdown(restart=False, update=False, checkout=False):
|
||||
logger.info(u"Stopping Tautulli web server...")
|
||||
cherrypy.engine.exit()
|
||||
webstart.stop()
|
||||
|
||||
# Shutdown the websocket connection
|
||||
if WEBSOCKET:
|
||||
|
@@ -411,7 +411,7 @@ class API2:
|
||||
|
||||
return
|
||||
|
||||
def notify(self, notifier_id='', subject='Tautulli', body='Test notification', **kwargs):
|
||||
def notify(self, notifier_id='', subject='', body='', **kwargs):
|
||||
""" Send a notification using Tautulli.
|
||||
|
||||
```
|
||||
|
@@ -197,7 +197,8 @@ SCHEDULER_LIST = [
|
||||
'Refresh libraries list',
|
||||
'Refresh Plex server URLs',
|
||||
'Backup Tautulli database',
|
||||
'Backup Tautulli config'
|
||||
'Backup Tautulli config',
|
||||
'Update GeoLite2 database'
|
||||
]
|
||||
|
||||
DATE_TIME_FORMATS = [
|
||||
|
@@ -175,6 +175,8 @@ _CONFIG_DEFINITIONS = {
|
||||
'FIRST_RUN_COMPLETE': (int, 'General', 0),
|
||||
'FREEZE_DB': (int, 'General', 0),
|
||||
'GEOIP_DB': (str, 'General', ''),
|
||||
'GEOIP_DB_INSTALLED': (int, 'General', 0),
|
||||
'GEOIP_DB_UPDATE_DAYS': (int, 'General', 30),
|
||||
'GET_FILE_SIZES': (int, 'General', 0),
|
||||
'GET_FILE_SIZES_HOLD': (dict, 'General', {'section_ids': [], 'rating_keys': []}),
|
||||
'GIT_BRANCH': (str, 'General', 'master'),
|
||||
@@ -289,6 +291,7 @@ _CONFIG_DEFINITIONS = {
|
||||
'LOG_BLACKLIST': (int, 'General', 1),
|
||||
'LOG_DIR': (str, 'General', ''),
|
||||
'LOGGING_IGNORE_INTERVAL': (int, 'Monitoring', 120),
|
||||
'MAXMIND_LICENSE_KEY': (str, 'General', ''),
|
||||
'METADATA_CACHE_SECONDS': (int, 'Advanced', 1800),
|
||||
'MOVIE_LOGGING_ENABLE': (int, 'Monitoring', 1),
|
||||
'MOVIE_NOTIFY_ENABLE': (int, 'Monitoring', 0),
|
||||
@@ -923,3 +926,9 @@ class Config(object):
|
||||
self.BUFFER_THRESHOLD = max(self.BUFFER_THRESHOLD, 10)
|
||||
|
||||
self.CONFIG_VERSION = 13
|
||||
|
||||
if self.CONFIG_VERSION == 13:
|
||||
if not self.GEOIP_DB:
|
||||
self.GEOIP_DB = os.path.join(plexpy.DATA_DIR, 'GeoLite2-City.mmdb')
|
||||
|
||||
self.CONFIG_VERSION = 14
|
||||
|
@@ -69,10 +69,17 @@ def db_filename(filename=FILENAME):
|
||||
def make_backup(cleanup=False, scheduler=False):
|
||||
""" Makes a backup of db, removes all but the last 5 backups """
|
||||
|
||||
# Check the integrity of the database first
|
||||
integrity = (integrity_check()['integrity_check'] == 'ok')
|
||||
|
||||
corrupt = ''
|
||||
if not integrity:
|
||||
corrupt = '.corrupt'
|
||||
|
||||
if scheduler:
|
||||
backup_file = 'tautulli.backup-%s.sched.db' % arrow.now().format('YYYYMMDDHHmmss')
|
||||
backup_file = 'tautulli.backup-{}{}.sched.db'.format(arrow.now().format('YYYYMMDDHHmmss'), corrupt)
|
||||
else:
|
||||
backup_file = 'tautulli.backup-%s.db' % arrow.now().format('YYYYMMDDHHmmss')
|
||||
backup_file = 'tautulli.backup-{}{}.db'.format(arrow.now().format('YYYYMMDDHHmmss'), corrupt)
|
||||
backup_folder = plexpy.CONFIG.BACKUP_DIR
|
||||
backup_file_fp = os.path.join(backup_folder, backup_file)
|
||||
|
||||
@@ -85,7 +92,8 @@ def make_backup(cleanup=False, scheduler=False):
|
||||
shutil.copyfile(db_filename(), backup_file_fp)
|
||||
db.connection.rollback()
|
||||
|
||||
if cleanup:
|
||||
# Only cleanup if the database integrity is okay
|
||||
if cleanup and integrity:
|
||||
now = time.time()
|
||||
# Delete all scheduled backup older than BACKUP_DAYS.
|
||||
for root, dirs, files in os.walk(backup_folder):
|
||||
|
@@ -369,19 +369,23 @@ class Graphs(object):
|
||||
|
||||
# create our date range as some months may not have any data
|
||||
# but we still want to display them
|
||||
base = time.localtime()
|
||||
month_range = [time.localtime(
|
||||
time.mktime((base.tm_year, base.tm_mon - n, 1, 0, 0, 0, 0, 0, 0))) for n in range(int(time_range))]
|
||||
dt_today = datetime.date.today()
|
||||
dt = dt_today
|
||||
month_range = [dt]
|
||||
for n in range(int(time_range)-1):
|
||||
if not ((dt_today.month-n) % 12)-1:
|
||||
dt = datetime.date(dt.year-1, 12, 1)
|
||||
else:
|
||||
dt = datetime.date(dt.year, dt.month-1, 1)
|
||||
month_range.append(dt)
|
||||
|
||||
categories = []
|
||||
series_1 = []
|
||||
series_2 = []
|
||||
series_3 = []
|
||||
|
||||
for month_item in sorted(month_range):
|
||||
dt = datetime.datetime(*month_item[:6])
|
||||
for dt in sorted(month_range):
|
||||
date_string = dt.strftime('%Y-%m')
|
||||
|
||||
categories.append(dt.strftime('%b %Y').decode(plexpy.SYS_ENCODING, 'replace'))
|
||||
series_1_value = 0
|
||||
series_2_value = 0
|
||||
|
@@ -36,6 +36,7 @@ import re
|
||||
import shlex
|
||||
import socket
|
||||
import sys
|
||||
import tarfile
|
||||
import time
|
||||
import unicodedata
|
||||
import urllib, urllib2
|
||||
@@ -565,7 +566,7 @@ def get_ip(host):
|
||||
ip_address = ''
|
||||
if is_valid_ip(host):
|
||||
return host
|
||||
elif not re.fullmatch(r'[0-9]+(?:\.[0-9]+){3}(?!\d*-[a-z0-9]{6})', host):
|
||||
elif not re.match(r'^[0-9]+(?:\.[0-9]+){3}(?!\d*-[a-z0-9]{6})$', host):
|
||||
try:
|
||||
ip_address = socket.getaddrinfo(host, None)[0][4][0]
|
||||
logger.debug(u"IP Checker :: Resolved %s to %s." % (host, ip_address))
|
||||
@@ -583,83 +584,120 @@ def is_valid_ip(address):
|
||||
return False
|
||||
|
||||
|
||||
def install_geoip_db():
|
||||
maxmind_url = 'http://geolite.maxmind.com/download/geoip/database/'
|
||||
geolite2_gz = 'GeoLite2-City.mmdb.gz'
|
||||
geolite2_md5 = 'GeoLite2-City.md5'
|
||||
geolite2_db = geolite2_gz[:-3]
|
||||
md5_checksum = ''
|
||||
def update_geoip_db():
|
||||
if plexpy.CONFIG.GEOIP_DB_INSTALLED:
|
||||
logger.info(u"Tautulli Helpers :: Checking for GeoLite2 database updates.")
|
||||
now = int(time.time())
|
||||
if now - plexpy.CONFIG.GEOIP_DB_INSTALLED >= plexpy.CONFIG.GEOIP_DB_UPDATE_DAYS * 24 * 60 * 60:
|
||||
return install_geoip_db(update=True)
|
||||
logger.info(u"Tautulli Helpers :: GeoLite2 database already updated within the last 30 days.")
|
||||
|
||||
|
||||
def install_geoip_db(update=False):
|
||||
if not plexpy.CONFIG.MAXMIND_LICENSE_KEY:
|
||||
logger.error(u"Tautulli Helpers :: Failed to download GeoLite2 database file from MaxMind: Missing MaxMindLicense Key")
|
||||
return False
|
||||
|
||||
maxmind_db = 'GeoLite2-City'
|
||||
maxmind_url = 'https://download.maxmind.com/app/geoip_download?edition_id={db}&suffix={{suffix}}&license_key={key}'.format(
|
||||
db=maxmind_db, key=plexpy.CONFIG.MAXMIND_LICENSE_KEY)
|
||||
geolite2_db_url = maxmind_url.format(suffix='tar.gz')
|
||||
geolite2_md5_url = maxmind_url.format(suffix='tar.gz.md5')
|
||||
geolite2_gz = maxmind_db + '.tar.gz'
|
||||
geolite2_md5 = geolite2_gz + '.md5'
|
||||
geolite2_db = maxmind_db + '.mmdb'
|
||||
geolite2_db_path = plexpy.CONFIG.GEOIP_DB or os.path.join(plexpy.DATA_DIR, geolite2_db)
|
||||
|
||||
temp_gz = os.path.join(plexpy.CONFIG.CACHE_DIR, geolite2_gz)
|
||||
geolite2_db = plexpy.CONFIG.GEOIP_DB or os.path.join(plexpy.DATA_DIR, geolite2_db)
|
||||
temp_md5 = os.path.join(plexpy.CONFIG.CACHE_DIR, geolite2_md5)
|
||||
|
||||
# Retrieve the GeoLite2 gzip file
|
||||
logger.debug(u"Tautulli Helpers :: Downloading GeoLite2 gzip file from MaxMind...")
|
||||
try:
|
||||
maxmind = urllib.URLopener()
|
||||
maxmind.retrieve(maxmind_url + geolite2_gz, temp_gz)
|
||||
md5_checksum = urllib2.urlopen(maxmind_url + geolite2_md5).read()
|
||||
maxmind.retrieve(geolite2_db_url, temp_gz)
|
||||
maxmind.retrieve(geolite2_md5_url, temp_md5)
|
||||
except Exception as e:
|
||||
logger.error(u"Tautulli Helpers :: Failed to download GeoLite2 gzip file from MaxMind: %s" % e)
|
||||
return False
|
||||
|
||||
# Extract the GeoLite2 database file
|
||||
logger.debug(u"Tautulli Helpers :: Extracting GeoLite2 database...")
|
||||
try:
|
||||
with gzip.open(temp_gz, 'rb') as gz:
|
||||
with open(geolite2_db, 'wb') as db:
|
||||
db.write(gz.read())
|
||||
except Exception as e:
|
||||
logger.error(u"Tautulli Helpers :: Failed to extract the GeoLite2 database: %s" % e)
|
||||
return False
|
||||
|
||||
# Check MD5 hash for GeoLite2 database file
|
||||
logger.debug(u"Tautulli Helpers :: Checking MD5 checksum for GeoLite2 database...")
|
||||
# Check MD5 hash for GeoLite2 tar.gz file
|
||||
logger.debug(u"Tautulli Helpers :: Checking MD5 checksum for GeoLite2 gzip file...")
|
||||
try:
|
||||
hash_md5 = hashlib.md5()
|
||||
with open(geolite2_db, 'rb') as f:
|
||||
with open(temp_gz, 'rb') as f:
|
||||
for chunk in iter(lambda: f.read(4096), b""):
|
||||
hash_md5.update(chunk)
|
||||
md5_hash = hash_md5.hexdigest()
|
||||
|
||||
with open(temp_md5, 'r') as f:
|
||||
md5_checksum = f.read()
|
||||
|
||||
if md5_hash != md5_checksum:
|
||||
logger.error(u"Tautulli Helpers :: MD5 checksum doesn't match for GeoLite2 database. "
|
||||
"Checksum: %s, file hash: %s" % (md5_checksum, md5_hash))
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(u"Tautulli Helpers :: Failed to generate MD5 checksum for GeoLite2 database: %s" % e)
|
||||
logger.error(u"Tautulli Helpers :: Failed to generate MD5 checksum for GeoLite2 gzip file: %s" % e)
|
||||
return False
|
||||
|
||||
# Extract the GeoLite2 database file
|
||||
logger.debug(u"Tautulli Helpers :: Extracting GeoLite2 database...")
|
||||
try:
|
||||
mmdb = None
|
||||
with tarfile.open(temp_gz, 'r:gz') as tar:
|
||||
for member in tar.getmembers():
|
||||
if geolite2_db in member.name:
|
||||
member.name = os.path.basename(member.name)
|
||||
tar.extractall(path=os.path.dirname(geolite2_db_path), members=[member])
|
||||
mmdb = True
|
||||
break
|
||||
if not mmdb:
|
||||
raise Exception("{} not found in gzip file.".format(geolite2_db))
|
||||
except Exception as e:
|
||||
logger.error(u"Tautulli Helpers :: Failed to extract the GeoLite2 database: %s" % e)
|
||||
return False
|
||||
|
||||
# Delete temportary GeoLite2 gzip file
|
||||
logger.debug(u"Tautulli Helpers :: Deleting temporary GeoLite2 gzip file...")
|
||||
try:
|
||||
os.remove(temp_gz)
|
||||
os.remove(temp_md5)
|
||||
except Exception as e:
|
||||
logger.warn(u"Tautulli Helpers :: Failed to remove temporary GeoLite2 gzip file: %s" % e)
|
||||
|
||||
logger.debug(u"Tautulli Helpers :: GeoLite2 database installed successfully.")
|
||||
plexpy.CONFIG.__setattr__('GEOIP_DB', geolite2_db)
|
||||
plexpy.CONFIG.__setattr__('GEOIP_DB', geolite2_db_path)
|
||||
plexpy.CONFIG.__setattr__('GEOIP_DB_INSTALLED', int(time.time()))
|
||||
plexpy.CONFIG.write()
|
||||
|
||||
return True
|
||||
logger.debug(u"Tautulli Helpers :: GeoLite2 database installed successfully.")
|
||||
|
||||
if not update:
|
||||
plexpy.schedule_job(update_geoip_db, 'Update GeoLite2 database', hours=12, minutes=0, seconds=0)
|
||||
|
||||
return plexpy.CONFIG.GEOIP_DB_INSTALLED
|
||||
|
||||
|
||||
def uninstall_geoip_db():
|
||||
logger.debug(u"Tautulli Helpers :: Uninstalling the GeoLite2 database...")
|
||||
try:
|
||||
os.remove(plexpy.CONFIG.GEOIP_DB)
|
||||
plexpy.CONFIG.__setattr__('GEOIP_DB', '')
|
||||
plexpy.CONFIG.write()
|
||||
except Exception as e:
|
||||
logger.error(u"Tautulli Helpers :: Failed to uninstall the GeoLite2 database: %s" % e)
|
||||
return False
|
||||
|
||||
plexpy.CONFIG.__setattr__('GEOIP_DB_INSTALLED', 0)
|
||||
plexpy.CONFIG.write()
|
||||
|
||||
logger.debug(u"Tautulli Helpers :: GeoLite2 database uninstalled successfully.")
|
||||
|
||||
plexpy.schedule_job(update_geoip_db, 'Update GeoLite2 database', hours=0, minutes=0, seconds=0)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def geoip_lookup(ip_address):
|
||||
if not plexpy.CONFIG.GEOIP_DB:
|
||||
if not plexpy.CONFIG.GEOIP_DB_INSTALLED:
|
||||
return 'GeoLite2 database not installed. Please install from the ' \
|
||||
'<a href="settings?install_geoip=true">Settings</a> page.'
|
||||
|
||||
@@ -677,7 +715,7 @@ def geoip_lookup(ip_address):
|
||||
'<a href="settings?install_geoip=true">Settings</a> page.'
|
||||
except maxminddb.InvalidDatabaseError as e:
|
||||
return 'Invalid GeoLite2 database. Please reinstall from the ' \
|
||||
'<a href="settings?reinstall_geoip=true">Settings</a> page.'
|
||||
'<a href="settings?install_geoip=true">Settings</a> page.'
|
||||
except geoip2.errors.AddressNotFoundError as e:
|
||||
return '%s' % e
|
||||
except Exception as e:
|
||||
|
@@ -90,6 +90,8 @@ def add_notifier_each(notifier_id=None, notify_action=None, stream_data=None, ti
|
||||
notifiers_enabled = notifiers.get_notifiers(notify_action=notify_action)
|
||||
|
||||
if notifiers_enabled and not manual_trigger:
|
||||
logger.debug(u"Tautulli NotificationHandler :: Notifiers enabled for notify_action '%s'." % notify_action)
|
||||
|
||||
# Check if notification conditions are satisfied
|
||||
conditions = notify_conditions(notify_action=notify_action,
|
||||
stream_data=stream_data,
|
||||
@@ -98,6 +100,9 @@ def add_notifier_each(notifier_id=None, notify_action=None, stream_data=None, ti
|
||||
conditions = True
|
||||
|
||||
if notifiers_enabled and (manual_trigger or conditions):
|
||||
if manual_trigger:
|
||||
logger.debug(u"Tautulli NotificationHandler :: Notifiers enabled for notify_action '%s' (manual trigger)." % notify_action)
|
||||
|
||||
if stream_data or timeline_data:
|
||||
# Build the notification parameters
|
||||
parameters = build_media_notify_params(notify_action=notify_action,
|
||||
@@ -138,6 +143,7 @@ def add_notifier_each(notifier_id=None, notify_action=None, stream_data=None, ti
|
||||
def notify_conditions(notify_action=None, stream_data=None, timeline_data=None):
|
||||
# Activity notifications
|
||||
if stream_data:
|
||||
logger.debug(u"Tautulli NotificationHandler :: Checking global notification conditions.")
|
||||
|
||||
# Check if notifications enabled for user and library
|
||||
# user_data = users.Users()
|
||||
@@ -163,36 +169,37 @@ def notify_conditions(notify_action=None, stream_data=None, timeline_data=None):
|
||||
user_sessions = [s for s in result['sessions'] if s['user_id'] == stream_data['user_id']]
|
||||
|
||||
if plexpy.CONFIG.NOTIFY_CONCURRENT_BY_IP:
|
||||
return len(Counter(s['ip_address'] for s in user_sessions)) >= plexpy.CONFIG.NOTIFY_CONCURRENT_THRESHOLD
|
||||
evaluated = len(Counter(s['ip_address'] for s in user_sessions)) >= plexpy.CONFIG.NOTIFY_CONCURRENT_THRESHOLD
|
||||
else:
|
||||
return len(user_sessions) >= plexpy.CONFIG.NOTIFY_CONCURRENT_THRESHOLD
|
||||
evaluated = len(user_sessions) >= plexpy.CONFIG.NOTIFY_CONCURRENT_THRESHOLD
|
||||
|
||||
elif notify_action == 'on_newdevice':
|
||||
data_factory = datafactory.DataFactory()
|
||||
user_devices = data_factory.get_user_devices(user_id=stream_data['user_id'])
|
||||
return stream_data['machine_id'] not in user_devices
|
||||
evaluated = stream_data['machine_id'] not in user_devices
|
||||
|
||||
elif stream_data['media_type'] in ('movie', 'episode', 'clip'):
|
||||
progress_percent = helpers.get_percent(stream_data['view_offset'], stream_data['duration'])
|
||||
|
||||
if notify_action == 'on_stop':
|
||||
return (plexpy.CONFIG.NOTIFY_CONSECUTIVE or
|
||||
evaluated = (plexpy.CONFIG.NOTIFY_CONSECUTIVE or
|
||||
(stream_data['media_type'] == 'movie' and progress_percent < plexpy.CONFIG.MOVIE_WATCHED_PERCENT) or
|
||||
(stream_data['media_type'] == 'episode' and progress_percent < plexpy.CONFIG.TV_WATCHED_PERCENT))
|
||||
|
||||
elif notify_action == 'on_resume':
|
||||
return plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < 99
|
||||
evaluated = plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < 99
|
||||
|
||||
# All other activity notify actions
|
||||
else:
|
||||
return True
|
||||
evaluated = True
|
||||
|
||||
elif stream_data['media_type'] == 'track':
|
||||
return True
|
||||
evaluated = True
|
||||
|
||||
else:
|
||||
return False
|
||||
evaluated = False
|
||||
|
||||
logger.debug(u"Tautulli NotificationHandler :: Global notification conditions evaluated to '{}'.".format(evaluated))
|
||||
# Recently Added notifications
|
||||
elif timeline_data:
|
||||
|
||||
@@ -204,11 +211,13 @@ def notify_conditions(notify_action=None, stream_data=None, timeline_data=None):
|
||||
# # logger.debug(u"Tautulli NotificationHandler :: Notifications for library '%s' is disabled." % library_details['section_name'])
|
||||
# return False
|
||||
|
||||
return True
|
||||
evaluated = True
|
||||
|
||||
# Server notifications
|
||||
else:
|
||||
return True
|
||||
evaluated = True
|
||||
|
||||
return evaluated
|
||||
|
||||
|
||||
def notify_custom_conditions(notifier_id=None, parameters=None):
|
||||
|
@@ -999,8 +999,8 @@ class ANDROIDAPP(Notifier):
|
||||
config_option.append({
|
||||
'label': 'Device',
|
||||
'description': 'No devices registered. '
|
||||
'<a data-tab-destination="tabs-android_app" data-toggle="tab" data-dismiss="modal" '
|
||||
'data-target="#top">Get the Android App</a> and register a device.',
|
||||
'<a data-tab-destination="android_app" data-toggle="tab" data-dismiss="modal">'
|
||||
'Get the Android App</a> and register a device.',
|
||||
'input_type': 'help'
|
||||
})
|
||||
else:
|
||||
@@ -1009,8 +1009,8 @@ class ANDROIDAPP(Notifier):
|
||||
'value': self.config['device_id'],
|
||||
'name': 'androidapp_device_id',
|
||||
'description': 'Set your Android app device or '
|
||||
'<a data-tab-destination="tabs-android_app" data-toggle="tab" data-dismiss="modal" '
|
||||
'data-target="#top">register a new device</a> with Tautulli.',
|
||||
'<a data-tab-destination="android_app" data-toggle="tab" data-dismiss="modal">'
|
||||
'register a new device</a> with Tautulli.',
|
||||
'input_type': 'select',
|
||||
'select_options': devices
|
||||
})
|
||||
@@ -1264,8 +1264,8 @@ class DISCORD(Notifier):
|
||||
'value': self.config['incl_card'],
|
||||
'name': 'discord_incl_card',
|
||||
'description': 'Include an info card with a poster and metadata with the notifications.<br>'
|
||||
'Note: <a data-tab-destination="tabs-notifications" data-dismiss="modal" '
|
||||
'data-target="#notify_upload_posters">Image Hosting</a> '
|
||||
'Note: <a data-tab-destination="3rd_party_apis" data-dismiss="modal" '
|
||||
'data-target="notify_upload_posters">Image Hosting</a> '
|
||||
'must be enabled under the notifications settings tab.',
|
||||
'input_type': 'checkbox'
|
||||
},
|
||||
@@ -1639,8 +1639,8 @@ class FACEBOOK(Notifier):
|
||||
'value': self.config['incl_card'],
|
||||
'name': 'facebook_incl_card',
|
||||
'description': 'Include an info card with a poster and metadata with the notifications.<br>'
|
||||
'Note: <a data-tab-destination="tabs-notifications" data-dismiss="modal" '
|
||||
'data-target="#notify_upload_posters">Image Hosting</a> '
|
||||
'Note: <a data-tab-destination="3rd_party_apis" data-dismiss="modal" '
|
||||
'data-target="notify_upload_posters">Image Hosting</a> '
|
||||
'must be enabled under the notifications settings tab.',
|
||||
'input_type': 'checkbox'
|
||||
},
|
||||
@@ -1962,8 +1962,8 @@ class HIPCHAT(Notifier):
|
||||
'value': self.config['incl_card'],
|
||||
'name': 'hipchat_incl_card',
|
||||
'description': 'Include an info card with a poster and metadata with the notifications.<br>'
|
||||
'Note: <a data-tab-destination="tabs-notifications" data-dismiss="modal" '
|
||||
'data-target="#notify_upload_posters">Image Hosting</a> '
|
||||
'Note: <a data-tab-destination="3rd_party_apis" data-dismiss="modal" '
|
||||
'data-target="notify_upload_posters">Image Hosting</a> '
|
||||
'must be enabled under the notifications settings tab.<br>'
|
||||
'Note: This will change the notification type to HTML and emoticons will no longer work.',
|
||||
'input_type': 'checkbox'
|
||||
@@ -2184,8 +2184,8 @@ class JOIN(Notifier):
|
||||
'value': self.config['incl_poster'],
|
||||
'name': 'join_incl_poster',
|
||||
'description': 'Include a poster with the notifications.<br>'
|
||||
'Note: <a data-tab-destination="tabs-notifications" data-dismiss="modal" '
|
||||
'data-target="#notify_upload_posters">Image Hosting</a> '
|
||||
'Note: <a data-tab-destination="3rd_party_apis" data-dismiss="modal" '
|
||||
'data-target="notify_upload_posters">Image Hosting</a> '
|
||||
'must be enabled under the notifications settings tab.',
|
||||
'input_type': 'checkbox'
|
||||
},
|
||||
@@ -3348,8 +3348,8 @@ class SLACK(Notifier):
|
||||
'value': self.config['incl_card'],
|
||||
'name': 'slack_incl_card',
|
||||
'description': 'Include an info card with a poster and metadata with the notifications.<br>'
|
||||
'Note: <a data-tab-destination="tabs-notifications" data-dismiss="modal" '
|
||||
'data-target="#notify_upload_posters">Image Hosting</a> '
|
||||
'Note: <a data-tab-destination="3rd_party_apis" data-dismiss="modal" '
|
||||
'data-target="notify_upload_posters">Image Hosting</a> '
|
||||
'must be enabled under the notifications settings tab.',
|
||||
'input_type': 'checkbox'
|
||||
},
|
||||
@@ -3593,8 +3593,8 @@ class TWITTER(Notifier):
|
||||
'value': self.config['incl_poster'],
|
||||
'name': 'twitter_incl_poster',
|
||||
'description': 'Include a poster with the notifications.<br>'
|
||||
'Note: <a data-tab-destination="tabs-notifications" data-dismiss="modal" '
|
||||
'data-target="#notify_upload_posters">Image Hosting</a> '
|
||||
'Note: <a data-tab-destination="3rd_party_apis" data-dismiss="modal" '
|
||||
'data-target="notify_upload_posters">Image Hosting</a> '
|
||||
'must be enabled under the notifications settings tab.',
|
||||
'input_type': 'checkbox'
|
||||
}
|
||||
|
@@ -1,2 +1,2 @@
|
||||
PLEXPY_BRANCH = "master"
|
||||
PLEXPY_RELEASE_VERSION = "v2.1.38"
|
||||
PLEXPY_RELEASE_VERSION = "v2.1.41"
|
||||
|
@@ -53,6 +53,7 @@ import pmsconnect
|
||||
import users
|
||||
import versioncheck
|
||||
import web_socket
|
||||
import webstart
|
||||
from plexpy.api2 import API2
|
||||
from plexpy.helpers import checked, addtoapi, get_ip, create_https_certificates, build_datatables_json, sanitize_out
|
||||
from plexpy.session import get_session_info, get_session_user_id, allow_session_user, allow_session_library
|
||||
@@ -2818,7 +2819,11 @@ class WebInterface(object):
|
||||
"newsletter_password": plexpy.CONFIG.NEWSLETTER_PASSWORD,
|
||||
"newsletter_inline_styles": checked(plexpy.CONFIG.NEWSLETTER_INLINE_STYLES),
|
||||
"newsletter_custom_dir": plexpy.CONFIG.NEWSLETTER_CUSTOM_DIR,
|
||||
"win_sys_tray": checked(plexpy.CONFIG.WIN_SYS_TRAY)
|
||||
"win_sys_tray": checked(plexpy.CONFIG.WIN_SYS_TRAY),
|
||||
"maxmind_license_key": plexpy.CONFIG.MAXMIND_LICENSE_KEY,
|
||||
"geoip_db": plexpy.CONFIG.GEOIP_DB,
|
||||
"geoip_db_installed": plexpy.CONFIG.GEOIP_DB_INSTALLED,
|
||||
"geoip_db_update_days": plexpy.CONFIG.GEOIP_DB_UPDATE_DAYS
|
||||
}
|
||||
|
||||
return serve_template(templatename="settings.html", title="Settings", config=config, kwargs=kwargs)
|
||||
@@ -2829,6 +2834,18 @@ class WebInterface(object):
|
||||
def configUpdate(self, **kwargs):
|
||||
# Handle the variable config options. Note - keys with False values aren't getting passed
|
||||
|
||||
# Check if we should refresh our data
|
||||
first_run = False
|
||||
server_changed = False
|
||||
reschedule = False
|
||||
https_changed = False
|
||||
refresh_libraries = False
|
||||
refresh_users = False
|
||||
|
||||
# First run from the setup wizard
|
||||
if kwargs.pop('first_run', None):
|
||||
first_run = True
|
||||
|
||||
checked_configs = [
|
||||
"launch_browser", "enable_https", "https_create_cert", "api_enabled", "freeze_db", "check_github",
|
||||
"grouping_global_history", "grouping_user_history", "grouping_charts", "group_history_tables",
|
||||
@@ -2863,13 +2880,13 @@ class WebInterface(object):
|
||||
kwargs['http_hashed_password'] = 1
|
||||
|
||||
# Flag to refresh JWT uuid to log out clients
|
||||
kwargs['jwt_update_secret'] = True
|
||||
kwargs['jwt_update_secret'] = True and not first_run
|
||||
|
||||
elif not kwargs.get('http_hash_password'):
|
||||
kwargs['http_hashed_password'] = 0
|
||||
|
||||
# Flag to refresh JWT uuid to log out clients
|
||||
kwargs['jwt_update_secret'] = True
|
||||
kwargs['jwt_update_secret'] = True and not first_run
|
||||
|
||||
else:
|
||||
kwargs['http_hashed_password'] = 0
|
||||
@@ -2879,18 +2896,6 @@ class WebInterface(object):
|
||||
kwargs[plain_config] = kwargs[use_config]
|
||||
del kwargs[use_config]
|
||||
|
||||
# Check if we should refresh our data
|
||||
first_run = False
|
||||
server_changed = False
|
||||
reschedule = False
|
||||
https_changed = False
|
||||
refresh_libraries = False
|
||||
refresh_users = False
|
||||
|
||||
# First run from the setup wizard
|
||||
if kwargs.pop('first_run', None):
|
||||
first_run = True
|
||||
|
||||
# If we change any monitoring settings, make sure we reschedule tasks.
|
||||
if kwargs.get('check_github') != plexpy.CONFIG.CHECK_GITHUB or \
|
||||
kwargs.get('refresh_libraries_interval') != str(plexpy.CONFIG.REFRESH_LIBRARIES_INTERVAL) or \
|
||||
@@ -2965,8 +2970,9 @@ class WebInterface(object):
|
||||
|
||||
# If first run, start websocket
|
||||
if first_run:
|
||||
webstart.restart()
|
||||
activity_pinger.connect_server(log=True, startup=True)
|
||||
|
||||
|
||||
# Reconfigure scheduler if intervals changed
|
||||
if reschedule:
|
||||
plexpy.initialize_scheduler()
|
||||
@@ -3053,15 +3059,17 @@ class WebInterface(object):
|
||||
@cherrypy.tools.json_out()
|
||||
@requireAuth(member_of("admin"))
|
||||
@addtoapi()
|
||||
def install_geoip_db(self, **kwargs):
|
||||
def install_geoip_db(self, update=False, **kwargs):
|
||||
""" Downloads and installs the GeoLite2 database """
|
||||
|
||||
result = helpers.install_geoip_db()
|
||||
update = True if update == 'true' else False
|
||||
|
||||
result = helpers.install_geoip_db(update=update)
|
||||
|
||||
if result:
|
||||
return {'result': 'success', 'message': 'GeoLite2 database installed successful.'}
|
||||
return {'result': 'success', 'message': 'GeoLite2 database installed successful.', 'updated': result}
|
||||
else:
|
||||
return {'result': 'error', 'message': 'GeoLite2 database install failed.'}
|
||||
return {'result': 'error', 'message': 'GeoLite2 database install failed.', 'updated': 0}
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out()
|
||||
|
@@ -25,6 +25,36 @@ from plexpy.helpers import create_https_certificates
|
||||
from plexpy.webserve import WebInterface
|
||||
|
||||
|
||||
def start():
|
||||
logger.info(u"Tautulli WebStart :: Initializing Tautulli web server...")
|
||||
web_config = {
|
||||
'http_port': plexpy.HTTP_PORT,
|
||||
'http_host': plexpy.CONFIG.HTTP_HOST,
|
||||
'http_root': plexpy.CONFIG.HTTP_ROOT,
|
||||
'http_environment': plexpy.CONFIG.HTTP_ENVIRONMENT,
|
||||
'http_proxy': plexpy.CONFIG.HTTP_PROXY,
|
||||
'enable_https': plexpy.CONFIG.ENABLE_HTTPS,
|
||||
'https_cert': plexpy.CONFIG.HTTPS_CERT,
|
||||
'https_cert_chain': plexpy.CONFIG.HTTPS_CERT_CHAIN,
|
||||
'https_key': plexpy.CONFIG.HTTPS_KEY,
|
||||
'http_username': plexpy.CONFIG.HTTP_USERNAME,
|
||||
'http_password': plexpy.CONFIG.HTTP_PASSWORD,
|
||||
'http_basic_auth': plexpy.CONFIG.HTTP_BASIC_AUTH
|
||||
}
|
||||
initialize(web_config)
|
||||
|
||||
|
||||
def stop():
|
||||
logger.info(u"Tautulli WebStart :: Stopping Tautulli web server...")
|
||||
cherrypy.engine.exit()
|
||||
|
||||
|
||||
def restart():
|
||||
logger.info(u"Tautulli WebStart :: Restarting Tautulli web server...")
|
||||
stop()
|
||||
start()
|
||||
|
||||
|
||||
def initialize(options):
|
||||
|
||||
# HTTPS stuff stolen from sickbeard
|
||||
|
Reference in New Issue
Block a user