mirror of
https://github.com/Tautulli/Tautulli.git
synced 2024-11-23 22:10:17 -08:00
980 lines
32 KiB
JavaScript
980 lines
32 KiB
JavaScript
var p = {
|
|
name: 'Unknown',
|
|
version: 'Unknown',
|
|
os: 'Unknown'
|
|
};
|
|
if (typeof platform !== 'undefined') {
|
|
p.name = platform.name;
|
|
p.version = platform.version;
|
|
p.os = platform.os.toString();
|
|
}
|
|
|
|
if (['IE', 'Microsoft Edge', 'IE Mobile'].indexOf(p.name) > -1) {
|
|
if (!getCookie('browserDismiss')) {
|
|
var $browser_warning = $('<div id="browser-warning">' +
|
|
'<i class="fa fa-exclamation-circle"></i> ' +
|
|
'Tautulli does not support Internet Explorer or Microsoft Edge! ' +
|
|
'Please use a different browser such as Chrome or Firefox.' +
|
|
'<button type="button" class="close"><i class="fa fa-remove"></i></button>' +
|
|
'</div>');
|
|
$('body').prepend($browser_warning);
|
|
var offset = $browser_warning.height();
|
|
warningOffset(offset);
|
|
|
|
$browser_warning.one('click', 'button.close', function () {
|
|
$browser_warning.remove();
|
|
warningOffset(-offset);
|
|
setCookie('browserDismiss', 'true', 7);
|
|
});
|
|
|
|
function warningOffset(offset) {
|
|
var navbar = $('.navbar-fixed-top');
|
|
if (navbar.length) {
|
|
navbar.offset({top: navbar.offset().top + offset});
|
|
}
|
|
var container = $('.body-container');
|
|
if (container.length) {
|
|
container.offset({top: container.offset().top + offset});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function initConfigCheckbox(elem, toggleElem, reverse) {
|
|
toggleElem = (toggleElem === undefined) ? null : toggleElem;
|
|
reverse = (reverse === undefined) ? false : reverse;
|
|
var config = toggleElem ? $(toggleElem) : $(elem).closest('div').next();
|
|
config.addClass('hidden-settings');
|
|
if ($(elem).is(":checked")) {
|
|
config.toggle(!reverse);
|
|
} else {
|
|
config.toggle(reverse);
|
|
}
|
|
$(elem).click(function () {
|
|
var config = toggleElem ? $(toggleElem) : $(this).closest('div').next();
|
|
if ($(this).is(":checked")) {
|
|
config.slideToggleBool(!reverse);
|
|
} else {
|
|
config.slideToggleBool(reverse);
|
|
}
|
|
});
|
|
}
|
|
|
|
function refreshTab() {
|
|
var url = $(location).attr('href');
|
|
var tabId = $('.ui-tabs-panel:visible').attr("id");
|
|
$('.ui-tabs-panel:visible').load(url + " #" + tabId, function () {
|
|
initThisPage();
|
|
});
|
|
}
|
|
|
|
function showMsg(msg, loader, timeout, ms, error) {
|
|
var feedback = $("#ajaxMsg");
|
|
var update = $("#updatebar");
|
|
var token_error = $("#token_error_bar");
|
|
if (update.is(":visible") || token_error.is(":visible")) {
|
|
var height = (update.is(":visible") ? update.height() : 0) + (token_error.is(":visible") ? token_error.height() : 0) + 35;
|
|
feedback.css("bottom", height + "px");
|
|
} else {
|
|
feedback.removeAttr("style");
|
|
}
|
|
var message = $("<div class='msg'>" + msg + "</div>");
|
|
if (loader) {
|
|
message = $("<div class='msg'><i class='fa fa-refresh fa-spin'></i> " + msg + "</div>");
|
|
feedback.css("padding", "14px 10px");
|
|
}
|
|
if (error) {
|
|
feedback.css("background-color", "rgba(255,0,0,0.5)");
|
|
}
|
|
$(feedback).html(message);
|
|
feedback.fadeIn();
|
|
if (timeout) {
|
|
setTimeout(function () {
|
|
message.fadeOut(function () {
|
|
$(this).remove();
|
|
feedback.fadeOut();
|
|
feedback.css("background-color", "");
|
|
});
|
|
}, ms);
|
|
}
|
|
}
|
|
|
|
function confirmAjaxCall(url, msg, data, loader_msg, callback) {
|
|
$("#confirm-message").html(msg);
|
|
$('#confirm-modal').modal();
|
|
$('#confirm-modal').off('click', '#confirm-button').one('click', '#confirm-button', function () {
|
|
if (loader_msg) {
|
|
showMsg(loader_msg, true, false);
|
|
}
|
|
$.ajax({
|
|
url: url,
|
|
type: 'POST',
|
|
cache: false,
|
|
async: true,
|
|
data: data,
|
|
complete: function (xhr, status) {
|
|
var result = $.parseJSON(xhr.responseText);
|
|
var msg = result.message;
|
|
if (result.result == 'success') {
|
|
showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 5000);
|
|
} else {
|
|
showMsg('<i class="fa fa-times"></i> ' + msg, false, true, 5000, true);
|
|
}
|
|
if (typeof callback === "function") {
|
|
callback(result);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function doAjaxCall(url, elem, reload, form, showMsg, callback) {
|
|
// Set Message
|
|
var feedback = (showMsg) ? $("#ajaxMsg") : $();
|
|
var update = $("#updatebar");
|
|
var token_error = $("#token_error_bar");
|
|
if (update.is(":visible") || token_error.is(":visible")) {
|
|
var height = (update.is(":visible") ? update.height() : 0) + (token_error.is(":visible") ? token_error.height() : 0) + 35;
|
|
feedback.css("bottom", height + "px");
|
|
} else {
|
|
feedback.removeAttr("style");
|
|
}
|
|
feedback.fadeIn();
|
|
// Get Form data
|
|
var formID = "#" + url;
|
|
var dataString;
|
|
if (form === true) {
|
|
dataString = $(formID).serialize();
|
|
}
|
|
// Loader Image
|
|
var loader = $("<div class='msg ajaxLoader-" + url +"'><i class='fa fa-refresh fa-spin'></i> Saving...</div>");
|
|
// Data Success Message
|
|
var dataSucces = $(elem).data('success');
|
|
if (typeof dataSucces === "undefined") {
|
|
// Standard Message when variable is not set
|
|
dataSucces = "Success!";
|
|
}
|
|
// Data Error Message
|
|
var dataError = $(elem).data('error');
|
|
if (typeof dataError === "undefined") {
|
|
// Standard Message when variable is not set
|
|
dataError = "There was an error";
|
|
}
|
|
// Get Success & Error message from inline data, else use standard message
|
|
var succesMsg = $("<div class='msg'><i class='fa fa-check'></i> " + dataSucces + "</div>");
|
|
var errorMsg = $("<div class='msg'><i class='fa fa-exclamation-triangle'></i> " + dataError + "</div>");
|
|
// Check if checkbox is selected
|
|
if (form) {
|
|
if ($('td#select input[type=checkbox]').length > 0 && !$('td#select input[type=checkbox]').is(':checked') ||
|
|
$('#importLastFM #username:visible').length > 0 && $("#importLastFM #username").val().length === 0) {
|
|
feedback.addClass('error');
|
|
$(feedback).prepend(errorMsg);
|
|
setTimeout(function () {
|
|
errorMsg.fadeOut(function () {
|
|
$(this).remove();
|
|
feedback.fadeOut(function () {
|
|
feedback.removeClass('error');
|
|
});
|
|
});
|
|
$(formID + " select").children('option[disabled=disabled]').attr('selected', 'selected');
|
|
}, 2000);
|
|
return false;
|
|
}
|
|
}
|
|
// Ajax Call
|
|
$.ajax({
|
|
url: url,
|
|
data: dataString,
|
|
type: 'POST',
|
|
beforeSend: function (jqXHR, settings) {
|
|
// Start loader etc.
|
|
feedback.prepend(loader);
|
|
},
|
|
error: function (jqXHR, textStatus, errorThrown) {
|
|
feedback.addClass('error');
|
|
feedback.prepend(errorMsg);
|
|
setTimeout(function () {
|
|
errorMsg.fadeOut(function () {
|
|
$(this).remove();
|
|
feedback.fadeOut(function () {
|
|
feedback.removeClass('error');
|
|
});
|
|
});
|
|
}, 2000);
|
|
},
|
|
success: function (data, jqXHR) {
|
|
feedback.prepend(succesMsg);
|
|
feedback.addClass('success');
|
|
setTimeout(function (e) {
|
|
succesMsg.fadeOut(function () {
|
|
$(this).remove();
|
|
feedback.fadeOut(function () {
|
|
feedback.removeClass('success');
|
|
});
|
|
if (reload === true) refreshSubmenu();
|
|
if (reload === "table") {
|
|
refreshTable();
|
|
}
|
|
if (reload === "tabs") refreshTab();
|
|
if (reload === "page") location.reload();
|
|
if (reload === "submenu&table") {
|
|
refreshSubmenu();
|
|
refreshTable();
|
|
}
|
|
if (form) {
|
|
// Change the option to 'choose...'
|
|
$(formID + " select").children('option[disabled=disabled]').attr(
|
|
'selected', 'selected');
|
|
}
|
|
});
|
|
}, 2000);
|
|
},
|
|
complete: function (jqXHR, textStatus) {
|
|
// Remove loaders and stuff, ajax request is complete!
|
|
$('.ajaxLoader-' + url).remove();
|
|
if (typeof callback === "function") {
|
|
callback(jqXHR);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
getBrowsePath = function (key, path, filter_ext) {
|
|
var deferred = $.Deferred();
|
|
|
|
$.ajax({
|
|
url: 'browse_path',
|
|
type: 'GET',
|
|
data: {
|
|
key: key,
|
|
path: path,
|
|
filter_ext: filter_ext
|
|
},
|
|
success: function(data) {
|
|
deferred.resolve(data);
|
|
},
|
|
error: function() {
|
|
deferred.reject();
|
|
}
|
|
});
|
|
return deferred;
|
|
};
|
|
|
|
function doSimpleAjaxCall(url) {
|
|
$.ajax(url);
|
|
}
|
|
|
|
function resetFilters(text) {
|
|
if ($(".dataTables_filter").length > 0) {
|
|
$(".dataTables_filter input").attr("placeholder", "filter " + text + "");
|
|
}
|
|
}
|
|
|
|
function isPrivateIP(ip_address) {
|
|
var defer = $.Deferred();
|
|
|
|
if (ipaddr.isValid(ip_address)) {
|
|
var addr = ipaddr.process(ip_address);
|
|
if (addr.range() === 'loopback' || addr.range() === 'private' || addr.range() === 'linkLocal') {
|
|
defer.resolve();
|
|
} else {
|
|
defer.reject();
|
|
}
|
|
} else {
|
|
defer.resolve('n/a');
|
|
}
|
|
|
|
return defer.promise();
|
|
}
|
|
|
|
function humanTime(seconds) {
|
|
var d = Math.floor(moment.duration(seconds, 'seconds').asDays());
|
|
var h = Math.floor(moment.duration((seconds % 86400), 'seconds').asHours());
|
|
var m = Math.round(moment.duration(((seconds % 86400) % 3600), 'seconds').asMinutes());
|
|
|
|
var text = '';
|
|
if (d > 0) {
|
|
text = '<h3>' + d + '</h3><p> day' + ((d > 1) ? 's' : '') + '</p>'
|
|
+ '<h3>' + h + '</h3><p> hr' + ((h > 1) ? 's' : '') + '</p>'
|
|
+ '<h3>' + m + '</h3><p> min' + ((m > 1) ? 's' : '') + '</p>';
|
|
} else if (h > 0) {
|
|
text = '<h3>' + h + '</h3><p> hr' + ((h > 1) ? 's' : '') + '</p>'
|
|
+ '<h3>' + m + '</h3><p> min' + ((m > 1) ? 's' : '') + '</p>';
|
|
} else {
|
|
text = '<h3>' + m + '</h3><p> min' + ((m > 1) ? 's' : '') + '</p>';
|
|
}
|
|
|
|
return text
|
|
}
|
|
|
|
String.prototype.toProperCase = function () {
|
|
return this.replace(/\w\S*/g, function (txt) {
|
|
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
|
|
});
|
|
};
|
|
|
|
function getPercent(value1, value2) {
|
|
value1 = parseFloat(value1) | 0
|
|
value2 = parseFloat(value2) | 0
|
|
|
|
var percent = 0;
|
|
if (value1 !== 0 && value2 !== 0) {
|
|
percent = (value1 / value2) * 100
|
|
}
|
|
|
|
return Math.round(percent)
|
|
}
|
|
|
|
function millisecondsToMinutes(ms, roundToMinute) {
|
|
if (ms > 0) {
|
|
var minutes = Math.floor(ms / 60000);
|
|
var seconds = ((ms % 60000) / 1000).toFixed(0);
|
|
if (roundToMinute) {
|
|
return (seconds >= 30 ? (minutes + 1) : minutes);
|
|
} else {
|
|
return (seconds == 60 ? (minutes + 1) + ":00" : minutes + ":" + (seconds < 10 ? "0" : "") + seconds);
|
|
}
|
|
} else {
|
|
if (roundToMinute) {
|
|
return '0';
|
|
} else {
|
|
return '0:00';
|
|
}
|
|
}
|
|
}
|
|
|
|
function humanDuration(ms, sig='dhm', units='ms', return_seconds=300000) {
|
|
var factors = {
|
|
d: 86400000,
|
|
h: 3600000,
|
|
m: 60000,
|
|
s: 1000,
|
|
ms: 1
|
|
}
|
|
|
|
ms = parseInt(ms);
|
|
var d, h, m, s;
|
|
|
|
if (ms > 0) {
|
|
if (return_seconds && ms < return_seconds) {
|
|
sig = 'dhms'
|
|
}
|
|
|
|
r = factors[sig.slice(-1)];
|
|
ms = Math.round(ms * factors[units] / r) * r;
|
|
|
|
h = ms % factors['d'];
|
|
d = Math.trunc(ms / factors['d']);
|
|
|
|
m = h % factors['h'];
|
|
h = Math.trunc(h / factors['h']);
|
|
|
|
s = m % factors['m'];
|
|
m = Math.trunc(m / factors['m']);
|
|
|
|
ms = s % factors['s'];
|
|
s = Math.trunc(s / factors['s']);
|
|
|
|
var hd_list = [];
|
|
if (sig >= 'd' && d > 0) {
|
|
d = (sig === 'd' && h >= 12) ? d + 1 : d;
|
|
hd_list.push(d.toString() + ' day' + ((d > 1) ? 's' : ''));
|
|
}
|
|
if (sig >= 'dh' && h > 0) {
|
|
h = (sig === 'dh' && m >= 30) ? h + 1 : h;
|
|
hd_list.push(h.toString() + ' hr' + ((h > 1) ? 's' : ''));
|
|
}
|
|
if (sig >= 'dhm' && m > 0) {
|
|
m = (sig === 'dhm' && s >= 30) ? m + 1 : m;
|
|
hd_list.push(m.toString() + ' min' + ((m > 1) ? 's' : ''));
|
|
}
|
|
if (sig >= 'dhms' && s > 0) {
|
|
hd_list.push(s.toString() + ' sec' + ((s > 1) ? 's' : ''));
|
|
}
|
|
|
|
return hd_list.join(' ')
|
|
} else {
|
|
return '0'
|
|
}
|
|
}
|
|
|
|
// Our countdown plugin takes a callback, a duration, and an optional message
|
|
$.fn.countdown = function (callback, duration, message) {
|
|
// If no message is provided, we use an empty string
|
|
message = message || "";
|
|
// Get reference to container, and set initial content
|
|
var container = $(this[0]).html(duration + message);
|
|
// Get reference to the interval doing the countdown
|
|
var countdown = setInterval(function () {
|
|
// If seconds remain
|
|
if (--duration) {
|
|
// Update our container's message
|
|
container.html(duration + message);
|
|
// Otherwise
|
|
} else {
|
|
// Clear the countdown interval
|
|
clearInterval(countdown);
|
|
// And fire the callback passing our container as `this`
|
|
callback.call(container);
|
|
}
|
|
// Run interval every 1000ms (1 second)
|
|
}, 1000);
|
|
};
|
|
|
|
function setCookie(cname, cvalue, exdays) {
|
|
var d = new Date();
|
|
d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
|
|
var expires = "expires=" + d.toUTCString();
|
|
document.cookie = cname + "=" + cvalue + "; " + expires;
|
|
}
|
|
|
|
function getCookie(cname) {
|
|
var name = cname + "=";
|
|
var ca = document.cookie.split(';');
|
|
for (var i = 0; i < ca.length; i++) {
|
|
var c = ca[i];
|
|
while (c.charAt(0) == ' ') c = c.substring(1);
|
|
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
|
|
}
|
|
return "";
|
|
}
|
|
var Accordion = function (el, multiple, close) {
|
|
this.el = el || {};
|
|
this.multiple = multiple || false;
|
|
this.close = (close === undefined) ? true : close;
|
|
// Variables privadas
|
|
var links = this.el.find('.link');
|
|
// Evento
|
|
links.on('click', {
|
|
el: this.el,
|
|
multiple: this.multiple,
|
|
close: this.close
|
|
}, this.dropdown);
|
|
};
|
|
Accordion.prototype.dropdown = function (e) {
|
|
var $el = e.data.el;
|
|
$this = $(this);
|
|
$next = $this.next();
|
|
if (!e.data.close && $this.parent().hasClass('open')) {
|
|
return
|
|
}
|
|
$next.slideToggle();
|
|
$this.parent().toggleClass('open');
|
|
if (!e.data.multiple) {
|
|
$el.find('.submenu').not($next).slideUp().parent().removeClass('open');
|
|
}
|
|
};
|
|
|
|
function clearSearchButton(tableName, table) {
|
|
$('#' + tableName + '_filter').find('input[type=search]').wrap(
|
|
'<div class="input-group" role="group" aria-label="Search"></div>').after(
|
|
'<span class="input-group-btn"><button class="btn btn-form" data-toggle="button" aria-pressed="false" autocomplete="off" id="clear-search-' +
|
|
tableName + '"><i class="fa fa-remove"></i></button></span>');
|
|
$('#clear-search-' + tableName).click(function () {
|
|
table.search('').draw();
|
|
});
|
|
}
|
|
// Taken from https://github.com/Hellowlol/HTPC-Manager
|
|
window.onerror = function (message, file, line) {
|
|
var e = {
|
|
'page': window.location.href,
|
|
'message': message,
|
|
'file': file,
|
|
'line': line
|
|
};
|
|
$.post("log_js_errors", e, function (data) { });
|
|
};
|
|
|
|
$('*').on('click', '.refresh_pms_image', function (e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
var background_div = $(this).parent().siblings(['style*=pms_image_proxy']).first();
|
|
var pms_proxy_url = background_div.css('background-image');
|
|
pms_proxy_url = /^url\((['"]?)(.*)\1\)$/.exec(pms_proxy_url);
|
|
pms_proxy_url = pms_proxy_url ? pms_proxy_url[2] : ""; // If matched, retrieve url, otherwise ""
|
|
|
|
if (pms_proxy_url.indexOf('pms_image_proxy') === -1) {
|
|
console.log('PMS image proxy url not found.');
|
|
} else {
|
|
background_div.css('background-image', 'none')
|
|
$.ajax({
|
|
url: pms_proxy_url,
|
|
headers: {
|
|
'Cache-Control': 'no-cache'
|
|
},
|
|
success: function () {
|
|
background_div.css('background-image', 'url(' + pms_proxy_url + ')');
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// Taken from http://stackoverflow.com/questions/10420352/converting-file-size-in-bytes-to-human-readable#answer-14919494
|
|
function humanFileSize(bytes, si = true) {
|
|
//var thresh = si ? 1000 : 1024;
|
|
var thresh = 1024; // Always divide by 2^10 but display SI units
|
|
if (Math.abs(bytes) < thresh) {
|
|
return bytes + ' B';
|
|
}
|
|
var units = si ? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
|
: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
|
|
var u = -1;
|
|
do {
|
|
bytes /= thresh;
|
|
++u;
|
|
} while (Math.abs(bytes) >= thresh && u < units.length - 1);
|
|
return bytes.toFixed(1) + ' ' + units[u];
|
|
}
|
|
|
|
// Force max/min in number inputs
|
|
function forceMinMax(elem) {
|
|
var min = parseInt(elem.attr('min'));
|
|
var max = parseInt(elem.attr('max'));
|
|
var val = parseInt(elem.val());
|
|
var default_val = parseInt(elem.data('default'));
|
|
if (isNaN(val)) {
|
|
elem.val(default_val);
|
|
}
|
|
else if (min !== undefined && val < min) {
|
|
elem.val(min);
|
|
}
|
|
else if (max !== undefined && val > max) {
|
|
elem.val(max);
|
|
}
|
|
else {
|
|
elem.val(val);
|
|
}
|
|
}
|
|
|
|
function capitalizeFirstLetter(string) {
|
|
return string.charAt(0).toUpperCase() + string.slice(1);
|
|
}
|
|
|
|
$.fn.slideToggleBool = function(bool, options) {
|
|
return bool ? $(this).slideDown(options) : $(this).slideUp(options);
|
|
};
|
|
|
|
function openPlexXML(endpoint, plextv, params) {
|
|
var data = $.extend({endpoint: endpoint, plextv: plextv || false}, params);
|
|
var query = new URLSearchParams(data)
|
|
window.open('open_plex_xml?' + query.toString(), '_blank');
|
|
}
|
|
|
|
function PopupCenter(url, title, w, h) {
|
|
// Fixes dual-screen position Most browsers Firefox
|
|
var dualScreenLeft = window.screenLeft != undefined ? window.screenLeft : window.screenX;
|
|
var dualScreenTop = window.screenTop != undefined ? window.screenTop : window.screenY;
|
|
|
|
var width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width;
|
|
var height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height;
|
|
|
|
var left = ((width / 2) - (w / 2)) + dualScreenLeft;
|
|
var top = ((height / 2) - (h / 2)) + dualScreenTop;
|
|
var newWindow = window.open(url, title, 'scrollbars=yes, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left);
|
|
|
|
// Puts focus on the newWindow
|
|
if (window.focus) {
|
|
newWindow.focus();
|
|
}
|
|
|
|
return newWindow;
|
|
}
|
|
|
|
|
|
function setLocalStorage(key, value, path) {
|
|
var key_path = key;
|
|
if (path !== false) {
|
|
key_path = key_path + '_' + window.location.pathname;
|
|
}
|
|
localStorage.setItem(key_path, value);
|
|
}
|
|
function getLocalStorage(key, default_value, path) {
|
|
var key_path = key;
|
|
if (path !== false) {
|
|
key_path = key_path + '_' + window.location.pathname;
|
|
}
|
|
var value = localStorage.getItem(key_path);
|
|
if (value !== null) {
|
|
return value
|
|
} else if (default_value !== undefined) {
|
|
setLocalStorage(key, default_value, path);
|
|
return default_value
|
|
}
|
|
}
|
|
|
|
function uuidv4() {
|
|
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, function(c) {
|
|
var cryptoObj = window.crypto || window.msCrypto; // for IE 11
|
|
return (c ^ cryptoObj.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
|
|
});
|
|
}
|
|
|
|
function getPlexHeaders(clientID) {
|
|
return {
|
|
'Accept': 'application/json',
|
|
'X-Plex-Product': 'Tautulli',
|
|
'X-Plex-Version': 'Plex OAuth',
|
|
'X-Plex-Client-Identifier': clientID ? clientID : getLocalStorage('Tautulli_ClientID', uuidv4(), false),
|
|
'X-Plex-Platform': p.name,
|
|
'X-Plex-Platform-Version': p.version,
|
|
'X-Plex-Model': 'Plex OAuth',
|
|
'X-Plex-Device': p.os,
|
|
'X-Plex-Device-Name': p.name + ' (Tautulli)',
|
|
'X-Plex-Device-Screen-Resolution': window.screen.width + 'x' + window.screen.height,
|
|
'X-Plex-Language': 'en'
|
|
};
|
|
}
|
|
|
|
var plex_oauth_window = null;
|
|
const plex_oauth_loader = '<style>' +
|
|
'.login-loader-container {' +
|
|
'font-family: "Open Sans", Arial, sans-serif;' +
|
|
'position: absolute;' +
|
|
'top: 0;' +
|
|
'right: 0;' +
|
|
'bottom: 0;' +
|
|
'left: 0;' +
|
|
'}' +
|
|
'.login-loader-message {' +
|
|
'color: #282A2D;' +
|
|
'text-align: center;' +
|
|
'position: absolute;' +
|
|
'left: 50%;' +
|
|
'top: 25%;' +
|
|
'transform: translate(-50%, -50%);' +
|
|
'}' +
|
|
'.login-loader {' +
|
|
'border: 5px solid #ccc;' +
|
|
'-webkit-animation: spin 1s linear infinite;' +
|
|
'animation: spin 1s linear infinite;' +
|
|
'border-top: 5px solid #282A2D;' +
|
|
'border-radius: 50%;' +
|
|
'width: 50px;' +
|
|
'height: 50px;' +
|
|
'position: relative;' +
|
|
'left: calc(50% - 25px);' +
|
|
'}' +
|
|
'@keyframes spin {' +
|
|
'0% { transform: rotate(0deg); }' +
|
|
'100% { transform: rotate(360deg); }' +
|
|
'}' +
|
|
'</style>' +
|
|
'<div class="login-loader-container">' +
|
|
'<div class="login-loader-message">' +
|
|
'<div class="login-loader"></div>' +
|
|
'<br>' +
|
|
'Redirecting to the Plex login page...' +
|
|
'</div>' +
|
|
'</div>';
|
|
|
|
function closePlexOAuthWindow() {
|
|
if (plex_oauth_window) {
|
|
plex_oauth_window.close();
|
|
}
|
|
}
|
|
|
|
getPlexOAuthPin = function (clientID) {
|
|
var x_plex_headers = getPlexHeaders(clientID);
|
|
var deferred = $.Deferred();
|
|
|
|
$.ajax({
|
|
url: 'https://plex.tv/api/v2/pins?strong=true',
|
|
type: 'POST',
|
|
headers: x_plex_headers,
|
|
success: function(data) {
|
|
deferred.resolve({pin: data.id, code: data.code});
|
|
},
|
|
error: function() {
|
|
closePlexOAuthWindow();
|
|
deferred.reject();
|
|
}
|
|
});
|
|
return deferred;
|
|
};
|
|
|
|
var polling = null;
|
|
|
|
function PlexOAuth(success, error, pre, clientID) {
|
|
if (typeof pre === "function") {
|
|
pre()
|
|
}
|
|
closePlexOAuthWindow();
|
|
plex_oauth_window = PopupCenter('', 'Plex-OAuth', 600, 700);
|
|
$(plex_oauth_window.document.body).html(plex_oauth_loader);
|
|
|
|
getPlexOAuthPin(clientID).then(function (data) {
|
|
var x_plex_headers = getPlexHeaders(clientID);
|
|
const pin = data.pin;
|
|
const code = data.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;
|
|
|
|
(function poll() {
|
|
$.ajax({
|
|
url: 'https://plex.tv/api/v2/pins/' + pin,
|
|
type: 'GET',
|
|
headers: x_plex_headers,
|
|
success: function (data) {
|
|
if (data.authToken){
|
|
closePlexOAuthWindow();
|
|
if (typeof success === "function") {
|
|
success(data.authToken)
|
|
}
|
|
}
|
|
},
|
|
error: function (jqXHR, textStatus, errorThrown) {
|
|
if (textStatus !== "timeout") {
|
|
closePlexOAuthWindow();
|
|
if (typeof error === "function") {
|
|
error()
|
|
}
|
|
}
|
|
},
|
|
complete: function () {
|
|
if (!plex_oauth_window.closed && polling === pin){
|
|
setTimeout(function() {poll()}, 1000);
|
|
}
|
|
},
|
|
timeout: 10000
|
|
});
|
|
})();
|
|
}, function () {
|
|
closePlexOAuthWindow();
|
|
if (typeof error === "function") {
|
|
error()
|
|
}
|
|
});
|
|
}
|
|
|
|
function encodeData(data) {
|
|
return Object.keys(data).map(function(key) {
|
|
return [key, data[key]].map(encodeURIComponent).join("=");
|
|
}).join("&");
|
|
}
|
|
|
|
function page(endpoint, ...args) {
|
|
let endpoints = {
|
|
'pms_image_proxy': pms_image_proxy,
|
|
'info': info_page,
|
|
'library': library_page,
|
|
'user': user_page
|
|
};
|
|
|
|
var params = {};
|
|
|
|
if (endpoint in endpoints) {
|
|
params = endpoints[endpoint](...args);
|
|
}
|
|
|
|
return endpoint + '?' + $.param(params).replace(/'/g, '%27');
|
|
}
|
|
|
|
function pms_image_proxy(img, rating_key, width, height, opacity, background, blur, fallback, refresh, clip, img_format) {
|
|
var params = {};
|
|
|
|
if (img != null) { params.img = img; }
|
|
if (rating_key != null) { params.rating_key = rating_key; }
|
|
if (width != null) { params.width = width; }
|
|
if (height != null) { params.height = height; }
|
|
if (opacity != null) { params.opacity = opacity; }
|
|
if (background != null) { params.background = background; }
|
|
if (blur != null) { params.blur = blur; }
|
|
if (fallback != null) { params.fallback = fallback; }
|
|
if (refresh != null) { params.refresh = true; }
|
|
if (clip != null) { params.clip = true; }
|
|
if (img_format != null) { params.img_format = img_format; }
|
|
|
|
return params;
|
|
}
|
|
|
|
function info_page(rating_key, guid, history, live) {
|
|
var params = {};
|
|
|
|
if (live && history) {
|
|
params.guid = guid;
|
|
} else {
|
|
params.rating_key = rating_key;
|
|
}
|
|
|
|
if (history) { params.source = 'history'; }
|
|
|
|
return params;
|
|
}
|
|
|
|
function library_page(section_id) {
|
|
var params = {};
|
|
|
|
if (section_id != null) { params.section_id = section_id; }
|
|
|
|
return params;
|
|
}
|
|
|
|
function user_page(user_id, user) {
|
|
var params = {};
|
|
|
|
if (user_id != null) { params.user_id = user_id; }
|
|
if (user != null) { params.user = user; }
|
|
|
|
return params;
|
|
}
|
|
|
|
MEDIA_TYPE_HEADERS = {
|
|
'movie': 'Movies',
|
|
'show': 'TV Shows',
|
|
'season': 'Seasons',
|
|
'episode': 'Episodes',
|
|
'artist': 'Artists',
|
|
'album': 'Albums',
|
|
'track': 'Tracks',
|
|
'video': 'Videos',
|
|
'audio': 'Tracks',
|
|
'photo': 'Photos'
|
|
}
|
|
|
|
function short_season(title) {
|
|
if (title.startsWith('Season ') && /^\d+$/.test(title.substring(7))) {
|
|
return 'S' + title.substring(7)
|
|
}
|
|
return title
|
|
}
|
|
|
|
function loadAllBlurHash() {
|
|
$('[data-blurhash]').each(function() {
|
|
const elem = $(this);
|
|
const src = elem.data('blurhash');
|
|
loadBlurHash(elem, src);
|
|
});
|
|
}
|
|
|
|
function loadBlurHash(elem, src) {
|
|
const img = new Image();
|
|
img.src = src;
|
|
img.onload = () => {
|
|
const imgData = blurhash.getImageData(img);
|
|
|
|
blurhash
|
|
.encodePromise(imgData, img.width, img.height, 4, 4)
|
|
.then(hash => {
|
|
return blurhash.decodePromise(
|
|
hash,
|
|
img.width,
|
|
img.height
|
|
);
|
|
})
|
|
.then(blurhashImgData => {
|
|
const imgObject = blurhash.getImageDataAsImage(
|
|
blurhashImgData,
|
|
img.width,
|
|
img.height,
|
|
(event, imgObject) => {
|
|
elem.css('background-image', 'url(' + imgObject.src + ')')
|
|
}
|
|
);
|
|
});
|
|
}
|
|
}
|
|
|
|
function _toggleRevealToken(elem, click) {
|
|
var input = elem.parent().siblings('input');
|
|
if ((input.prop('type') === 'password' && click) || !input.val()) {
|
|
input.prop('type', 'text');
|
|
elem.children('.fa').removeClass('fa-eye-slash').addClass('fa-eye');
|
|
} else {
|
|
input.prop('type', 'password');
|
|
elem.children('.fa').removeClass('fa-eye').addClass('fa-eye-slash');
|
|
}
|
|
}
|
|
function toggleRevealTokens() {
|
|
$('.reveal-token').each(function () {
|
|
_toggleRevealToken($(this));
|
|
});
|
|
}
|
|
|
|
$('body').on('click', '.reveal-token', function() {
|
|
_toggleRevealToken($(this), true);
|
|
});
|
|
|
|
// https://stackoverflow.com/a/57414592
|
|
// prevent modal close when click starts in modal and ends on backdrop
|
|
$(document).on('mousedown', '.modal', function(e){
|
|
window.clickStartedInModal = $(e.target).is('.modal-dialog *');
|
|
});
|
|
$(document).on('mouseup', '.modal', function(e){
|
|
if(!$(e.target).is('.modal-dialog *') && window.clickStartedInModal) {
|
|
window.preventModalClose = true;
|
|
}
|
|
});
|
|
$('.modal').on('hide.bs.modal', function (e) {
|
|
if(window.preventModalClose){
|
|
window.preventModalClose = false;
|
|
return false;
|
|
}
|
|
});
|
|
|
|
$.fn.hasScrollBar = function() {
|
|
return this.get(0).scrollHeight > this.get(0).clientHeight;
|
|
}
|
|
|
|
function paginateScroller(scrollerId, buttonClass) {
|
|
$(buttonClass).click(function (e) {
|
|
e.preventDefault();
|
|
var scroller = $(scrollerId + "-row-scroller");
|
|
var scrollerParent = scroller.parent();
|
|
var containerWidth = scrollerParent.width();
|
|
var scrollCurrent = scrollerParent.scrollLeft();
|
|
var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175;
|
|
var scrollMax = scroller.width() - Math.abs(scrollAmount);
|
|
var scrollTotal = Math.min(parseInt(scrollCurrent / 175) * 175 + scrollAmount, scrollMax);
|
|
scrollerParent.animate({ scrollLeft: scrollTotal }, 250);
|
|
});
|
|
}
|
|
|
|
function highlightScrollerButton(scrollerId) {
|
|
var scroller = $(scrollerId + "-row-scroller");
|
|
var scrollerParent = scroller.parent();
|
|
var buttonLeft = $(scrollerId + "-page-left");
|
|
var buttonRight = $(scrollerId + "-page-right");
|
|
|
|
var numElems = scroller.find("li").length;
|
|
scroller.width(numElems * 175);
|
|
$(buttonLeft).addClass("disabled").blur();
|
|
if (scroller.width() > scrollerParent.width()) {
|
|
$(buttonRight).removeClass("disabled");
|
|
} else {
|
|
$(buttonRight).addClass("disabled");
|
|
}
|
|
|
|
scrollerParent.scroll(function () {
|
|
var scrollCurrent = $(this).scrollLeft();
|
|
var scrollMax = scroller.width() - $(this).width();
|
|
|
|
if (scrollCurrent == 0) {
|
|
$(buttonLeft).addClass("disabled").blur();
|
|
} else {
|
|
$(buttonLeft).removeClass("disabled");
|
|
}
|
|
|
|
if (scrollCurrent >= scrollMax) {
|
|
$(buttonRight).addClass("disabled").blur();
|
|
} else {
|
|
$(buttonRight).removeClass("disabled");
|
|
}
|
|
});
|
|
}
|