Finally fixed #72

pull/158/head
tidusjar 9 years ago
parent 5352422688
commit 6190eceb60

@ -33,7 +33,7 @@ namespace PlexRequests.Core.SettingModels
public class PlexRequestSettings : Settings
{
public int Port { get; set; }
public string AssetLocation { get; set; }
public string BaseUrl { get; set; }
public bool SearchForMovies { get; set; }
public bool SearchForTvShows { get; set; }
public bool SearchForMusic { get; set; }

@ -44,7 +44,7 @@ namespace PlexRequests.Core
{
private static Logger Log = LogManager.GetCurrentClassLogger();
private static DbConfiguration Db { get; set; }
public string SetupDb(string assetLocation)
public string SetupDb(string urlBase)
{
Db = new DbConfiguration(new SqliteFactory());
var created = Db.CheckDb();
@ -52,7 +52,7 @@ namespace PlexRequests.Core
if (created)
{
CreateDefaultSettingsPage(assetLocation);
CreateDefaultSettingsPage(urlBase);
}
var version = CheckSchema();
@ -89,7 +89,7 @@ namespace PlexRequests.Core
return version;
}
private void CreateDefaultSettingsPage(string assetLocation)
private void CreateDefaultSettingsPage(string baseUrl)
{
var defaultSettings = new PlexRequestSettings
{
@ -98,7 +98,7 @@ namespace PlexRequests.Core
SearchForMovies = true,
SearchForTvShows = true,
WeeklyRequestLimit = 0,
AssetLocation = assetLocation ?? "assets"
BaseUrl = baseUrl ?? string.Empty
};
var s = new SettingsServiceV2<PlexRequestSettings>(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider()));
s.SaveSettings(defaultSettings);

@ -50,6 +50,7 @@ using PlexRequests.Services.Notification;
using PlexRequests.Store;
using PlexRequests.Store.Models;
using PlexRequests.Store.Repository;
using PlexRequests.UI.Helpers;
using PlexRequests.UI.Jobs;
using TaskFactory = FluentScheduler.TaskFactory;
@ -61,7 +62,6 @@ namespace PlexRequests.UI
// by overriding the various methods and properties.
// For more information https://github.com/NancyFx/Nancy/wiki/Bootstrapper
private TinyIoCContainer _container;
protected override void ConfigureRequestContainer(TinyIoCContainer container, NancyContext context)
{
@ -109,8 +109,8 @@ namespace PlexRequests.UI
SubscribeAllObservers(container);
base.ConfigureRequestContainer(container, context);
var loc = ServiceLocator.Instance;
loc.SetContainer(container);
}
protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
@ -121,10 +121,14 @@ namespace PlexRequests.UI
base.ApplicationStartup(container, pipelines);
var settings = new SettingsServiceV2<PlexRequestSettings>(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider()));
var baseUrl = settings.GetSettings().BaseUrl;
var redirect = string.IsNullOrEmpty(baseUrl) ? "~/login" : $"~/{baseUrl}/login";
// Enable forms auth
var formsAuthConfiguration = new FormsAuthenticationConfiguration
{
RedirectUrl = "~/login",
RedirectUrl = redirect,
UserMapper = container.Resolve<IUserMapper>()
};
@ -141,9 +145,9 @@ namespace PlexRequests.UI
base.ConfigureConventions(nancyConventions);
var settings = new SettingsServiceV2<PlexRequestSettings>(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()),new MemoryCacheProvider()));
var assetLocation = settings.GetSettings().AssetLocation;
var assetLocation = settings.GetSettings().BaseUrl;
nancyConventions.StaticContentsConventions.Add(
StaticContentConventionBuilder.AddDirectory("assets", $"{assetLocation}/Content")
StaticContentConventionBuilder.AddDirectory($"{assetLocation}/Content", "Content")
);
}

@ -269,8 +269,8 @@ th {
}
@font-face {
font-family: 'Glyphicons Halflings';
src: url('../assets/fonts/glyphicons-halflings-regular.eot');
src: url('../assets/fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../assets/fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../assets/fonts/glyphicons-halflings-regular.woff') format('woff'), url('../assets/fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../assets/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
src: url('../Content/fonts/glyphicons-halflings-regular.eot');
src: url('../Content/fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../Content/fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../Content/fonts/glyphicons-halflings-regular.woff') format('woff'), url('../Content/fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../Content/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
}
.glyphicon {
position: relative;

@ -6,8 +6,8 @@
* -------------------------- */
@font-face {
font-family: 'FontAwesome';
src: url('../assets/fonts/fontawesome-webfont.eot?v=4.5.0');
src: url('../assets/fonts/fontawesome-webfont.eot?#iefix&v=4.5.0') format('embedded-opentype'), url('../assets/fonts/fontawesome-webfont.woff2?v=4.5.0') format('woff2'), url('../assets/fonts/fontawesome-webfont.woff?v=4.5.0') format('woff'), url('../assets/fonts/fontawesome-webfont.ttf?v=4.5.0') format('truetype'), url('../assets/fonts/fontawesome-webfont.svg?v=4.5.0#fontawesomeregular') format('svg');
src: url('../Content/fonts/fontawesome-webfont.eot?v=4.5.0');
src: url('../Content/fonts/fontawesome-webfont.eot?#iefix&v=4.5.0') format('embedded-opentype'), url('../Content/fonts/fontawesome-webfont.woff2?v=4.5.0') format('woff2'), url('../Content/fonts/fontawesome-webfont.woff?v=4.5.0') format('woff'), url('../Content/fonts/fontawesome-webfont.ttf?v=4.5.0') format('truetype'), url('../Content/fonts/fontawesome-webfont.svg?v=4.5.0#fontawesomeregular') format('svg');
font-weight: normal;
font-style: normal;
}

@ -0,0 +1,109 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: AssetHelper.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System.Text;
using Nancy.ViewEngines.Razor;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
namespace PlexRequests.UI.Helpers
{
public static class AssetHelper
{
private static ServiceLocator Locator => ServiceLocator.Instance;
public static IHtmlString LoadAssets(this HtmlHelpers helper)
{
var settings = Locator.Resolve<ISettingsService<PlexRequestSettings>>().GetSettings();
var sb = new StringBuilder();
var assetLocation = settings.BaseUrl;
var content = GetContentUrl(assetLocation);
sb.AppendLine($"<link rel=\"stylesheet\" href=\"{content}/Content/bootstrap.css\" type=\"text/css\"/>");
sb.AppendLine($"<link rel=\"stylesheet\" href=\"{content}/Content/custom.min.css\" type=\"text/css\" />");
sb.AppendLine($"<link rel=\"stylesheet\" href=\"{content}/Content/font-awesome.css\" type=\"text/css\"/>");
sb.AppendLine($"<link rel=\"stylesheet\" href=\"{content}/Content/pace.min.css\" type=\"text/css\"/>");
sb.AppendLine($"<script src=\"{content}/Content/jquery-2.2.1.min.js\"></script>");
sb.AppendLine($"<script src=\"{content}/Content/handlebars.min.js\"></script>");
sb.AppendLine($"<script src=\"{content}/Content/bootstrap.min.js\"></script>");
sb.AppendLine($"<script src=\"{content}/Content/site.js\"></script>");
sb.AppendLine($"<script src=\"{content}/Content/pace.min.js\"></script>");
sb.AppendLine($"<script src=\"{content}/Content/jquery.mixitup.js\"></script>");
sb.AppendLine($"<script src=\"{content}/Content/moment.min.js\"></script>");
return helper.Raw(sb.ToString());
}
public static IHtmlString LoadSearchAssets(this HtmlHelpers helper)
{
var settings = Locator.Resolve<ISettingsService<PlexRequestSettings>>().GetSettings();
var sb = new StringBuilder();
var assetLocation = settings.BaseUrl;
var content = GetContentUrl(assetLocation);
sb.AppendLine($"<script src=\"{content}/Content/search.js\" type=\"text/javascript\"></script>");
return helper.Raw(sb.ToString());
}
public static IHtmlString LoadRequestAssets(this HtmlHelpers helper)
{
var settings = Locator.Resolve<ISettingsService<PlexRequestSettings>>().GetSettings();
var sb = new StringBuilder();
var assetLocation = settings.BaseUrl;
var content = GetContentUrl(assetLocation);
sb.AppendLine($"<script src=\"{content}/Content/requests.js\" type=\"text/javascript\"></script>");
return helper.Raw(sb.ToString());
}
public static IHtmlString LoadLogsAssets(this HtmlHelpers helper)
{
var settings = Locator.Resolve<ISettingsService<PlexRequestSettings>>().GetSettings();
var sb = new StringBuilder();
var assetLocation = settings.BaseUrl;
var content = GetContentUrl(assetLocation);
sb.AppendLine($"<script src=\"{content}/Content/datatables.min.js\" type=\"text/javascript\"></script>");
sb.AppendLine($"<link rel=\"stylesheet\" type=\"text/css\" href=\"{content}/Content/dataTables.bootstrap.css\" />");
return helper.Raw(sb.ToString());
}
private static string GetContentUrl(string assetLocation)
{
return string.IsNullOrEmpty(assetLocation) ? "~" : $"/{assetLocation}";
}
}
}

@ -0,0 +1,55 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: ServiceLocator.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using Nancy.TinyIoc;
namespace PlexRequests.UI.Helpers
{
public class ServiceLocator
{
static ServiceLocator()
{
Singleton = new ServiceLocator();
}
private static ServiceLocator Singleton { get; set; }
private TinyIoCContainer Container { get; set; }
public static ServiceLocator Instance => Singleton;
public void SetContainer(TinyIoCContainer container)
{
Container = container;
}
public T Resolve<T>() where T : class
{
if (Container != null)
{
return Container.Resolve<T>();
}
return null;
}
}
}

@ -39,7 +39,7 @@ using PlexRequests.UI.Models;
namespace PlexRequests.UI.Modules
{
public class ApplicationTesterModule : BaseModule
public class ApplicationTesterModule : BaseAuthModule
{
public ApplicationTesterModule(ICouchPotatoApi cpApi, ISonarrApi sonarrApi, IPlexApi plexApi,

@ -43,7 +43,7 @@ using PlexRequests.UI.Models;
namespace PlexRequests.UI.Modules
{
public class ApprovalModule : BaseModule
public class ApprovalModule : BaseAuthModule
{
public ApprovalModule(IRequestService service, ISettingsService<CouchPotatoSettings> cpService, ICouchPotatoApi cpApi, ISonarrApi sonarrApi,

@ -0,0 +1,85 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: BaseAuthModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using Nancy;
using Nancy.Extensions;
using PlexRequests.UI.Models;
using System;
namespace PlexRequests.UI.Modules
{
public class BaseAuthModule : BaseModule
{
private string _username;
private int _dateTimeOffset = -1;
protected string Username
{
get
{
if (string.IsNullOrEmpty(_username))
{
_username = Session[SessionKeys.UsernameKey].ToString();
}
return _username;
}
}
protected int DateTimeOffset
{
get
{
if (_dateTimeOffset == -1)
{
_dateTimeOffset = (int?)Session[SessionKeys.ClientDateTimeOffsetKey] ?? new DateTimeOffset().Offset.Minutes;
}
return _dateTimeOffset;
}
}
public BaseAuthModule()
{
Before += (ctx) => CheckAuth();
}
public BaseAuthModule(string modulePath) : base(modulePath)
{
Before += (ctx) => CheckAuth();
}
private Response CheckAuth()
{
if (Session[SessionKeys.UsernameKey] == null)
{
return Context.GetRedirect("~/test/userlogin");
}
return null;
}
}
}

@ -24,63 +24,35 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using Nancy;
using Nancy.Extensions;
using PlexRequests.UI.Models;
using System;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.UI.Helpers;
namespace PlexRequests.UI.Modules
{
public class BaseModule : NancyModule
{
private string _username;
private int _dateTimeOffset = -1;
protected string Username
private ServiceLocator Locator => ServiceLocator.Instance;
public BaseModule()
{
get
{
if (string.IsNullOrEmpty(_username))
{
_username = Session[SessionKeys.UsernameKey].ToString();
}
return _username;
}
}
var settings = Locator.Resolve<ISettingsService<PlexRequestSettings>>().GetSettings();
var baseUrl = settings.BaseUrl;
protected int DateTimeOffset
{
get
{
if (_dateTimeOffset == -1)
{
_dateTimeOffset = Session[SessionKeys.ClientDateTimeOffsetKey] != null ?
(int)Session[SessionKeys.ClientDateTimeOffsetKey] : (new DateTimeOffset().Offset).Minutes;
}
return _dateTimeOffset;
}
}
var modulePath = string.IsNullOrEmpty(baseUrl) ? string.Empty : baseUrl;
public BaseModule()
{
Before += (ctx) => CheckAuth();
ModulePath = modulePath;
}
public BaseModule(string modulePath) : base(modulePath)
public BaseModule(string modulePath)
{
Before += (ctx) => CheckAuth();
}
var settings = Locator.Resolve<ISettingsService<PlexRequestSettings>>().GetSettings();
var baseUrl = settings.BaseUrl;
var settingModulePath = string.IsNullOrEmpty(baseUrl) ? modulePath : $"{baseUrl}/{modulePath}";
private Response CheckAuth()
{
if (Session[SessionKeys.UsernameKey] == null)
{
return Context.GetRedirect("~/userlogin");
}
return null;
ModulePath = settingModulePath;
}
}
}

@ -29,7 +29,7 @@ using Nancy.Extensions;
namespace PlexRequests.UI.Modules
{
public class IndexModule : BaseModule
public class IndexModule : BaseAuthModule
{
public IndexModule()
{

@ -46,7 +46,7 @@ using System.Threading.Tasks;
namespace PlexRequests.UI.Modules
{
public class RequestsModule : BaseModule
public class RequestsModule : BaseAuthModule
{
public RequestsModule(
IRequestService service,

@ -55,7 +55,7 @@ using TMDbLib.Objects.General;
namespace PlexRequests.UI.Modules
{
public class SearchModule : BaseModule
public class SearchModule : BaseAuthModule
{
public SearchModule(ICacheProvider cache, ISettingsService<CouchPotatoSettings> cpSettings,
ISettingsService<PlexRequestSettings> prSettings, IAvailabilityChecker checker,

@ -44,7 +44,7 @@ using PlexRequests.UI.Models;
namespace PlexRequests.UI.Modules
{
public class UserLoginModule : NancyModule
public class UserLoginModule : BaseModule
{
public UserLoginModule(ISettingsService<AuthenticationSettings> auth, IPlexApi api) : base("userlogin")
{

@ -164,7 +164,9 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Bootstrapper.cs" />
<Compile Include="Helpers\AssetHelper.cs" />
<Compile Include="Helpers\HeadphonesSender.cs" />
<Compile Include="Helpers\ServiceLocator.cs" />
<Compile Include="Helpers\StringHelper.cs" />
<Compile Include="Helpers\TvSender.cs" />
<Compile Include="Helpers\ValidationHelper.cs" />
@ -175,6 +177,7 @@
<Compile Include="Models\SearchViewModel.cs" />
<Compile Include="Models\SearchMusicViewModel.cs" />
<Compile Include="Models\SearchMovieViewModel.cs" />
<Compile Include="Modules\BaseModule.cs" />
<Compile Include="Validators\PushoverSettingsValidator.cs" />
<Compile Include="Validators\PushbulletSettingsValidator.cs" />
<Compile Include="Validators\EmailNotificationSettingsValidator.cs" />
@ -197,7 +200,7 @@
<Compile Include="Models\SessionKeys.cs" />
<Compile Include="Modules\AdminModule.cs" />
<Compile Include="Modules\ApplicationTesterModule.cs" />
<Compile Include="Modules\BaseModule.cs" />
<Compile Include="Modules\BaseAuthModule.cs" />
<Compile Include="Modules\IndexModule.cs" />
<Compile Include="Modules\ApprovalModule.cs" />
<Compile Include="Modules\UserLoginModule.cs" />

@ -53,7 +53,7 @@ namespace PlexRequests.UI
private static Logger Log = LogManager.GetCurrentClassLogger();
static void Main(string[] args)
{
var assetLocation = "assets";
var baseUrl = "assets";
var port = -1;
if (args.Length > 0)
{
@ -65,8 +65,8 @@ namespace PlexRequests.UI
case "base":
i++;
var value = args[i];
Console.WriteLine("Settings URL Base");
assetLocation = value;
Console.WriteLine($"Using a Base URL {args[i]}");
baseUrl = value;
break;
default:
int portResult;
@ -88,7 +88,7 @@ namespace PlexRequests.UI
WriteOutVersion();
var s = new Setup();
var cn = s.SetupDb(assetLocation);
var cn = s.SetupDb(baseUrl);
s.CacheQualityProfiles();
ConfigureTargets(cn);

@ -1,7 +1,6 @@
@Html.Partial("_Sidebar")
<link rel="stylesheet" type="text/css" href="~/assets/dataTables.bootstrap.css" />
<script type="text/javascript" src="~/assets/datatables.min.js"></script>
@using PlexRequests.UI.Helpers
@Html.Partial("_Sidebar")
@Html.LoadLogsAssets()
<div class="col-sm-8 col-sm-push-1">
<fieldset>

@ -23,6 +23,16 @@
</div>
</div>
<small class="control-label">You will have to restart after changing the port.</small>
<div class="form-group">
<label for="BaseUrl" class="control-label">Base Url</label>
<div>
<input type="text" class="form-control form-control-custom " id="BaseUrl" name="BaseUrl" placeholder="Base Url" value="@Model.BaseUrl">
</div>
</div>
<small class="control-label">You will have to restart after changing the url base.</small>
<div class="form-group">
<div class="checkbox">
<label>

@ -1,4 +1,5 @@
@using Nancy.Security
@using PlexRequests.UI.Helpers
<div>
<h1>Requests</h1>
<h4>Below you can see yours and all other requests, as well as their download and approval status.</h4>
@ -403,6 +404,6 @@
</div>
</div>
<script src="~/assets/requests.js" type="text/javascript"></script>
@Html.LoadRequestAssets()

@ -1,4 +1,5 @@
<div>
@using PlexRequests.UI.Helpers
<div>
<h1>Search</h1>
<h4>Want to watch something that is not currently on Plex?! No problem! Just search for it below and request it!</h4>
<br />
@ -209,4 +210,4 @@
</script>
<script src="~/assets/search.js" type="text/javascript"></script>
@Html.LoadSearchAssets()

@ -1,5 +1,6 @@
@using Nancy.Security
@using Nancy.Session
@using PlexRequests.UI.Helpers
@using PlexRequests.UI.Models
@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase
<html>
@ -7,21 +8,7 @@
<title>Plex Requests</title>
<!-- Styles -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="~/assets/bootstrap.css" type="text/css"/>
<link rel="stylesheet" href="~/assets/custom.min.css" type="text/css" />
<link rel="stylesheet" href="~/assets/font-awesome.css" type="text/css"/>
<link rel="stylesheet" href="~/assets/pace.min.css" type="text/css"/>
<!-- Scripts -->
<script src="~/assets/jquery-2.2.1.min.js"></script>
<script src="~/assets/handlebars.min.js"></script>
<script src="~/assets/bootstrap.min.js"></script>
<script src="~/assets/bootstrap-notify.min.js"></script>
<script src="~/assets/site.js"></script>
<script src="~/assets/pace.min.js"></script>
<script src="~/assets/jquery.mixitup.js"></script>
<script src="~/assets/moment.min.js"></script>
@Html.LoadAssets()
</head>
<body>

Loading…
Cancel
Save