New: Support for running from a sub folder (reverse proxy)

This commit is contained in:
Mark McDowall 2014-01-01 22:56:19 -08:00
parent cec479923f
commit b5c9a811dd
22 changed files with 161 additions and 89 deletions

View File

@ -1,4 +1,5 @@
using System.IO; using System.IO;
using System.Text.RegularExpressions;
using Nancy; using Nancy;
using NLog; using NLog;
using NzbDrone.Common; using NzbDrone.Common;
@ -12,6 +13,7 @@ namespace NzbDrone.Api.Frontend.Mappers
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly IConfigFileProvider _configFileProvider; private readonly IConfigFileProvider _configFileProvider;
private readonly string _indexPath; private readonly string _indexPath;
private static readonly Regex ReplaceRegex = new Regex("(?<=(?:href|src|data-main)=\").*?(?=\")", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public IndexHtmlMapper(IAppFolderInfo appFolderInfo, public IndexHtmlMapper(IAppFolderInfo appFolderInfo,
IDiskProvider diskProvider, IDiskProvider diskProvider,
@ -47,13 +49,15 @@ namespace NzbDrone.Api.Frontend.Mappers
return StringToStream(GetIndexText()); return StringToStream(GetIndexText());
} }
private string GetIndexText() private string GetIndexText()
{ {
var text = _diskProvider.ReadAllText(_indexPath); var text = _diskProvider.ReadAllText(_indexPath);
text = ReplaceRegex.Replace(text, match => _configFileProvider.UrlBase + match.Value);
text = text.Replace(".css", ".css?v=" + BuildInfo.Version); text = text.Replace(".css", ".css?v=" + BuildInfo.Version);
text = text.Replace(".js", ".js?v=" + BuildInfo.Version); text = text.Replace(".js", ".js?v=" + BuildInfo.Version);
text = text.Replace("API_ROOT", _configFileProvider.UrlBase + "/api");
text = text.Replace("API_KEY", _configFileProvider.ApiKey); text = text.Replace("API_KEY", _configFileProvider.ApiKey);
text = text.Replace("APP_VERSION", BuildInfo.Version.ToString()); text = text.Replace("APP_VERSION", BuildInfo.Version.ToString());

View File

@ -1,21 +1,25 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Nancy.Responses;
using NLog; using NLog;
using Nancy; using Nancy;
using NzbDrone.Api.Frontend.Mappers; using NzbDrone.Api.Frontend.Mappers;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Api.Frontend namespace NzbDrone.Api.Frontend
{ {
public class StaticResourceModule : NancyModule public class StaticResourceModule : NancyModule
{ {
private readonly IEnumerable<IMapHttpRequestsToDisk> _requestMappers; private readonly IEnumerable<IMapHttpRequestsToDisk> _requestMappers;
private readonly IConfigFileProvider _configFileProvider;
private readonly Logger _logger; private readonly Logger _logger;
public StaticResourceModule(IEnumerable<IMapHttpRequestsToDisk> requestMappers, Logger logger) public StaticResourceModule(IEnumerable<IMapHttpRequestsToDisk> requestMappers, IConfigFileProvider configFileProvider, Logger logger)
{ {
_requestMappers = requestMappers; _requestMappers = requestMappers;
_configFileProvider = configFileProvider;
_logger = logger; _logger = logger;
Get["/{resource*}"] = x => Index(); Get["/{resource*}"] = x => Index();
@ -34,8 +38,21 @@ namespace NzbDrone.Api.Frontend
return new NotFoundResponse(); return new NotFoundResponse();
} }
var mapper = _requestMappers.SingleOrDefault(m => m.CanHandle(path)); //Redirect to the subfolder if the request went to the base URL
if (path.Equals("/"))
{
var urlBase = _configFileProvider.UrlBase;
if (!String.IsNullOrEmpty(urlBase))
{
if (Request.Url.BasePath != urlBase)
{
return new RedirectResponse(urlBase + "/");
}
}
}
var mapper = _requestMappers.SingleOrDefault(m => m.CanHandle(path));
if (mapper != null) if (mapper != null)
{ {

View File

@ -43,7 +43,8 @@ namespace NzbDrone.Api.System
IsWindows = OsInfo.IsWindows, IsWindows = OsInfo.IsWindows,
Branch = _configFileProvider.Branch, Branch = _configFileProvider.Branch,
Authentication = _configFileProvider.AuthenticationEnabled, Authentication = _configFileProvider.AuthenticationEnabled,
StartOfWeek = (int)OsInfo.FirstDayOfWeek StartOfWeek = (int)OsInfo.FirstDayOfWeek,
UrlBase = _configFileProvider.UrlBase
}.AsResponse(); }.AsResponse();
} }

View File

@ -31,6 +31,7 @@ namespace NzbDrone.Core.Configuration
string ApiKey { get; } string ApiKey { get; }
bool Torrent { get; } bool Torrent { get; }
string SslCertHash { get; } string SslCertHash { get; }
string UrlBase { get; }
} }
public class ConfigFileProvider : IConfigFileProvider public class ConfigFileProvider : IConfigFileProvider
@ -152,6 +153,21 @@ namespace NzbDrone.Core.Configuration
get { return GetValue("SslCertHash", ""); } get { return GetValue("SslCertHash", ""); }
} }
public string UrlBase
{
get
{
var urlBase = GetValue("UrlBase", "");
if (String.IsNullOrEmpty(urlBase))
{
return urlBase;
}
return "/" + urlBase.Trim('/').ToLower();
}
}
public int GetValueInt(string key, int defaultValue) public int GetValueInt(string key, int defaultValue)
{ {
return Convert.ToInt32(GetValue(key, defaultValue)); return Convert.ToInt32(GetValue(key, defaultValue));

View File

@ -5,6 +5,7 @@ using System.Net;
using NLog; using NLog;
using NzbDrone.Common; using NzbDrone.Common;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Core.Tv.Events; using NzbDrone.Core.Tv.Events;
@ -19,16 +20,18 @@ namespace NzbDrone.Core.MediaCover
private readonly IHttpProvider _httpProvider; private readonly IHttpProvider _httpProvider;
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly ICoverExistsSpecification _coverExistsSpecification; private readonly ICoverExistsSpecification _coverExistsSpecification;
private readonly IConfigFileProvider _configFileProvider;
private readonly Logger _logger; private readonly Logger _logger;
private readonly string _coverRootFolder; private readonly string _coverRootFolder;
public MediaCoverService(IHttpProvider httpProvider, IDiskProvider diskProvider, IAppFolderInfo appFolderInfo, public MediaCoverService(IHttpProvider httpProvider, IDiskProvider diskProvider, IAppFolderInfo appFolderInfo,
ICoverExistsSpecification coverExistsSpecification, Logger logger) ICoverExistsSpecification coverExistsSpecification, IConfigFileProvider configFileProvider, Logger logger)
{ {
_httpProvider = httpProvider; _httpProvider = httpProvider;
_diskProvider = diskProvider; _diskProvider = diskProvider;
_coverExistsSpecification = coverExistsSpecification; _coverExistsSpecification = coverExistsSpecification;
_configFileProvider = configFileProvider;
_logger = logger; _logger = logger;
_coverRootFolder = appFolderInfo.GetMediaCoverPath(); _coverRootFolder = appFolderInfo.GetMediaCoverPath();
@ -96,7 +99,7 @@ namespace NzbDrone.Core.MediaCover
{ {
var filePath = GetCoverPath(seriesId, mediaCover.CoverType); var filePath = GetCoverPath(seriesId, mediaCover.CoverType);
mediaCover.Url = @"/MediaCover/" + seriesId + "/" + mediaCover.CoverType.ToString().ToLower() + ".jpg"; mediaCover.Url = _configFileProvider.UrlBase + @"/MediaCover/" + seriesId + "/" + mediaCover.CoverType.ToString().ToLower() + ".jpg";
if (_diskProvider.FileExists(filePath)) if (_diskProvider.FileExists(filePath))
{ {

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using NLog; using NLog;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
@ -9,8 +10,7 @@ namespace NzbDrone.Host.AccessControl
public interface IUrlAclAdapter public interface IUrlAclAdapter
{ {
void ConfigureUrl(); void ConfigureUrl();
string Url { get; } List<String> Urls { get; }
string HttpsUrl { get; }
} }
public class UrlAclAdapter : IUrlAclAdapter public class UrlAclAdapter : IUrlAclAdapter
@ -20,13 +20,7 @@ namespace NzbDrone.Host.AccessControl
private readonly IRuntimeInfo _runtimeInfo; private readonly IRuntimeInfo _runtimeInfo;
private readonly Logger _logger; private readonly Logger _logger;
public string Url { get; private set; } public List<String> Urls { get; private set; }
public string HttpsUrl { get; private set; }
private string _localUrl;
private string _wildcardUrl;
private string _localHttpsUrl;
private string _wildcardHttpsUrl;
public UrlAclAdapter(INetshProvider netshProvider, public UrlAclAdapter(INetshProvider netshProvider,
IConfigFileProvider configFileProvider, IConfigFileProvider configFileProvider,
@ -38,25 +32,31 @@ namespace NzbDrone.Host.AccessControl
_runtimeInfo = runtimeInfo; _runtimeInfo = runtimeInfo;
_logger = logger; _logger = logger;
_localUrl = String.Format("http://localhost:{0}/", _configFileProvider.Port); Urls = new List<String>();
_wildcardUrl = String.Format("http://*:{0}/", _configFileProvider.Port);
_localHttpsUrl = String.Format("https://localhost:{0}/", _configFileProvider.SslPort);
_wildcardHttpsUrl = String.Format("https://*:{0}/", _configFileProvider.SslPort);
Url = _wildcardUrl;
HttpsUrl = _wildcardHttpsUrl;
} }
public void ConfigureUrl() public void ConfigureUrl()
{ {
var localHttpUrls = BuildUrls("http", "localhost", _configFileProvider.Port);
var wildcardHttpUrls = BuildUrls("http", "*", _configFileProvider.Port);
var localHttpsUrls = BuildUrls("https", "localhost", _configFileProvider.SslPort);
var wildcardHttpsUrls = BuildUrls("https", "*", _configFileProvider.SslPort);
if (!_runtimeInfo.IsAdmin) if (!_runtimeInfo.IsAdmin)
{ {
if (!IsRegistered(_wildcardUrl)) Url = _localUrl; var httpUrls = wildcardHttpUrls.All(IsRegistered) ? wildcardHttpUrls : localHttpUrls;
if (!IsRegistered(_wildcardHttpsUrl)) HttpsUrl = _localHttpsUrl; var httpsUrls = wildcardHttpsUrls.All(IsRegistered) ? wildcardHttpsUrls : localHttpsUrls;
Urls.AddRange(httpUrls);
Urls.AddRange(httpsUrls);
} }
if (_runtimeInfo.IsAdmin) else
{ {
Urls.AddRange(wildcardHttpUrls);
Urls.AddRange(wildcardHttpsUrls);
RefreshRegistration(); RefreshRegistration();
} }
} }
@ -66,8 +66,7 @@ namespace NzbDrone.Host.AccessControl
if (OsInfo.Version.Major < 6) if (OsInfo.Version.Major < 6)
return; return;
RegisterUrl(Url); Urls.ForEach(RegisterUrl);
RegisterUrl(HttpsUrl);
} }
private bool IsRegistered(string urlAcl) private bool IsRegistered(string urlAcl)
@ -85,5 +84,28 @@ namespace NzbDrone.Host.AccessControl
var arguments = String.Format("http add urlacl {0} sddl=D:(A;;GX;;;S-1-1-0)", urlAcl); var arguments = String.Format("http add urlacl {0} sddl=D:(A;;GX;;;S-1-1-0)", urlAcl);
_netshProvider.Run(arguments); _netshProvider.Run(arguments);
} }
private string BuildUrl(string protocol, string url, int port, string urlBase)
{
var result = protocol + "://" + url + ":" + port;
result += String.IsNullOrEmpty(urlBase) ? "/" : urlBase + "/";
return result;
}
private List<String> BuildUrls(string protocol, string url, int port)
{
var urls = new List<String>();
var urlBase = _configFileProvider.UrlBase;
if (!String.IsNullOrEmpty(urlBase))
{
urls.Add(BuildUrl(protocol, url, port, urlBase));
}
urls.Add(BuildUrl(protocol, url, port, ""));
return urls;
}
} }
} }

View File

@ -57,25 +57,21 @@ namespace NzbDrone.Host.Owin
_urlAclAdapter.ConfigureUrl(); _urlAclAdapter.ConfigureUrl();
} }
var options = new StartOptions(_urlAclAdapter.Url) var options = new StartOptions()
{ {
ServerFactory = "Microsoft.Owin.Host.HttpListener" ServerFactory = "Microsoft.Owin.Host.HttpListener"
}; };
if (_configFileProvider.EnableSsl) _urlAclAdapter.Urls.ForEach(options.Urls.Add);
_logger.Info("Listening on the following URLs:");
foreach (var url in options.Urls)
{ {
_logger.Trace("SSL enabled, listening on: {0}", _urlAclAdapter.HttpsUrl); _logger.Info(" {0}", url);
options.Urls.Add(_urlAclAdapter.HttpsUrl);
} }
_logger.Info("starting server on {0}", _urlAclAdapter.Url);
try try
{ {
// options.ServerFactory = new
//_host = WebApp.Start(OwinServiceProviderFactory.Create(), options, BuildApp);
//_host = WebApp.Start(options, BuildApp);
_host = WebApp.Start(OwinServiceProviderFactory.Create(), options, BuildApp); _host = WebApp.Start(OwinServiceProviderFactory.Create(), options, BuildApp);
} }
catch (TargetInvocationException ex) catch (TargetInvocationException ex)

View File

@ -2,46 +2,46 @@
font-family: 'Open Sans'; font-family: 'Open Sans';
font-style: normal; font-style: normal;
font-weight: 300; font-weight: 300;
src: url('/Content/fonts/opensans-light.eot'); src: url('./fonts/opensans-light.eot');
src: local('Open Sans Light'), src: local('Open Sans Light'),
local('OpenSans-Light'), local('OpenSans-Light'),
url('/Content/fonts/opensans-light.eot?#iefix') format('embedded-opentype'), url('./fonts/opensans-light.eot?#iefix') format('embedded-opentype'),
url('/Content/fonts/opensans-light.woff') format('woff'), url('./fonts/opensans-light.woff') format('woff'),
url('/Content/fonts/opensans-light.ttf') format('truetype'); url('./fonts/opensans-light.ttf') format('truetype');
} }
@font-face { @font-face {
font-family: 'Open Sans'; font-family: 'Open Sans';
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
src: url('/Content/fonts/opensans-regular.eot'); src: url('./fonts/opensans-regular.eot');
src: local('Open Sans'), src: local('Open Sans'),
local('OpenSans'), local('OpenSans'),
url('/Content/fonts/opensans-regular.eot?#iefix') format('embedded-opentype'), url('./fonts/opensans-regular.eot?#iefix') format('embedded-opentype'),
url('/Content/fonts/opensans-regular.woff') format('woff'), url('./fonts/opensans-regular.woff') format('woff'),
url('/Content/fonts/opensans-regular.ttf') format('truetype') url('./fonts/opensans-regular.ttf') format('truetype')
} }
@font-face { @font-face {
font-family: 'Open Sans'; font-family: 'Open Sans';
font-style: normal; font-style: normal;
font-weight: 600; font-weight: 600;
src: url('/Content/fonts/opensans-semibold.eot'); src: url('./fonts/opensans-semibold.eot');
src: local('Open Sans SemiBold'), src: local('Open Sans SemiBold'),
local('OpenSans-SemiBold'), local('OpenSans-SemiBold'),
url('/Content/fonts/opensans-semibold.eot?#iefix') format('embedded-opentype'), url('./fonts/opensans-semibold.eot?#iefix') format('embedded-opentype'),
url('/Content/fonts/opensans-semibold.woff') format('woff'), url('./fonts/opensans-semibold.woff') format('woff'),
url('/Content/fonts/opensans-semibold.ttf') format('truetype') url('./fonts/opensans-semibold.ttf') format('truetype')
} }
@font-face { @font-face {
font-family: 'Ubuntu Mono'; font-family: 'Ubuntu Mono';
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
src: url('/Content/fonts/ubuntumono-regular.eot'); src: url('./fonts/ubuntumono-regular.eot');
src: local('Open Sans'), src: local('Open Sans'),
local('OpenSans'), local('OpenSans'),
url('/Content/fonts/ubuntumono-regular.eot?#iefix') format('embedded-opentype'), url('./fonts/ubuntumono-regular.eot?#iefix') format('embedded-opentype'),
url('/Content/fonts/ubuntumono-regular.woff') format('woff'), url('./fonts/ubuntumono-regular.woff') format('woff'),
url('/Content/fonts/ubuntumono-regular.ttf') format('truetype') url('./fonts/ubuntumono-regular.ttf') format('truetype')
} }

View File

@ -2,10 +2,11 @@
define( define(
[ [
'handlebars' 'handlebars',
], function (Handlebars) { 'System/StatusModel'
], function (Handlebars, StatusModel) {
var placeHolder = '/Content/Images/poster-dark.jpg'; var placeHolder = StatusModel.get('urlBase') + '/Content/Images/poster-dark.jpg';
window.NzbDrone.imageError = function (img) { window.NzbDrone.imageError = function (img) {
if (!img.src.contains(placeHolder)) { if (!img.src.contains(placeHolder)) {
@ -17,4 +18,8 @@ define(
Handlebars.registerHelper('defaultImg', function () { Handlebars.registerHelper('defaultImg', function () {
return new Handlebars.SafeString('onerror=window.NzbDrone.imageError(this)'); return new Handlebars.SafeString('onerror=window.NzbDrone.imageError(this)');
}); });
Handlebars.registerHelper('UrlBase', function () {
return new Handlebars.SafeString(StatusModel.get('urlBase'));
});
}); });

View File

@ -2,8 +2,9 @@
define( define(
[ [
'handlebars', 'handlebars',
'System/StatusModel',
'underscore' 'underscore'
], function (Handlebars, _) { ], function (Handlebars, StatusModel, _) {
Handlebars.registerHelper('poster', function () { Handlebars.registerHelper('poster', function () {
var poster = _.where(this.images, {coverType: 'poster'}); var poster = _.where(this.images, {coverType: 'poster'});
@ -32,7 +33,7 @@ define(
}); });
Handlebars.registerHelper('route', function () { Handlebars.registerHelper('route', function () {
return '/series/' + this.titleSlug; return StatusModel.get('urlBase') + '/series/' + this.titleSlug;
}); });
Handlebars.registerHelper('percentOfEpisodes', function () { Handlebars.registerHelper('percentOfEpisodes', function () {

View File

@ -3,47 +3,47 @@
<div class="span12"> <div class="span12">
<ul id="main-menu-region"> <ul id="main-menu-region">
<div class="pull-left logo"> <div class="pull-left logo">
<a href="/"> <a href="{{UrlBase}}/">
<img src="/Content/Images/logo.png" alt="NzbDrone"> <img src="{{UrlBase}}/Content/Images/logo.png" alt="NzbDrone">
</a> </a>
</div> </div>
<li> <li>
<a href="/"> <a href="{{UrlBase}}/">
<i class="icon-play"></i> <i class="icon-play"></i>
<br> <br>
Series Series
</a> </a>
</li> </li>
<li> <li>
<a href="/calendar"> <a href="{{UrlBase}}/calendar">
<i class="icon-calendar"></i> <i class="icon-calendar"></i>
<br> <br>
Calendar Calendar
</a> </a>
</li> </li>
<li> <li>
<a href="/history"> <a href="{{UrlBase}}/history">
<i class="icon-time"></i> <i class="icon-time"></i>
<br> <br>
History History
</a> </a>
</li> </li>
<li> <li>
<a href="/missing"> <a href="{{UrlBase}}/missing">
<i class="icon-warning-sign"></i> <i class="icon-warning-sign"></i>
<br> <br>
Missing Missing
</a> </a>
</li> </li>
<li> <li>
<a href="/settings"> <a href="{{UrlBase}}/settings">
<i class="icon-cogs"></i> <i class="icon-cogs"></i>
<br> <br>
Settings Settings
</a> </a>
</li> </li>
<li> <li>
<a href="/system"> <a href="{{UrlBase}}/system">
<i class="icon-laptop"></i> <i class="icon-laptop"></i>
<br> <br>
System System

View File

@ -29,7 +29,7 @@ define(
//look down for <a/> //look down for <a/>
var href = event.target.getAttribute('href'); var href = event.target.getAttribute('href');
//if couldn't find it look up //if couldn't find it look up'
if (!href && target.parent('a') && target.parent('a')[0]) { if (!href && target.parent('a') && target.parent('a')[0]) {
var linkElement = target.parent('a')[0]; var linkElement = target.parent('a')[0];

View File

@ -17,7 +17,6 @@ define(
this.route('series/:query', this.seriesDetails); this.route('series/:query', this.seriesDetails);
}, },
series: function () { series: function () {
this.setTitle('NzbDrone'); this.setTitle('NzbDrone');
AppLayout.mainRegion.show(new SeriesIndexLayout()); AppLayout.mainRegion.show(new SeriesIndexLayout());

View File

@ -6,8 +6,9 @@ define(
'Settings/Indexers/ItemView', 'Settings/Indexers/ItemView',
'Settings/Indexers/EditView', 'Settings/Indexers/EditView',
'Settings/Indexers/Collection', 'Settings/Indexers/Collection',
'System/StatusModel',
'underscore' 'underscore'
], function (AppLayout, Marionette, IndexerItemView, IndexerEditView, IndexerCollection, _) { ], function (AppLayout, Marionette, IndexerItemView, IndexerEditView, IndexerCollection, StatusModel, _) {
return Marionette.CompositeView.extend({ return Marionette.CompositeView.extend({
itemView : IndexerItemView, itemView : IndexerItemView,
itemViewContainer: '#x-indexers', itemViewContainer: '#x-indexers',
@ -29,10 +30,10 @@ define(
var self = this; var self = this;
//TODO: Is there a better way to deal with changing URLs? //TODO: Is there a better way to deal with changing URLs?
var schemaCollection = new IndexerCollection(); var schemaCollection = new IndexerCollection();
schemaCollection.url = '/api/indexer/schema'; schemaCollection.url = StatusModel.get('urlBase') + '/api/indexer/schema';
schemaCollection.fetch({ schemaCollection.fetch({
success: function (collection) { success: function (collection) {
collection.url = '/api/indexer'; collection.url = './api/indexer';
var model = _.first(collection.models); var model = _.first(collection.models);
model.set({ model.set({

View File

@ -2,15 +2,16 @@
define([ define([
'AppLayout', 'AppLayout',
'Settings/Notifications/Collection', 'Settings/Notifications/Collection',
'Settings/Notifications/AddView' 'Settings/Notifications/AddView',
], function (AppLayout, NotificationCollection, AddSelectionNotificationView) { 'System/StatusModel'
], function (AppLayout, NotificationCollection, AddSelectionNotificationView, StatusModel) {
return ({ return ({
open: function (collection) { open: function (collection) {
var schemaCollection = new NotificationCollection(); var schemaCollection = new NotificationCollection();
schemaCollection.url = '/api/notification/schema'; schemaCollection.url = StatusModel.get('urlBase') + '/api/notification/schema';
schemaCollection.fetch(); schemaCollection.fetch();
schemaCollection.url = '/api/notification'; schemaCollection.url = StatusModel.get('urlBase') + '/api/notification';
var view = new AddSelectionNotificationView({ collection: schemaCollection, notificationCollection: collection}); var view = new AddSelectionNotificationView({ collection: schemaCollection, notificationCollection: collection});
AppLayout.modalRegion.show(view); AppLayout.modalRegion.show(view);

View File

@ -1,3 +1,4 @@
<div> <div>
<img src="/Content/Images/404.png" style="height:400px; margin-top: 50px"/> <img src="{{UrlBase}}/Content/Images/404.png" style="height:400px; margin-top: 50px"/>
</div> </div>

View File

@ -31,7 +31,7 @@ define(
var messengerId = 'signalR'; var messengerId = 'signalR';
var reconnectTimeout; var reconnectTimeout;
this.signalRconnection = $.connection('/signalr'); this.signalRconnection = $.connection(StatusModel.get('urlBase') + '/signalr');
this.signalRconnection.stateChanged(function (change) { this.signalRconnection.stateChanged(function (change) {
console.debug('SignalR: [{0}]'.format(getStatus(change.newState))); console.debug('SignalR: [{0}]'.format(getStatus(change.newState)));

View File

@ -1,11 +1,12 @@
'use strict'; 'use strict';
define( define(
[ [
'backbone' 'backbone',
], function (Backbone) { 'System/StatusModel'
], function (Backbone, StatusModel) {
return Backbone.Model.extend({ return Backbone.Model.extend({
url: function () { url: function () {
return '/log/' + this.get('filename'); return StatusModel.get('urlBase') + '/log/' + this.get('filename');
}, },
parse: function (contents) { parse: function (contents) {

View File

@ -1,15 +1,16 @@
'use strict'; 'use strict';
define( define(
[ [
'Cells/NzbDroneCell' 'Cells/NzbDroneCell',
], function (NzbDroneCell) { 'System/StatusModel'
], function (NzbDroneCell, StatusModel) {
return NzbDroneCell.extend({ return NzbDroneCell.extend({
className: 'download-log-cell', className: 'download-log-cell',
render: function () { render: function () {
this.$el.empty(); this.$el.empty();
this.$el.html('<a href="/log/{0}" class="no-router" target="_blank">Download</a>'.format(this.cellValue)); this.$el.html('<a href="{0}/log/{1}" class="no-router" target="_blank">Download</a>'.format(StatusModel.get('urlBase'), this.cellValue));
return this; return this;
} }

View File

@ -231,7 +231,7 @@ define(
}); });
app.addInitializer(function () { app.addInitializer(function () {
Backbone.history.start({ pushState: true }); Backbone.history.start({ pushState: true, root: serverStatusModel.get('urlBase') });
RouteBinder.bind(); RouteBinder.bind();
AppLayout.navbarRegion.show(new NavbarView()); AppLayout.navbarRegion.show(new NavbarView());
$('body').addClass('started'); $('body').addClass('started');

View File

@ -65,7 +65,7 @@
<div id="errors"></div> <div id="errors"></div>
<script type="text/javascript"> <script type="text/javascript">
window.NzbDrone = { window.NzbDrone = {
ApiRoot: '/api', ApiRoot: 'API_ROOT',
ApiKey : 'API_KEY', ApiKey : 'API_KEY',
Version: 'APP_VERSION' Version: 'APP_VERSION'
}; };

View File

@ -2,8 +2,9 @@
define( define(
[ [
'backbone', 'backbone',
'jquery' 'jquery',
], function (Backbone,$) { 'System/StatusModel'
], function (Backbone, $, StatusModel) {
//This module will automatically route all relative links through backbone router rather than //This module will automatically route all relative links through backbone router rather than
//causing links to reload pages. //causing links to reload pages.
@ -45,7 +46,9 @@ define(
if (!href.startsWith('http')) { if (!href.startsWith('http')) {
Backbone.history.navigate(href, { trigger: true }); var relativeHref = href.replace(StatusModel.get('urlBase'), '');
Backbone.history.navigate(relativeHref, { trigger: true });
} }
else { else {