From 76fe155c0aa6850756ae410a28d08b5710589761 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Sat, 11 Dec 2021 09:30:05 -0500 Subject: [PATCH] visitor improvements --- Oqtane.Client/Modules/Admin/Logs/Detail.razor | 101 ++-------- Oqtane.Client/Modules/Admin/Logs/Index.razor | 10 +- .../Modules/Admin/Visitors/Detail.razor | 123 ++++++++++++ .../Modules/Admin/Visitors/Index.razor | 4 + Oqtane.Client/Oqtane.Client.csproj | 4 - .../Modules/Admin/UrlMappings/Index.resx | 8 +- .../Modules/Admin/Visitors/Detail.resx | 177 ++++++++++++++++++ .../Modules/Admin/Visitors/Index.resx | 11 +- .../Services/Interfaces/IVisitorService.cs | 8 + Oqtane.Client/Services/VisitorService.cs | 5 + .../Controllers/SettingController.cs | 5 +- .../Controllers/VisitorController.cs | 28 +++ .../Tenant/03000104_AddVisitorReferrer.cs | 33 ++++ Oqtane.Server/Pages/_Host.cshtml.cs | 45 +++-- Oqtane.Shared/Models/Visitor.cs | 16 +- 15 files changed, 458 insertions(+), 120 deletions(-) create mode 100644 Oqtane.Client/Modules/Admin/Visitors/Detail.razor create mode 100644 Oqtane.Client/Resources/Modules/Admin/Visitors/Detail.resx create mode 100644 Oqtane.Server/Migrations/Tenant/03000104_AddVisitorReferrer.cs diff --git a/Oqtane.Client/Modules/Admin/Logs/Detail.razor b/Oqtane.Client/Modules/Admin/Logs/Detail.razor index 91e4d0e7..e2623a02 100644 --- a/Oqtane.Client/Modules/Admin/Logs/Detail.razor +++ b/Oqtane.Client/Modules/Admin/Logs/Detail.razor @@ -10,100 +10,32 @@ @inject IStringLocalizer SharedLocalizer
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
- -
- +
- +
- +
- +
- +
@@ -111,7 +43,7 @@ @if (_pageName != string.Empty) {
- +
@@ -120,7 +52,7 @@ @if (_moduleTitle != string.Empty) {
- +
@@ -129,26 +61,26 @@ @if (_username != string.Empty) {
- +
}
- +
- +
- +
@@ -156,24 +88,25 @@ @if (!string.IsNullOrEmpty(_exception)) {
- -
+ +
}
- +
- -
+ +
+
@SharedLocalizer["Cancel"] diff --git a/Oqtane.Client/Modules/Admin/Logs/Index.razor b/Oqtane.Client/Modules/Admin/Logs/Index.razor index 920ec709..311e73b0 100644 --- a/Oqtane.Client/Modules/Admin/Logs/Index.razor +++ b/Oqtane.Client/Modules/Admin/Logs/Index.razor @@ -51,11 +51,11 @@ else {
-   - @Localizer["Date"] - @Localizer["Level"] - @Localizer["Feature"] - @Localizer["Function"] +   + @Localizer["Date"] + @Localizer["Level"] + @Localizer["Feature"] + @Localizer["Function"]
diff --git a/Oqtane.Client/Modules/Admin/Visitors/Detail.razor b/Oqtane.Client/Modules/Admin/Visitors/Detail.razor new file mode 100644 index 00000000..d1adf75e --- /dev/null +++ b/Oqtane.Client/Modules/Admin/Visitors/Detail.razor @@ -0,0 +1,123 @@ +@namespace Oqtane.Modules.Admin.Visitors +@using System.Globalization +@inherits ModuleBase +@inject NavigationManager NavigationManager +@inject IVisitorService VisitorService +@inject IUserService UserService +@inject IStringLocalizer Localizer +@inject IStringLocalizer SharedLocalizer + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+ @if (_user != string.Empty) + { +
+ +
+ +
+
+ } +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ + @SharedLocalizer["Cancel"] + +@code { + private int _visitorId; + private string _ip = string.Empty; + private string _language = string.Empty; + private string _useragent = string.Empty; + private string _url = string.Empty; + private string _referrer = string.Empty; + private string _user = string.Empty; + private string _visits = string.Empty; + private string _visited = string.Empty; + private string _created = string.Empty; + + public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; + + protected override async Task OnInitializedAsync() + { + try + { + _visitorId = Int32.Parse(PageState.QueryString["id"]); + var visitor = await VisitorService.GetVisitorAsync(_visitorId); + if (visitor != null) + { + _ip = visitor.IPAddress; + _language = visitor.Language; + _useragent = visitor.UserAgent; + _url = visitor.Url; + _referrer = visitor.Referrer; + _visits = visitor.Visits.ToString(); + _visited = visitor.VisitedOn.ToString(CultureInfo.CurrentCulture); + _created = visitor.CreatedOn.ToString(CultureInfo.CurrentCulture); + + if (visitor.UserId != null) + { + var user = await UserService.GetUserAsync(visitor.UserId.Value, PageState.Site.SiteId); + if (user != null) + { + _user = user.DisplayName; + } + } + } + else + { + AddModuleMessage(Localizer["Error.LoadVisitor"], MessageType.Error); + } + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Loading Visitor {VisitorId} {Error}", _visitorId, ex.Message); + AddModuleMessage(Localizer["Error.LoadVisitor"], MessageType.Error); + } + } + } diff --git a/Oqtane.Client/Modules/Admin/Visitors/Index.razor b/Oqtane.Client/Modules/Admin/Visitors/Index.razor index 87b13296..d09d8e0d 100644 --- a/Oqtane.Client/Modules/Admin/Visitors/Index.razor +++ b/Oqtane.Client/Modules/Admin/Visitors/Index.razor @@ -33,13 +33,16 @@ else
+   @Localizer["IP"] @Localizer["User"] @Localizer["Language"] @Localizer["Visits"] @Localizer["Visited"] + @Localizer["Created"]
+ @context.IPAddress @if (context.UserId != null) @@ -50,6 +53,7 @@ else @context.Language @context.Visits @context.VisitedOn + @context.CreatedOn
diff --git a/Oqtane.Client/Oqtane.Client.csproj b/Oqtane.Client/Oqtane.Client.csproj index 2e4893ab..a2366502 100644 --- a/Oqtane.Client/Oqtane.Client.csproj +++ b/Oqtane.Client/Oqtane.Client.csproj @@ -34,10 +34,6 @@ - - - - diff --git a/Oqtane.Client/Resources/Modules/Admin/UrlMappings/Index.resx b/Oqtane.Client/Resources/Modules/Admin/UrlMappings/Index.resx index a2eed5e6..a0719b24 100644 --- a/Oqtane.Client/Resources/Modules/Admin/UrlMappings/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/UrlMappings/Index.resx @@ -132,11 +132,11 @@ User - - Visited + + Requested - - Visits + + Requests Mapped Urls diff --git a/Oqtane.Client/Resources/Modules/Admin/Visitors/Detail.resx b/Oqtane.Client/Resources/Modules/Admin/Visitors/Detail.resx new file mode 100644 index 00000000..d392f0b2 --- /dev/null +++ b/Oqtane.Client/Resources/Modules/Admin/Visitors/Detail.resx @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + IP Address: + + + The last recorded IP address for this visitor + + + The last recorded language for this visitor + + + Language: + + + Error Loading Visitor + + + The frst recorded date/time when the visitor visited the site + + + Created: + + + The last recorded referrer for this visitor + + + Referrer: + + + The last recorded Url for this visitor + + + Url: + + + The last recorded user associated with this visitor + + + User: + + + The last recorded user agent for this visitor + + + User Agent: + + + The last recorded date/time when the visitor visited the site + + + Visited: + + + The total number of visits by this visitor all time + + + Visits: + + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/Visitors/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Visitors/Index.resx index 096b6d2a..6a5ee181 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Visitors/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Visitors/Index.resx @@ -120,11 +120,11 @@ Url - - Requested + + Visited - - Requests + + Visits All Visitors @@ -162,4 +162,7 @@ Visitor Tracking Enabled? + + Created + \ No newline at end of file diff --git a/Oqtane.Client/Services/Interfaces/IVisitorService.cs b/Oqtane.Client/Services/Interfaces/IVisitorService.cs index b25779a5..3f067144 100644 --- a/Oqtane.Client/Services/Interfaces/IVisitorService.cs +++ b/Oqtane.Client/Services/Interfaces/IVisitorService.cs @@ -17,5 +17,13 @@ namespace Oqtane.Services /// ID-reference of a /// Task> GetVisitorsAsync(int siteId, DateTime fromDate); + + /// + /// Get a specific of this . + /// + /// + /// ID-reference of a + /// + Task GetVisitorAsync(int visitorId); } } diff --git a/Oqtane.Client/Services/VisitorService.cs b/Oqtane.Client/Services/VisitorService.cs index ca36ee3a..730ca5d4 100644 --- a/Oqtane.Client/Services/VisitorService.cs +++ b/Oqtane.Client/Services/VisitorService.cs @@ -28,5 +28,10 @@ namespace Oqtane.Services List visitors = await GetJsonAsync>($"{Apiurl}?siteid={siteId}&fromdate={fromDate.ToString("dd-MMM-yyyy")}"); return visitors.OrderByDescending(item => item.VisitedOn).ToList(); } + + public async Task GetVisitorAsync(int visitorId) + { + return await GetJsonAsync($"{Apiurl}/{visitorId}"); + } } } diff --git a/Oqtane.Server/Controllers/SettingController.cs b/Oqtane.Server/Controllers/SettingController.cs index f30847f2..33e48589 100644 --- a/Oqtane.Server/Controllers/SettingController.cs +++ b/Oqtane.Server/Controllers/SettingController.cs @@ -204,12 +204,15 @@ namespace Oqtane.Controllers } break; case EntityNames.Visitor: - authorized = false; var visitorCookie = "APP_VISITOR_" + _alias.SiteId.ToString(); if (int.TryParse(Request.Cookies[visitorCookie], out int visitorId)) { authorized = (visitorId == entityId); } + else + { + authorized = User.IsInRole(RoleNames.Admin); + } break; } return authorized; diff --git a/Oqtane.Server/Controllers/VisitorController.cs b/Oqtane.Server/Controllers/VisitorController.cs index 901c5ea5..3f789efd 100644 --- a/Oqtane.Server/Controllers/VisitorController.cs +++ b/Oqtane.Server/Controllers/VisitorController.cs @@ -42,5 +42,33 @@ namespace Oqtane.Controllers return null; } } + + // GET api//5 + [HttpGet("{id}")] + public Visitor Get(int id) + { + bool authorized; + var visitorCookie = "APP_VISITOR_" + _alias.SiteId.ToString(); + if (int.TryParse(Request.Cookies[visitorCookie], out int visitorId)) + { + authorized = (visitorId == id); + } + else + { + authorized = User.IsInRole(RoleNames.Admin); + } + + var visitor = _visitors.GetVisitor(id); + if (authorized && visitor != null && visitor.SiteId == _alias.SiteId) + { + return visitor; + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Visitor Get Attempt {VisitorId}", id); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + return null; + } + } } } diff --git a/Oqtane.Server/Migrations/Tenant/03000104_AddVisitorReferrer.cs b/Oqtane.Server/Migrations/Tenant/03000104_AddVisitorReferrer.cs new file mode 100644 index 00000000..bdc645a8 --- /dev/null +++ b/Oqtane.Server/Migrations/Tenant/03000104_AddVisitorReferrer.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Databases.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + +namespace Oqtane.Migrations.Tenant +{ + [DbContext(typeof(TenantDBContext))] + [Migration("Tenant.03.00.01.05")] + public class AddVisitorReferrer : MultiDatabaseMigration + { + public AddVisitorReferrer(IDatabase database) : base(database) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + var visitorEntityBuilder = new VisitorEntityBuilder(migrationBuilder, ActiveDatabase); + + visitorEntityBuilder.AddStringColumn("Referrer", 500, true); + visitorEntityBuilder.AddStringColumn("Url", 500, true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + var visitorEntityBuilder = new VisitorEntityBuilder(migrationBuilder, ActiveDatabase); + + visitorEntityBuilder.DropColumn("Referrer"); + visitorEntityBuilder.DropColumn("Url"); + } + } +} diff --git a/Oqtane.Server/Pages/_Host.cshtml.cs b/Oqtane.Server/Pages/_Host.cshtml.cs index 7882eeb9..8c878dc6 100644 --- a/Oqtane.Server/Pages/_Host.cshtml.cs +++ b/Oqtane.Server/Pages/_Host.cshtml.cs @@ -189,19 +189,33 @@ namespace Oqtane.Pages private void TrackVisitor(int SiteId) { + // get request attributes + string ip = HttpContext.Connection.RemoteIpAddress.ToString(); + string useragent = Request.Headers[HeaderNames.UserAgent]; + string language = Request.Headers[HeaderNames.AcceptLanguage]; + if (language.Contains(",")) + { + language = language.Substring(0, language.IndexOf(",")); + } + string url = Request.GetEncodedUrl(); + string referrer = Request.Headers[HeaderNames.Referer]; + int? userid = null; + if (User.HasClaim(item => item.Type == ClaimTypes.PrimarySid)) + { + userid = int.Parse(User.Claims.First(item => item.Type == ClaimTypes.PrimarySid).Value); + } + var VisitorCookie = "APP_VISITOR_" + SiteId.ToString(); if (!int.TryParse(Request.Cookies[VisitorCookie], out VisitorId)) { var visitor = new Visitor(); visitor.SiteId = SiteId; - visitor.IPAddress = HttpContext.Connection.RemoteIpAddress.ToString(); - visitor.UserAgent = Request.Headers[HeaderNames.UserAgent]; - visitor.Language = Request.Headers[HeaderNames.AcceptLanguage]; - if (visitor.Language.Contains(",")) - { - visitor.Language = visitor.Language.Substring(0, visitor.Language.IndexOf(",")); - } - visitor.UserId = null; + visitor.IPAddress = ip; + visitor.UserAgent = useragent; + visitor.Language = language; + visitor.Url = url; + visitor.Referrer = referrer; + visitor.UserId = userid; visitor.Visits = 1; visitor.CreatedOn = DateTime.UtcNow; visitor.VisitedOn = DateTime.UtcNow; @@ -222,16 +236,17 @@ namespace Oqtane.Pages var visitor = _visitors.GetVisitor(VisitorId); if (visitor != null) { - visitor.IPAddress = HttpContext.Connection.RemoteIpAddress.ToString(); - visitor.UserAgent = Request.Headers[HeaderNames.UserAgent]; - visitor.Language = Request.Headers[HeaderNames.AcceptLanguage]; - if (visitor.Language.Contains(",")) + visitor.IPAddress = ip; + visitor.UserAgent = useragent; + visitor.Language = language; + visitor.Url = url; + if (!string.IsNullOrEmpty(referrer)) { - visitor.Language = visitor.Language.Substring(0, visitor.Language.IndexOf(",")); + visitor.Referrer = referrer; } - if (User.HasClaim(item => item.Type == ClaimTypes.PrimarySid)) + if (userid != null) { - visitor.UserId = int.Parse(User.Claims.First(item => item.Type == ClaimTypes.PrimarySid).Value); + visitor.UserId = userid; } visitor.Visits += 1; visitor.VisitedOn = DateTime.UtcNow; diff --git a/Oqtane.Shared/Models/Visitor.cs b/Oqtane.Shared/Models/Visitor.cs index b27fdca5..8c349246 100644 --- a/Oqtane.Shared/Models/Visitor.cs +++ b/Oqtane.Shared/Models/Visitor.cs @@ -29,20 +29,30 @@ namespace Oqtane.Models public int Visits { get; set; } /// - /// IP Address of visitor + /// Last recorded IP Address of visitor /// public string IPAddress { get; set; } /// - /// User agent of visitor + /// Last recorded user agent of visitor /// public string UserAgent { get; set; } /// - /// Language of visitor + /// Last recorded language of visitor /// public string Language { get; set; } + /// + /// Last recorded Url of visitor + /// + public string Url { get; set; } + + /// + /// Last recorded Referrer of visitor + /// + public string Referrer { get; set; } + /// /// Date the visitor first visited the site ///