Compare commits
26 Commits
v2.1.30-be
...
v2.1.34
Author | SHA1 | Date | |
---|---|---|---|
![]() |
08714436c3 | ||
![]() |
f65f5d07c0 | ||
![]() |
a9b10c4560 | ||
![]() |
589fbd3158 | ||
![]() |
0ffc8c5d19 | ||
![]() |
7498617b74 | ||
![]() |
f21d505ab8 | ||
![]() |
7b16af0585 | ||
![]() |
a83108282a | ||
![]() |
1c4d01d6ec | ||
![]() |
22e6d4067d | ||
![]() |
1046b29c1a | ||
![]() |
d6127e28f3 | ||
![]() |
25a949356d | ||
![]() |
72a012b817 | ||
![]() |
f439bd639c | ||
![]() |
91476a420a | ||
![]() |
96c0f9cad5 | ||
![]() |
df50559495 | ||
![]() |
6d35bd7947 | ||
![]() |
d27356bbba | ||
![]() |
3054a824ce | ||
![]() |
3b22b6a3f7 | ||
![]() |
e4be5a716f | ||
![]() |
13579b8140 | ||
![]() |
b11437b86b |
7
API.md
7
API.md
@@ -700,8 +700,9 @@ Returns:
|
|||||||
"parent_title": "",
|
"parent_title": "",
|
||||||
"paused_counter": 0,
|
"paused_counter": 0,
|
||||||
"percent_complete": 84,
|
"percent_complete": 84,
|
||||||
"platform": "Chrome",
|
"platform": "Windows",
|
||||||
"player": "Plex Web (Chrome)",
|
"product": "Plex for Windows",
|
||||||
|
"player": "Castle-PC",
|
||||||
"rating_key": 4348,
|
"rating_key": 4348,
|
||||||
"reference_id": 1123,
|
"reference_id": 1123,
|
||||||
"session_key": null,
|
"session_key": null,
|
||||||
@@ -833,6 +834,7 @@ Required parameters:
|
|||||||
None
|
None
|
||||||
|
|
||||||
Optional parameters:
|
Optional parameters:
|
||||||
|
grouping (int): 0 or 1
|
||||||
order_column (str): "library_thumb", "section_name", "section_type", "count", "parent_count",
|
order_column (str): "library_thumb", "section_name", "section_type", "count", "parent_count",
|
||||||
"child_count", "last_accessed", "last_played", "plays", "duration"
|
"child_count", "last_accessed", "last_played", "plays", "duration"
|
||||||
order_dir (str): "desc" or "asc"
|
order_dir (str): "desc" or "asc"
|
||||||
@@ -2341,6 +2343,7 @@ Required parameters:
|
|||||||
None
|
None
|
||||||
|
|
||||||
Optional parameters:
|
Optional parameters:
|
||||||
|
grouping (int): 0 or 1
|
||||||
order_column (str): "user_thumb", "friendly_name", "last_seen", "ip_address", "platform",
|
order_column (str): "user_thumb", "friendly_name", "last_seen", "ip_address", "platform",
|
||||||
"player", "last_played", "plays", "duration"
|
"player", "last_played", "plays", "duration"
|
||||||
order_dir (str): "desc" or "asc"
|
order_dir (str): "desc" or "asc"
|
||||||
|
49
CHANGELOG.md
49
CHANGELOG.md
@@ -1,5 +1,52 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v2.1.34 (2019-09-03)
|
||||||
|
|
||||||
|
* History:
|
||||||
|
* New: Added Product column to history tables.
|
||||||
|
* Notifications:
|
||||||
|
* Fix: IMDB/TMDb/TVDB/TVmaze ID notification parameters showing blank values after lookup.
|
||||||
|
* UI:
|
||||||
|
* Fix: Libraries and Users tables did not respect the group history setting.
|
||||||
|
* API:
|
||||||
|
* Fix: Title field was not searchable in get_library_media_info command.
|
||||||
|
* New: Added grouping option to get_libraries_table and get_users_table commands.
|
||||||
|
* New: Added product value to get_history command.
|
||||||
|
* Other:
|
||||||
|
* Fix: Could not verify Plex Media Server with unpublished hostnames.
|
||||||
|
* Change: Automatically logout all Tautulli instances when changing the admin password.
|
||||||
|
|
||||||
|
|
||||||
|
## v2.1.33 (2019-07-27)
|
||||||
|
|
||||||
|
* Notifications:
|
||||||
|
* Change: Mask notification agent password fields.
|
||||||
|
* Change: Enable searching by email address in dropdown menu.
|
||||||
|
* Other:
|
||||||
|
* Fix: Version number being overwritten with "None" which prevented updating in some instances.
|
||||||
|
* Change: Update Plex OAuth request headers.
|
||||||
|
|
||||||
|
|
||||||
|
## v2.1.32 (2019-06-26)
|
||||||
|
|
||||||
|
* Newsletters:
|
||||||
|
* Fix: Newsletter scheduler issue for QNAP devices using an invalid "local" timezone preventing Tautulli from starting.
|
||||||
|
|
||||||
|
|
||||||
|
## v2.1.31 (2019-06-24)
|
||||||
|
|
||||||
|
* No additional changes from v2.1.31-beta.
|
||||||
|
|
||||||
|
|
||||||
|
## v2.1.31-beta (2019-06-13)
|
||||||
|
|
||||||
|
* Monitoring:
|
||||||
|
* Fix: Synced content showing incorrect stream info.
|
||||||
|
* Other:
|
||||||
|
* Fix: Unable to view database status when authentication is enabled.
|
||||||
|
* Change: Default database synchronous mode changed to prevent database corruption. Database response may be slower.
|
||||||
|
|
||||||
|
|
||||||
## v2.1.30-beta (2019-05-11)
|
## v2.1.30-beta (2019-05-11)
|
||||||
|
|
||||||
* Monitoring:
|
* Monitoring:
|
||||||
@@ -14,6 +61,7 @@
|
|||||||
|
|
||||||
|
|
||||||
## v2.1.29 (2019-05-11)
|
## v2.1.29 (2019-05-11)
|
||||||
|
|
||||||
* No additional changes from v2.1.29-beta.
|
* No additional changes from v2.1.29-beta.
|
||||||
|
|
||||||
|
|
||||||
@@ -158,6 +206,7 @@
|
|||||||
|
|
||||||
|
|
||||||
## v2.1.20 (2018-09-05)
|
## v2.1.20 (2018-09-05)
|
||||||
|
|
||||||
* No additional changes from v2.1.20-beta.
|
* No additional changes from v2.1.20-beta.
|
||||||
|
|
||||||
|
|
||||||
|
@@ -69,7 +69,7 @@ DOCUMENTATION :: END
|
|||||||
% endif
|
% endif
|
||||||
<tr>
|
<tr>
|
||||||
<td>Platform:</td>
|
<td>Platform:</td>
|
||||||
<td>${common.PLATFORM} ${common.PLATFORM_RELEASE} (${common.PLATFORM_VERSION + (' - {}'.format(common.PLATFORM_LINUX_DISTRO) if common.PLATFORM_LINUX_DISTRO else '')})</td>
|
<td>${'[Docker] ' if plexpy.DOCKER else ''}${common.PLATFORM} ${common.PLATFORM_RELEASE} (${common.PLATFORM_VERSION + (' - {}'.format(common.PLATFORM_LINUX_DISTRO) if common.PLATFORM_LINUX_DISTRO else '')})</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>System Timezone:</td>
|
<td>System Timezone:</td>
|
||||||
|
@@ -218,7 +218,7 @@ DOCUMENTATION :: END
|
|||||||
% if data['stream_container_decision'] == 'transcode':
|
% if data['stream_container_decision'] == 'transcode':
|
||||||
Transcode (${data['container'].upper()} <i class="fa fa-long-arrow-right"></i> ${data['stream_container'].upper()})
|
Transcode (${data['container'].upper()} <i class="fa fa-long-arrow-right"></i> ${data['stream_container'].upper()})
|
||||||
% else:
|
% else:
|
||||||
Direct Play (${data['container'].upper()})
|
Direct Play (${data['stream_container'].upper()})
|
||||||
% endif
|
% endif
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@@ -236,7 +236,7 @@ DOCUMENTATION :: END
|
|||||||
% elif data['stream_video_decision'] == 'copy':
|
% elif data['stream_video_decision'] == 'copy':
|
||||||
Direct Stream (${data['stream_video_codec'].upper()} ${VIDEO_RESOLUTION_OVERRIDES.get(data['stream_video_resolution'], data['stream_video_resolution'])})
|
Direct Stream (${data['stream_video_codec'].upper()} ${VIDEO_RESOLUTION_OVERRIDES.get(data['stream_video_resolution'], data['stream_video_resolution'])})
|
||||||
% else:
|
% else:
|
||||||
Direct Play (${data['video_codec'].upper()} ${VIDEO_RESOLUTION_OVERRIDES.get(data['video_resolution'], data['video_resolution'])})
|
Direct Play (${data['stream_video_codec'].upper()} ${VIDEO_RESOLUTION_OVERRIDES.get(data['stream_video_resolution'], data['stream_video_resolution'])})
|
||||||
% endif
|
% endif
|
||||||
% elif data['media_type'] == 'photo':
|
% elif data['media_type'] == 'photo':
|
||||||
Direct Play (${data['width']}x${data['height']})
|
Direct Play (${data['width']}x${data['height']})
|
||||||
@@ -253,7 +253,7 @@ DOCUMENTATION :: END
|
|||||||
% elif data['stream_audio_decision'] == 'copy':
|
% elif data['stream_audio_decision'] == 'copy':
|
||||||
Direct Stream (${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()})
|
Direct Stream (${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()})
|
||||||
% else:
|
% else:
|
||||||
Direct Play (${AUDIO_CODEC_OVERRIDES.get(data['audio_codec'], data['audio_codec'].upper())} ${data['audio_channel_layout'].split('(')[0].capitalize()})
|
Direct Play (${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()})
|
||||||
% endif
|
% endif
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@@ -270,7 +270,7 @@ DOCUMENTATION :: END
|
|||||||
% elif data['stream_subtitle_decision'] == 'burn':
|
% elif data['stream_subtitle_decision'] == 'burn':
|
||||||
Burn (${data['subtitle_codec'].upper()})
|
Burn (${data['subtitle_codec'].upper()})
|
||||||
% else:
|
% else:
|
||||||
Direct Play (${data['stream_subtitle_codec'].upper() if data['synced_version'] else data['subtitle_codec'].upper()})
|
Direct Play (${data['subtitle_codec'].upper() if data['synced_version'] else data['stream_subtitle_codec'].upper()})
|
||||||
% endif
|
% endif
|
||||||
% else:
|
% else:
|
||||||
None
|
None
|
||||||
|
@@ -60,7 +60,8 @@
|
|||||||
<th align="left" id="friendly_name">User</th>
|
<th align="left" id="friendly_name">User</th>
|
||||||
<th align="left" id="ip_address">IP Address</th>
|
<th align="left" id="ip_address">IP Address</th>
|
||||||
<th align="left" id="platform">Platform</th>
|
<th align="left" id="platform">Platform</th>
|
||||||
<th align="left" id="device">Player</th>
|
<th align="left" id="product">Product</th>
|
||||||
|
<th align="left" id="player">Player</th>
|
||||||
<th align="left" id="title">Title</th>
|
<th align="left" id="title">Title</th>
|
||||||
<th align="left" id="started">Started</th>
|
<th align="left" id="started">Started</th>
|
||||||
<th align="left" id="paused_counter">Paused</th>
|
<th align="left" id="paused_counter">Paused</th>
|
||||||
@@ -143,7 +144,7 @@
|
|||||||
var colvis = new $.fn.dataTable.ColVis(history_table, {
|
var colvis = new $.fn.dataTable.ColVis(history_table, {
|
||||||
buttonText: '<i class="fa fa-columns"></i> Select columns',
|
buttonText: '<i class="fa fa-columns"></i> Select columns',
|
||||||
buttonClass: 'btn btn-dark',
|
buttonClass: 'btn btn-dark',
|
||||||
exclude: [0, 11]
|
exclude: [0, 12]
|
||||||
});
|
});
|
||||||
$(colvis.button()).appendTo('div.colvis-button-bar');
|
$(colvis.button()).appendTo('div.colvis-button-bar');
|
||||||
|
|
||||||
|
@@ -26,6 +26,7 @@
|
|||||||
<th align="left" id="friendly_name">User</th>
|
<th align="left" id="friendly_name">User</th>
|
||||||
<th align="left" id="ip_address">IP Address</th>
|
<th align="left" id="ip_address">IP Address</th>
|
||||||
<th align="left" id="platform">Platform</th>
|
<th align="left" id="platform">Platform</th>
|
||||||
|
<th align="left" id="product">Product</th>
|
||||||
<th align="left" id="device">Player</th>
|
<th align="left" id="device">Player</th>
|
||||||
<th align="left" id="title">Title</th>
|
<th align="left" id="title">Title</th>
|
||||||
<th align="left" id="started">Started</th>
|
<th align="left" id="started">Started</th>
|
||||||
@@ -61,7 +62,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
history_table = $('#history_table_modal').DataTable(history_table_options);
|
history_table = $('#history_table_modal').DataTable(history_table_options);
|
||||||
history_table.columns([0, 3, 4, 8, 10, 11]).visible(false);
|
history_table.columns([0, 3, 4, 5, 9, 11, 12]).visible(false);
|
||||||
|
|
||||||
clearSearchButton('history_table_modal', history_table);
|
clearSearchButton('history_table_modal', history_table);
|
||||||
|
|
||||||
|
@@ -430,7 +430,7 @@
|
|||||||
if (s.stream_container_decision === 'transcode') {
|
if (s.stream_container_decision === 'transcode') {
|
||||||
transcode_container = 'Transcode (' + s.container.toUpperCase() + ' <i class="fa fa-long-arrow-right"></i> ' + s.stream_container.toUpperCase() + ')';
|
transcode_container = 'Transcode (' + s.container.toUpperCase() + ' <i class="fa fa-long-arrow-right"></i> ' + s.stream_container.toUpperCase() + ')';
|
||||||
} else {
|
} else {
|
||||||
transcode_container = 'Direct Play (' + s.container.toUpperCase() + ')';
|
transcode_container = 'Direct Play (' + s.stream_container.toUpperCase() + ')';
|
||||||
}
|
}
|
||||||
$('#transcode_container-' + key).html(transcode_container);
|
$('#transcode_container-' + key).html(transcode_container);
|
||||||
|
|
||||||
@@ -465,7 +465,7 @@
|
|||||||
} else if (s.stream_video_decision === 'copy') {
|
} else if (s.stream_video_decision === 'copy') {
|
||||||
video_decision = 'Direct Stream (' + s.stream_video_codec.toUpperCase() + ' ' + sv_res + ')';
|
video_decision = 'Direct Stream (' + s.stream_video_codec.toUpperCase() + ' ' + sv_res + ')';
|
||||||
} else {
|
} else {
|
||||||
video_decision = 'Direct Play (' + s.video_codec.toUpperCase() + ' ' + v_res + ')';
|
video_decision = 'Direct Play (' + s.stream_video_codec.toUpperCase() + ' ' + sv_res + ')';
|
||||||
}
|
}
|
||||||
} else if (s.media_type === 'photo') {
|
} else if (s.media_type === 'photo') {
|
||||||
video_decision = 'Direct Play (' + s.width + 'x' + s.height + ')';
|
video_decision = 'Direct Play (' + s.width + 'x' + s.height + ')';
|
||||||
@@ -481,7 +481,7 @@
|
|||||||
} else if (s.stream_audio_decision === 'copy') {
|
} else if (s.stream_audio_decision === 'copy') {
|
||||||
audio_decision = 'Direct Stream (' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')';
|
audio_decision = 'Direct Stream (' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')';
|
||||||
} else {
|
} else {
|
||||||
audio_decision = 'Direct Play (' + a_codec + ' ' + capitalizeFirstLetter(s.audio_channel_layout.split('(')[0]) + ')';
|
audio_decision = 'Direct Play (' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$('#audio_decision-' + key).html(audio_decision);
|
$('#audio_decision-' + key).html(audio_decision);
|
||||||
@@ -495,7 +495,7 @@
|
|||||||
} else if (s.stream_subtitle_decision === 'burn') {
|
} else if (s.stream_subtitle_decision === 'burn') {
|
||||||
subtitle_decision = 'Burn (' + s.subtitle_codec.toUpperCase() + ')';
|
subtitle_decision = 'Burn (' + s.subtitle_codec.toUpperCase() + ')';
|
||||||
} else {
|
} else {
|
||||||
subtitle_decision = 'Direct Play (' + ((s.synced_version === '1') ? s.stream_subtitle_codec.toUpperCase() : s.subtitle_codec.toUpperCase()) + ')';
|
subtitle_decision = 'Direct Play (' + ((s.synced_version === '1') ? s.subtitle_codec.toUpperCase() : s.stream_subtitle_codec.toUpperCase()) + ')';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$('#subtitle_decision-' + key).html(subtitle_decision);
|
$('#subtitle_decision-' + key).html(subtitle_decision);
|
||||||
|
@@ -451,6 +451,7 @@ DOCUMENTATION :: END
|
|||||||
<th align="left" id="friendly_name">User</th>
|
<th align="left" id="friendly_name">User</th>
|
||||||
<th align="left" id="ip_address">IP Address</th>
|
<th align="left" id="ip_address">IP Address</th>
|
||||||
<th align="left" id="platform">Platform</th>
|
<th align="left" id="platform">Platform</th>
|
||||||
|
<th align="left" id="product">Product</th>
|
||||||
<th align="left" id="player">Player</th>
|
<th align="left" id="player">Player</th>
|
||||||
<th align="left" id="title">Title</th>
|
<th align="left" id="title">Title</th>
|
||||||
<th align="left" id="started">Started</th>
|
<th align="left" id="started">Started</th>
|
||||||
@@ -613,7 +614,7 @@ DOCUMENTATION :: END
|
|||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
get_history();
|
get_history();
|
||||||
history_table = $('#history_table-RK-${data["rating_key"]}').DataTable(history_table_options);
|
history_table = $('#history_table-RK-${data["rating_key"]}').DataTable(history_table_options);
|
||||||
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] });
|
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 12] });
|
||||||
$(colvis.button()).appendTo('div.colvis-button-bar');
|
$(colvis.button()).appendTo('div.colvis-button-bar');
|
||||||
|
|
||||||
clearSearchButton('history_table-RK-${data["rating_key"]}', history_table);
|
clearSearchButton('history_table-RK-${data["rating_key"]}', history_table);
|
||||||
|
@@ -568,8 +568,11 @@ function getPlexHeaders() {
|
|||||||
'X-Plex-Client-Identifier': getLocalStorage('Tautulli_ClientID', uuidv4(), false),
|
'X-Plex-Client-Identifier': getLocalStorage('Tautulli_ClientID', uuidv4(), false),
|
||||||
'X-Plex-Platform': p.name,
|
'X-Plex-Platform': p.name,
|
||||||
'X-Plex-Platform-Version': p.version,
|
'X-Plex-Platform-Version': p.version,
|
||||||
|
'X-Plex-Model': 'Plex OAuth',
|
||||||
'X-Plex-Device': p.os,
|
'X-Plex-Device': p.os,
|
||||||
'X-Plex-Device-Name': p.name
|
'X-Plex-Device-Name': p.name,
|
||||||
|
'X-Plex-Device-Screen-Resolution': window.screen.width + 'x' + window.screen.height,
|
||||||
|
'X-Plex-Language': 'en'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -655,7 +658,21 @@ function PlexOAuth(success, error, pre) {
|
|||||||
const pin = data.pin;
|
const pin = data.pin;
|
||||||
const code = data.code;
|
const code = data.code;
|
||||||
|
|
||||||
plex_oauth_window.location = 'https://app.plex.tv/auth/#!?clientID=' + x_plex_headers['X-Plex-Client-Identifier'] + '&code=' + code;
|
var oauth_params = {
|
||||||
|
'clientID': x_plex_headers['X-Plex-Client-Identifier'],
|
||||||
|
'context[device][product]': x_plex_headers['X-Plex-Product'],
|
||||||
|
'context[device][version]': x_plex_headers['X-Plex-Version'],
|
||||||
|
'context[device][platform]': x_plex_headers['X-Plex-Platform'],
|
||||||
|
'context[device][platformVersion]': x_plex_headers['X-Plex-Platform-Version'],
|
||||||
|
'context[device][device]': x_plex_headers['X-Plex-Device'],
|
||||||
|
'context[device][deviceName]': x_plex_headers['X-Plex-Device-Name'],
|
||||||
|
'context[device][model]': x_plex_headers['X-Plex-Model'],
|
||||||
|
'context[device][screenResolution]': x_plex_headers['X-Plex-Device-Screen-Resolution'],
|
||||||
|
'context[device][layout]': 'desktop',
|
||||||
|
'code': code
|
||||||
|
}
|
||||||
|
|
||||||
|
plex_oauth_window.location = 'https://app.plex.tv/auth/#!?' + encodeData(oauth_params);
|
||||||
polling = pin;
|
polling = pin;
|
||||||
|
|
||||||
(function poll() {
|
(function poll() {
|
||||||
@@ -693,4 +710,10 @@ function PlexOAuth(success, error, pre) {
|
|||||||
error()
|
error()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function encodeData(data) {
|
||||||
|
return Object.keys(data).map(function(key) {
|
||||||
|
return [key, data[key]].map(encodeURIComponent).join("=");
|
||||||
|
}).join("&");
|
||||||
|
}
|
||||||
|
@@ -49,7 +49,7 @@ history_table_options = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"targets": [1],
|
"targets": [1],
|
||||||
"data":"date",
|
"data": "date",
|
||||||
"createdCell": function (td, cellData, rowData, row, col) {
|
"createdCell": function (td, cellData, rowData, row, col) {
|
||||||
var date = moment(cellData, "X").format(date_format);
|
var date = moment(cellData, "X").format(date_format);
|
||||||
if (rowData['state'] !== null) {
|
if (rowData['state'] !== null) {
|
||||||
@@ -77,7 +77,7 @@ history_table_options = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"targets": [2],
|
"targets": [2],
|
||||||
"data":"friendly_name",
|
"data": "friendly_name",
|
||||||
"createdCell": function (td, cellData, rowData, row, col) {
|
"createdCell": function (td, cellData, rowData, row, col) {
|
||||||
if (cellData !== '') {
|
if (cellData !== '') {
|
||||||
if (rowData['user_id']) {
|
if (rowData['user_id']) {
|
||||||
@@ -112,7 +112,18 @@ history_table_options = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"targets": [4],
|
"targets": [4],
|
||||||
"data":"platform",
|
"data": "platform",
|
||||||
|
"createdCell": function (td, cellData, rowData, row, col) {
|
||||||
|
if (cellData !== '') {
|
||||||
|
$(td).html(capitalizeFirstLetter(cellData));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"width": "10%",
|
||||||
|
"className": "no-wrap"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"targets": [5],
|
||||||
|
"data": "product",
|
||||||
"createdCell": function (td, cellData, rowData, row, col) {
|
"createdCell": function (td, cellData, rowData, row, col) {
|
||||||
if (cellData !== '') {
|
if (cellData !== '') {
|
||||||
$(td).html(cellData);
|
$(td).html(cellData);
|
||||||
@@ -122,7 +133,7 @@ history_table_options = {
|
|||||||
"className": "no-wrap"
|
"className": "no-wrap"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"targets": [5],
|
"targets": [6],
|
||||||
"data": "player",
|
"data": "player",
|
||||||
"createdCell": function (td, cellData, rowData, row, col) {
|
"createdCell": function (td, cellData, rowData, row, col) {
|
||||||
if (cellData !== '') {
|
if (cellData !== '') {
|
||||||
@@ -137,12 +148,12 @@ history_table_options = {
|
|||||||
$(td).html('<div><a href="#" data-target="#info-modal" data-toggle="modal"><div style="float: left;">' + transcode_dec + ' ' + cellData + '</div></a></div>');
|
$(td).html('<div><a href="#" data-target="#info-modal" data-toggle="modal"><div style="float: left;">' + transcode_dec + ' ' + cellData + '</div></a></div>');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"width": "12%",
|
"width": "10%",
|
||||||
"className": "no-wrap modal-control"
|
"className": "no-wrap modal-control"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"targets": [6],
|
"targets": [7],
|
||||||
"data":"full_title",
|
"data": "full_title",
|
||||||
"createdCell": function (td, cellData, rowData, row, col) {
|
"createdCell": function (td, cellData, rowData, row, col) {
|
||||||
if (cellData !== '') {
|
if (cellData !== '') {
|
||||||
var parent_info = '';
|
var parent_info = '';
|
||||||
@@ -155,7 +166,7 @@ history_table_options = {
|
|||||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
|
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
|
||||||
$(td).html('<div class="history-title"><a href="info?' + source + 'rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
$(td).html('<div class="history-title"><a href="info?' + source + 'rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
||||||
} else if (rowData['media_type'] === 'episode') {
|
} else if (rowData['media_type'] === 'episode') {
|
||||||
if (rowData['parent_media_index'] && rowData['media_index']) { parent_info = ' (S' + rowData['parent_media_index'] + '· E' + rowData['media_index'] + ')'; }
|
if (rowData['parent_media_index'] && rowData['media_index']) { parent_info = ' (S' + rowData['parent_media_index'] + ' · E' + rowData['media_index'] + ')'; }
|
||||||
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Episode"><i class="fa fa-television fa-fw"></i></span>';
|
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Episode"><i class="fa fa-television fa-fw"></i></span>';
|
||||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
|
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
|
||||||
$(td).html('<div class="history-title"><a href="info?' + source + 'rating_key=' + rowData['rating_key'] + '"><div style="float: left;" >' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
$(td).html('<div class="history-title"><a href="info?' + source + 'rating_key=' + rowData['rating_key'] + '"><div style="float: left;" >' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
||||||
@@ -171,12 +182,12 @@ history_table_options = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"width": "33%",
|
"width": "25%",
|
||||||
"className": "datatable-wrap"
|
"className": "datatable-wrap"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"targets": [7],
|
"targets": [8],
|
||||||
"data":"started",
|
"data": "started",
|
||||||
"createdCell": function (td, cellData, rowData, row, col) {
|
"createdCell": function (td, cellData, rowData, row, col) {
|
||||||
if (cellData === null) {
|
if (cellData === null) {
|
||||||
$(td).html('n/a');
|
$(td).html('n/a');
|
||||||
@@ -189,8 +200,8 @@ history_table_options = {
|
|||||||
"className": "no-wrap"
|
"className": "no-wrap"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"targets": [8],
|
"targets": [9],
|
||||||
"data":"paused_counter",
|
"data": "paused_counter",
|
||||||
"render": function (data, type, full) {
|
"render": function (data, type, full) {
|
||||||
if (data !== null) {
|
if (data !== null) {
|
||||||
return Math.round(moment.duration(data, 'seconds').as('minutes')) + ' mins';
|
return Math.round(moment.duration(data, 'seconds').as('minutes')) + ' mins';
|
||||||
@@ -203,8 +214,8 @@ history_table_options = {
|
|||||||
"className": "no-wrap"
|
"className": "no-wrap"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"targets": [9],
|
"targets": [10],
|
||||||
"data":"stopped",
|
"data": "stopped",
|
||||||
"createdCell": function (td, cellData, rowData, row, col) {
|
"createdCell": function (td, cellData, rowData, row, col) {
|
||||||
if (cellData === null || (rowData['state'] != null && rowData['state'] != "stopped")) {
|
if (cellData === null || (rowData['state'] != null && rowData['state'] != "stopped")) {
|
||||||
$(td).html('n/a');
|
$(td).html('n/a');
|
||||||
@@ -217,8 +228,8 @@ history_table_options = {
|
|||||||
"className": "no-wrap"
|
"className": "no-wrap"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"targets": [10],
|
"targets": [11],
|
||||||
"data":"duration",
|
"data": "duration",
|
||||||
"render": function (data, type, full) {
|
"render": function (data, type, full) {
|
||||||
if (data !== null) {
|
if (data !== null) {
|
||||||
return Math.round(moment.duration(data, 'seconds').as('minutes')) + ' mins';
|
return Math.round(moment.duration(data, 'seconds').as('minutes')) + ' mins';
|
||||||
@@ -231,7 +242,7 @@ history_table_options = {
|
|||||||
"className": "no-wrap"
|
"className": "no-wrap"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"targets": [11],
|
"targets": [12],
|
||||||
"data": "watched_status",
|
"data": "watched_status",
|
||||||
"createdCell": function (td, cellData, rowData, row, col) {
|
"createdCell": function (td, cellData, rowData, row, col) {
|
||||||
if (cellData == 1) {
|
if (cellData == 1) {
|
||||||
@@ -489,7 +500,8 @@ function childTableFormat(rowData) {
|
|||||||
'<th align="left" id="friendly_name">User</th>' +
|
'<th align="left" id="friendly_name">User</th>' +
|
||||||
'<th align="left" id="ip_address">IP Address</th>' +
|
'<th align="left" id="ip_address">IP Address</th>' +
|
||||||
'<th align="left" id="platform">Platform</th>' +
|
'<th align="left" id="platform">Platform</th>' +
|
||||||
'<th align="left" id="platform">Player</th>' +
|
'<th align="left" id="product">Product</th>' +
|
||||||
|
'<th align="left" id="player">Player</th>' +
|
||||||
'<th align="left" id="title">Title</th>' +
|
'<th align="left" id="title">Title</th>' +
|
||||||
'<th align="left" id="started">Started</th>' +
|
'<th align="left" id="started">Started</th>' +
|
||||||
'<th align="left" id="paused_counter">Paused</th>' +
|
'<th align="left" id="paused_counter">Paused</th>' +
|
||||||
|
@@ -107,7 +107,7 @@ history_table_modal_options = {
|
|||||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
|
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
|
||||||
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
||||||
} else if (rowData['media_type'] === 'episode') {
|
} else if (rowData['media_type'] === 'episode') {
|
||||||
if (rowData['parent_media_index'] && rowData['media_index']) { parent_info = ' (S' + rowData['parent_media_index'] + '· E' + rowData['media_index'] + ')'; }
|
if (rowData['parent_media_index'] && rowData['media_index']) { parent_info = ' (S' + rowData['parent_media_index'] + ' · E' + rowData['media_index'] + ')'; }
|
||||||
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Episode"><i class="fa fa-television fa-fw"></i></span>';
|
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Episode"><i class="fa fa-television fa-fw"></i></span>';
|
||||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
|
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
|
||||||
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;" >' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;" >' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
||||||
|
@@ -153,7 +153,7 @@ libraries_list_table_options = {
|
|||||||
} else if (rowData['media_type'] === 'episode') {
|
} else if (rowData['media_type'] === 'episode') {
|
||||||
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Episode"><i class="fa fa-television fa-fw"></i></span>';
|
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Episode"><i class="fa fa-television fa-fw"></i></span>';
|
||||||
if (rowData['rating_key']) {
|
if (rowData['rating_key']) {
|
||||||
if (rowData['parent_media_index'] && rowData['media_index']) { parent_info = ' (S' + rowData['parent_media_index'] + '· E' + rowData['media_index'] + ')'; }
|
if (rowData['parent_media_index'] && rowData['media_index']) { parent_info = ' (S' + rowData['parent_media_index'] + ' · E' + rowData['media_index'] + ')'; }
|
||||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
|
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
|
||||||
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;" >' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;" >' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
||||||
} else {
|
} else {
|
||||||
|
@@ -91,7 +91,7 @@ user_ip_table_options = {
|
|||||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
|
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
|
||||||
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
||||||
} else if (rowData['media_type'] === 'episode') {
|
} else if (rowData['media_type'] === 'episode') {
|
||||||
if (rowData['parent_media_index'] && rowData['media_index']) { parent_info = ' (S' + rowData['parent_media_index'] + '· E' + rowData['media_index'] + ')'; }
|
if (rowData['parent_media_index'] && rowData['media_index']) { parent_info = ' (S' + rowData['parent_media_index'] + ' · E' + rowData['media_index'] + ')'; }
|
||||||
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Episode"><i class="fa fa-television fa-fw"></i></span>';
|
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Episode"><i class="fa fa-television fa-fw"></i></span>';
|
||||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
|
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
|
||||||
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;" >' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;" >' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
||||||
|
@@ -166,7 +166,7 @@ users_list_table_options = {
|
|||||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
|
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
|
||||||
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
||||||
} else if (rowData['media_type'] === 'episode') {
|
} else if (rowData['media_type'] === 'episode') {
|
||||||
if (rowData['parent_media_index'] && rowData['media_index']) { parent_info = ' (S' + rowData['parent_media_index'] + '· E' + rowData['media_index'] + ')'; }
|
if (rowData['parent_media_index'] && rowData['media_index']) { parent_info = ' (S' + rowData['parent_media_index'] + ' · E' + rowData['media_index'] + ')'; }
|
||||||
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Episode"><i class="fa fa-television fa-fw"></i></span>';
|
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Episode"><i class="fa fa-television fa-fw"></i></span>';
|
||||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
|
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
|
||||||
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;" >' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;" >' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
||||||
|
@@ -205,6 +205,7 @@ DOCUMENTATION :: END
|
|||||||
<th align="left" id="friendly_name">User</th>
|
<th align="left" id="friendly_name">User</th>
|
||||||
<th align="left" id="ip_address">IP Address</th>
|
<th align="left" id="ip_address">IP Address</th>
|
||||||
<th align="left" id="platform">Platform</th>
|
<th align="left" id="platform">Platform</th>
|
||||||
|
<th align="left" id="product">Product</th>
|
||||||
<th align="left" id="player">Player</th>
|
<th align="left" id="player">Player</th>
|
||||||
<th align="left" id="title">Title</th>
|
<th align="left" id="title">Title</th>
|
||||||
<th align="left" id="started">Started</th>
|
<th align="left" id="started">Started</th>
|
||||||
@@ -385,7 +386,7 @@ DOCUMENTATION :: END
|
|||||||
};
|
};
|
||||||
history_table = $('#history_table-SID-${data["section_id"]}').DataTable(history_table_options);
|
history_table = $('#history_table-SID-${data["section_id"]}').DataTable(history_table_options);
|
||||||
|
|
||||||
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] });
|
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 12] });
|
||||||
$(colvis.button()).appendTo('#button-bar-history');
|
$(colvis.button()).appendTo('#button-bar-history');
|
||||||
|
|
||||||
clearSearchButton('history_table-SID-${data["section_id"]}', history_table);
|
clearSearchButton('history_table-SID-${data["section_id"]}', history_table);
|
||||||
|
@@ -584,6 +584,7 @@
|
|||||||
var $email_selectors = $('#newsletter_email_to, #newsletter_email_cc, #newsletter_email_bcc').selectize({
|
var $email_selectors = $('#newsletter_email_to, #newsletter_email_cc, #newsletter_email_bcc').selectize({
|
||||||
plugins: ['remove_button'],
|
plugins: ['remove_button'],
|
||||||
maxItems: null,
|
maxItems: null,
|
||||||
|
searchField: ['text', 'value'],
|
||||||
render: {
|
render: {
|
||||||
item: function(item, escape) {
|
item: function(item, escape) {
|
||||||
return '<div>' +
|
return '<div>' +
|
||||||
|
@@ -566,6 +566,7 @@
|
|||||||
var $email_selectors = $('#email_to, #email_cc, #email_bcc').selectize({
|
var $email_selectors = $('#email_to, #email_cc, #email_bcc').selectize({
|
||||||
plugins: ['remove_button'],
|
plugins: ['remove_button'],
|
||||||
maxItems: null,
|
maxItems: null,
|
||||||
|
searchField: ['text', 'value'],
|
||||||
render: {
|
render: {
|
||||||
item: function(item, escape) {
|
item: function(item, escape) {
|
||||||
return '<div>' +
|
return '<div>' +
|
||||||
|
@@ -184,6 +184,7 @@ DOCUMENTATION :: END
|
|||||||
<th align="left" id="friendly_name">User</th>
|
<th align="left" id="friendly_name">User</th>
|
||||||
<th align="left" id="ip_address">IP Address</th>
|
<th align="left" id="ip_address">IP Address</th>
|
||||||
<th align="left" id="platform">Platform</th>
|
<th align="left" id="platform">Platform</th>
|
||||||
|
<th align="left" id="product">Product</th>
|
||||||
<th align="left" id="player">Player</th>
|
<th align="left" id="player">Player</th>
|
||||||
<th align="left" id="title">Title</th>
|
<th align="left" id="title">Title</th>
|
||||||
<th align="left" id="started">Started</th>
|
<th align="left" id="started">Started</th>
|
||||||
@@ -425,7 +426,7 @@ DOCUMENTATION :: END
|
|||||||
history_table = $('#history_table-UID-${data["user_id"]}').DataTable(history_table_options);
|
history_table = $('#history_table-UID-${data["user_id"]}').DataTable(history_table_options);
|
||||||
history_table.column(2).visible(false);
|
history_table.column(2).visible(false);
|
||||||
|
|
||||||
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] });
|
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 12] });
|
||||||
$(colvis.button()).appendTo('#button-bar-history');
|
$(colvis.button()).appendTo('#button-bar-history');
|
||||||
|
|
||||||
clearSearchButton('history_table-UID-${data["user_id"]}', history_table);
|
clearSearchButton('history_table-UID-${data["user_id"]}', history_table);
|
||||||
|
@@ -100,6 +100,7 @@ UMASK = None
|
|||||||
|
|
||||||
HTTP_PORT = None
|
HTTP_PORT = None
|
||||||
HTTP_ROOT = None
|
HTTP_ROOT = None
|
||||||
|
AUTH_ENABLED = None
|
||||||
|
|
||||||
DEV = False
|
DEV = False
|
||||||
|
|
||||||
@@ -114,6 +115,7 @@ WIN_SYS_TRAY_ICON = None
|
|||||||
SYS_TIMEZONE = None
|
SYS_TIMEZONE = None
|
||||||
SYS_UTC_OFFSET = None
|
SYS_UTC_OFFSET = None
|
||||||
|
|
||||||
|
|
||||||
def initialize(config_file):
|
def initialize(config_file):
|
||||||
with INIT_LOCK:
|
with INIT_LOCK:
|
||||||
|
|
||||||
@@ -156,8 +158,8 @@ def initialize(config_file):
|
|||||||
logger.info(u"Starting Tautulli {}".format(
|
logger.info(u"Starting Tautulli {}".format(
|
||||||
common.RELEASE
|
common.RELEASE
|
||||||
))
|
))
|
||||||
logger.info(u"{} {} ({}{})".format(
|
logger.info(u"{}{} {} ({}{})".format(
|
||||||
common.PLATFORM, common.PLATFORM_RELEASE, common.PLATFORM_VERSION,
|
'[Docker] ' if DOCKER else '', common.PLATFORM, common.PLATFORM_RELEASE, common.PLATFORM_VERSION,
|
||||||
' - {}'.format(common.PLATFORM_LINUX_DISTRO) if common.PLATFORM_LINUX_DISTRO else ''
|
' - {}'.format(common.PLATFORM_LINUX_DISTRO) if common.PLATFORM_LINUX_DISTRO else ''
|
||||||
))
|
))
|
||||||
logger.info(u"{} (UTC{})".format(
|
logger.info(u"{} (UTC{})".format(
|
||||||
@@ -215,9 +217,10 @@ def initialize(config_file):
|
|||||||
CONFIG.write()
|
CONFIG.write()
|
||||||
|
|
||||||
# Check if Tautulli has a jwt_secret
|
# Check if Tautulli has a jwt_secret
|
||||||
if CONFIG.JWT_SECRET == '' or not CONFIG.JWT_SECRET:
|
if CONFIG.JWT_SECRET == '' or not CONFIG.JWT_SECRET or CONFIG.JWT_UPDATE_SECRET:
|
||||||
logger.debug(u"Generating JWT secret...")
|
logger.debug(u"Generating JWT secret...")
|
||||||
CONFIG.JWT_SECRET = generate_uuid()
|
CONFIG.JWT_SECRET = generate_uuid()
|
||||||
|
CONFIG.JWT_UPDATE_SECRET = False
|
||||||
CONFIG.write()
|
CONFIG.write()
|
||||||
|
|
||||||
# Get the previous version from the file
|
# Get the previous version from the file
|
||||||
@@ -251,7 +254,7 @@ def initialize(config_file):
|
|||||||
# Check for new versions
|
# Check for new versions
|
||||||
if CONFIG.CHECK_GITHUB_ON_STARTUP and CONFIG.CHECK_GITHUB:
|
if CONFIG.CHECK_GITHUB_ON_STARTUP and CONFIG.CHECK_GITHUB:
|
||||||
try:
|
try:
|
||||||
LATEST_VERSION = versioncheck.check_update()
|
versioncheck.check_update()
|
||||||
except:
|
except:
|
||||||
logger.exception(u"Unhandled exception")
|
logger.exception(u"Unhandled exception")
|
||||||
LATEST_VERSION = CURRENT_VERSION
|
LATEST_VERSION = CURRENT_VERSION
|
||||||
|
@@ -284,7 +284,7 @@ _CONFIG_DEFINITIONS = {
|
|||||||
'JOIN_ON_PMSUPDATE': (int, 'Join', 0),
|
'JOIN_ON_PMSUPDATE': (int, 'Join', 0),
|
||||||
'JOIN_ON_CONCURRENT': (int, 'Join', 0),
|
'JOIN_ON_CONCURRENT': (int, 'Join', 0),
|
||||||
'JOIN_ON_NEWDEVICE': (int, 'Join', 0),
|
'JOIN_ON_NEWDEVICE': (int, 'Join', 0),
|
||||||
'JOURNAL_MODE': (str, 'Advanced', 'wal'),
|
'JOURNAL_MODE': (str, 'Advanced', 'WAL'),
|
||||||
'LAUNCH_BROWSER': (int, 'General', 1),
|
'LAUNCH_BROWSER': (int, 'General', 1),
|
||||||
'LOG_BLACKLIST': (int, 'General', 1),
|
'LOG_BLACKLIST': (int, 'General', 1),
|
||||||
'LOG_DIR': (str, 'General', ''),
|
'LOG_DIR': (str, 'General', ''),
|
||||||
@@ -541,6 +541,7 @@ _CONFIG_DEFINITIONS = {
|
|||||||
'SCRIPTS_ON_PMSUPDATE_SCRIPT': (unicode, 'Scripts', ''),
|
'SCRIPTS_ON_PMSUPDATE_SCRIPT': (unicode, 'Scripts', ''),
|
||||||
'SCRIPTS_ON_CONCURRENT_SCRIPT': (unicode, 'Scripts', ''),
|
'SCRIPTS_ON_CONCURRENT_SCRIPT': (unicode, 'Scripts', ''),
|
||||||
'SCRIPTS_ON_NEWDEVICE_SCRIPT': (unicode, 'Scripts', ''),
|
'SCRIPTS_ON_NEWDEVICE_SCRIPT': (unicode, 'Scripts', ''),
|
||||||
|
'SYNCHRONOUS_MODE': (str, 'Advanced', 'NORMAL'),
|
||||||
'TELEGRAM_BOT_TOKEN': (str, 'Telegram', ''),
|
'TELEGRAM_BOT_TOKEN': (str, 'Telegram', ''),
|
||||||
'TELEGRAM_ENABLED': (int, 'Telegram', 0),
|
'TELEGRAM_ENABLED': (int, 'Telegram', 0),
|
||||||
'TELEGRAM_CHAT_ID': (str, 'Telegram', ''),
|
'TELEGRAM_CHAT_ID': (str, 'Telegram', ''),
|
||||||
@@ -623,6 +624,7 @@ _CONFIG_DEFINITIONS = {
|
|||||||
'XBMC_ON_CONCURRENT': (int, 'XBMC', 0),
|
'XBMC_ON_CONCURRENT': (int, 'XBMC', 0),
|
||||||
'XBMC_ON_NEWDEVICE': (int, 'XBMC', 0),
|
'XBMC_ON_NEWDEVICE': (int, 'XBMC', 0),
|
||||||
'JWT_SECRET': (str, 'Advanced', ''),
|
'JWT_SECRET': (str, 'Advanced', ''),
|
||||||
|
'JWT_UPDATE_SECRET': (bool_int, 'Advanced', 0),
|
||||||
'SYSTEM_ANALYTICS': (int, 'Advanced', 1),
|
'SYSTEM_ANALYTICS': (int, 'Advanced', 1),
|
||||||
'WIN_SYS_TRAY': (int, 'General', 1)
|
'WIN_SYS_TRAY': (int, 'General', 1)
|
||||||
}
|
}
|
||||||
|
@@ -126,12 +126,12 @@ class MonitorDatabase(object):
|
|||||||
def __init__(self, filename=FILENAME):
|
def __init__(self, filename=FILENAME):
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.connection = sqlite3.connect(db_filename(filename), timeout=20)
|
self.connection = sqlite3.connect(db_filename(filename), timeout=20)
|
||||||
# Don't wait for the disk to finish writing
|
# Set database synchronous mode (default NORMAL)
|
||||||
self.connection.execute("PRAGMA synchronous = OFF")
|
self.connection.execute("PRAGMA synchronous = %s" % plexpy.CONFIG.SYNCHRONOUS_MODE)
|
||||||
# Journal disabled since we never do rollbacks
|
# Set database journal mode (default WAL)
|
||||||
self.connection.execute("PRAGMA journal_mode = %s" % plexpy.CONFIG.JOURNAL_MODE)
|
self.connection.execute("PRAGMA journal_mode = %s" % plexpy.CONFIG.JOURNAL_MODE)
|
||||||
# 64mb of cache memory, probably need to make it user configurable
|
# Set database cache size (default 32MB)
|
||||||
self.connection.execute("PRAGMA cache_size=-%s" % (get_cache_size() * 1024))
|
self.connection.execute("PRAGMA cache_size = -%s" % (get_cache_size() * 1024))
|
||||||
self.connection.row_factory = dict_factory
|
self.connection.row_factory = dict_factory
|
||||||
|
|
||||||
def action(self, query, args=None, return_last_id=False):
|
def action(self, query, args=None, return_last_id=False):
|
||||||
|
@@ -76,6 +76,7 @@ class DataFactory(object):
|
|||||||
'(CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = "" \
|
'(CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = "" \
|
||||||
THEN users.username ELSE users.friendly_name END) AS friendly_name',
|
THEN users.username ELSE users.friendly_name END) AS friendly_name',
|
||||||
'platform',
|
'platform',
|
||||||
|
'product',
|
||||||
'player',
|
'player',
|
||||||
'ip_address',
|
'ip_address',
|
||||||
'session_history.media_type',
|
'session_history.media_type',
|
||||||
@@ -123,6 +124,7 @@ class DataFactory(object):
|
|||||||
'(CASE WHEN friendly_name IS NULL OR TRIM(friendly_name) = "" \
|
'(CASE WHEN friendly_name IS NULL OR TRIM(friendly_name) = "" \
|
||||||
THEN user ELSE friendly_name END) AS friendly_name',
|
THEN user ELSE friendly_name END) AS friendly_name',
|
||||||
'platform',
|
'platform',
|
||||||
|
'product',
|
||||||
'player',
|
'player',
|
||||||
'ip_address',
|
'ip_address',
|
||||||
'media_type',
|
'media_type',
|
||||||
@@ -225,6 +227,7 @@ class DataFactory(object):
|
|||||||
'user': item['user'],
|
'user': item['user'],
|
||||||
'friendly_name': item['friendly_name'],
|
'friendly_name': item['friendly_name'],
|
||||||
'platform': platform,
|
'platform': platform,
|
||||||
|
'product': item['product'],
|
||||||
'player': item['player'],
|
'player': item['player'],
|
||||||
'ip_address': item['ip_address'],
|
'ip_address': item['ip_address'],
|
||||||
'media_type': item['media_type'],
|
'media_type': item['media_type'],
|
||||||
|
@@ -1178,3 +1178,18 @@ def split_args(args=None):
|
|||||||
return [arg.decode(plexpy.SYS_ENCODING, 'ignore')
|
return [arg.decode(plexpy.SYS_ENCODING, 'ignore')
|
||||||
for arg in shlex.split(args.encode(plexpy.SYS_ENCODING, 'ignore'))]
|
for arg in shlex.split(args.encode(plexpy.SYS_ENCODING, 'ignore'))]
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def mask_config_passwords(config):
|
||||||
|
if isinstance(config, list):
|
||||||
|
for cfg in config:
|
||||||
|
if 'password' in cfg.get('name', '') and cfg.get('value', '') != '':
|
||||||
|
cfg['value'] = ' '
|
||||||
|
|
||||||
|
elif isinstance(config, dict):
|
||||||
|
for cfg, val in config.iteritems():
|
||||||
|
# Check for a password config keys and if the password is not blank
|
||||||
|
if 'password' in cfg and val != '':
|
||||||
|
# Set the password to blank so it is not exposed in the HTML form
|
||||||
|
config[cfg] = ' '
|
||||||
|
|
||||||
|
return config
|
||||||
|
@@ -239,7 +239,7 @@ class Libraries(object):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_datatables_list(self, kwargs=None):
|
def get_datatables_list(self, kwargs=None, grouping=None):
|
||||||
default_return = {'recordsFiltered': 0,
|
default_return = {'recordsFiltered': 0,
|
||||||
'recordsTotal': 0,
|
'recordsTotal': 0,
|
||||||
'draw': 0,
|
'draw': 0,
|
||||||
@@ -250,9 +250,14 @@ class Libraries(object):
|
|||||||
|
|
||||||
custom_where = [['library_sections.deleted_section', 0]]
|
custom_where = [['library_sections.deleted_section', 0]]
|
||||||
|
|
||||||
|
if grouping is None:
|
||||||
|
grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES
|
||||||
|
|
||||||
if session.get_session_shared_libraries():
|
if session.get_session_shared_libraries():
|
||||||
custom_where.append(['library_sections.section_id', session.get_session_shared_libraries()])
|
custom_where.append(['library_sections.section_id', session.get_session_shared_libraries()])
|
||||||
|
|
||||||
|
group_by = 'session_history.reference_id' if grouping else 'session_history.id'
|
||||||
|
|
||||||
columns = ['library_sections.section_id',
|
columns = ['library_sections.section_id',
|
||||||
'library_sections.section_name',
|
'library_sections.section_name',
|
||||||
'library_sections.section_type',
|
'library_sections.section_type',
|
||||||
@@ -262,7 +267,7 @@ class Libraries(object):
|
|||||||
'library_sections.thumb AS library_thumb',
|
'library_sections.thumb AS library_thumb',
|
||||||
'library_sections.custom_thumb_url AS custom_thumb',
|
'library_sections.custom_thumb_url AS custom_thumb',
|
||||||
'library_sections.art',
|
'library_sections.art',
|
||||||
'COUNT(session_history.id) AS plays',
|
'COUNT(DISTINCT %s) AS plays' % group_by,
|
||||||
'SUM(CASE WHEN session_history.stopped > 0 THEN (session_history.stopped - session_history.started) \
|
'SUM(CASE WHEN session_history.stopped > 0 THEN (session_history.stopped - session_history.started) \
|
||||||
ELSE 0 END) - SUM(CASE WHEN session_history.paused_counter IS NULL THEN 0 ELSE \
|
ELSE 0 END) - SUM(CASE WHEN session_history.paused_counter IS NULL THEN 0 ELSE \
|
||||||
session_history.paused_counter END) AS duration',
|
session_history.paused_counter END) AS duration',
|
||||||
@@ -533,7 +538,7 @@ class Libraries(object):
|
|||||||
# Search results
|
# Search results
|
||||||
search_value = json_data['search']['value'].lower()
|
search_value = json_data['search']['value'].lower()
|
||||||
if search_value:
|
if search_value:
|
||||||
searchable_columns = [d['data'] for d in json_data['columns'] if d['searchable']]
|
searchable_columns = [d['data'] for d in json_data['columns'] if d['searchable']] + ['title']
|
||||||
for row in rows:
|
for row in rows:
|
||||||
for k,v in row.iteritems():
|
for k,v in row.iteritems():
|
||||||
if k in searchable_columns and search_value in v.lower():
|
if k in searchable_columns and search_value in v.lower():
|
||||||
|
@@ -61,13 +61,11 @@ def schedule_newsletter_job(newsletter_job_id, name='', func=None, remove_job=Fa
|
|||||||
logger.info(u"Tautulli NewsletterHandler :: Removed scheduled newsletter: %s" % name)
|
logger.info(u"Tautulli NewsletterHandler :: Removed scheduled newsletter: %s" % name)
|
||||||
else:
|
else:
|
||||||
NEWSLETTER_SCHED.reschedule_job(
|
NEWSLETTER_SCHED.reschedule_job(
|
||||||
newsletter_job_id, args=args, trigger=CronTrigger().from_crontab(
|
newsletter_job_id, args=args, trigger=CronTrigger.from_crontab(cron))
|
||||||
cron, timezone=plexpy.SYS_TIMEZONE))
|
|
||||||
logger.info(u"Tautulli NewsletterHandler :: Re-scheduled newsletter: %s" % name)
|
logger.info(u"Tautulli NewsletterHandler :: Re-scheduled newsletter: %s" % name)
|
||||||
elif not remove_job:
|
elif not remove_job:
|
||||||
NEWSLETTER_SCHED.add_job(
|
NEWSLETTER_SCHED.add_job(
|
||||||
func, args=args, id=newsletter_job_id, trigger=CronTrigger().from_crontab(
|
func, args=args, id=newsletter_job_id, trigger=CronTrigger.from_crontab(cron))
|
||||||
cron, timezone=plexpy.SYS_TIMEZONE))
|
|
||||||
logger.info(u"Tautulli NewsletterHandler :: Scheduled newsletter: %s" % name)
|
logger.info(u"Tautulli NewsletterHandler :: Scheduled newsletter: %s" % name)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -125,7 +125,7 @@ def delete_newsletter(newsletter_id=None):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def get_newsletter_config(newsletter_id=None):
|
def get_newsletter_config(newsletter_id=None, mask_passwords=False):
|
||||||
if str(newsletter_id).isdigit():
|
if str(newsletter_id).isdigit():
|
||||||
newsletter_id = int(newsletter_id)
|
newsletter_id = int(newsletter_id)
|
||||||
else:
|
else:
|
||||||
@@ -153,13 +153,16 @@ def get_newsletter_config(newsletter_id=None):
|
|||||||
logger.error(u"Tautulli Newsletters :: Failed to get newsletter config options: %s." % e)
|
logger.error(u"Tautulli Newsletters :: Failed to get newsletter config options: %s." % e)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if mask_passwords:
|
||||||
|
newsletter_agent.email_config = helpers.mask_config_passwords(newsletter_agent.email_config)
|
||||||
|
|
||||||
result['subject'] = newsletter_agent.subject
|
result['subject'] = newsletter_agent.subject
|
||||||
result['body'] = newsletter_agent.body
|
result['body'] = newsletter_agent.body
|
||||||
result['message'] = newsletter_agent.message
|
result['message'] = newsletter_agent.message
|
||||||
result['config'] = newsletter_agent.config
|
result['config'] = newsletter_agent.config
|
||||||
result['email_config'] = newsletter_agent.email_config
|
result['email_config'] = newsletter_agent.email_config
|
||||||
result['config_options'] = newsletter_agent.return_config_options()
|
result['config_options'] = newsletter_agent.return_config_options(mask_passwords=mask_passwords)
|
||||||
result['email_config_options'] = newsletter_agent.return_email_config_options()
|
result['email_config_options'] = newsletter_agent.return_email_config_options(mask_passwords=mask_passwords)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -230,6 +233,13 @@ def set_newsletter_config(newsletter_id=None, agent_id=None, **kwargs):
|
|||||||
email_config = {k[len(email_config_prefix):]: kwargs.pop(k)
|
email_config = {k[len(email_config_prefix):]: kwargs.pop(k)
|
||||||
for k in kwargs.keys() if k.startswith(email_config_prefix)}
|
for k in kwargs.keys() if k.startswith(email_config_prefix)}
|
||||||
|
|
||||||
|
for cfg, val in email_config.iteritems():
|
||||||
|
# Check for a password config keys and a blank password from the HTML form
|
||||||
|
if 'password' in cfg and val == ' ':
|
||||||
|
# Get the previous password so we don't overwrite it with a blank value
|
||||||
|
old_newsletter_config = get_newsletter_config(newsletter_id=newsletter_id)
|
||||||
|
email_config[cfg] = old_newsletter_config['email_config'][cfg]
|
||||||
|
|
||||||
subject = kwargs.pop('subject')
|
subject = kwargs.pop('subject')
|
||||||
body = kwargs.pop('body')
|
body = kwargs.pop('body')
|
||||||
message = kwargs.pop('message')
|
message = kwargs.pop('message')
|
||||||
@@ -647,16 +657,21 @@ class Newsletter(object):
|
|||||||
|
|
||||||
return filename
|
return filename
|
||||||
|
|
||||||
def return_config_options(self):
|
def return_config_options(self, mask_passwords=False):
|
||||||
return self._return_config_options()
|
config_options = self._return_config_options()
|
||||||
|
|
||||||
def _return_config_options(self):
|
# Mask password config options
|
||||||
config_options = []
|
if mask_passwords:
|
||||||
|
helpers.mask_config_passwords(config_options)
|
||||||
|
|
||||||
return config_options
|
return config_options
|
||||||
|
|
||||||
def return_email_config_options(self):
|
def _return_config_options(self):
|
||||||
config_options = EMAIL(self.email_config).return_config_options()
|
config_options = []
|
||||||
|
return config_options
|
||||||
|
|
||||||
|
def return_email_config_options(self, mask_passwords=False):
|
||||||
|
config_options = EMAIL(self.email_config).return_config_options(mask_passwords=mask_passwords)
|
||||||
for c in config_options:
|
for c in config_options:
|
||||||
c['name'] = 'newsletter_' + c['name']
|
c['name'] = 'newsletter_' + c['name']
|
||||||
return config_options
|
return config_options
|
||||||
@@ -926,10 +941,8 @@ class RecentlyAdded(Newsletter):
|
|||||||
|
|
||||||
return parameters
|
return parameters
|
||||||
|
|
||||||
def return_config_options(self):
|
def _return_config_options(self):
|
||||||
config_options = self._return_config_options()
|
config_options = [
|
||||||
|
|
||||||
additional_config = [
|
|
||||||
{'label': 'Included Libraries',
|
{'label': 'Included Libraries',
|
||||||
'value': self.config['incl_libraries'],
|
'value': self.config['incl_libraries'],
|
||||||
'description': 'Select the libraries to include in the newsletter.',
|
'description': 'Select the libraries to include in the newsletter.',
|
||||||
@@ -939,4 +952,4 @@ class RecentlyAdded(Newsletter):
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
return additional_config + config_options
|
return config_options
|
||||||
|
@@ -1332,6 +1332,7 @@ def lookup_tvmaze_by_id(rating_key=None, thetvdb_id=None, imdb_id=None):
|
|||||||
'tvmaze_json': json.dumps(tvmaze_json)}
|
'tvmaze_json': json.dumps(tvmaze_json)}
|
||||||
db.upsert(table_name='tvmaze_lookup', key_dict=keys, value_dict=tvmaze_info)
|
db.upsert(table_name='tvmaze_lookup', key_dict=keys, value_dict=tvmaze_info)
|
||||||
|
|
||||||
|
tvmaze_info.update(keys)
|
||||||
tvmaze_info.pop('tvmaze_json')
|
tvmaze_info.pop('tvmaze_json')
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -1394,6 +1395,7 @@ def lookup_themoviedb_by_id(rating_key=None, thetvdb_id=None, imdb_id=None):
|
|||||||
|
|
||||||
db.upsert(table_name='themoviedb_lookup', key_dict=keys, value_dict=themoviedb_info)
|
db.upsert(table_name='themoviedb_lookup', key_dict=keys, value_dict=themoviedb_info)
|
||||||
|
|
||||||
|
themoviedb_info.update(keys)
|
||||||
themoviedb_info.pop('themoviedb_json')
|
themoviedb_info.pop('themoviedb_json')
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -1447,6 +1449,8 @@ def get_themoviedb_info(rating_key=None, media_type=None, themoviedb_id=None):
|
|||||||
|
|
||||||
db.upsert(table_name='themoviedb_lookup', key_dict=keys, value_dict=themoviedb_info)
|
db.upsert(table_name='themoviedb_lookup', key_dict=keys, value_dict=themoviedb_info)
|
||||||
|
|
||||||
|
themoviedb_info.update(keys)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if err_msg:
|
if err_msg:
|
||||||
logger.error(u"Tautulli NotificationHandler :: {}".format(err_msg))
|
logger.error(u"Tautulli NotificationHandler :: {}".format(err_msg))
|
||||||
|
@@ -455,7 +455,7 @@ def delete_notifier(notifier_id=None):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def get_notifier_config(notifier_id=None):
|
def get_notifier_config(notifier_id=None, mask_passwords=False):
|
||||||
if str(notifier_id).isdigit():
|
if str(notifier_id).isdigit():
|
||||||
notifier_id = int(notifier_id)
|
notifier_id = int(notifier_id)
|
||||||
else:
|
else:
|
||||||
@@ -472,11 +472,13 @@ def get_notifier_config(notifier_id=None):
|
|||||||
try:
|
try:
|
||||||
config = json.loads(result.pop('notifier_config', '{}'))
|
config = json.loads(result.pop('notifier_config', '{}'))
|
||||||
notifier_agent = get_agent_class(agent_id=result['agent_id'], config=config)
|
notifier_agent = get_agent_class(agent_id=result['agent_id'], config=config)
|
||||||
notifier_config = notifier_agent.return_config_options()
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(u"Tautulli Notifiers :: Failed to get notifier config options: %s." % e)
|
logger.error(u"Tautulli Notifiers :: Failed to get notifier config options: %s." % e)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if mask_passwords:
|
||||||
|
notifier_agent.config = helpers.mask_config_passwords(notifier_agent.config)
|
||||||
|
|
||||||
notify_actions = get_notify_actions(return_dict=True)
|
notify_actions = get_notify_actions(return_dict=True)
|
||||||
|
|
||||||
notifier_actions = {}
|
notifier_actions = {}
|
||||||
@@ -503,8 +505,8 @@ def get_notifier_config(notifier_id=None):
|
|||||||
if not result['custom_conditions_logic']:
|
if not result['custom_conditions_logic']:
|
||||||
result['custom_conditions_logic'] = ''
|
result['custom_conditions_logic'] = ''
|
||||||
|
|
||||||
result['config'] = config
|
result['config'] = notifier_agent.config
|
||||||
result['config_options'] = notifier_config
|
result['config_options'] = notifier_agent.return_config_options(mask_passwords=mask_passwords)
|
||||||
result['actions'] = notifier_actions
|
result['actions'] = notifier_actions
|
||||||
result['notify_text'] = notifier_text
|
result['notify_text'] = notifier_text
|
||||||
|
|
||||||
@@ -587,6 +589,13 @@ def set_notifier_config(notifier_id=None, agent_id=None, **kwargs):
|
|||||||
notifier_config = {k[len(config_prefix):]: kwargs.pop(k)
|
notifier_config = {k[len(config_prefix):]: kwargs.pop(k)
|
||||||
for k in kwargs.keys() if k.startswith(config_prefix)}
|
for k in kwargs.keys() if k.startswith(config_prefix)}
|
||||||
|
|
||||||
|
for cfg, val in notifier_config.iteritems():
|
||||||
|
# Check for a password config keys and a blank password from the HTML form
|
||||||
|
if 'password' in cfg and val == ' ':
|
||||||
|
# Get the previous password so we don't overwrite it with a blank value
|
||||||
|
old_notifier_config = get_notifier_config(notifier_id=notifier_id)
|
||||||
|
notifier_config[cfg] = old_notifier_config['config'][cfg]
|
||||||
|
|
||||||
agent_class = get_agent_class(agent_id=agent['id'], config=notifier_config)
|
agent_class = get_agent_class(agent_id=agent['id'], config=notifier_config)
|
||||||
|
|
||||||
keys = {'id': notifier_id}
|
keys = {'id': notifier_id}
|
||||||
@@ -835,7 +844,16 @@ class Notifier(object):
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def return_config_options(self):
|
def return_config_options(self, mask_passwords=False):
|
||||||
|
config_options = self._return_config_options()
|
||||||
|
|
||||||
|
# Mask password config options
|
||||||
|
if mask_passwords:
|
||||||
|
helpers.mask_config_passwords(config_options)
|
||||||
|
|
||||||
|
return config_options
|
||||||
|
|
||||||
|
def _return_config_options(self):
|
||||||
config_options = []
|
config_options = []
|
||||||
return config_options
|
return config_options
|
||||||
|
|
||||||
@@ -942,7 +960,7 @@ class ANDROIDAPP(Notifier):
|
|||||||
|
|
||||||
return devices
|
return devices
|
||||||
|
|
||||||
def return_config_options(self):
|
def _return_config_options(self):
|
||||||
config_option = []
|
config_option = []
|
||||||
|
|
||||||
if not CRYPTODOME:
|
if not CRYPTODOME:
|
||||||
@@ -1058,7 +1076,7 @@ class BOXCAR(Notifier):
|
|||||||
|
|
||||||
return sounds
|
return sounds
|
||||||
|
|
||||||
def return_config_options(self):
|
def _return_config_options(self):
|
||||||
config_option = [{'label': 'Boxcar Access Token',
|
config_option = [{'label': 'Boxcar Access Token',
|
||||||
'value': self.config['token'],
|
'value': self.config['token'],
|
||||||
'name': 'boxcar_token',
|
'name': 'boxcar_token',
|
||||||
@@ -1089,7 +1107,7 @@ class BROWSER(Notifier):
|
|||||||
logger.info(u"Tautulli Notifiers :: {name} notification sent.".format(name=self.NAME))
|
logger.info(u"Tautulli Notifiers :: {name} notification sent.".format(name=self.NAME))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def return_config_options(self):
|
def _return_config_options(self):
|
||||||
config_option = [{'label': 'Allow Notifications',
|
config_option = [{'label': 'Allow Notifications',
|
||||||
'value': 'Allow Notifications',
|
'value': 'Allow Notifications',
|
||||||
'name': 'browser_allow_browser',
|
'name': 'browser_allow_browser',
|
||||||
@@ -1202,7 +1220,7 @@ class DISCORD(Notifier):
|
|||||||
|
|
||||||
return self.make_request(self.config['hook'], params=params, headers=headers, json=data)
|
return self.make_request(self.config['hook'], params=params, headers=headers, json=data)
|
||||||
|
|
||||||
def return_config_options(self):
|
def _return_config_options(self):
|
||||||
config_option = [{'label': 'Discord Webhook URL',
|
config_option = [{'label': 'Discord Webhook URL',
|
||||||
'value': self.config['hook'],
|
'value': self.config['hook'],
|
||||||
'name': 'discord_hook',
|
'name': 'discord_hook',
|
||||||
@@ -1389,7 +1407,7 @@ class EMAIL(Notifier):
|
|||||||
|
|
||||||
return user_emails_to, user_emails_cc, user_emails_bcc
|
return user_emails_to, user_emails_cc, user_emails_bcc
|
||||||
|
|
||||||
def return_config_options(self):
|
def _return_config_options(self):
|
||||||
user_emails_to, user_emails_cc, user_emails_bcc = self.get_user_emails()
|
user_emails_to, user_emails_cc, user_emails_bcc = self.get_user_emails()
|
||||||
|
|
||||||
config_option = [{'label': 'From Name',
|
config_option = [{'label': 'From Name',
|
||||||
@@ -1569,7 +1587,7 @@ class FACEBOOK(Notifier):
|
|||||||
|
|
||||||
return self._post_facebook(**data)
|
return self._post_facebook(**data)
|
||||||
|
|
||||||
def return_config_options(self):
|
def _return_config_options(self):
|
||||||
config_option = [{'label': 'OAuth Redirect URI',
|
config_option = [{'label': 'OAuth Redirect URI',
|
||||||
'value': self.config['redirect_uri'],
|
'value': self.config['redirect_uri'],
|
||||||
'name': 'facebook_redirect_uri',
|
'name': 'facebook_redirect_uri',
|
||||||
@@ -1699,7 +1717,7 @@ class GROUPME(Notifier):
|
|||||||
|
|
||||||
return self.make_request('https://api.groupme.com/v3/bots/post', json=data)
|
return self.make_request('https://api.groupme.com/v3/bots/post', json=data)
|
||||||
|
|
||||||
def return_config_options(self):
|
def _return_config_options(self):
|
||||||
config_option = [{'label': 'GroupMe Access Token',
|
config_option = [{'label': 'GroupMe Access Token',
|
||||||
'value': self.config['access_token'],
|
'value': self.config['access_token'],
|
||||||
'name': 'groupme_access_token',
|
'name': 'groupme_access_token',
|
||||||
@@ -1796,7 +1814,7 @@ class GROWL(Notifier):
|
|||||||
logger.error(u"Tautulli Notifiers :: {name} notification failed: network error".format(name=self.NAME))
|
logger.error(u"Tautulli Notifiers :: {name} notification failed: network error".format(name=self.NAME))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def return_config_options(self):
|
def _return_config_options(self):
|
||||||
config_option = [{'label': 'Growl Host',
|
config_option = [{'label': 'Growl Host',
|
||||||
'value': self.config['host'],
|
'value': self.config['host'],
|
||||||
'name': 'growl_host',
|
'name': 'growl_host',
|
||||||
@@ -1901,7 +1919,7 @@ class HIPCHAT(Notifier):
|
|||||||
|
|
||||||
return self.make_request(self.config['hook'], headers=headers, json=data)
|
return self.make_request(self.config['hook'], headers=headers, json=data)
|
||||||
|
|
||||||
def return_config_options(self):
|
def _return_config_options(self):
|
||||||
config_option = [{'label': 'Hipchat Custom Integrations URL',
|
config_option = [{'label': 'Hipchat Custom Integrations URL',
|
||||||
'value': self.config['hook'],
|
'value': self.config['hook'],
|
||||||
'name': 'hipchat_hook',
|
'name': 'hipchat_hook',
|
||||||
@@ -2012,7 +2030,7 @@ class IFTTT(Notifier):
|
|||||||
return self.make_request('https://maker.ifttt.com/trigger/{}/with/key/{}'.format(event, self.config['key']),
|
return self.make_request('https://maker.ifttt.com/trigger/{}/with/key/{}'.format(event, self.config['key']),
|
||||||
headers=headers, json=data)
|
headers=headers, json=data)
|
||||||
|
|
||||||
def return_config_options(self):
|
def _return_config_options(self):
|
||||||
config_option = [{'label': 'IFTTT Webhook Key',
|
config_option = [{'label': 'IFTTT Webhook Key',
|
||||||
'value': self.config['key'],
|
'value': self.config['key'],
|
||||||
'name': 'ifttt_key',
|
'name': 'ifttt_key',
|
||||||
@@ -2131,7 +2149,7 @@ class JOIN(Notifier):
|
|||||||
|
|
||||||
return devices
|
return devices
|
||||||
|
|
||||||
def return_config_options(self):
|
def _return_config_options(self):
|
||||||
config_option = [{'label': 'Join API Key',
|
config_option = [{'label': 'Join API Key',
|
||||||
'value': self.config['api_key'],
|
'value': self.config['api_key'],
|
||||||
'name': 'join_api_key',
|
'name': 'join_api_key',
|
||||||
@@ -2233,7 +2251,7 @@ class MQTT(Notifier):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def return_config_options(self):
|
def _return_config_options(self):
|
||||||
config_option = [{'label': 'Broker',
|
config_option = [{'label': 'Broker',
|
||||||
'value': self.config['broker'],
|
'value': self.config['broker'],
|
||||||
'name': 'mqtt_broker',
|
'name': 'mqtt_broker',
|
||||||
@@ -2335,7 +2353,7 @@ class NMA(Notifier):
|
|||||||
logger.error(u"Tautulli Notifiers :: {name} notification failed.".format(name=self.NAME))
|
logger.error(u"Tautulli Notifiers :: {name} notification failed.".format(name=self.NAME))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def return_config_options(self):
|
def _return_config_options(self):
|
||||||
config_option = [{'label': 'NotifyMyAndroid API Key',
|
config_option = [{'label': 'NotifyMyAndroid API Key',
|
||||||
'value': self.config['api_key'],
|
'value': self.config['api_key'],
|
||||||
'name': 'nma_api_key',
|
'name': 'nma_api_key',
|
||||||
@@ -2437,7 +2455,7 @@ class OSX(Notifier):
|
|||||||
logger.error(u"Tautulli Notifiers :: {name} failed: {e}".format(name=self.NAME, e=e))
|
logger.error(u"Tautulli Notifiers :: {name} failed: {e}".format(name=self.NAME, e=e))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def return_config_options(self):
|
def _return_config_options(self):
|
||||||
config_option = [{'label': 'Register Notify App',
|
config_option = [{'label': 'Register Notify App',
|
||||||
'value': self.config['notify_app'],
|
'value': self.config['notify_app'],
|
||||||
'name': 'osx_notify_app',
|
'name': 'osx_notify_app',
|
||||||
@@ -2530,7 +2548,7 @@ class PLEX(Notifier):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def return_config_options(self):
|
def _return_config_options(self):
|
||||||
config_option = [{'label': 'Plex Home Theater Host Address',
|
config_option = [{'label': 'Plex Home Theater Host Address',
|
||||||
'value': self.config['hosts'],
|
'value': self.config['hosts'],
|
||||||
'name': 'plex_hosts',
|
'name': 'plex_hosts',
|
||||||
@@ -2586,7 +2604,7 @@ class PROWL(Notifier):
|
|||||||
|
|
||||||
return self.make_request('https://api.prowlapp.com/publicapi/add', headers=headers, data=data)
|
return self.make_request('https://api.prowlapp.com/publicapi/add', headers=headers, data=data)
|
||||||
|
|
||||||
def return_config_options(self):
|
def _return_config_options(self):
|
||||||
config_option = [{'label': 'Prowl API Key',
|
config_option = [{'label': 'Prowl API Key',
|
||||||
'value': self.config['key'],
|
'value': self.config['key'],
|
||||||
'name': 'prowl_key',
|
'name': 'prowl_key',
|
||||||
@@ -2622,7 +2640,7 @@ class PUSHALOT(Notifier):
|
|||||||
|
|
||||||
return self.make_request('https://pushalot.com/api/sendmessage', headers=headers, data=data)
|
return self.make_request('https://pushalot.com/api/sendmessage', headers=headers, data=data)
|
||||||
|
|
||||||
def return_config_options(self):
|
def _return_config_options(self):
|
||||||
config_option = [{'label': 'Pushalot API Key',
|
config_option = [{'label': 'Pushalot API Key',
|
||||||
'value': self.config['api_key'],
|
'value': self.config['api_key'],
|
||||||
'name': 'pushalot_api_key',
|
'name': 'pushalot_api_key',
|
||||||
@@ -2722,7 +2740,7 @@ class PUSHBULLET(Notifier):
|
|||||||
|
|
||||||
return devices
|
return devices
|
||||||
|
|
||||||
def return_config_options(self):
|
def _return_config_options(self):
|
||||||
config_option = [{'label': 'Pushbullet Access Token',
|
config_option = [{'label': 'Pushbullet Access Token',
|
||||||
'value': self.config['api_key'],
|
'value': self.config['api_key'],
|
||||||
'name': 'pushbullet_api_key',
|
'name': 'pushbullet_api_key',
|
||||||
@@ -2888,7 +2906,7 @@ class PUSHOVER(Notifier):
|
|||||||
# else:
|
# else:
|
||||||
# return {'': ''}
|
# return {'': ''}
|
||||||
|
|
||||||
def return_config_options(self):
|
def _return_config_options(self):
|
||||||
config_option = [{'label': 'Pushover API Token',
|
config_option = [{'label': 'Pushover API Token',
|
||||||
'value': self.config['api_token'],
|
'value': self.config['api_token'],
|
||||||
'name': 'pushover_api_token',
|
'name': 'pushover_api_token',
|
||||||
@@ -3163,7 +3181,7 @@ class SCRIPTS(Notifier):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def return_config_options(self):
|
def _return_config_options(self):
|
||||||
config_option = [{'label': 'Supported File Types',
|
config_option = [{'label': 'Supported File Types',
|
||||||
'description': '<span class="inline-pre">' + \
|
'description': '<span class="inline-pre">' + \
|
||||||
', '.join(self.script_exts.keys()) + '</span>',
|
', '.join(self.script_exts.keys()) + '</span>',
|
||||||
@@ -3286,7 +3304,7 @@ class SLACK(Notifier):
|
|||||||
|
|
||||||
return self.make_request(self.config['hook'], headers=headers, json=data)
|
return self.make_request(self.config['hook'], headers=headers, json=data)
|
||||||
|
|
||||||
def return_config_options(self):
|
def _return_config_options(self):
|
||||||
config_option = [{'label': 'Slack Webhook URL',
|
config_option = [{'label': 'Slack Webhook URL',
|
||||||
'value': self.config['hook'],
|
'value': self.config['hook'],
|
||||||
'name': 'slack_hook',
|
'name': 'slack_hook',
|
||||||
@@ -3439,7 +3457,7 @@ class TELEGRAM(Notifier):
|
|||||||
return self.make_request('https://api.telegram.org/bot{}/sendMessage'.format(self.config['bot_token']),
|
return self.make_request('https://api.telegram.org/bot{}/sendMessage'.format(self.config['bot_token']),
|
||||||
headers=headers, data=data)
|
headers=headers, data=data)
|
||||||
|
|
||||||
def return_config_options(self):
|
def _return_config_options(self):
|
||||||
config_option = [{'label': 'Telegram Bot Token',
|
config_option = [{'label': 'Telegram Bot Token',
|
||||||
'value': self.config['bot_token'],
|
'value': self.config['bot_token'],
|
||||||
'name': 'telegram_bot_token',
|
'name': 'telegram_bot_token',
|
||||||
@@ -3537,7 +3555,7 @@ class TWITTER(Notifier):
|
|||||||
else:
|
else:
|
||||||
return self._send_tweet(body, attachment=poster_url)
|
return self._send_tweet(body, attachment=poster_url)
|
||||||
|
|
||||||
def return_config_options(self):
|
def _return_config_options(self):
|
||||||
config_option = [{'label': 'Twitter Consumer Key',
|
config_option = [{'label': 'Twitter Consumer Key',
|
||||||
'value': self.config['consumer_key'],
|
'value': self.config['consumer_key'],
|
||||||
'name': 'twitter_consumer_key',
|
'name': 'twitter_consumer_key',
|
||||||
@@ -3606,7 +3624,7 @@ class WEBHOOK(Notifier):
|
|||||||
|
|
||||||
return self.make_request(self.config['hook'], method=self.config['method'], headers=headers, json=webhook_data)
|
return self.make_request(self.config['hook'], method=self.config['method'], headers=headers, json=webhook_data)
|
||||||
|
|
||||||
def return_config_options(self):
|
def _return_config_options(self):
|
||||||
config_option = [{'label': 'Webhook URL',
|
config_option = [{'label': 'Webhook URL',
|
||||||
'value': self.config['hook'],
|
'value': self.config['hook'],
|
||||||
'name': 'webhook_hook',
|
'name': 'webhook_hook',
|
||||||
@@ -3703,7 +3721,7 @@ class XBMC(Notifier):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def return_config_options(self):
|
def _return_config_options(self):
|
||||||
config_option = [{'label': 'Kodi Host Address',
|
config_option = [{'label': 'Kodi Host Address',
|
||||||
'value': self.config['hosts'],
|
'value': self.config['hosts'],
|
||||||
'name': 'xbmc_hosts',
|
'name': 'xbmc_hosts',
|
||||||
@@ -3796,7 +3814,7 @@ class ZAPIER(Notifier):
|
|||||||
|
|
||||||
return self.make_request(self.config['hook'], headers=headers, json=data)
|
return self.make_request(self.config['hook'], headers=headers, json=data)
|
||||||
|
|
||||||
def return_config_options(self):
|
def _return_config_options(self):
|
||||||
config_option = [{'label': 'Zapier Webhook URL',
|
config_option = [{'label': 'Zapier Webhook URL',
|
||||||
'value': self.config['hook'],
|
'value': self.config['hook'],
|
||||||
'name': 'zapier_hook',
|
'name': 'zapier_hook',
|
||||||
|
@@ -1646,10 +1646,10 @@ class PmsConnect(object):
|
|||||||
if helpers.get_xml_attr(stream, 'streamType') == '1':
|
if helpers.get_xml_attr(stream, 'streamType') == '1':
|
||||||
video_stream_info = stream
|
video_stream_info = stream
|
||||||
|
|
||||||
elif helpers.get_xml_attr(stream, 'streamType') == '2':
|
elif helpers.get_xml_attr(stream, 'streamType') == '2' and helpers.get_xml_attr(stream, 'selected') == '1':
|
||||||
audio_stream_info = stream
|
audio_stream_info = stream
|
||||||
|
|
||||||
elif helpers.get_xml_attr(stream, 'streamType') == '3':
|
elif helpers.get_xml_attr(stream, 'streamType') == '3' and helpers.get_xml_attr(stream, 'selected') == '1':
|
||||||
subtitle_stream_info = stream
|
subtitle_stream_info = stream
|
||||||
|
|
||||||
video_id = audio_id = subtitle_id = None
|
video_id = audio_id = subtitle_id = None
|
||||||
|
@@ -70,7 +70,7 @@ class Users(object):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_datatables_list(self, kwargs=None):
|
def get_datatables_list(self, kwargs=None, grouping=None):
|
||||||
default_return = {'recordsFiltered': 0,
|
default_return = {'recordsFiltered': 0,
|
||||||
'recordsTotal': 0,
|
'recordsTotal': 0,
|
||||||
'draw': 0,
|
'draw': 0,
|
||||||
@@ -81,18 +81,23 @@ class Users(object):
|
|||||||
|
|
||||||
custom_where = [['users.deleted_user', 0]]
|
custom_where = [['users.deleted_user', 0]]
|
||||||
|
|
||||||
|
if grouping is None:
|
||||||
|
grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES
|
||||||
|
|
||||||
if session.get_session_user_id():
|
if session.get_session_user_id():
|
||||||
custom_where.append(['users.user_id', session.get_session_user_id()])
|
custom_where.append(['users.user_id', session.get_session_user_id()])
|
||||||
|
|
||||||
if kwargs.get('user_id'):
|
if kwargs.get('user_id'):
|
||||||
custom_where.append(['users.user_id', kwargs.get('user_id')])
|
custom_where.append(['users.user_id', kwargs.get('user_id')])
|
||||||
|
|
||||||
|
group_by = 'session_history.reference_id' if grouping else 'session_history.id'
|
||||||
|
|
||||||
columns = ['users.user_id',
|
columns = ['users.user_id',
|
||||||
'(CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = "" \
|
'(CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = "" \
|
||||||
THEN users.username ELSE users.friendly_name END) AS friendly_name',
|
THEN users.username ELSE users.friendly_name END) AS friendly_name',
|
||||||
'users.thumb AS user_thumb',
|
'users.thumb AS user_thumb',
|
||||||
'users.custom_avatar_url AS custom_thumb',
|
'users.custom_avatar_url AS custom_thumb',
|
||||||
'COUNT(session_history.id) AS plays',
|
'COUNT(DISTINCT %s) AS plays' % group_by,
|
||||||
'SUM(CASE WHEN session_history.stopped > 0 THEN (session_history.stopped - session_history.started) \
|
'SUM(CASE WHEN session_history.stopped > 0 THEN (session_history.stopped - session_history.started) \
|
||||||
ELSE 0 END) - SUM(CASE WHEN session_history.paused_counter IS NULL THEN 0 ELSE \
|
ELSE 0 END) - SUM(CASE WHEN session_history.paused_counter IS NULL THEN 0 ELSE \
|
||||||
session_history.paused_counter END) AS duration',
|
session_history.paused_counter END) AS duration',
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
PLEXPY_BRANCH = "beta"
|
PLEXPY_BRANCH = "master"
|
||||||
PLEXPY_RELEASE_VERSION = "v2.1.30-beta"
|
PLEXPY_RELEASE_VERSION = "v2.1.34"
|
||||||
|
@@ -353,7 +353,7 @@ class WebInterface(object):
|
|||||||
@requireAuth()
|
@requireAuth()
|
||||||
@sanitize_out()
|
@sanitize_out()
|
||||||
@addtoapi("get_libraries_table")
|
@addtoapi("get_libraries_table")
|
||||||
def get_library_list(self, **kwargs):
|
def get_library_list(self, grouping=None, **kwargs):
|
||||||
""" Get the data on the Tautulli libraries table.
|
""" Get the data on the Tautulli libraries table.
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -361,6 +361,7 @@ class WebInterface(object):
|
|||||||
None
|
None
|
||||||
|
|
||||||
Optional parameters:
|
Optional parameters:
|
||||||
|
grouping (int): 0 or 1
|
||||||
order_column (str): "library_thumb", "section_name", "section_type", "count", "parent_count",
|
order_column (str): "library_thumb", "section_name", "section_type", "count", "parent_count",
|
||||||
"child_count", "last_accessed", "last_played", "plays", "duration"
|
"child_count", "last_accessed", "last_played", "plays", "duration"
|
||||||
order_dir (str): "desc" or "asc"
|
order_dir (str): "desc" or "asc"
|
||||||
@@ -423,7 +424,7 @@ class WebInterface(object):
|
|||||||
kwargs['json_data'] = build_datatables_json(kwargs, dt_columns, "section_name")
|
kwargs['json_data'] = build_datatables_json(kwargs, dt_columns, "section_name")
|
||||||
|
|
||||||
library_data = libraries.Libraries()
|
library_data = libraries.Libraries()
|
||||||
library_list = library_data.get_datatables_list(kwargs=kwargs)
|
library_list = library_data.get_datatables_list(kwargs=kwargs, grouping=grouping)
|
||||||
|
|
||||||
return library_list
|
return library_list
|
||||||
|
|
||||||
@@ -1016,7 +1017,7 @@ class WebInterface(object):
|
|||||||
@requireAuth()
|
@requireAuth()
|
||||||
@sanitize_out()
|
@sanitize_out()
|
||||||
@addtoapi("get_users_table")
|
@addtoapi("get_users_table")
|
||||||
def get_user_list(self, **kwargs):
|
def get_user_list(self, grouping=None, **kwargs):
|
||||||
""" Get the data on Tautulli users table.
|
""" Get the data on Tautulli users table.
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -1024,6 +1025,7 @@ class WebInterface(object):
|
|||||||
None
|
None
|
||||||
|
|
||||||
Optional parameters:
|
Optional parameters:
|
||||||
|
grouping (int): 0 or 1
|
||||||
order_column (str): "user_thumb", "friendly_name", "last_seen", "ip_address", "platform",
|
order_column (str): "user_thumb", "friendly_name", "last_seen", "ip_address", "platform",
|
||||||
"player", "last_played", "plays", "duration"
|
"player", "last_played", "plays", "duration"
|
||||||
order_dir (str): "desc" or "asc"
|
order_dir (str): "desc" or "asc"
|
||||||
@@ -1082,7 +1084,7 @@ class WebInterface(object):
|
|||||||
kwargs['json_data'] = build_datatables_json(kwargs, dt_columns, "friendly_name")
|
kwargs['json_data'] = build_datatables_json(kwargs, dt_columns, "friendly_name")
|
||||||
|
|
||||||
user_data = users.Users()
|
user_data = users.Users()
|
||||||
user_list = user_data.get_datatables_list(kwargs=kwargs)
|
user_list = user_data.get_datatables_list(kwargs=kwargs, grouping=grouping)
|
||||||
|
|
||||||
return user_list
|
return user_list
|
||||||
|
|
||||||
@@ -1628,8 +1630,9 @@ class WebInterface(object):
|
|||||||
"parent_title": "",
|
"parent_title": "",
|
||||||
"paused_counter": 0,
|
"paused_counter": 0,
|
||||||
"percent_complete": 84,
|
"percent_complete": 84,
|
||||||
"platform": "Chrome",
|
"platform": "Windows",
|
||||||
"player": "Plex Web (Chrome)",
|
"product": "Plex for Windows",
|
||||||
|
"player": "Castle-PC",
|
||||||
"rating_key": 4348,
|
"rating_key": 4348,
|
||||||
"reference_id": 1123,
|
"reference_id": 1123,
|
||||||
"session_key": null,
|
"session_key": null,
|
||||||
@@ -1658,6 +1661,7 @@ class WebInterface(object):
|
|||||||
("friendly_name", True, True),
|
("friendly_name", True, True),
|
||||||
("ip_address", True, True),
|
("ip_address", True, True),
|
||||||
("platform", True, True),
|
("platform", True, True),
|
||||||
|
("product", True, True),
|
||||||
("player", True, True),
|
("player", True, True),
|
||||||
("full_title", True, True),
|
("full_title", True, True),
|
||||||
("started", True, False),
|
("started", True, False),
|
||||||
@@ -2853,10 +2857,16 @@ class WebInterface(object):
|
|||||||
else:
|
else:
|
||||||
kwargs['http_password'] = plexpy.CONFIG.HTTP_PASSWORD
|
kwargs['http_password'] = plexpy.CONFIG.HTTP_PASSWORD
|
||||||
|
|
||||||
|
# Flag to refresh JWT uuid to log out clients
|
||||||
|
kwargs['jwt_update_secret'] = True
|
||||||
|
|
||||||
elif kwargs['http_password'] and kwargs.get('http_hash_password'):
|
elif kwargs['http_password'] and kwargs.get('http_hash_password'):
|
||||||
kwargs['http_password'] = make_hash(kwargs['http_password'])
|
kwargs['http_password'] = make_hash(kwargs['http_password'])
|
||||||
kwargs['http_hashed_password'] = 1
|
kwargs['http_hashed_password'] = 1
|
||||||
|
|
||||||
|
# Flag to refresh JWT uuid to log out clients
|
||||||
|
kwargs['jwt_update_secret'] = True
|
||||||
|
|
||||||
elif not kwargs.get('http_hash_password'):
|
elif not kwargs.get('http_hash_password'):
|
||||||
kwargs['http_hashed_password'] = 0
|
kwargs['http_hashed_password'] = 0
|
||||||
else:
|
else:
|
||||||
@@ -3168,13 +3178,13 @@ class WebInterface(object):
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
result = notifiers.get_notifier_config(notifier_id=notifier_id)
|
result = notifiers.get_notifier_config(notifier_id=notifier_id, mask_passwords=True)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@requireAuth(member_of("admin"))
|
@requireAuth(member_of("admin"))
|
||||||
def get_notifier_config_modal(self, notifier_id=None, **kwargs):
|
def get_notifier_config_modal(self, notifier_id=None, **kwargs):
|
||||||
result = notifiers.get_notifier_config(notifier_id=notifier_id)
|
result = notifiers.get_notifier_config(notifier_id=notifier_id, mask_passwords=True)
|
||||||
|
|
||||||
parameters = [
|
parameters = [
|
||||||
{'name': param['name'], 'type': param['type'], 'value': param['value']}
|
{'name': param['name'], 'type': param['type'], 'value': param['value']}
|
||||||
@@ -3657,10 +3667,10 @@ class WebInterface(object):
|
|||||||
identifier = server['clientIdentifier']
|
identifier = server['clientIdentifier']
|
||||||
break
|
break
|
||||||
|
|
||||||
# Fallback to checking /identity endpoint is server is unpublished
|
# Fallback to checking /identity endpoint if the server is unpublished
|
||||||
# Cannot set SSL settings on the PMS if unpublished so 'http' is okay
|
# Cannot set SSL settings on the PMS if unpublished so 'http' is okay
|
||||||
if not identifier:
|
if not identifier:
|
||||||
scheme = 'https' if ssl else 'http'
|
scheme = 'https' if helpers.cast_to_int(ssl) else 'http'
|
||||||
url = '{scheme}://{hostname}:{port}'.format(scheme=scheme, hostname=hostname, port=port)
|
url = '{scheme}://{hostname}:{port}'.format(scheme=scheme, hostname=hostname, port=port)
|
||||||
uri = '/identity'
|
uri = '/identity'
|
||||||
|
|
||||||
@@ -5667,13 +5677,13 @@ class WebInterface(object):
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
result = newsletters.get_newsletter_config(newsletter_id=newsletter_id)
|
result = newsletters.get_newsletter_config(newsletter_id=newsletter_id, mask_passwords=True)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@requireAuth(member_of("admin"))
|
@requireAuth(member_of("admin"))
|
||||||
def get_newsletter_config_modal(self, newsletter_id=None, **kwargs):
|
def get_newsletter_config_modal(self, newsletter_id=None, **kwargs):
|
||||||
result = newsletters.get_newsletter_config(newsletter_id=newsletter_id)
|
result = newsletters.get_newsletter_config(newsletter_id=newsletter_id, mask_passwords=True)
|
||||||
return serve_template(templatename="newsletter_config.html", newsletter=result)
|
return serve_template(templatename="newsletter_config.html", newsletter=result)
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@@ -5884,7 +5894,7 @@ class WebInterface(object):
|
|||||||
status = {'result': 'success', 'message': 'Ok'}
|
status = {'result': 'success', 'message': 'Ok'}
|
||||||
|
|
||||||
if args or kwargs:
|
if args or kwargs:
|
||||||
if not cherrypy.request.path_info == '/api/v2':
|
if not cherrypy.request.path_info == '/api/v2' and plexpy.AUTH_ENABLED:
|
||||||
cherrypy.request.config['auth.require'] = []
|
cherrypy.request.config['auth.require'] = []
|
||||||
check_auth()
|
check_auth()
|
||||||
|
|
||||||
|
@@ -80,14 +80,15 @@ def initialize(options):
|
|||||||
logger.info(u"Tautulli WebStart :: Web server authentication is enabled: %s.", ' and '.join(login_allowed))
|
logger.info(u"Tautulli WebStart :: Web server authentication is enabled: %s.", ' and '.join(login_allowed))
|
||||||
|
|
||||||
if options['http_basic_auth']:
|
if options['http_basic_auth']:
|
||||||
auth_enabled = False
|
plexpy.AUTH_ENABLED = False
|
||||||
basic_auth_enabled = True
|
basic_auth_enabled = True
|
||||||
else:
|
else:
|
||||||
auth_enabled = True
|
plexpy.AUTH_ENABLED = True
|
||||||
basic_auth_enabled = False
|
basic_auth_enabled = False
|
||||||
cherrypy.tools.auth = cherrypy.Tool('before_handler', webauth.check_auth, priority=2)
|
cherrypy.tools.auth = cherrypy.Tool('before_handler', webauth.check_auth, priority=2)
|
||||||
else:
|
else:
|
||||||
auth_enabled = basic_auth_enabled = False
|
plexpy.AUTH_ENABLED = False
|
||||||
|
basic_auth_enabled = False
|
||||||
|
|
||||||
if options['http_root'].strip('/'):
|
if options['http_root'].strip('/'):
|
||||||
plexpy.HTTP_ROOT = options['http_root'] = '/' + options['http_root'].strip('/') + '/'
|
plexpy.HTTP_ROOT = options['http_root'] = '/' + options['http_root'].strip('/') + '/'
|
||||||
@@ -104,7 +105,7 @@ def initialize(options):
|
|||||||
'tools.gzip.mime_types': ['text/html', 'text/plain', 'text/css',
|
'tools.gzip.mime_types': ['text/html', 'text/plain', 'text/css',
|
||||||
'text/javascript', 'application/json',
|
'text/javascript', 'application/json',
|
||||||
'application/javascript'],
|
'application/javascript'],
|
||||||
'tools.auth.on': auth_enabled,
|
'tools.auth.on': plexpy.AUTH_ENABLED,
|
||||||
'tools.auth_basic.on': basic_auth_enabled,
|
'tools.auth_basic.on': basic_auth_enabled,
|
||||||
'tools.auth_basic.realm': 'Tautulli web server',
|
'tools.auth_basic.realm': 'Tautulli web server',
|
||||||
'tools.auth_basic.checkpassword': cherrypy.lib.auth_basic.checkpassword_dict({
|
'tools.auth_basic.checkpassword': cherrypy.lib.auth_basic.checkpassword_dict({
|
||||||
|
Reference in New Issue
Block a user