diff --git a/Oqtane.Client/App.razor b/Oqtane.Client/App.razor index 14f64da5..92b73433 100644 --- a/Oqtane.Client/App.razor +++ b/Oqtane.Client/App.razor @@ -30,28 +30,32 @@ } @code { - [Parameter] - public string AntiForgeryToken { get; set; } + [Parameter] + public string AntiForgeryToken { get; set; } - [Parameter] - public string Runtime { get; set; } + [Parameter] + public string Runtime { get; set; } - [Parameter] - public string RenderMode { get; set; } + [Parameter] + public string RenderMode { get; set; } - [Parameter] - public int VisitorId { get; set; } + [Parameter] + public int VisitorId { get; set; } - private bool _initialized = false; - private string _display = "display: none;"; - private Installation _installation = new Installation { Success = false, Message = "" }; + [Parameter] + public string RemoteIPAddress { get; set; } - private PageState PageState { get; set; } + private bool _initialized = false; + private string _display = "display: none;"; + private Installation _installation = new Installation { Success = false, Message = "" }; - protected override async Task OnParametersSetAsync() - { - SiteState.AntiForgeryToken = AntiForgeryToken; - InstallationService.SetAntiForgeryTokenHeader(AntiForgeryToken); + private PageState PageState { get; set; } + + protected override async Task OnParametersSetAsync() + { + SiteState.RemoteIPAddress = RemoteIPAddress; + SiteState.AntiForgeryToken = AntiForgeryToken; + InstallationService.SetAntiForgeryTokenHeader(AntiForgeryToken); _installation = await InstallationService.IsInstalled(); if (_installation.Alias != null) diff --git a/Oqtane.Client/Modules/Admin/Site/Index.razor b/Oqtane.Client/Modules/Admin/Site/Index.razor index 108c4b02..63ce2afc 100644 --- a/Oqtane.Client/Modules/Admin/Site/Index.razor +++ b/Oqtane.Client/Modules/Admin/Site/Index.razor @@ -533,6 +533,7 @@ else { AddModuleMessage(Localizer["Success.Settings.SaveSite"], MessageType.Success); + await interop.ScrollTo(0, 0, "smooth"); } } } diff --git a/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor b/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor index 61728e5f..ef09cd2a 100644 --- a/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor +++ b/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor @@ -33,7 +33,7 @@
- +
@@ -123,11 +123,7 @@ _clrversion = systeminfo["clrversion"]; _osversion = systeminfo["osversion"]; _serverpath = systeminfo["serverpath"]; - _servertime = systeminfo["servertime"]; - if (DateTime.TryParse(_servertime, out DateTime date)) - { - _servertime += " (" + date.ToUniversalTime().ToString() + " UTC)"; - } + _servertime = systeminfo["servertime"] + " UTC"; _installationid = systeminfo["installationid"]; _detailederrors = systeminfo["detailederrors"]; diff --git a/Oqtane.Client/Resources/Modules/Admin/SystemInfo/Index.resx b/Oqtane.Client/Resources/Modules/Admin/SystemInfo/Index.resx index 724b58dd..e564fcf8 100644 --- a/Oqtane.Client/Resources/Modules/Admin/SystemInfo/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/SystemInfo/Index.resx @@ -133,7 +133,7 @@ Server Path - Server Time + Server Date/Time (in UTC) Framework Version: @@ -148,7 +148,7 @@ Server Path: - Server Time: + Server Date/Time: Restart Application diff --git a/Oqtane.Client/UI/Interop.cs b/Oqtane.Client/UI/Interop.cs index 2aa9fb9f..28b72307 100644 --- a/Oqtane.Client/UI/Interop.cs +++ b/Oqtane.Client/UI/Interop.cs @@ -262,5 +262,22 @@ namespace Oqtane.UI return Task.CompletedTask; } } + + public Task ScrollTo(int top, int left, string behavior) + { + try + { + if (string.IsNullOrEmpty(behavior)) behavior = "smooth"; + _jsRuntime.InvokeVoidAsync( + "Oqtane.Interop.scrollTo", + top, left, behavior); + return Task.CompletedTask; + } + catch + { + return Task.CompletedTask; + } + } + } } diff --git a/Oqtane.Client/UI/PageState.cs b/Oqtane.Client/UI/PageState.cs index bc0fdacf..5b14ad00 100644 --- a/Oqtane.Client/UI/PageState.cs +++ b/Oqtane.Client/UI/PageState.cs @@ -21,5 +21,6 @@ namespace Oqtane.UI public DateTime LastSyncDate { get; set; } public Oqtane.Shared.Runtime Runtime { get; set; } public int VisitorId { get; set; } + public string RemoteIPAddress { get; set; } } } diff --git a/Oqtane.Client/UI/SiteRouter.razor b/Oqtane.Client/UI/SiteRouter.razor index a5339642..360ff6a1 100644 --- a/Oqtane.Client/UI/SiteRouter.razor +++ b/Oqtane.Client/UI/SiteRouter.razor @@ -229,7 +229,8 @@ EditMode = editmode, LastSyncDate = lastsyncdate, Runtime = runtime, - VisitorId = VisitorId + VisitorId = VisitorId, + RemoteIPAddress = SiteState.RemoteIPAddress }; OnStateChange?.Invoke(_pagestate); diff --git a/Oqtane.Client/UI/ThemeBuilder.razor b/Oqtane.Client/UI/ThemeBuilder.razor index a5437288..77f95a6c 100644 --- a/Oqtane.Client/UI/ThemeBuilder.razor +++ b/Oqtane.Client/UI/ThemeBuilder.razor @@ -31,7 +31,7 @@ var interop = new Interop(JsRuntime); // manage stylesheets for this page - string batch = DateTime.Now.ToString("yyyyMMddHHmmssfff"); + string batch = DateTime.UtcNow.ToString("yyyyMMddHHmmssfff"); var links = new List(); foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet && item.Declaration != ResourceDeclaration.Global)) { diff --git a/Oqtane.Server/Controllers/SystemController.cs b/Oqtane.Server/Controllers/SystemController.cs index 7a95cc5c..6ec0fe11 100644 --- a/Oqtane.Server/Controllers/SystemController.cs +++ b/Oqtane.Server/Controllers/SystemController.cs @@ -31,7 +31,7 @@ namespace Oqtane.Controllers systeminfo.Add("osversion", Environment.OSVersion.ToString()); systeminfo.Add("machinename", Environment.MachineName); systeminfo.Add("serverpath", _environment.ContentRootPath); - systeminfo.Add("servertime", DateTime.Now.ToString()); + systeminfo.Add("servertime", DateTime.UtcNow.ToString()); systeminfo.Add("installationid", _configManager.GetInstallationId()); systeminfo.Add("runtime", _configManager.GetSetting("Runtime", "Server")); diff --git a/Oqtane.Server/Infrastructure/DatabaseManager.cs b/Oqtane.Server/Infrastructure/DatabaseManager.cs index a4a94e61..cad82ac9 100644 --- a/Oqtane.Server/Infrastructure/DatabaseManager.cs +++ b/Oqtane.Server/Infrastructure/DatabaseManager.cs @@ -441,8 +441,7 @@ namespace Oqtane.Infrastructure var index = Array.FindIndex(versions, item => item == version); if (index != (versions.Length - 1)) { - if (index == -1) index = 0; - for (var i = index; i < versions.Length; i++) + for (var i = (index + 1); i < versions.Length; i++) { upgrades.Upgrade(tenant, versions[i]); } diff --git a/Oqtane.Server/Pages/_Host.cshtml.cs b/Oqtane.Server/Pages/_Host.cshtml.cs index 66c4b0cc..852b23dd 100644 --- a/Oqtane.Server/Pages/_Host.cshtml.cs +++ b/Oqtane.Server/Pages/_Host.cshtml.cs @@ -56,6 +56,7 @@ namespace Oqtane.Pages public string Runtime = "Server"; public RenderMode RenderMode = RenderMode.Server; public int VisitorId = -1; + public string RemoteIPAddress = ""; public string HeadResources = ""; public string BodyResources = ""; public string Title = ""; @@ -66,6 +67,7 @@ namespace Oqtane.Pages public IActionResult OnGet() { AntiForgeryToken = _antiforgery.GetAndStoreTokens(HttpContext).RequestToken; + RemoteIPAddress = HttpContext.Connection.RemoteIpAddress?.ToString() ?? ""; if (_configuration.GetSection("Runtime").Exists()) { @@ -194,12 +196,11 @@ namespace Oqtane.Pages private void TrackVisitor(int SiteId) { // get request attributes - string ip = HttpContext.Connection.RemoteIpAddress?.ToString() ?? ""; string useragent = (Request.Headers[HeaderNames.UserAgent] != StringValues.Empty) ? Request.Headers[HeaderNames.UserAgent] : ""; string language = (Request.Headers[HeaderNames.AcceptLanguage] != StringValues.Empty) ? Request.Headers[HeaderNames.AcceptLanguage] : ""; language = (language.Contains(",")) ? language.Substring(0, language.IndexOf(",")) : language; language = (language.Contains(";")) ? language.Substring(0, language.IndexOf(";")) : language; - language = (language.Trim().Length == 0) ? "*" : language; + language = (language.Trim().Length == 0) ? "??" : language; // filter var filter = _settings.GetSetting(EntityNames.Site, SiteId, "VisitorFilter"); @@ -207,7 +208,7 @@ namespace Oqtane.Pages { foreach (string term in filter.SettingValue.ToLower().Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(sValue => sValue.Trim()).ToArray()) { - if (ip.ToLower().Contains(term) || useragent.ToLower().Contains(term) || language.ToLower().Contains(term)) + if (RemoteIPAddress.ToLower().Contains(term) || useragent.ToLower().Contains(term) || language.ToLower().Contains(term)) { return; } @@ -227,7 +228,7 @@ namespace Oqtane.Pages { var visitor = new Visitor(); visitor.SiteId = SiteId; - visitor.IPAddress = ip; + visitor.IPAddress = RemoteIPAddress; visitor.UserAgent = useragent; visitor.Language = language; visitor.Url = url; @@ -253,7 +254,7 @@ namespace Oqtane.Pages var visitor = _visitors.GetVisitor(VisitorId); if (visitor != null) { - visitor.IPAddress = ip; + visitor.IPAddress = RemoteIPAddress; visitor.UserAgent = useragent; visitor.Language = language; visitor.Url = url; @@ -380,7 +381,7 @@ namespace Oqtane.Pages case ResourceType.Stylesheet: if (!HeadResources.Contains(resource.Url, StringComparison.OrdinalIgnoreCase)) { - var id = (resource.Declaration == ResourceDeclaration.Global) ? "" : "id=\"app-stylesheet-" + DateTime.Now.ToString("yyyyMMddHHmmssfff") + "-" + count.ToString("00") + "\" "; + var id = (resource.Declaration == ResourceDeclaration.Global) ? "" : "id=\"app-stylesheet-" + DateTime.UtcNow.ToString("yyyyMMddHHmmssfff") + "-" + count.ToString("00") + "\" "; HeadResources += "" + Environment.NewLine; } break; diff --git a/Oqtane.Server/Repository/LogRepository.cs b/Oqtane.Server/Repository/LogRepository.cs index 1d67aaab..24920d42 100644 --- a/Oqtane.Server/Repository/LogRepository.cs +++ b/Oqtane.Server/Repository/LogRepository.cs @@ -53,7 +53,7 @@ namespace Oqtane.Repository { // delete logs in batches of 100 records int count = 0; - var purgedate = DateTime.Now.AddDays(-age); + var purgedate = DateTime.UtcNow.AddDays(-age); var logs = _db.Log.Where(item => item.Level != "Error" && item.LogDate < purgedate) .OrderBy(item => item.LogDate).Take(100).ToList(); while (logs.Count > 0) diff --git a/Oqtane.Server/Repository/VisitorRepository.cs b/Oqtane.Server/Repository/VisitorRepository.cs index 5feeaad9..6572d973 100644 --- a/Oqtane.Server/Repository/VisitorRepository.cs +++ b/Oqtane.Server/Repository/VisitorRepository.cs @@ -52,7 +52,7 @@ namespace Oqtane.Repository { // delete visitors in batches of 100 records int count = 0; - var purgedate = DateTime.Now.AddDays(-age); + var purgedate = DateTime.UtcNow.AddDays(-age); var visitors = _db.Visitor.Where(item => item.Visits <= 1 && item.VisitedOn < purgedate) .OrderBy(item => item.VisitedOn).Take(100).ToList(); while (visitors.Count > 0) diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index cf8bf6a2..eb6a26a2 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -15,6 +15,7 @@ using Oqtane.Models; using Oqtane.Repository; using Oqtane.Security; using Oqtane.Shared; +using Microsoft.AspNetCore.HttpOverrides; namespace Oqtane { @@ -49,7 +50,13 @@ namespace Oqtane // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { - // Register localization services + // process forwarded headers on load balancers and proxy servers + services.Configure(options => + { + options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; + }); + + // register localization services services.AddLocalization(options => options.ResourcesPath = "Resources"); services.AddOptions>().Bind(Configuration.GetSection(SettingKeys.AvailableDatabasesSection)); @@ -111,7 +118,6 @@ namespace Oqtane services.AddOqtane(_runtime, _supportedCultures); services.AddOqtaneDbContext(); - services.AddMvc() .AddNewtonsoftJson() .AddOqtaneApplicationParts() // register any Controllers from custom modules @@ -133,9 +139,11 @@ namespace Oqtane { app.UseDeveloperExceptionPage(); app.UseWebAssemblyDebugging(); + app.UseForwardedHeaders(); } else { + app.UseForwardedHeaders(); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } diff --git a/Oqtane.Server/wwwroot/js/interop.js b/Oqtane.Server/wwwroot/js/interop.js index 0db5c13e..e4e07e8e 100644 --- a/Oqtane.Server/wwwroot/js/interop.js +++ b/Oqtane.Server/wwwroot/js/interop.js @@ -369,5 +369,12 @@ Oqtane.Interop = { if (element !== null) { element.setAttribute(attribute, value); } + }, + scrollTo: function (top, left, behavior) { + window.scrollTo({ + top: top, + left: left, + behavior: behavior + }); } }; diff --git a/Oqtane.Shared/Shared/SiteState.cs b/Oqtane.Shared/Shared/SiteState.cs index 475a9d59..67d5cbd8 100644 --- a/Oqtane.Shared/Shared/SiteState.cs +++ b/Oqtane.Shared/Shared/SiteState.cs @@ -7,6 +7,6 @@ namespace Oqtane.Shared { public Alias Alias { get; set; } public string AntiForgeryToken { get; set; } // for use in client services - + public string RemoteIPAddress { get; set; } // captured in _host as cannot be reliably retrieved on Blazor Server } }