Compare commits

..

27 Commits

Author SHA1 Message Date
JonnyWong16
76893100fc v2.2.0 2020-03-08 15:37:20 -07:00
JonnyWong16
96e8b808da Fix wording on reset git install note 2020-03-08 15:32:54 -07:00
JonnyWong16
595bff94b4 Add release to reset git install log message 2020-03-08 13:36:29 -07:00
JonnyWong16
5661c00497 Capitalize previous Windows platform in database 2020-03-08 11:26:13 -07:00
JonnyWong16
a98d7bd4eb Remove blank line 2020-03-06 15:43:18 -08:00
JonnyWong16
097203162d Update reset message to show release version 2020-03-06 15:39:15 -08:00
JonnyWong16
823c9b3159 Format git reset release 2020-03-06 15:36:11 -08:00
JonnyWong16
35965a6a1b Add message that Tautulli will restart when resetting git branch 2020-03-06 15:30:47 -08:00
JonnyWong16
8d67cc4c5a Restart after resetting git install 2020-03-06 15:26:26 -08:00
JonnyWong16
42e33a0468 Reset git install to the release version 2020-03-06 15:25:52 -08:00
JonnyWong16
b2529db026 Fix Live TV channel logo cut off in popover 2020-03-05 20:58:32 -08:00
JonnyWong16
e99c4aec4a Substitute after matching hyphenated ip in logs 2020-03-03 20:36:09 -08:00
JonnyWong16
2d0a97f259 Add hyphenated IP address to log filter 2020-03-03 20:20:53 -08:00
JonnyWong16
fabb52763b Fix graphs not loading when history grouping is disabled 2020-03-03 14:00:55 -08:00
JonnyWong16
03dd1a6974 Add note to repair git install setting 2020-03-03 14:00:42 -08:00
JonnyWong16
ecee50a5e4 Add option to change library background art 2020-03-03 12:24:05 -08:00
JonnyWong16
cbab7c4cbf Add clip to notification posters 2020-03-03 10:58:50 -08:00
JonnyWong16
257ea14c59 Add Plex background art to notification parameters 2020-03-03 10:51:27 -08:00
JonnyWong16
11299291b0 Remove ipaddr js XHR 2020-03-03 09:38:22 -08:00
JonnyWong16
533b8076e4 Merge pull request #1366 from felixbuenemann/fix-ipv6-display-in-activity
Fix display of IPv6 addresses in activity panes
2020-03-02 10:11:42 -08:00
Felix Bünemann
b0383b4813 Fix display of IPv6 addresses in activity panes
* Fix wrong width of ip-address field, causing it to be displayed as
"…", when exceeding 125px
* Add tooltip with full IP address on hover, if the address is longer
than 20 characters (maximum length that can be shown in 125px)
2020-03-01 16:19:17 +01:00
JonnyWong16
96500f75b0 Add option to reset Tautulli installation 2020-02-29 14:47:45 -08:00
JonnyWong16
2a3bd3413f Add --ff-only to git pull command 2020-02-29 14:10:20 -08:00
JonnyWong16
8ec136a0ca Fix creating metadata cache when starting session 2020-02-29 13:54:52 -08:00
JonnyWong16
2bead0fc29 Add live tv to log message 2020-02-29 13:54:24 -08:00
JonnyWong16
359776d48a Default webhook notification method to POST 2020-02-28 11:27:03 -08:00
JonnyWong16
05a16bb199 Fix tag in Docker release build workflow 2020-02-27 15:26:52 -08:00
27 changed files with 317 additions and 152 deletions

View File

@@ -10,9 +10,7 @@ jobs:
- name: Checkout Code
uses: actions/checkout@master
- name: Get Branch
run: echo ::set-env name=BRANCH::${GITHUB_REF#refs/heads/}
- name: Get Release Version
run: echo ::set-env name=RELEASE_VERSION::${GITHUB_REF/refs\/tags\//}
run: echo ::set-env name=BRANCH::${GITHUB_REF/refs\/tags\//}
- name: Publish to Registry
uses: elgohr/Publish-Docker-Github-Action@master
env:
@@ -23,7 +21,7 @@ jobs:
password: ${{ secrets.DOCKER_PASSWORD }}
dockerfile: Dockerfile
buildargs: VERSION, BRANCH
tags: ${{ env.RELEASE_VERSION }}
tags: ${{ env.BRANCH }}
- name: Post Status to Discord
uses: sarisia/actions-status-discord@v1
if: always()

1
API.md
View File

@@ -327,6 +327,7 @@ Required parameters:
Optional parameters:
custom_thumb (str): The URL for the custom library thumbnail
custom_art (str): The URL for the custom library background art
keep_history (int): 0 or 1
Returns:

View File

@@ -1,10 +1,11 @@
# Changelog
## v2.2.0-beta (2020-02-27)
## v2.2.0 (2020-03-08)
* Important Note!
* All Live TV changes requires Plex Media Server 1.18.7 or higher.
* Monitoring:
* Fix: Improved IPv6 display on the activity cards. (Thanks @felixbuenemann)
* New: Added Live TV metadata and posters to the activity cards.
* Change: Show bandwidth in Gbps when greater than 1000 Mbps.
* History:
@@ -18,8 +19,17 @@
* Notifications:
* Fix: Race condition causing stream count to be incorrect for playback stop notifications.
* New: Added Live TV channel notification parameters.
* New: Added Plex background art notification parameter.
* Note: This is the Plex API endpoint to retrieve the background art, not the actual image.
* New: Added poster images for clip notifications.
* Change: Default Webhook notification method to POST.
* UI:
* Fix: Windows platform showing up twice on the Most Active Platforms statistics card.
* New: Added option to change the background art for library sections when editing a library.
* New: Added button to reset Tautulli git installation in settings to fix failed git updates.
* API:
* New: Added ability to filter history using a "live" media type and by guid for the get_history API command.
* New: Added cutsom_art parameter to the edit_library API command.
* Other:
* Change: Add crossorigin use-credentials attribute to manifest tags. (Thanks @pkoenig10)
* Change: Disable automatic updates for Docker containers. Updates are now handled by updating the Docker container.

View File

@@ -263,6 +263,8 @@ def main():
plexpy.shutdown(restart=True)
elif plexpy.SIGNAL == 'checkout':
plexpy.shutdown(restart=True, checkout=True)
elif plexpy.SIGNAL == 'reset':
plexpy.shutdown(restart=True, reset=True)
else:
plexpy.shutdown(restart=True, update=True)

View File

@@ -291,6 +291,7 @@ ${next.modalIncludes()}
<script src="${http_root}js/bootstrap-hover-dropdown.min.js"></script>
<script src="${http_root}js/pnotify.custom.min.js"></script>
<script src="${http_root}js/platform.min.js"></script>
<script src="${http_root}js/ipaddr.min.js"></script>
<script src="${http_root}js/script.js${cache_param}"></script>
<script src="${http_root}js/jquery.tripleclick.min.js"></script>
% if _session['user_group'] == 'admin' and BROWSER_NOTIFIERS:

View File

@@ -973,7 +973,7 @@ a .users-poster-face:hover {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 140px;
max-width: 125px;
}
.dashboard-activity-info-time {
position: absolute;
@@ -3102,6 +3102,7 @@ div.dataTables_info {
border-left-color: #fff;
}
.tooltip-inner {
max-width: 250px;
color: #000;
background: #fff;
border: 0;
@@ -3152,7 +3153,7 @@ div.dataTables_info {
.channel-thumbnail {
background-color: #868b8b;
background-position: center;
background-size: cover;
background-size: contain;
background-origin: content-box;
background-repeat: no-repeat;
height: 50px;

View File

@@ -305,7 +305,11 @@ DOCUMENTATION :: END
% endif
<span id="location-${sk}">${data['location'].upper()}</span>:
% if data['ip_address'] != 'N/A':
<span class="ip-container"><span class="ip-address">${data['ip_address']}</span></span>
% if len(data['ip_address']) > 20:
<span class="ip-container"><span class="ip-address" data-toggle="tooltip" title="${data['ip_address']}">${data['ip_address']}</span></span>
% else:
<span class="ip-container"><span class="ip-address">${data['ip_address']}</span></span>
% endif
% if data['relayed']:
<span data-toggle="tooltip" title="Plex Relay"><i class="fa fa-exclamation-circle"></i></span>
% else:

View File

@@ -40,13 +40,22 @@ DOCUMENTATION :: END
<div class="modal-body" id="modal-text">
<fieldset>
<div class="form-group">
<label for="profile_url">Library Picture URL</label>
<label for="profile_url">Library Thumbnail URL</label>
<div class="row">
<div class="col-md-8">
<input type="text" class="form-control" id="custom_thumb_url" name="custom_thumb_url" value="${data['library_thumb']}">
</div>
</div>
<p class="help-block">Change the library's picture in Tautulli. To reset to default, leave this field empty and save.</p>
<p class="help-block">Change the library's thumbnail in Tautulli. To reset to default, leave this field empty and save.</p>
</div>
<div class="form-group">
<label for="profile_url">Library Background Art URL</label>
<div class="row">
<div class="col-md-8">
<input type="text" class="form-control" id="custom_art_url" name="custom_art_url" value="${data['library_art']}">
</div>
</div>
<p class="help-block">Change the library's background art in Tautulli. To reset to default, leave this field empty and save.</p>
</div>
<div class="checkbox">
<label>
@@ -80,6 +89,7 @@ DOCUMENTATION :: END
// Save library options
$("#save_library").on('click', function () {
var custom_thumb = $("#custom_thumb_url").val();
var custom_art = $("#custom_art_url").val();
var keep_history = 0;
if ($("#keep_history").is(":checked")) {
keep_history = 1;
@@ -90,6 +100,7 @@ DOCUMENTATION :: END
data: {
section_id: '${data["section_id"]}',
custom_thumb: custom_thumb,
custom_art: custom_art,
keep_history: keep_history
},
cache: false,

View File

@@ -183,76 +183,6 @@ DOCUMENTATION :: END
</div>
% endif
% endfor
<script>
$('.dashboard-stats-instance .dashboard-stats-info-scroller').scrollbar();
function changeImages(elem) {
var stat_id = $(elem).data('stat_id');
var art = $(elem).data('art');
var thumb = $(elem).data('thumb');
var user_id = $(elem).data('user_id');
var user_thumb = $(elem).data('user_thumb');
var rating_key = $(elem).data('rating_key');
var guid = $(elem).data('guid');
var live = $(elem).data('live');
var [height, fallback_poster, fallback_art] = [450, 'poster', 'art'];
if ($.inArray(stat_id, ['top_music', 'popular_music']) > -1) {
[height, fallback_poster, fallback_art] = [300, 'cover', 'art'];
} else if (live) {
[height, fallback_poster, fallback_art] = [450, 'poster-live', 'art-live'];
}
var href = '#';
if (stat_id === 'most_concurrent') {
return
} else if (stat_id === 'top_users') {
$('#stats-thumb-' + stat_id).css('background-image', 'url(' + (user_thumb || 'images/gravatar-default.png') + ')');
if (user_id) {
href = page('user', user_id);
}
$('#stats-thumb-url-' + stat_id).attr('href', href).prop('title', $(elem).data('friendly_name'));
} else if (stat_id === 'top_platforms') {
$('#stats-thumb-' + stat_id).removeClass(function (index, className) {
return (className.match (/(^|\s)platform-\S+/g) || []).join(' ');
}).addClass('platform-' + $(elem).data('platform'));
$('#stats-background-' + stat_id).removeClass(function (index, className) {
return (className.match (/(^|\s)platform-\S+/g) || []).join(' ');
}).addClass('platform-' + $(elem).data('platform') + '-rgba');
} else {
if (rating_key) {
if (live) {
href = page('info', rating_key, guid, true, live);
} else {
href = page('info', rating_key);
}
}
$('#stats-thumb-url-' + stat_id).attr('href', href).prop('title', $(elem).data('title'));
$('#stats-background-' + stat_id).css('background-image', 'url(' + page('pms_image_proxy', art, rating_key, 500, 280, 40, '282828', 3, fallback_art) + ')');
$('#stats-thumb-' + stat_id).css('background-image', 'url(' + page('pms_image_proxy', thumb, rating_key, 300, height, null, null, null, fallback_poster) + ')');
$('#stats-thumb-' + stat_id + '-bg').css('background-image', 'url(' + page('pms_image_proxy', thumb, rating_key, 300, height, 60, '282828', 3, fallback_poster) + ')');
}
}
$('.dashboard-stats-info-item').mouseenter(function () {
changeImages(this);
if ($(this).data('stat_id') === 'last_watched') {
var friendly_name = $(this).data('friendly_name');
var last_watch = moment($(this).data('last_watch'), 'X').format(date_format);
$('#last-watched-header-info').html(friendly_name);
} else if ($(this).data('stat_id') === 'most_concurrent') {
var started = moment($(this).data('started'), 'X').format(date_format + ' ' + time_format);
$('#most-concurrent-header-info').html(started);
}
});
$('.dashboard-stats-instance').mouseleave(function () {
changeImages($(this).find('.dashboard-stats-info-item').first());
if ($(this).data('stat_id') === 'last_watched') {
$('#last-watched-header-info').text($(this).find('.dashboard-stats-info-item').first().data('friendly_name'));
} else if ($(this).data('stat_id') === 'most_concurrent') {
$('#most-concurrent-header-info').text('streams');
}
});
</script>
% else:
<div class="text-muted">No stats to show for the selected period.</div><br>
% endif

View File

@@ -726,6 +726,88 @@
% endif
</script>
% endif
% if 'watch_stats' in config['home_sections'] or 'library_stats' in config['home_sections']:
<script>
function statsCardCallback() {
$('.dashboard-stats-instance .dashboard-stats-info-scroller').scrollbar();
function changeImages(elem) {
var stat_id = $(elem).data('stat_id');
var art = $(elem).data('art');
var thumb = $(elem).data('thumb');
var user_id = $(elem).data('user_id');
var user_thumb = $(elem).data('user_thumb');
var rating_key = $(elem).data('rating_key');
var guid = $(elem).data('guid');
var live = $(elem).data('live');
var [height, fallback_poster, fallback_art] = [450, 'poster', 'art'];
if ($.inArray(stat_id, ['top_music', 'popular_music']) > -1) {
[height, fallback_poster, fallback_art] = [300, 'cover', 'art'];
} else if (live) {
[height, fallback_poster, fallback_art] = [450, 'poster-live', 'art-live'];
}
var href = '#';
if (stat_id === 'most_concurrent') {
return
} else if (stat_id === 'top_users') {
$('#stats-thumb-' + stat_id).css('background-image', 'url(' + (user_thumb || 'images/gravatar-default.png') + ')');
if (user_id) {
href = page('user', user_id);
}
$('#stats-thumb-url-' + stat_id).attr('href', href).prop('title', $(elem).data('friendly_name'));
} else if (stat_id === 'top_platforms') {
$('#stats-thumb-' + stat_id).removeClass(function (index, className) {
return (className.match (/(^|\s)platform-\S+/g) || []).join(' ');
}).addClass('platform-' + $(elem).data('platform'));
$('#stats-background-' + stat_id).removeClass(function (index, className) {
return (className.match (/(^|\s)platform-\S+/g) || []).join(' ');
}).addClass('platform-' + $(elem).data('platform') + '-rgba');
} else {
if (rating_key) {
if (live) {
href = page('info', rating_key, guid, true, live);
} else {
href = page('info', rating_key);
}
}
$('#stats-thumb-url-' + stat_id).attr('href', href).prop('title', $(elem).data('title'));
$('#stats-background-' + stat_id).css('background-image', 'url(' + page('pms_image_proxy', art, rating_key, 500, 280, 40, '282828', 3, fallback_art) + ')');
$('#stats-thumb-' + stat_id).css('background-image', 'url(' + page('pms_image_proxy', thumb, rating_key, 300, height, null, null, null, fallback_poster) + ')');
$('#stats-thumb-' + stat_id + '-bg').css('background-image', 'url(' + page('pms_image_proxy', thumb, rating_key, 300, height, 60, '282828', 3, fallback_poster) + ')');
$('#library-stats-background-' + stat_id).css('background-image', 'url(' + page('pms_image_proxy', art, rating_key, 500, 280, 40, '282828', 3, fallback_art) + ')');
if (thumb.startsWith('http')) {
$('#library-stats-thumb-' + stat_id).css('background-image', 'url(' + page('pms_image_proxy', thumb, rating_key, 300, 300, null, null, null, 'cover') + ')')
.removeClass('svg-icon library-' + stat_id);
} else {
$('#library-stats-thumb-' + stat_id).css('background-image', '')
.addClass('svg-icon library-' + stat_id);
}
}
}
$('.dashboard-stats-info-item').mouseenter(function () {
changeImages(this);
if ($(this).data('stat_id') === 'last_watched') {
var friendly_name = $(this).data('friendly_name');
var last_watch = moment($(this).data('last_watch'), 'X').format(date_format);
$('#last-watched-header-info').html(friendly_name);
} else if ($(this).data('stat_id') === 'most_concurrent') {
var started = moment($(this).data('started'), 'X').format(date_format + ' ' + time_format);
$('#most-concurrent-header-info').html(started);
}
});
$('.dashboard-stats-instance').mouseleave(function () {
changeImages($(this).find('.dashboard-stats-info-item').first());
if ($(this).data('stat_id') === 'last_watched') {
$('#last-watched-header-info').text($(this).find('.dashboard-stats-info-item').first().data('friendly_name'));
} else if ($(this).data('stat_id') === 'most_concurrent') {
$('#most-concurrent-header-info').text('streams');
}
});
}
</script>
% endif
% if 'watch_stats' in config['home_sections']:
<script>
function getHomeStats(time_range, stats_type) {
@@ -744,6 +826,7 @@
$("#home-stats").html(xhr.responseText);
$('#ajaxMsg').fadeOut();
lockScroll('#home-stats .dashboard-stats-info-scroller');
statsCardCallback();
}
});
}
@@ -783,6 +866,7 @@
data: { },
complete: function (xhr, status) {
$("#library-stats").html(xhr.responseText);
statsCardCallback();
}
});
}

View File

@@ -258,33 +258,31 @@ $.cachedScript = function (url) {
function isPrivateIP(ip_address) {
var defer = $.Deferred();
$.cachedScript('js/ipaddr.min.js').done(function () {
if (ipaddr.isValid(ip_address)) {
var addr = ipaddr.process(ip_address);
if (ipaddr.isValid(ip_address)) {
var addr = ipaddr.process(ip_address);
var rangeList = [];
if (addr.kind() === 'ipv4') {
rangeList = [
ipaddr.parseCIDR('127.0.0.0/8'),
ipaddr.parseCIDR('10.0.0.0/8'),
ipaddr.parseCIDR('172.16.0.0/12'),
ipaddr.parseCIDR('192.168.0.0/16')
];
} else {
rangeList = [
ipaddr.parseCIDR('fd00::/8')
];
}
if (ipaddr.subnetMatch(addr, rangeList, -1) >= 0) {
defer.resolve();
} else {
defer.reject();
}
var rangeList = [];
if (addr.kind() === 'ipv4') {
rangeList = [
ipaddr.parseCIDR('127.0.0.0/8'),
ipaddr.parseCIDR('10.0.0.0/8'),
ipaddr.parseCIDR('172.16.0.0/12'),
ipaddr.parseCIDR('192.168.0.0/16')
];
} else {
defer.resolve('n/a');
rangeList = [
ipaddr.parseCIDR('fd00::/8')
];
}
});
if (ipaddr.subnetMatch(addr, rangeList, -1) >= 0) {
defer.resolve();
} else {
defer.reject();
}
} else {
defer.resolve('n/a');
}
return defer.promise();
}

View File

@@ -61,8 +61,8 @@ DOCUMENTATION :: END
<div class="col-md-12">
<div class="table-card-back">
<div class="user-info-wrapper">
% if data['library_thumb'][:4] == 'http':
<div class="library-info-poster-face" style="background-image: url(${data['library_thumb']});"></div>
% if data['library_thumb'].startswith('http'):
<div class="library-info-poster-face" style="background-image: url(${page('pms_image_proxy', data['library_thumb'], None, 80, 80)});"></div>
% else:
<div class="library-info-poster-face svg-icon library-${data['section_type']}"></div>
% endif

View File

@@ -35,10 +35,17 @@ DOCUMENTATION :: END
%>
% for section_type in types:
% if section_type in data:
<%
row0 = data[section_type][0]
%>
<div class="dashboard-stats-instance" id="library-stats-instance-${section_type}" data-section_type="${section_type}">
<div class="dashboard-stats-container">
<div id="library-stats-background-${section_type}" class="dashboard-stats-background" style="background-image: url(${page('pms_image_proxy', '/:/resources/' + section_type + '-fanart.jpg', None, 500, 280, 40, '282828', 3, fallback='art')});">
<div id="library-stats-background-${section_type}" class="dashboard-stats-background" style="background-image: url(${page('pms_image_proxy', row0['art'], None, 500, 280, 40, '282828', 3, fallback='art')});">
% if row0['thumb'].startswith('http'):
<div id="library-stats-thumb-${section_type}" class="dashboard-stats-flat hidden-xs" style="background-image: url(${page('pms_image_proxy', row0['thumb'], None, 80, 80)});"></div>
% else:
<div id="library-stats-thumb-${section_type}" class="dashboard-stats-flat svg-icon library-${section_type} hidden-xs"></div>
% endif
<div class="dashboard-stats-info-container">
<div id="library-stats-title-${section_type}" class="dashboard-stats-info-title">
<h4>${headers[section_type][0]}</h4>
@@ -48,7 +55,8 @@ DOCUMENTATION :: END
<div class="dashboard-stats-info scoller-content">
<ul class="list-unstyled dashboard-stats-info-list">
% for section in data[section_type]:
<li class="dashboard-stats-info-item ${'expanded' if loop.index == 0 else ''}">
<li class="dashboard-stats-info-item ${'expanded' if loop.index == 0 else ''}" data-stat_id="${section_type}"
data-art="${section.get('art')}" data-thumb="${section.get('thumb')}">
<div class="sub-list">${loop.index + 1}</div>
<div class="sub-value">
<a href="${page('library', section['section_id'])}" title="${section['section_name']}">

View File

@@ -265,6 +265,20 @@
</div>
<p class="help-block">Optional: The path to your git environment variable. Leave blank for default.</p>
</div>
<div class="form-group advanced-setting">
<label>Repair Git Install</label>
<p class="help-block">
Attempt to fix updating by resetting your Tautulli installation back to <strong>${common.RELEASE}</strong>.<br />
Note: This will not affect any saved history or settings.
</p>
<div class="row">
<div class="col-md-4">
<div class="btn-group">
<button class="btn btn-form" type="button" id="reset_git_install">Reset</button>
</div>
</div>
</div>
</div>
% endif
<p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p>
@@ -2154,6 +2168,17 @@ $(document).ready(function() {
}
});
$("#reset_git_install").click(function () {
var msg = 'Are you sure you want to reset your Tautulli installtion back to <strong>${common.RELEASE}</strong>?' +
'<br /><br />Tautulli will restart.';
$('#confirm-message').html(msg);
$('#confirm-modal').modal();
$('#confirm-modal').one('click', '#confirm-button', function () {
settingsChanged = false;
window.location.href = 'reset_git_install';
});
});
$('#api_key').click(function(){ $('#api_key').select() });
$("#generate_api").click(function() {
$.get('generate_api_key',

View File

@@ -672,7 +672,8 @@ def dbcheck():
c_db.execute(
'CREATE TABLE IF NOT EXISTS library_sections (id INTEGER PRIMARY KEY AUTOINCREMENT, '
'server_id TEXT, section_id INTEGER, section_name TEXT, section_type TEXT, agent TEXT, '
'thumb TEXT, custom_thumb_url TEXT, art TEXT, count INTEGER, parent_count INTEGER, child_count INTEGER, '
'thumb TEXT, custom_thumb_url TEXT, art TEXT, custom_art_url TEXT, '
'count INTEGER, parent_count INTEGER, child_count INTEGER, '
'do_notify INTEGER DEFAULT 1, do_notify_created INTEGER DEFAULT 1, keep_history INTEGER DEFAULT 1, '
'deleted_section INTEGER DEFAULT 0, UNIQUE(server_id, section_id))'
)
@@ -1320,6 +1321,18 @@ def dbcheck():
'ALTER TABLE session_history ADD COLUMN relayed INTEGER'
)
# Upgrade session_history table from earlier versions
try:
result = c_db.execute('SELECT platform FROM session_history '
'WHERE platform = "windows"').fetchall()
if len(result) > 0:
logger.debug(u"Altering database. Capitalizing Windows platform values in session_history table.")
c_db.execute(
'UPDATE session_history SET platform = "Windows" WHERE platform = "windows" '
)
except sqlite3.OperationalError:
logger.warn(u"Unable to capitalize Windows platform values in session_history table.")
# Upgrade session_history_metadata table from earlier versions
try:
c_db.execute('SELECT full_title FROM session_history_metadata')
@@ -1881,6 +1894,15 @@ def dbcheck():
'ALTER TABLE library_sections ADD COLUMN agent TEXT'
)
# Upgrade library_sections table from earlier versions
try:
c_db.execute('SELECT custom_art_url FROM library_sections')
except sqlite3.OperationalError:
logger.debug(u"Altering database. Updating database table library_sections.")
c_db.execute(
'ALTER TABLE library_sections ADD COLUMN custom_art_url TEXT'
)
# Upgrade users table from earlier versions (remove UNIQUE constraint on username)
try:
result = c_db.execute('SELECT SQL FROM sqlite_master WHERE type="table" AND name="users"').fetchone()
@@ -2067,7 +2089,7 @@ def upgrade():
libraries.update_libraries_db_notify()
def shutdown(restart=False, update=False, checkout=False):
def shutdown(restart=False, update=False, checkout=False, reset=False):
webstart.stop()
# Shutdown the websocket connection
@@ -2102,6 +2124,13 @@ def shutdown(restart=False, update=False, checkout=False):
except Exception as e:
logger.warn(u"Tautulli failed to switch git branch: %s. Restarting." % e)
if reset:
logger.info(u"Tautulli is resetting the git install...")
try:
versioncheck.reset_git_install()
except Exception as e:
logger.warn(u"Tautulli failed to reset git install: %s. Restarting." % e)
if CREATEPID:
logger.info(u"Removing pidfile %s", PIDFILE)
os.remove(PIDFILE)

View File

@@ -117,9 +117,9 @@ class ActivityHandler(object):
if not session:
return
logger.debug(u"Tautulli ActivityHandler :: Session %s started by user %s (%s) with ratingKey %s (%s)."
logger.debug(u"Tautulli ActivityHandler :: Session %s started by user %s (%s) with ratingKey %s (%s)%s."
% (str(session['session_key']), str(session['user_id']), session['username'],
str(session['rating_key']), session['full_title']))
str(session['rating_key']), session['full_title'], '[Live TV]' if session['live'] else ''))
plexpy.NOTIFY_QUEUE.put({'stream_data': session.copy(), 'notify_action': 'on_play'})

View File

@@ -531,6 +531,7 @@ NOTIFICATION_PARAMETERS = [
{'name': 'Rating Key', 'type': 'int', 'value': 'rating_key', 'description': 'The unique identifier for the movie, episode, or track.'},
{'name': 'Parent Rating Key', 'type': 'int', 'value': 'parent_rating_key', 'description': 'The unique identifier for the season or album.'},
{'name': 'Grandparent Rating Key', 'type': 'int', 'value': 'grandparent_rating_key', 'description': 'The unique identifier for the TV show or artist.'},
{'name': 'Art', 'type': 'str', 'value': 'art', 'description': 'The Plex background art for the media.'},
{'name': 'Thumb', 'type': 'str', 'value': 'thumb', 'description': 'The Plex thumbnail for the movie or episode.'},
{'name': 'Parent Thumb', 'type': 'str', 'value': 'parent_thumb', 'description': 'The Plex thumbnail for the season or album.'},
{'name': 'Grandparent Thumb', 'type': 'str', 'value': 'grandparent_thumb', 'description': 'The Plex thumbnail for the TV show or artist.'},

View File

@@ -862,7 +862,8 @@ class DataFactory(object):
try:
query = 'SELECT section_id, section_name, section_type, thumb AS library_thumb, ' \
'custom_thumb_url AS custom_thumb, art, count, parent_count, child_count ' \
'custom_thumb_url AS custom_thumb, art AS library_art, custom_art_url AS custom_art, ' \
'count, parent_count, child_count ' \
'FROM library_sections ' \
'WHERE section_id IN (%s) ' \
'ORDER BY section_type, count DESC, parent_count DESC, child_count DESC ' % ','.join(library_cards)
@@ -879,11 +880,16 @@ class DataFactory(object):
else:
library_thumb = common.DEFAULT_COVER_THUMB
if item['custom_art'] and item['custom_art'] != item['library_art']:
library_art = item['custom_art']
else:
library_art = item['library_art']
library = {'section_id': item['section_id'],
'section_name': item['section_name'],
'section_type': item['section_type'],
'thumb': library_thumb,
'art': item['art'],
'art': library_art,
'count': item['count'],
'child_count': item['parent_count'],
'grandchild_count': item['child_count']

View File

@@ -43,7 +43,7 @@ class Graphs(object):
if grouping is None:
grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES
group_by = 'reference_id' if grouping else 'id'
group_by = 'session_history.reference_id' if grouping else 'session_history.id'
try:
if y_axis == 'plays':
@@ -157,7 +157,7 @@ class Graphs(object):
if grouping is None:
grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES
group_by = 'reference_id' if grouping else 'id'
group_by = 'session_history.reference_id' if grouping else 'session_history.id'
try:
if y_axis == 'plays':
@@ -288,7 +288,7 @@ class Graphs(object):
if grouping is None:
grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES
group_by = 'reference_id' if grouping else 'id'
group_by = 'session_history.reference_id' if grouping else 'session_history.id'
try:
if y_axis == 'plays':
@@ -401,7 +401,7 @@ class Graphs(object):
if grouping is None:
grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES
group_by = 'reference_id' if grouping else 'id'
group_by = 'session_history.reference_id' if grouping else 'session_history.id'
try:
if y_axis == 'plays':
@@ -522,7 +522,7 @@ class Graphs(object):
if grouping is None:
grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES
group_by = 'reference_id' if grouping else 'id'
group_by = 'session_history.reference_id' if grouping else 'session_history.id'
try:
if y_axis == 'plays':
@@ -618,7 +618,7 @@ class Graphs(object):
if grouping is None:
grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES
group_by = 'reference_id' if grouping else 'id'
group_by = 'session_history.reference_id' if grouping else 'session_history.id'
try:
if y_axis == 'plays':
@@ -727,7 +727,7 @@ class Graphs(object):
if grouping is None:
grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES
group_by = 'reference_id' if grouping else 'id'
group_by = 'session_history.reference_id' if grouping else 'session_history.id'
try:
if y_axis == 'plays':
@@ -833,7 +833,7 @@ class Graphs(object):
if grouping is None:
grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES
group_by = 'reference_id' if grouping else 'id'
group_by = 'session_history.reference_id' if grouping else 'session_history.id'
try:
if y_axis == 'plays':
@@ -919,7 +919,7 @@ class Graphs(object):
if grouping is None:
grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES
group_by = 'reference_id' if grouping else 'id'
group_by = 'session_history.reference_id' if grouping else 'session_history.id'
try:
if y_axis == 'plays':
@@ -1029,7 +1029,7 @@ class Graphs(object):
if grouping is None:
grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES
group_by = 'reference_id' if grouping else 'id'
group_by = 'session_history.reference_id' if grouping else 'session_history.id'
try:
if y_axis == 'plays':
@@ -1119,7 +1119,7 @@ class Graphs(object):
if grouping is None:
grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES
group_by = 'reference_id' if grouping else 'id'
group_by = 'session_history.reference_id' if grouping else 'session_history.id'
try:
if y_axis == 'plays':

View File

@@ -708,12 +708,14 @@ class Libraries(object):
return True
def set_config(self, section_id=None, custom_thumb='', do_notify=1, keep_history=1, do_notify_created=1):
def set_config(self, section_id=None, custom_thumb='', custom_art='',
do_notify=1, keep_history=1, do_notify_created=1):
if section_id:
monitor_db = database.MonitorDatabase()
key_dict = {'section_id': section_id}
value_dict = {'custom_thumb_url': custom_thumb,
'custom_art_url': custom_art,
'do_notify': do_notify,
'do_notify_created': do_notify_created,
'keep_history': keep_history}
@@ -746,7 +748,8 @@ class Libraries(object):
try:
if str(section_id).isdigit():
query = 'SELECT section_id, section_name, section_type, count, parent_count, child_count, ' \
'thumb AS library_thumb, custom_thumb_url AS custom_thumb, art, ' \
'thumb AS library_thumb, custom_thumb_url AS custom_thumb, art AS library_art, ' \
'custom_art_url AS custom_art, ' \
'do_notify, do_notify_created, keep_history, deleted_section ' \
'FROM library_sections ' \
'WHERE section_id = ? '
@@ -767,11 +770,16 @@ class Libraries(object):
else:
library_thumb = common.DEFAULT_COVER_THUMB
if item['custom_art'] and item['custom_art'] != item['library_art']:
library_art = item['custom_art']
else:
library_art = item['library_art']
library_details = {'section_id': item['section_id'],
'section_name': item['section_name'],
'section_type': item['section_type'],
'library_thumb': library_thumb,
'library_art': item['art'],
'library_art': library_art,
'count': item['count'],
'parent_count': item['parent_count'],
'child_count': item['child_count'],

View File

@@ -140,11 +140,12 @@ class PublicIPFilter(RegexFilter):
super(PublicIPFilter, self).__init__()
# Currently only checking for ipv4 addresses
self.regex = re.compile(r'[0-9]+(?:\.[0-9]+){3}(?!\d*-[a-z0-9]{6})')
self.regex = re.compile(r'[0-9]+(?:[.-][0-9]+){3}(?!\d*-[a-z0-9]{6})')
def replace(self, text, ip):
if helpers.is_public_ip(ip):
return text.replace(ip, ip.partition('.')[0] + '.***.***.***')
if helpers.is_public_ip(ip.replace('-', '.')):
partition = '-' if '-' in ip else '.'
return text.replace(ip, ip.partition(partition)[0] + (partition + '***') * 3)
return text

View File

@@ -690,6 +690,13 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
poster_key = notify_params['parent_rating_key']
poster_title = '%s - %s' % (notify_params['grandparent_title'],
notify_params['parent_title'])
elif notify_params['media_type'] == 'clip':
if notify_params['extra_type']:
poster_thumb = notify_params['art'].replace('/art', '/thumb') or notify_params['thumb']
else:
poster_thumb = notify_params['parent_thumb'] or notify_params['thumb']
poster_key = notify_params['rating_key']
poster_title = notify_params['title']
else:
poster_thumb = ''
poster_key = ''
@@ -995,6 +1002,7 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
'rating_key': notify_params['rating_key'],
'parent_rating_key': notify_params['parent_rating_key'],
'grandparent_rating_key': notify_params['grandparent_rating_key'],
'art': notify_params['art'],
'thumb': notify_params['thumb'],
'parent_thumb': notify_params['parent_thumb'],
'grandparent_thumb': notify_params['grandparent_thumb'],

View File

@@ -3609,7 +3609,7 @@ class WEBHOOK(Notifier):
"""
NAME = 'Webhook'
_DEFAULT_CONFIG = {'hook': '',
'method': ''
'method': 'POST'
}
def agent_notify(self, subject='', body='', action='', **kwargs):
@@ -3655,8 +3655,7 @@ class WEBHOOK(Notifier):
'name': 'webhook_method',
'description': 'The Webhook HTTP request method.',
'input_type': 'select',
'select_options': {'': '',
'GET': 'GET',
'select_options': {'GET': 'GET',
'POST': 'POST',
'PUT': 'PUT',
'DELETE': 'DELETE'}

View File

@@ -572,7 +572,7 @@ class PmsConnect(object):
return output
def get_metadata_details(self, rating_key='', sync_id='', plex_guid='',
cache_key=None, return_cache=False, media_info=True):
skip_cache=False, cache_key=None, return_cache=False, media_info=True):
"""
Return processed and validated metadata list for requested item.
@@ -582,7 +582,7 @@ class PmsConnect(object):
"""
metadata = {}
if cache_key:
if not skip_cache and cache_key:
in_file_folder = os.path.join(plexpy.CONFIG.CACHE_DIR, 'session_metadata')
in_file_path = os.path.join(in_file_folder, 'metadata-sessionKey-%s.json' % cache_key)
@@ -1929,12 +1929,12 @@ class PmsConnect(object):
media_id = helpers.get_xml_attr(stream_media_info, 'id')
part_id = helpers.get_xml_attr(stream_media_parts_info, 'id')
cache_key = None if skip_cache else session_key
if sync_id:
metadata_details = self.get_metadata_details(rating_key=rating_key, sync_id=sync_id, cache_key=cache_key)
metadata_details = self.get_metadata_details(rating_key=rating_key, sync_id=sync_id,
skip_cache=skip_cache, cache_key=session_key)
else:
metadata_details = self.get_metadata_details(rating_key=rating_key, cache_key=cache_key)
metadata_details = self.get_metadata_details(rating_key=rating_key,
skip_cache=skip_cache, cache_key=session_key)
# Get the media info, fallback to first item if match id is not found
source_medias = metadata_details.pop('media_info', [])

View File

@@ -1,2 +1,2 @@
PLEXPY_BRANCH = "beta"
PLEXPY_RELEASE_VERSION = "v2.2.0-beta"
PLEXPY_BRANCH = "master"
PLEXPY_RELEASE_VERSION = "v2.2.0"

View File

@@ -251,26 +251,26 @@ def update():
logger.info('Windows .exe updating not supported yet.')
elif plexpy.INSTALL_TYPE == 'git':
output, err = runGit('pull ' + plexpy.CONFIG.GIT_REMOTE + ' ' + plexpy.CONFIG.GIT_BRANCH)
output, err = runGit('pull {} {} --ff-only'.format(plexpy.CONFIG.GIT_REMOTE,
plexpy.CONFIG.GIT_BRANCH))
if not output:
logger.error('Unable to download latest version')
return
for line in output.split('\n'):
if 'Already up-to-date.' in line:
if 'Already up-to-date.' in line or 'Already up to date.' in line:
logger.info('No update available, not updating')
logger.info('Output: ' + str(output))
elif line.endswith(('Aborting', 'Aborting.')):
logger.error('Unable to update from git: ' + line)
logger.info('Output: ' + str(output))
elif plexpy.INSTALL_TYPE == 'docker':
return
else:
tar_download_url = 'https://github.com/{}/{}/tarball/{}'.format(plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO, plexpy.CONFIG.GIT_BRANCH)
tar_download_url = 'https://github.com/{}/{}/tarball/{}'.format(plexpy.CONFIG.GIT_USER,
plexpy.CONFIG.GIT_REPO,
plexpy.CONFIG.GIT_BRANCH)
update_dir = os.path.join(plexpy.PROG_DIR, 'update')
version_path = os.path.join(plexpy.PROG_DIR, 'version.txt')
@@ -328,10 +328,41 @@ def update():
return
def reset_git_install():
if plexpy.INSTALL_TYPE == 'git':
logger.info('Attempting to reset git install to "{}/{}/{}"'.format(plexpy.CONFIG.GIT_REMOTE,
plexpy.CONFIG.GIT_BRANCH,
common.RELEASE))
output, err = runGit('remote set-url {} https://github.com/{}/{}.git'.format(plexpy.CONFIG.GIT_REMOTE,
plexpy.CONFIG.GIT_USER,
plexpy.CONFIG.GIT_REPO))
output, err = runGit('fetch {}'.format(plexpy.CONFIG.GIT_REMOTE))
output, err = runGit('checkout {}'.format(plexpy.CONFIG.GIT_BRANCH))
output, err = runGit('branch -u {}/{}'.format(plexpy.CONFIG.GIT_REMOTE,
plexpy.CONFIG.GIT_BRANCH))
output, err = runGit('reset --hard {}'.format(common.RELEASE))
if not output:
logger.error('Unable to reset Tautulli installation.')
return False
for line in output.split('\n'):
if 'Already up-to-date.' in line or 'Already up to date.' in line:
logger.info('Tautulli installation reset successfully.')
return True
elif line.endswith(('Aborting', 'Aborting.')):
logger.error('Unable to reset Tautulli installation: ' + line)
return False
def checkout_git_branch():
if plexpy.INSTALL_TYPE == 'git':
output, err = runGit('fetch %s' % plexpy.CONFIG.GIT_REMOTE)
output, err = runGit('checkout %s' % plexpy.CONFIG.GIT_BRANCH)
logger.info('Attempting to checkout git branch "{}/{}"'.format(plexpy.CONFIG.GIT_REMOTE,
plexpy.CONFIG.GIT_BRANCH))
output, err = runGit('fetch {}'.format(plexpy.CONFIG.GIT_REMOTE))
output, err = runGit('checkout {}'.format(plexpy.CONFIG.GIT_BRANCH))
if not output:
logger.error('Unable to change git branch.')
@@ -340,9 +371,10 @@ def checkout_git_branch():
for line in output.split('\n'):
if line.endswith(('Aborting', 'Aborting.')):
logger.error('Unable to checkout from git: ' + line)
logger.info('Output: ' + str(output))
return
output, err = runGit('pull %s %s' % (plexpy.CONFIG.GIT_REMOTE, plexpy.CONFIG.GIT_BRANCH))
output, err = runGit('pull {} {}'.format(plexpy.CONFIG.GIT_REMOTE,
plexpy.CONFIG.GIT_BRANCH))
def read_changelog(latest_only=False, since_prev_release=False):

View File

@@ -526,6 +526,7 @@ class WebInterface(object):
Optional parameters:
custom_thumb (str): The URL for the custom library thumbnail
custom_art (str): The URL for the custom library background art
keep_history (int): 0 or 1
Returns:
@@ -533,6 +534,7 @@ class WebInterface(object):
```
"""
custom_thumb = kwargs.get('custom_thumb', '')
custom_art = kwargs.get('custom_art', '')
do_notify = kwargs.get('do_notify', 0)
do_notify_created = kwargs.get('do_notify_created', 0)
keep_history = kwargs.get('keep_history', 0)
@@ -542,6 +544,7 @@ class WebInterface(object):
library_data = libraries.Libraries()
library_data.set_config(section_id=section_id,
custom_thumb=custom_thumb,
custom_art=custom_art,
do_notify=do_notify,
do_notify_created=do_notify_created,
keep_history=keep_history)
@@ -3916,6 +3919,11 @@ class WebInterface(object):
plexpy.CONFIG.write()
return self.do_state_change('checkout', 'Switching Git Branches', 120)
@cherrypy.expose
@requireAuth(member_of("admin"))
def reset_git_install(self, **kwargs):
return self.do_state_change('reset', 'Resetting to {}'.format(common.RELEASE), 120)
@cherrypy.expose
@requireAuth(member_of("admin"))
def get_changelog(self, latest_only=False, since_prev_release=False, update_shown=False, **kwargs):