mirror of
https://github.com/Tautulli/Tautulli.git
synced 2024-11-21 04:50:36 -08:00
544 lines
25 KiB
HTML
544 lines
25 KiB
HTML
<%
|
|
import plexpy
|
|
from plexpy import common, helpers
|
|
%>
|
|
|
|
<!doctype html>
|
|
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Tautulli - ${title} | ${server_name}</title>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta name="description" content="">
|
|
<meta name="author" content="">
|
|
<meta name="referrer" content="no-referrer">
|
|
<link href="${http_root}css/bootstrap3/bootstrap.min.css" rel="stylesheet">
|
|
<link href="${http_root}css/bootstrap-wizard.css" rel="stylesheet">
|
|
<link href="${http_root}css/tautulli.css${cache_param}" rel="stylesheet">
|
|
<link href="${http_root}css/selectize.bootstrap3.css" rel="stylesheet">
|
|
<link href="${http_root}css/opensans.min.css" rel="stylesheet">
|
|
<link href="${http_root}css/font-awesome.all.min.css" rel="stylesheet">
|
|
<link href="${http_root}css/font-awesome.v4-shims.min.css" rel="stylesheet">
|
|
|
|
<!-- Favicons -->
|
|
<link rel="icon" type="image/png" sizes="32x32" href="${http_root}images/favicon/favicon-32x32.png?v=2.6.0">
|
|
<link rel="icon" type="image/png" sizes="16x16" href="${http_root}images/favicon/favicon-16x16.png?v=2.6.0">
|
|
<link rel="shortcut icon" href="${http_root}images/favicon/favicon.ico?v=2.6.0">
|
|
|
|
<!-- ICONS -->
|
|
<!-- Android -->
|
|
<link rel="manifest" href="${http_root}images/favicon/manifest.json?v=2.9.0" crossorigin="use-credentials">
|
|
<meta name="theme-color" content="#282a2d">
|
|
<!-- Apple -->
|
|
<link rel="apple-touch-icon" sizes="180x180" href="${http_root}images/favicon/apple-touch-icon.png?v=2.6.0">
|
|
<link rel="mask-icon" href="${http_root}images/favicon/safari-pinned-tab.svg?v=2.6.0" color="#282a2d">
|
|
<meta name="apple-mobile-web-app-title" content="Tautulli">
|
|
<!-- Microsoft -->
|
|
<meta name="application-name" content="Tautulli">
|
|
<meta name="msapplication-config" content="${http_root}images/favicon/browserconfig.xml?v=2.6.0">
|
|
</head>
|
|
|
|
<body>
|
|
<div class="container-fluid">
|
|
% if plexpy.CONFIG.CHECK_DOCKER_MOUNT and plexpy.DOCKER and not plexpy.DOCKER_MOUNT:
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<div class="alert alert-danger docker-mount" role="alert" style="width: 700px; z-index: 9999">
|
|
The <span class="inline-pre">/config</span> volume mount was not configured properly for the Docker container.
|
|
All data may be cleared when the container is recreated or updated.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
% endif
|
|
<div class="row">
|
|
<div class="wizard" id="setup-wizard" data-title="Tautulli Setup Wizard">
|
|
<form>
|
|
<div class="wizard-card" data-cardname="card1">
|
|
<div style="float: right;">
|
|
<img src="${http_root}images/logo-tautulli-45.png" height="45" alt="Tautulli">
|
|
</div>
|
|
<h3 style="line-height: 50px;">Welcome!</h3>
|
|
<div class="wizard-input-section">
|
|
<p class="welcome-message">
|
|
Thanks for taking the time to try out Tautulli. Hope you find it useful.
|
|
</p>
|
|
<p class="welcome-message">
|
|
Tautulli requires a permanent internet connection to ensure a reliable experience.
|
|
</p>
|
|
<p class="welcome-message">
|
|
This wizard will help you get set up, to continue press Next.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="wizard-card" data-cardname="card2">
|
|
<h3>Authentication</h3>
|
|
<div class="wizard-input-section">
|
|
<p class="help-block">
|
|
Please setup an admin username and password for Tautulli.
|
|
</p>
|
|
</div>
|
|
<div class="wizard-input-section">
|
|
<label for="http_username">HTTP Username</label>
|
|
<div class="row">
|
|
<div class="col-xs-8">
|
|
<input type="text" class="form-control auth-settings" id="http_username" name="http_username" value="" size="30">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="wizard-input-section">
|
|
<label for="http_password">HTTP Password</label>
|
|
<div class="row">
|
|
<div class="col-xs-8">
|
|
<div class="input-group">
|
|
<input type="password" class="form-control auth-settings" id="http_password" name="http_password" value="" size="30" autocomplete="new-password">
|
|
<span class="input-group-btn">
|
|
<button class="btn btn-form reveal-token" type="button"><i class="fa fa-eye-slash"></i></button>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<input type="hidden" class="form-control" name="http_plex_admin" id="http_plex_admin" value="1">
|
|
<input type="hidden" id="authentication_valid" data-validate="validateAuthentication" value="">
|
|
<span style="display: none;" id="authentication-status"></span>
|
|
</div>
|
|
|
|
<div class="wizard-card" data-cardname="card3">
|
|
<h3>Plex Account</h3>
|
|
<div class="wizard-input-section">
|
|
<p class="help-block">
|
|
Tautulli requires a Plex.tv account. Click the button below to sign in on Plex.tv. You may need to allow popups in your browser.
|
|
</p>
|
|
</div>
|
|
<input type="hidden" id="pms_token_validated" value="0" data-validate="validatePMStoken">
|
|
<input type="hidden" name="pms_client_id" id="pms_client_id" value="">
|
|
<button id="sign-in-plex" class="btn btn-form" type="button">Sign In with Plex</button>
|
|
<span style="margin-left: 10px; display: none;" id="pms-token-status"></span>
|
|
</div>
|
|
|
|
<div class="wizard-card" data-cardname="card4">
|
|
<h3>Plex Media Server</h3>
|
|
<div class="wizard-input-section">
|
|
<p class="help-block">
|
|
Select your Plex Media Server from the dropdown menu or enter an IP address or hostname.
|
|
</p>
|
|
</div>
|
|
<div class="wizard-input-section">
|
|
<label for="pms_ip_selectize">Plex IP Address or Hostname</label>
|
|
<div class="row">
|
|
<div class="col-xs-12">
|
|
<select class="form-control pms-settings selectize-pms-ip" id="pms_ip_selectize">
|
|
% if config['pms_identifier']:
|
|
<option value="${config['pms_ip']}:${config['pms_port']}"
|
|
data-identifier="${config['pms_identifier']}"
|
|
data-ip="${config['pms_ip']}"
|
|
data-port="${config['pms_port']}"
|
|
data-ssl="${config['pms_ssl']}"
|
|
data-is_cloud="${config['pms_is_cloud']}"
|
|
data-label="${config['pms_name'] or 'Local'}"
|
|
selected>${config['pms_ip']}</option>
|
|
% endif
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="wizard-input-section">
|
|
<label for="pms_port">Plex Port</label>
|
|
<div class="row">
|
|
<div class="col-xs-3">
|
|
<input type="text" class="form-control pms-settings" name="pms_port" id="pms_port" placeholder="32400" value="${config['pms_port']}" required>
|
|
</div>
|
|
<div class="col-xs-9">
|
|
<div class="checkbox">
|
|
<label>
|
|
<input type="checkbox" id="pms_ssl_checkbox" class="checkbox-toggle pms-settings" data-id="pms_ssl" value="1" ${helpers.checked(config['pms_ssl'])}> Use Secure Connection
|
|
<input type="hidden" id="pms_ssl" name="pms_ssl" value="${config['pms_ssl']}">
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<input type="hidden" id="pms_valid" data-validate="validatePMSip" value="">
|
|
<input type="hidden" id="pms_ip" name="pms_ip" value="${config['pms_ip']}">
|
|
<input type="hidden" id="pms_is_cloud" name="pms_is_cloud" value="${config['pms_is_cloud']}">
|
|
<input type="hidden" id="pms_identifier" name="pms_identifier" value="${config['pms_identifier']}">
|
|
<a class="btn btn-dark" id="verify-plex-server" href="#" role="button">Verify</a>
|
|
<span style="margin-left: 10px; display: none;" id="pms-verify-status"></span>
|
|
</div>
|
|
|
|
<div class="wizard-card" data-cardname="card5">
|
|
<h3>Activity Logging</h3>
|
|
<div class="wizard-input-section">
|
|
<p class="help-block">
|
|
Tautulli will keep a history of all streaming activity on your Plex server.
|
|
</p>
|
|
</div>
|
|
<div class="wizard-input-section">
|
|
<label for="logging_ignore_interval">Ignore Interval</label>
|
|
<div class="row">
|
|
<div class="col-xs-4">
|
|
<input type="text" class="form-control pms-monitoring" id="logging_ignore_interval" name="logging_ignore_interval" placeholder="120" value="${config['logging_ignore_interval']}" data-validate="validateIgnoreInterval" required>
|
|
</div>
|
|
<span style="margin-left: 10px; line-height: 35px; display: none;" id="ignore-int-status"></span>
|
|
</div>
|
|
<p class="help-block">The interval (in seconds) an item must be in a playing state before logging it. 0 to disable.</p>
|
|
</div>
|
|
<div class="wizard-input-section">
|
|
<p class="help-block">
|
|
Additional options to disable history logging for certain libraries or users can be found by editing them
|
|
on the <strong>Libraries</strong> or <strong>Users</strong> pages.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="wizard-card" data-cardname="card6">
|
|
<h3>Notifications</h3>
|
|
<div class="wizard-input-section">
|
|
<p class="help-block">
|
|
Tautulli can send a wide variety of notifications to alert you of activity on your Plex server.
|
|
</p>
|
|
<p class="help-block">
|
|
To set up a notification agent, navigate to the <strong>Settings</strong> page
|
|
and to the <strong>Notification Agents</strong> tab after you have completed this setup wizard.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="wizard-card" data-cardname="card7">
|
|
<h3>Database Import</h3>
|
|
<div class="wizard-input-section">
|
|
<p class="help-block">
|
|
If you have an existing Tautulli, PlexWatch, or Plexivity database, you can import the data into Tautulli.
|
|
</p>
|
|
<p class="help-block">
|
|
To import a database, navigate to the <strong>Settings</strong> page
|
|
and to the <strong>Import & Backups</strong> tab after you have completed this setup wizard.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Required fields but hidden -->
|
|
<div style="display: none;">
|
|
<input type="checkbox" name="first_run" id="first_run" value="1" checked>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</form>
|
|
<div class="wizard-success">
|
|
<h3>Setup Complete!</h3>
|
|
<br/>
|
|
<p>Setup is now complete. For more configuration options please visit the Settings menu on the home page.</p>
|
|
<br/>
|
|
<i class="fa fa-refresh fa-spin"></i> Waiting <span class="countdown">5</span> seconds to ensure authentication token is registered...
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="${http_root}js/jquery-3.6.0.min.js"></script>
|
|
<script src="${http_root}js/bootstrap.min.js"></script>
|
|
<script src="${http_root}js/selectize.min.js"></script>
|
|
<script src="${http_root}js/platform.min.js"></script>
|
|
<script src="${http_root}js/script.js${cache_param}"></script>
|
|
<script src="${http_root}js/bootstrap-wizard.min.js"></script>
|
|
<script>
|
|
function validateAuthentication(el) {
|
|
var http_username = $("#http_username").val();
|
|
var http_password = $("#http_password").val();
|
|
var valid_authentication = el.val();
|
|
var retValue = {};
|
|
|
|
if (http_username === "" || http_password === "") {
|
|
retValue.status = false;
|
|
retValue.msg = "Please enter a username and password.";
|
|
$("#authentication-status").html('<i class="fa fa-exclamation-circle"></i> Please enter a username and password.');
|
|
$('#authentication-status').fadeIn('fast').delay(2000).fadeOut('fast');
|
|
} else {
|
|
retValue.status = true;
|
|
}
|
|
|
|
return retValue;
|
|
}
|
|
|
|
function validatePMSip(el) {
|
|
var valid_pms_ip = el.val();
|
|
var retValue = {};
|
|
|
|
if (valid_pms_ip === "") {
|
|
retValue.status = false;
|
|
retValue.msg = "Please verify your server.";
|
|
$("#pms-verify-status").html('<i class="fa fa-exclamation-circle"></i> Please verify your server.');
|
|
$('#pms-verify-status').fadeIn('fast').delay(2000).fadeOut('fast');
|
|
} else {
|
|
retValue.status = true;
|
|
}
|
|
|
|
return retValue;
|
|
}
|
|
|
|
function validatePMStoken(el) {
|
|
var valid_pms_token = el.val();
|
|
var retValue = {};
|
|
|
|
if (valid_pms_token !== "1") {
|
|
retValue.status = false;
|
|
retValue.msg = "Please authenticate.";
|
|
$("#pms-token-status").html('<i class="fa fa-exclamation-circle"></i> Please authenticate.');
|
|
$('#pms-token-status').fadeIn('fast').delay(2000).fadeOut('fast');
|
|
} else {
|
|
retValue.status = true;
|
|
}
|
|
|
|
return retValue;
|
|
}
|
|
|
|
function validateIgnoreInterval(el) {
|
|
var valid_ignore_int = el.val();
|
|
var retValue = {};
|
|
|
|
if (!isPositiveInt(valid_ignore_int)) {
|
|
retValue.status = false;
|
|
retValue.msg = "Please enter a valid integer.";
|
|
$("#ignore-int-status").html('<i class="fa fa-exclamation-circle"></i> Please enter a valid ignore interval.');
|
|
$('#ignore-int-status').fadeIn('fast').delay(2000).fadeOut('fast');
|
|
} else {
|
|
retValue.status = true;
|
|
}
|
|
|
|
return retValue;
|
|
}
|
|
|
|
function isPositiveInt(n) {
|
|
return $.isNumeric(n) && (Math.floor(n) == n) && (n >= 0)
|
|
}
|
|
|
|
$(document).ready(function() {
|
|
|
|
$.fn.wizard.logging = false;
|
|
var options = {
|
|
keyboard : false,
|
|
contentHeight : 450,
|
|
contentWidth : 700,
|
|
backdrop: 'static',
|
|
buttons: {submitText: 'Finish'},
|
|
submitUrl: "configUpdate"
|
|
};
|
|
var wizard = $("#setup-wizard").wizard(options);
|
|
wizard.show();
|
|
|
|
// Change button classes
|
|
wizard.find('.wizard-back').addClass('btn-dark');
|
|
wizard.on('incrementCard', function(wizard) {
|
|
wizard.find('.wizard-next.btn-success').removeClass('btn-success').addClass('btn-bright');
|
|
});
|
|
wizard.on('decrementCard', function(wizard) {
|
|
wizard.find('.wizard-next').removeClass('btn-bright').text('Next');
|
|
});
|
|
|
|
wizard.on("submit", function(wizard) {
|
|
// Probably should not success before we know, but hopefully validation is good enough.
|
|
wizard.submitSuccess();
|
|
$.ajax({
|
|
type: "POST",
|
|
url: wizard.args.submitUrl,
|
|
data: wizard.serialize(),
|
|
dataType: "json",
|
|
complete: function (data) {
|
|
$(".countdown").countdown(function () { location.reload(); }, 5, "");
|
|
}
|
|
})
|
|
});
|
|
|
|
$('.checkbox-toggle').click(function () {
|
|
var configToggle = $(this).data('id');
|
|
if ($(this).is(':checked')) {
|
|
$('#'+configToggle).val(1);
|
|
} else {
|
|
$('#'+configToggle).val(0);
|
|
}
|
|
});
|
|
|
|
var $select_pms = $('#pms_ip_selectize').selectize({
|
|
createOnBlur: true,
|
|
openOnFocus: true,
|
|
maxItems: 1,
|
|
closeAfterSelect: true,
|
|
sortField: 'label',
|
|
searchField: ['label', 'value'],
|
|
inputClass: 'form-control selectize-input',
|
|
render: {
|
|
item: function (item, escape) {
|
|
if (!item.label) {
|
|
$.extend(item,
|
|
$(this.revertSettings.$children)
|
|
.filter('[value="' + item.value + '"]').data()
|
|
);
|
|
}
|
|
var label = item.label || item.value;
|
|
var caption = item.label ? item.ip : null;
|
|
return '<div data-identifier="' + item.clientIdentifier +
|
|
'" data-ip="' + item.ip +
|
|
'" data-port="' + item.port +
|
|
'" data-ssl="' + item.httpsRequired +
|
|
'" data-is_cloud="' + item.is_cloud +
|
|
'" data-label="' + item.label + '">' +
|
|
'<span class="item-text">' + escape(label) + '</span>' +
|
|
(caption ? '<span class="item-value">' + escape(caption) + '</span>' : '') +
|
|
'</div>';
|
|
},
|
|
option: function (item, escape) {
|
|
var label = item.label || item.value;
|
|
var caption = item.label ? item.value : null;
|
|
return '<div data-identifier="' + item.clientIdentifier +
|
|
'" data-ip="' + item.ip +
|
|
'" data-port="' + item.port +
|
|
'" data-ssl="' + item.httpsRequired +
|
|
'" data-is_cloud="' + item.is_cloud +
|
|
'" data-label="' + item.label + '">' +
|
|
escape(label) +
|
|
(caption ? '<span class="caption">' + escape(caption) + '</span>' : '') +
|
|
'</div>';
|
|
}
|
|
},
|
|
create: function(input) {
|
|
return {label: '', value: input};
|
|
},
|
|
onInitialize: function () {
|
|
var s = this;
|
|
this.revertSettings.$children.each(function () {
|
|
$.extend(s.options[this.value], $(this).data());
|
|
});
|
|
},
|
|
onChange: function (item) {
|
|
var pms_ip_selected = this.getItem(item)[0];
|
|
var identifier = $(pms_ip_selected).data('identifier');
|
|
var ip = $(pms_ip_selected).data('ip');
|
|
var port = $(pms_ip_selected).data('port');
|
|
var ssl = $(pms_ip_selected).data('ssl');
|
|
var is_cloud = $(pms_ip_selected).data('is_cloud');
|
|
var value = $(pms_ip_selected).data('value');
|
|
|
|
$("#pms_valid").val(identifier !== 'undefined' ? 'valid' : '');
|
|
$("#pms-verify-status").html(identifier !== 'undefined' ? '<i class="fa fa-check"></i> Server found!' : '').fadeIn('fast');
|
|
|
|
$("#pms_identifier").val(identifier !== 'undefined' ? identifier : '');
|
|
$('#pms_ip').val(ip !== 'undefined' ? ip : value);
|
|
$('#pms_port').val(port !== 'undefined' ? port : 32400);
|
|
$('#pms_ssl_checkbox').prop('checked', (ssl !== 'undefined' && ssl === 1));
|
|
$('#pms_ssl').val(ssl !== 'undefined' && ssl === 1 ? 1 : 0);
|
|
$('#pms_is_cloud').val(is_cloud !== 'undefined' && is_cloud === true ? 1 : 0);
|
|
|
|
if (is_cloud === true) {
|
|
$('#pms_port').prop('readonly', true);
|
|
$('#pms_ssl_checkbox').prop('disabled', true);
|
|
} else {
|
|
$('#pms_port').prop('readonly', false);
|
|
$('#pms_ssl_checkbox').prop('disabled', false);
|
|
}
|
|
},
|
|
onDropdownOpen: function() {
|
|
this.clear();
|
|
}
|
|
});
|
|
var select_pms = $select_pms[0].selectize;
|
|
|
|
function getServerOptions() {
|
|
$.ajax({
|
|
url: 'discover',
|
|
success: function (result) {
|
|
if (result) {
|
|
var existing_ip = $('#pms_ip').val();
|
|
var existing_port = $('#pms_port').val();
|
|
result.forEach(function (item) {
|
|
if (item.ip === existing_ip && item.port === existing_port) {
|
|
select_pms.updateOption(item.value, item);
|
|
} else {
|
|
select_pms.addOption(item);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
})
|
|
}
|
|
var pms_verified = false;
|
|
var authenticated = false;
|
|
|
|
$("#verify-plex-server").click(function () {
|
|
if (!(pms_verified)) {
|
|
var pms_ip = $("#pms_ip").val().trim();
|
|
var pms_port = $("#pms_port").val().trim();
|
|
var pms_identifier = $("#pms_identifier").val();
|
|
var pms_ssl = $("#pms_ssl").val();
|
|
if ((pms_ip !== '') || (pms_port !== '')) {
|
|
$("#pms-verify-status").html('<i class="fa fa-refresh fa-spin"></i> Verifying server...');
|
|
$('#pms-verify-status').fadeIn('fast');
|
|
$.ajax({
|
|
url: 'get_server_id',
|
|
data: {
|
|
hostname: pms_ip,
|
|
port: pms_port,
|
|
identifier: pms_identifier,
|
|
ssl: pms_ssl
|
|
},
|
|
cache: true,
|
|
async: true,
|
|
timeout: 5000,
|
|
error: function (jqXHR, textStatus, errorThrown) {
|
|
$("#pms-verify-status").html('<i class="fa fa-exclamation-circle"></i> Error verifying server: ' + textStatus);
|
|
$('#pms-verify-status').fadeIn('fast');
|
|
},
|
|
success: function(xhr, status) {
|
|
var result = xhr;
|
|
var identifier = result.identifier;
|
|
if (identifier) {
|
|
$("#pms_identifier").val(identifier);
|
|
$("#pms-verify-status").html('<i class="fa fa-check"></i> Server found!');
|
|
$('#pms-verify-status').fadeIn('fast');
|
|
pms_verified = true;
|
|
$("#pms_valid").val("valid");
|
|
} else {
|
|
$("#pms-verify-status").html('<i class="fa fa-exclamation-circle"></i> This is not a Plex Server!');
|
|
$('#pms-verify-status').fadeIn('fast');
|
|
}
|
|
}
|
|
});
|
|
} else {
|
|
$("#pms-verify-status").html('<i class="fa fa-exclamation-circle"></i> Please enter both fields.');
|
|
$('#pms-verify-status').fadeIn('fast');
|
|
}
|
|
}
|
|
});
|
|
|
|
$( ".pms-settings" ).change(function() {
|
|
pms_verified = false;
|
|
$("#pms_valid").val("");
|
|
$("#pms-verify-status").html("");
|
|
});
|
|
|
|
function OAuthPreFunction() {
|
|
$("#pms-token-status").html('<i class="fa fa-refresh fa-spin"></i> Waiting for authentication...').fadeIn('fast');
|
|
}
|
|
function OAuthSuccessCallback(authToken) {
|
|
$.post('save_pms_token', { token: authToken, client_id: $('#pms_client_id').val() }, function () {
|
|
$("#pms_token_validated").val(1);
|
|
getServerOptions();
|
|
});
|
|
$("#pms-token-status").html('<i class="fa fa-check"></i> Authentication successful.').fadeIn('fast');
|
|
authenticated = true;
|
|
}
|
|
function OAuthErrorCallback() {
|
|
$("#pms-token-status").html('<i class="fa fa-exclamation-circle"></i> Error communicating with Plex.tv.').fadeIn('fast');
|
|
}
|
|
|
|
$('#sign-in-plex').click(function() {
|
|
PlexOAuth(OAuthSuccessCallback, OAuthErrorCallback, OAuthPreFunction, $('#pms_client_id').val(uuidv4()).val());
|
|
});
|
|
});
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|