Better reverse proxy support

Added "base path override" config option that makes all links and
redirects work with your reverse proxy.
Fixed post config update reload to work properly.
Make redirects and ajax calls use relative pathing.
This commit is contained in:
Michael Robinson
2016-01-08 15:45:08 -07:00
parent 71c583d359
commit 42ec634cd3
10 changed files with 100 additions and 62 deletions

View File

@@ -1,15 +1,17 @@
$(document).ready(function () {
var basePath = "";
$(document).ready(function () {
$.ajaxSetup({ cache: false });
window.jackettIsLocal = window.location.hostname === 'localhost' ||
window.location.hostname === '127.0.0.1';
bindUIButtons();
reloadIndexers();
loadJackettSettings();
reloadIndexers();
});
function getJackettConfig(callback) {
var jqxhr = $.get("/admin/get_jackett_config", function (data) {
var jqxhr = $.get("get_jackett_config", function (data) {
callback(data);
}).fail(function () {
@@ -22,6 +24,8 @@ function loadJackettSettings() {
$("#api-key-input").val(data.config.api_key);
$("#app-version").html(data.app_version);
$("#jackett-port").val(data.config.port);
$("#jackett-basepathoverride").val(data.config.basepathoverride);
basePath = data.config.basepathoverride;
$("#jackett-savedir").val(data.config.blackholedir);
$("#jackett-allowext").attr('checked', data.config.external);
$("#jackett-allowupdate").attr('checked', data.config.updatedisabled);
@@ -39,7 +43,7 @@ function reloadIndexers() {
$('#indexers').hide();
$('#indexers > .indexer').remove();
$('#unconfigured-indexers').empty();
var jqxhr = $.get("/admin/get_indexers", function (data) {
var jqxhr = $.get("get_indexers", function (data) {
displayIndexers(data.items);
}).fail(function () {
doNotify("Error loading indexers, request to Jackett server failed", "danger", "glyphicon glyphicon-alert");
@@ -52,8 +56,8 @@ function displayIndexers(items) {
$('#unconfigured-indexers-template').empty();
for (var i = 0; i < items.length; i++) {
var item = items[i];
item.torznab_host = resolveUrl("/torznab/" + item.id);
item.potato_host = resolveUrl("/potato/" + item.id);
item.torznab_host = resolveUrl("/" + basePath + "/torznab/" + item.id);
item.potato_host = resolveUrl("/" + basePath + "/potato/" + item.id);
if (item.configured)
$('#indexers').append(indexerTemplate(item));
else
@@ -92,7 +96,7 @@ function prepareDeleteButtons() {
var $btn = $(btn);
var id = $btn.data("id");
$btn.click(function () {
var jqxhr = $.post("/admin/delete_indexer", JSON.stringify({ indexer: id }), function (data) {
var jqxhr = $.post("delete_indexer", JSON.stringify({ indexer: id }), function (data) {
if (data.result == "error") {
doNotify("Delete error for " + id + "\n" + data.error, "danger", "glyphicon glyphicon-alert");
}
@@ -125,7 +129,7 @@ function prepareTestButtons() {
var id = $btn.data("id");
$btn.click(function () {
doNotify("Test started for " + id, "info", "glyphicon glyphicon-transfer");
var jqxhr = $.post("/admin/test_indexer", JSON.stringify({ indexer: id }), function (data) {
var jqxhr = $.post("test_indexer", JSON.stringify({ indexer: id }), function (data) {
if (data.result == "error") {
doNotify("Test failed for " + id + ": \n" + data.error, "danger", "glyphicon glyphicon-alert");
}
@@ -141,7 +145,7 @@ function prepareTestButtons() {
function displayIndexerSetup(id, link) {
var jqxhr = $.post("/admin/get_config_form", JSON.stringify({ indexer: id }), function (data) {
var jqxhr = $.post("get_config_form", JSON.stringify({ indexer: id }), function (data) {
if (data.result == "error") {
doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert");
return;
@@ -247,7 +251,7 @@ function populateSetupForm(indexerId, name, config, caps, link) {
$goButton.prop('disabled', true);
$goButton.html($('#spinner').html());
var jqxhr = $.post("/admin/configure_indexer", JSON.stringify(data), function (data) {
var jqxhr = $.post("configure_indexer", JSON.stringify(data), function (data) {
if (data.result == "error") {
if (data.config) {
populateConfigItems(configForm, data.config);
@@ -321,7 +325,7 @@ function bindUIButtons() {
});
$("#jackett-show-releases").click(function () {
var jqxhr = $.get("/admin/GetCache", function (data) {
var jqxhr = $.get("GetCache", function (data) {
var releaseTemplate = Handlebars.compile($("#jackett-releases").html());
var item = { releases: data, Title: 'Releases' };
var releaseDialog = $(releaseTemplate(item));
@@ -403,7 +407,7 @@ function bindUIButtons() {
$("#jackett-show-search").click(function () {
$('#select-indexer-modal').remove();
var jqxhr = $.get("/admin/get_indexers", function (data) {
var jqxhr = $.get("get_indexers", function (data) {
var scope = {
items: data.items
};
@@ -459,7 +463,7 @@ function bindUIButtons() {
$('#searchResults').empty();
$('#jackett-search-perform').html($('#spinner').html());
var jqxhr = $.post("/admin/search", queryObj, function (data) {
var jqxhr = $.post("search", queryObj, function (data) {
$('#jackett-search-perform').html('Search trackers');
var resultsTemplate = Handlebars.compile($("#jackett-search-results").html());
var results = $('#searchResults');
@@ -534,7 +538,7 @@ function bindUIButtons() {
});
$("#view-jackett-logs").click(function () {
var jqxhr = $.get("/admin/GetLogs", function (data) {
var jqxhr = $.get("GetLogs", function (data) {
var releaseTemplate = Handlebars.compile($("#jackett-logs").html());
var item = { logs: data };
var releaseDialog = $(releaseTemplate(item));
@@ -548,6 +552,7 @@ function bindUIButtons() {
$("#change-jackett-port").click(function () {
var jackett_port = $("#jackett-port").val();
var jackett_basepathoverride = $("#jackett-basepathoverride").val();
var jackett_external = $("#jackett-allowext").is(':checked');
var jackett_update = $("#jackett-allowupdate").is(':checked');
var jackett_prerelease = $("#jackett-prerelease").is(':checked');
@@ -558,21 +563,17 @@ function bindUIButtons() {
updatedisabled: jackett_update,
prerelease: jackett_prerelease,
blackholedir: $("#jackett-savedir").val(),
logging: jackett_logging
logging: jackett_logging,
basepathoverride: jackett_basepathoverride
};
var jqxhr = $.post("/admin/set_config", JSON.stringify(jsonObject), function (data) {
var jqxhr = $.post("set_config", JSON.stringify(jsonObject), function (data) {
if (data.result == "error") {
doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert");
return;
} else {
doNotify("Redirecting you to complete configuration update..", "success", "glyphicon glyphicon-ok");
window.setTimeout(function () {
url = window.location.href;
if (data.external) {
window.location.href = url.substr(0, url.lastIndexOf(":") + 1) + data.port;
} else {
window.location.href = 'http://127.0.0.1:' + data.port;
}
window.location.reload(true);
}, 3000);
}
@@ -582,7 +583,7 @@ function bindUIButtons() {
});
$("#trigger-updater").click(function () {
var jqxhr = $.get("/admin/trigger_update", function (data) {
var jqxhr = $.get("trigger_update", function (data) {
if (data.result == "error") {
doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert");
return;
@@ -598,7 +599,7 @@ function bindUIButtons() {
var password = $("#jackett-adminpwd").val();
var jsonObject = { password: password };
var jqxhr = $.post("/admin/set_admin_password", JSON.stringify(jsonObject), function (data) {
var jqxhr = $.post("set_admin_password", JSON.stringify(jsonObject), function (data) {
if (data.result == "error") {
doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert");

View File

@@ -4,27 +4,27 @@
<head>
<meta charset="utf-8" />
<link rel='shortcut icon' type='image/x-icon' href='/favicon.ico' />
<script src="../../libs/filesize.min.js"></script>
<script src="../../libs/jquery.min.js"></script>
<script src="../../libs/jquery.dataTables.min.js"></script>
<script src="../../libs/handlebars.min.js"></script>
<script src="../../libs/moment.min.js"></script>
<script src="../../libs/handlebarsmoment.js"></script>
<script src="../../bootstrap/bootstrap.min.js"></script>
<script src="../../libs/bootstrap-notify.js"></script>
<script src="../libs/filesize.min.js"></script>
<script src="../libs/jquery.min.js"></script>
<script src="../libs/jquery.dataTables.min.js"></script>
<script src="../libs/handlebars.min.js"></script>
<script src="../libs/moment.min.js"></script>
<script src="../libs/handlebarsmoment.js"></script>
<script src="../bootstrap/bootstrap.min.js"></script>
<script src="../libs/bootstrap-notify.js"></script>
<script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async defer></script>
<link href="../../bootstrap/bootstrap.min.css" rel="stylesheet">
<link href="../../animate.css" rel="stylesheet">
<link href="../../custom.css" rel="stylesheet">
<link href="../../css/jquery.dataTables.css" rel="stylesheet">
<link rel="stylesheet" href="../../css/font-awesome.min.css">
<link href="../bootstrap/bootstrap.min.css" rel="stylesheet">
<link href="../animate.css" rel="stylesheet">
<link href="../custom.css" rel="stylesheet">
<link href="../css/jquery.dataTables.css" rel="stylesheet">
<link rel="stylesheet" href="../css/font-awesome.min.css">
<title>Jackett</title>
</head>
<body>
<div id="page">
<img id="logo" src="../../jacket_medium.png" alt="Logo" /><span id="header-title">Jackett</span>
<img id="logo" src="../jacket_medium.png" alt="Logo" /><span id="header-title">Jackett</span>
<div class="pull-right jackett-apikey">
<span class="input-header">API Key: </span>
@@ -84,10 +84,13 @@
Logout
</a>
</div>
<div class="input-area">
<span class="input-header">Base Path Override: </span>
<input id="jackett-basepathoverride" class="form-control input-right" type="text" value="" placeholder="jackett/">
</div>
<div class="input-area">
<span class="input-header">Server port: </span>
<input id="jackett-port" class="form-control input-right" type="text" value="" placeholder="9117">
</div>
<div class="input-area">
<span class="input-header">Manual download blackhole directory: </span>
@@ -172,7 +175,7 @@
<div class="indexer-logo">
<!-- Make section browser searchable -->
<span class="hidden-name">{{name}}</span>
<img alt="{{name}}" title="{{name}}" src="../../logos/{{id}}.png" />
<img alt="{{name}}" title="{{name}}" src="../logos/{{id}}.png" />
</div>
<div class="indexer-buttons">
<button class="btn btn-primary btn-sm indexer-setup" data-id="{{id}}" data-link="{{site_link}}">
@@ -206,7 +209,7 @@
<div class="indexer-logo indexer-setup" data-id="{{id}}" data-link="{{site_link}}">
<!-- Make section browser searchable -->
<span class="hidden-name">{{name}}</span>
<img alt="{{name}}" title="{{name}}" src="../../logos/{{id}}.png" />
<img alt="{{name}}" title="{{name}}" src="../logos/{{id}}.png" />
</div>
</div>
@@ -478,6 +481,6 @@
<span class="spinner glyphicon glyphicon-refresh"></span>
</script>
<script src="../../custom.js"></script>
<script src="../custom.js"></script>
</body>
</html>

View File

@@ -6,28 +6,28 @@
<link rel='shortcut icon' type='image/x-icon' href='/favicon.ico' />
<script src="../../libs/jquery.min.js"></script>
<script src="../../libs/jquery.dataTables.min.js"></script>
<script src="../../libs/handlebars.min.js"></script>
<script src="../../libs/moment.min.js"></script>
<script src="../../libs/handlebarsmoment.js"></script>
<script src="../../bootstrap/bootstrap.min.js"></script>
<script src="../../libs/bootstrap-notify.js"></script>
<script src="../libs/jquery.min.js"></script>
<script src="../libs/jquery.dataTables.min.js"></script>
<script src="../libs/handlebars.min.js"></script>
<script src="../libs/moment.min.js"></script>
<script src="../libs/handlebarsmoment.js"></script>
<script src="../bootstrap/bootstrap.min.js"></script>
<script src="../libs/bootstrap-notify.js"></script>
<link href="../../bootstrap/bootstrap.min.css" rel="stylesheet">
<link href="../../animate.css" rel="stylesheet">
<link href="../../custom.css" rel="stylesheet">
<link href="../bootstrap/bootstrap.min.css" rel="stylesheet">
<link href="../animate.css" rel="stylesheet">
<link href="../custom.css" rel="stylesheet">
<title>Jackett</title>
</head>
<body>
<div id="page">
<img id="logo" src="../../jacket_medium.png" /><span id="header-title">Jackett</span>
<img id="logo" src="../jacket_medium.png" /><span id="header-title">Jackett</span>
<hr />
<h1>Login</h1>
<form action="../Admin/Dashboard" method="post">
<form action="Dashboard" method="post">
<div class="input-area">
<span class="input-header">Admin password</span>
<input id="password" name="password" class="form-control input-right" type="password">

View File

@@ -80,7 +80,7 @@ namespace Jackett.Controllers
var ctx = Request.GetOwinContext();
var authManager = ctx.Authentication;
authManager.SignOut("ApplicationCookie");
return Redirect("/Admin/Dashboard");
return Redirect("Admin/Dashboard");
}
[HttpGet]
@@ -318,6 +318,7 @@ namespace Jackett.Controllers
cfg["prerelease"] = serverService.Config.UpdatePrerelease;
cfg["password"] = string.IsNullOrEmpty(serverService.Config.AdminPassword) ? string.Empty : serverService.Config.AdminPassword.Substring(0, 10);
cfg["logging"] = Startup.TracingEnabled;
cfg["basepathoverride"] = serverService.Config.BasePathOverride;
jsonReply["config"] = cfg;
@@ -349,9 +350,12 @@ namespace Jackett.Controllers
bool updateDisabled = (bool)postData["updatedisabled"];
bool preRelease = (bool)postData["prerelease"];
bool logging = (bool)postData["logging"];
string basePathOverride = (string)postData["basepathoverride"];
Engine.Server.Config.UpdateDisabled = updateDisabled;
Engine.Server.Config.UpdatePrerelease = preRelease;
Engine.Server.Config.BasePathOverride = basePathOverride;
Startup.BasePath = Engine.Server.BasePath();
Engine.Server.SaveConfig();
Engine.SetLogLevel(logging ? LogLevel.Debug : LogLevel.Info);
@@ -446,7 +450,7 @@ namespace Jackett.Controllers
private void ConfigureCacheResults(List<TrackerCacheResult> results)
{
var serverUrl = string.Format("{0}://{1}:{2}/", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port);
var serverUrl = string.Format("{0}://{1}:{2}{3}", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port, serverService.BasePath());
foreach (var result in results)
{
var link = result.Link;

View File

@@ -117,7 +117,7 @@ namespace Jackett.Controllers
}
releases = indexer.FilterResults(torznabQuery, releases);
var serverUrl = string.Format("{0}://{1}:{2}/", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port);
var serverUrl = string.Format("{0}://{1}:{2}{3}", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port, serverService.BasePath());
var potatoResponse = new TorrentPotatoResponse();
releases = TorznabUtil.FilterResultsToTitle(releases, torznabQuery.SanitizedSearchTerm, year);

View File

@@ -100,7 +100,7 @@ namespace Jackett.Controllers
logger.Info(logBuilder.ToString());
var serverUrl = string.Format("{0}://{1}:{2}/", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port);
var serverUrl = string.Format("{0}://{1}:{2}{3}", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port, serverService.BasePath());
var resultPage = new ResultPage(new ChannelInfo
{
Title = indexer.DisplayName,

View File

@@ -23,6 +23,7 @@ namespace Jackett.Models.Config
public string BlackholeDir { get; set; }
public bool UpdateDisabled { get; set; }
public bool UpdatePrerelease { get; set; }
public string BasePathOverride { get; set; }
public string[] GetListenAddresses(bool? external = null)
{

View File

@@ -19,6 +19,7 @@ using System.Net;
using System.Net.NetworkInformation;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
@@ -32,7 +33,8 @@ namespace Jackett.Services
void ReserveUrls(bool doInstall = true);
ServerConfig Config { get; }
void SaveConfig();
Uri ConvertToProxyLink(Uri link, string serverUrl, string indexerId, string action = "dl", string file = "t.torrent");
Uri ConvertToProxyLink(Uri link, string serverUrl, string indexerId, string action = "dl", string file = "t.torrent");
string BasePath();
}
public class ServerService : IServerService
@@ -77,6 +79,23 @@ namespace Jackett.Services
return new Uri(proxyLink);
}
public string BasePath()
{
if (config.BasePathOverride == null || config.BasePathOverride == "") {
return "/";
}
var path = config.BasePathOverride;
if (!path.EndsWith("/"))
{
path = path + "/";
}
if (!path.StartsWith("/"))
{
path = "/" + path;
}
return path;
}
private void LoadConfig()
{
// Load config
@@ -139,6 +158,7 @@ namespace Jackett.Services
logger.Info("Starting web server at " + config.GetListenAddresses()[0]);
var startOptions = new StartOptions();
config.GetListenAddresses().ToList().ForEach(u => startOptions.Urls.Add(u));
Startup.BasePath = BasePath();
_server = WebApp.Start<Startup>(startOptions);
logger.Debug("Web server started");
updater.StartUpdateChecker();

View File

@@ -58,6 +58,12 @@ namespace Jackett
set;
}
public static string BasePath
{
get;
set;
}
public void Configuration(IAppBuilder appBuilder)
{
// Configure Web API for self-host.

View File

@@ -1,4 +1,5 @@
using Microsoft.Owin;
using Jackett.Services;
using Microsoft.Owin;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -11,7 +12,7 @@ namespace Jackett.Utils
{
public WebApiRootRedirectMiddleware(OwinMiddleware next)
: base(next)
{
{
}
public async override Task Invoke(IOwinContext context)
@@ -21,7 +22,9 @@ namespace Jackett.Utils
{
// 301 is the status code of permanent redirect
context.Response.StatusCode = 301;
context.Response.Headers.Set("Location", "/Admin/Dashboard");
var redir = Startup.BasePath + "Admin/Dashboard";
Engine.Logger.Info("redirecting to " + redir);
context.Response.Headers.Set("Location", redir);
}
else
{