added support for url mapping and viitors
This commit is contained in:
@ -15,7 +15,7 @@
|
||||
<div style="@_display">
|
||||
<CascadingAuthenticationState>
|
||||
<CascadingValue Value="@PageState">
|
||||
<SiteRouter Runtime="@Runtime" RenderMode="@RenderMode" OnStateChange="@ChangeState" />
|
||||
<SiteRouter Runtime="@Runtime" RenderMode="@RenderMode" VisitorId="@VisitorId" OnStateChange="@ChangeState" />
|
||||
</CascadingValue>
|
||||
</CascadingAuthenticationState>
|
||||
</div>
|
||||
@ -39,6 +39,9 @@
|
||||
[Parameter]
|
||||
public string RenderMode { 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 = "" };
|
||||
|
@ -46,6 +46,8 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
services.AddScoped<ILocalizationService, LocalizationService>();
|
||||
services.AddScoped<ILanguageService, LanguageService>();
|
||||
services.AddScoped<IDatabaseService, DatabaseService>();
|
||||
services.AddScoped<IUrlMappingService, UrlMappingService>();
|
||||
services.AddScoped<IVisitorService, VisitorService>();
|
||||
services.AddScoped<ISyncService, SyncService>();
|
||||
|
||||
return services;
|
||||
|
@ -34,15 +34,6 @@
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="allowRegister" HelpText="Do you want the users to be able to register for an account on the site" ResourceKey="AllowRegistration">Allow User Registration? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="allowRegister" class="form-select" @bind="@_allowregistration" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="isDeleted" HelpText="Is this site deleted?" ResourceKey="IsDeleted">Is Deleted? </Label>
|
||||
<div class="col-sm-9">
|
||||
@ -258,7 +249,6 @@
|
||||
private string _themetype = "-";
|
||||
private string _containertype = "-";
|
||||
private string _admincontainertype = "-";
|
||||
private string _allowregistration;
|
||||
private string _smtphost = string.Empty;
|
||||
private string _smtpport = string.Empty;
|
||||
private string _smtpssl = "False";
|
||||
@ -294,7 +284,6 @@
|
||||
_name = site.Name;
|
||||
_runtime = site.Runtime;
|
||||
_prerender = site.RenderMode.Replace(_runtime, "");
|
||||
_allowregistration = site.AllowRegistration.ToString();
|
||||
_isdeleted = site.IsDeleted.ToString();
|
||||
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
@ -437,7 +426,6 @@
|
||||
reload = true; // needs to be reloaded on server
|
||||
}
|
||||
}
|
||||
site.AllowRegistration = (_allowregistration == null ? true : Boolean.Parse(_allowregistration));
|
||||
site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
|
||||
|
||||
site.LogoFileId = null;
|
||||
|
71
Oqtane.Client/Modules/Admin/UrlMappings/Add.razor
Normal file
71
Oqtane.Client/Modules/Admin/UrlMappings/Add.razor
Normal file
@ -0,0 +1,71 @@
|
||||
@namespace Oqtane.Modules.Admin.UrlMappings
|
||||
@inherits ModuleBase
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IUrlMappingService UrlMappingService
|
||||
@inject IStringLocalizer<Edit> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="url" HelpText="The fully qualified Url for this site" ResourceKey="Url">Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="url" class="form-control" @bind="@_url" maxlength="500" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="mappedurl" HelpText="A fully qualified Url where the user will be redirected" ResourceKey="MappedUrl">Redirect To:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="mappedurl" class="form-control" @bind="@_mappedurl" maxlength="500" required />
|
||||
</div>
|
||||
</div>
|
||||
<br /><br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveUrlMapping">@SharedLocalizer["Save"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@code {
|
||||
private ElementReference form;
|
||||
private bool validated = false;
|
||||
|
||||
private string _url = string.Empty;
|
||||
private string _mappedurl = string.Empty;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||
|
||||
private async Task SaveUrlMapping()
|
||||
{
|
||||
validated = true;
|
||||
var interop = new Interop(JSRuntime);
|
||||
if (await interop.FormValid(form))
|
||||
{
|
||||
var route = new Route(_url, PageState.Alias.Path);
|
||||
var url = route.SiteUrl + "/" + route.PagePath;
|
||||
|
||||
var urlmapping = new UrlMapping();
|
||||
urlmapping.SiteId = PageState.Site.SiteId;
|
||||
urlmapping.Url = url;
|
||||
urlmapping.MappedUrl = _mappedurl;
|
||||
urlmapping.Requests = 0;
|
||||
urlmapping.CreatedOn = DateTime.UtcNow;
|
||||
urlmapping.RequestedOn = DateTime.UtcNow;
|
||||
|
||||
try
|
||||
{
|
||||
urlmapping = await UrlMappingService.AddUrlMappingAsync(urlmapping);
|
||||
await logger.LogInformation("UrlMapping Saved {UrlMapping}", urlmapping);
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Saving UrlMapping {UrlMapping} {Error}", urlmapping, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.SaveUrlMapping"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
}
|
83
Oqtane.Client/Modules/Admin/UrlMappings/Edit.razor
Normal file
83
Oqtane.Client/Modules/Admin/UrlMappings/Edit.razor
Normal file
@ -0,0 +1,83 @@
|
||||
@namespace Oqtane.Modules.Admin.UrlMappings
|
||||
@inherits ModuleBase
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IUrlMappingService UrlMappingService
|
||||
@inject IStringLocalizer<Edit> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="url" HelpText="A fully qualified Url for this site" ResourceKey="Url">Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="url" class="form-control" @bind="@_url" maxlength="500" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="mappedurl" HelpText="A fully qualified Url where the user will be redirected" ResourceKey="MappedUrl">Redirect To:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="mappedurl" class="form-control" @bind="@_mappedurl" maxlength="500" required />
|
||||
</div>
|
||||
</div>
|
||||
<br /><br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveUrlMapping">@SharedLocalizer["Save"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@code {
|
||||
private ElementReference form;
|
||||
private bool validated = false;
|
||||
|
||||
private int _urlmappingid;
|
||||
private string _url = string.Empty;
|
||||
private string _mappedurl = string.Empty;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_urlmappingid = Int32.Parse(PageState.QueryString["id"]);
|
||||
var urlmapping = await UrlMappingService.GetUrlMappingAsync(_urlmappingid);
|
||||
if (urlmapping != null)
|
||||
{
|
||||
_url = urlmapping.Url;
|
||||
_mappedurl = urlmapping.MappedUrl;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading UrlMapping {UrlMappingId} {Error}", _urlmappingid, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.LoadUrlMapping"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveUrlMapping()
|
||||
{
|
||||
validated = true;
|
||||
var interop = new Interop(JSRuntime);
|
||||
if (await interop.FormValid(form))
|
||||
{
|
||||
var urlmapping = await UrlMappingService.GetUrlMappingAsync(_urlmappingid);
|
||||
urlmapping.MappedUrl = _mappedurl;
|
||||
|
||||
try
|
||||
{
|
||||
urlmapping = await UrlMappingService.UpdateUrlMappingAsync(urlmapping);
|
||||
await logger.LogInformation("UrlMapping Saved {UrlMapping}", urlmapping);
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Saving UrlMapping {UrlMapping} {Error}", urlmapping, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.SaveUrlMapping"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
}
|
134
Oqtane.Client/Modules/Admin/UrlMappings/Index.razor
Normal file
134
Oqtane.Client/Modules/Admin/UrlMappings/Index.razor
Normal file
@ -0,0 +1,134 @@
|
||||
@namespace Oqtane.Modules.Admin.UrlMappings
|
||||
@inherits ModuleBase
|
||||
@inject IUrlMappingService UrlMappingService
|
||||
@inject ISiteService SiteService
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@if (_urlMappings == null)
|
||||
{
|
||||
<p><em>@SharedLocalizer["Loading"]</em></p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<TabStrip>
|
||||
<TabPanel Name="Urls" Heading="Urls" ResourceKey="Urls">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<div class="col-sm-6">
|
||||
<ActionLink Action="Add" Text="Add Url Mapping" ResourceKey="AddUrlMapping" />
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<select id="type" class="form-select custom-select" @onchange="(e => MappedChanged(e))">
|
||||
<option value="true">@Localizer["Mapped"]</option>
|
||||
<option value="false">@Localizer["Broken"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<Pager Items="@_urlMappings">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@Localizer["Url"]</th>
|
||||
<th>@Localizer["Requests"]</th>
|
||||
<th>@Localizer["Requested"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.UrlMappingId.ToString())" ResourceKey="Edit" /></td>
|
||||
<td><ActionDialog Header="Delete Url Mapping" Message="@string.Format(Localizer["Confirm.DeleteUrlMapping"], context.Url)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteUrlMapping(context))" ResourceKey="DeleteUrlMapping" /></td>
|
||||
<td>
|
||||
@context.Url
|
||||
@if (_mapped)
|
||||
{
|
||||
@((MarkupString)"<br />>> ")@context.MappedUrl
|
||||
}
|
||||
</td>
|
||||
<td>@context.Requests</td>
|
||||
<td>@context.RequestedOn</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
</TabPanel>
|
||||
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="capturebrokenurls" HelpText="Specify if broken Urls should be captured automatically and saved in Url Mappings" ResourceKey="CaptureBrokenUrls">Capture Broken Urls? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="capturebrokenurls" class="form-select" @bind="@_capturebrokenurls" >
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||
</TabPanel>
|
||||
</TabStrip>
|
||||
}
|
||||
|
||||
@code {
|
||||
private bool _mapped = true;
|
||||
private List<UrlMapping> _urlMappings;
|
||||
private string _capturebrokenurls;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
await GetUrlMappings();
|
||||
_capturebrokenurls = PageState.Site.CaptureBrokenUrls.ToString();
|
||||
}
|
||||
|
||||
private async void MappedChanged(ChangeEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_mapped = bool.Parse(e.Value.ToString());
|
||||
await GetUrlMappings();
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error On TypeChanged");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteUrlMapping(UrlMapping urlMapping)
|
||||
{
|
||||
try
|
||||
{
|
||||
await UrlMappingService.DeleteUrlMappingAsync(urlMapping.UrlMappingId);
|
||||
await logger.LogInformation("UrlMapping Deleted {UrlMapping}", urlMapping);
|
||||
await GetUrlMappings();
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Deleting UrlMapping {UrlMapping} {Error}", urlMapping, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.DeleteUrlMapping"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GetUrlMappings()
|
||||
{
|
||||
_urlMappings = await UrlMappingService.GetUrlMappingsAsync(PageState.Site.SiteId, _mapped);
|
||||
}
|
||||
|
||||
private async Task SaveSiteSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
var site = PageState.Site;
|
||||
site.CaptureBrokenUrls = bool.Parse(_capturebrokenurls);
|
||||
await SiteService.UpdateSiteAsync(site);
|
||||
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
@inject IUserRoleService UserRoleService
|
||||
@inject IUserService UserService
|
||||
@inject ISettingService SettingService
|
||||
@inject ISiteService SiteService
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@ -14,45 +15,65 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<div class="col-sm-4">
|
||||
<ActionLink Action="Add" Text="Add User" ResourceKey="AddUser" />
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<input class="form-control" @bind="@_search" />
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<button type="button" class="btn btn-secondary" @onclick="OnSearch">@SharedLocalizer["Search"]</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Pager Items="@userroles">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@SharedLocalizer["Name"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td>
|
||||
<ActionLink Action="Edit" Parameters="@($"id=" + context.UserId.ToString())" ResourceKey="EditUser" />
|
||||
</td>
|
||||
<td>
|
||||
<ActionDialog Header="Delete User" Message="@string.Format(Localizer["Confirm.User.Delete"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteUser(context))" Disabled="@(context.UserId == PageState.User.UserId)" ResourceKey="DeleteUser" />
|
||||
</td>
|
||||
<td>
|
||||
<ActionLink Action="Roles" Parameters="@($"id=" + context.UserId.ToString())" ResourceKey="Roles" />
|
||||
</td>
|
||||
<td>@context.User.DisplayName</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
<TabStrip>
|
||||
<TabPanel Name="Users" Heading="Users" ResourceKey="Users">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<div class="col-sm-4">
|
||||
<ActionLink Action="Add" Text="Add User" ResourceKey="AddUser" />
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<input class="form-control" @bind="@_search" />
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<button type="button" class="btn btn-secondary" @onclick="OnSearch">@SharedLocalizer["Search"]</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Pager Items="@userroles">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@SharedLocalizer["Name"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td>
|
||||
<ActionLink Action="Edit" Parameters="@($"id=" + context.UserId.ToString())" ResourceKey="EditUser" />
|
||||
</td>
|
||||
<td>
|
||||
<ActionDialog Header="Delete User" Message="@string.Format(Localizer["Confirm.User.Delete"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteUser(context))" Disabled="@(context.UserId == PageState.User.UserId)" ResourceKey="DeleteUser" />
|
||||
</td>
|
||||
<td>
|
||||
<ActionLink Action="Roles" Parameters="@($"id=" + context.UserId.ToString())" ResourceKey="Roles" />
|
||||
</td>
|
||||
<td>@context.User.DisplayName</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
</TabPanel>
|
||||
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="allowregistration" HelpText="Do you want to allow visitors to be able to register for a user account on the site" ResourceKey="AllowRegistration">Allow User Registration? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="allowregistration" class="form-select" @bind="@_allowregistration" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||
</TabPanel>
|
||||
</TabStrip>
|
||||
}
|
||||
|
||||
@code {
|
||||
private List<UserRole> allroles;
|
||||
private List<UserRole> userroles;
|
||||
private string _search;
|
||||
private string _allowregistration;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||
|
||||
@ -61,6 +82,7 @@ else
|
||||
allroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId);
|
||||
await LoadSettingsAsync();
|
||||
userroles = Search(_search);
|
||||
_allowregistration = PageState.Site.AllowRegistration.ToString();
|
||||
}
|
||||
|
||||
private List<UserRole> Search(string search)
|
||||
@ -122,4 +144,20 @@ else
|
||||
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
|
||||
}
|
||||
|
||||
private async Task SaveSiteSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
var site = PageState.Site;
|
||||
site.AllowRegistration = bool.Parse(_allowregistration);
|
||||
await SiteService.UpdateSiteAsync(site);
|
||||
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
140
Oqtane.Client/Modules/Admin/Visitors/Index.razor
Normal file
140
Oqtane.Client/Modules/Admin/Visitors/Index.razor
Normal file
@ -0,0 +1,140 @@
|
||||
@namespace Oqtane.Modules.Admin.Visitors
|
||||
@inherits ModuleBase
|
||||
@inject IVisitorService VisitorService
|
||||
@inject ISiteService SiteService
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@if (_visitors == null)
|
||||
{
|
||||
<p><em>@SharedLocalizer["Loading"]</em></p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<TabStrip>
|
||||
<TabPanel Name="Visitors" Heading="Visitors" ResourceKey="Visitors">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<div class="col-sm-6">
|
||||
<select id="type" class="form-select custom-select" @onchange="(e => TypeChanged(e))">
|
||||
<option value="false">@Localizer["AllVisitors"]</option>
|
||||
<option value="true">@Localizer["UsersOnly"]</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<select id="type" class="form-select custom-select" @onchange="(e => DateChanged(e))">
|
||||
<option value="1">@Localizer["PastDay"]</option>
|
||||
<option value="7">@Localizer["PastWeek"]</option>
|
||||
<option value="30">@Localizer["PastMonth"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<Pager Items="@_visitors">
|
||||
<Header>
|
||||
<th>@Localizer["IP"]</th>
|
||||
<th>@Localizer["User"]</th>
|
||||
<th>@Localizer["Language"]</th>
|
||||
<th>@Localizer["Visits"]</th>
|
||||
<th>@Localizer["Visited"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td>@context.IPAddress</td>
|
||||
<td>
|
||||
@if (context.UserId != null)
|
||||
{
|
||||
@context.User.DisplayName
|
||||
}
|
||||
</td>
|
||||
<td>@context.Language</td>
|
||||
<td>@context.Visits</td>
|
||||
<td>@context.VisitedOn</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
</TabPanel>
|
||||
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="visitortracking" HelpText="Specify if visitor tracking is enabled" ResourceKey="VisitorTracking">Visitor Tracking Enabled? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="visitortracking" class="form-select" @bind="@_visitortracking" >
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||
</TabPanel>
|
||||
</TabStrip>
|
||||
}
|
||||
|
||||
@code {
|
||||
private bool _users = false;
|
||||
private int _days = 1;
|
||||
private List<Visitor> _visitors;
|
||||
private string _visitortracking;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
await GetVisitors();
|
||||
_visitortracking = PageState.Site.VisitorTracking.ToString();
|
||||
}
|
||||
|
||||
private async void TypeChanged(ChangeEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_users = bool.Parse(e.Value.ToString());
|
||||
await GetVisitors();
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error On TypeChanged");
|
||||
}
|
||||
}
|
||||
|
||||
private async void DateChanged(ChangeEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_days = int.Parse(e.Value.ToString());
|
||||
await GetVisitors();
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error On DateChanged");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GetVisitors()
|
||||
{
|
||||
_visitors = await VisitorService.GetVisitorsAsync(PageState.Site.SiteId, DateTime.UtcNow.AddDays(-_days));
|
||||
if (_users)
|
||||
{
|
||||
_visitors = _visitors.Where(item => item.UserId != null).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveSiteSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
var site = PageState.Site;
|
||||
site.VisitorTracking = bool.Parse(_visitortracking);
|
||||
await SiteService.UpdateSiteAsync(site);
|
||||
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
}
|
@ -36,7 +36,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Resources\" />
|
||||
<Folder Include="Resources\Themes\Controls\Theme\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -129,7 +129,7 @@
|
||||
<data name="DefaultContainer.Text" xml:space="preserve">
|
||||
<value>Default Container: </value>
|
||||
</data>
|
||||
<data name="Appearance.Name" xml:space="preserve">
|
||||
<data name="Appearance.Heading" xml:space="preserve">
|
||||
<value>Appearance</value>
|
||||
</data>
|
||||
<data name="DefaultAdminContainer" xml:space="preserve">
|
||||
@ -171,9 +171,6 @@
|
||||
<data name="Aliases.HelpText" xml:space="preserve">
|
||||
<value>The aliases for the site. An alias can be a domain name (www.site.com) or a virtual folder (ie. www.site.com/folder). If a site has multiple aliases they should be separated by commas.</value>
|
||||
</data>
|
||||
<data name="AllowRegistration.HelpText" xml:space="preserve">
|
||||
<value>Do you want the users to be able to register for an account on the site</value>
|
||||
</data>
|
||||
<data name="IsDeleted.HelpText" xml:space="preserve">
|
||||
<value>Is this site deleted?</value>
|
||||
</data>
|
||||
@ -225,9 +222,6 @@
|
||||
<data name="Aliases.Text" xml:space="preserve">
|
||||
<value>Aliases: </value>
|
||||
</data>
|
||||
<data name="AllowRegistration.Text" xml:space="preserve">
|
||||
<value>Allow User Registration? </value>
|
||||
</data>
|
||||
<data name="IsDeleted.Text" xml:space="preserve">
|
||||
<value>Is Deleted? </value>
|
||||
</data>
|
||||
@ -282,7 +276,7 @@
|
||||
<data name="Theme.Select" xml:space="preserve">
|
||||
<value>Select Theme</value>
|
||||
</data>
|
||||
<data name="Hosting" xml:space="preserve">
|
||||
<data name="Hosting.Heading" xml:space="preserve">
|
||||
<value>Hosting Model</value>
|
||||
</data>
|
||||
<data name="Prerender.HelpText" xml:space="preserve">
|
||||
@ -300,4 +294,13 @@
|
||||
<data name="Browse" xml:space="preserve">
|
||||
<value>Browse</value>
|
||||
</data>
|
||||
<data name="TenantInformation.Heading" xml:space="preserve">
|
||||
<value>Tenant Information</value>
|
||||
</data>
|
||||
<data name="PWASettings.Heading" xml:space="preserve">
|
||||
<value>PWA Settings</value>
|
||||
</data>
|
||||
<data name="SMTPSettings.Heading" xml:space="preserve">
|
||||
<value>SMTP Settings</value>
|
||||
</data>
|
||||
</root>
|
138
Oqtane.Client/Resources/Modules/Admin/UrlMappings/Add.resx
Normal file
138
Oqtane.Client/Resources/Modules/Admin/UrlMappings/Add.resx
Normal file
@ -0,0 +1,138 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="MappedUrl.Text" xml:space="preserve">
|
||||
<value>Redirect To:</value>
|
||||
</data>
|
||||
<data name="MappedUrl.HelpText" xml:space="preserve">
|
||||
<value>A fully qualified Url where the user will be redirected</value>
|
||||
</data>
|
||||
<data name="Url.HelpText" xml:space="preserve">
|
||||
<value>A fully qualified Url for this site</value>
|
||||
</data>
|
||||
<data name="Url.Text" xml:space="preserve">
|
||||
<value>Url:</value>
|
||||
</data>
|
||||
<data name="Error.SaveUrlMapping" xml:space="preserve">
|
||||
<value>Error Saving Url Mapping</value>
|
||||
</data>
|
||||
<data name="Message.InfoRequired" xml:space="preserve">
|
||||
<value>Please Provide All Required Information</value>
|
||||
</data>
|
||||
</root>
|
141
Oqtane.Client/Resources/Modules/Admin/UrlMappings/Edit.resx
Normal file
141
Oqtane.Client/Resources/Modules/Admin/UrlMappings/Edit.resx
Normal file
@ -0,0 +1,141 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="MappedUrl.Text" xml:space="preserve">
|
||||
<value>Redirect To:</value>
|
||||
</data>
|
||||
<data name="MappedUrl.HelpText" xml:space="preserve">
|
||||
<value>A fully qualified Url where the user will be redirected</value>
|
||||
</data>
|
||||
<data name="Url.HelpText" xml:space="preserve">
|
||||
<value>A fully qualified Url for this site</value>
|
||||
</data>
|
||||
<data name="Url.Text" xml:space="preserve">
|
||||
<value>Url:</value>
|
||||
</data>
|
||||
<data name="Error.LoadUrlMapping" xml:space="preserve">
|
||||
<value>Error Loading Url Mapping</value>
|
||||
</data>
|
||||
<data name="Error.SaveUrlMapping" xml:space="preserve">
|
||||
<value>Error Saving Url Mapping</value>
|
||||
</data>
|
||||
<data name="Message.InfoRequired" xml:space="preserve">
|
||||
<value>Please Provide All Required Information</value>
|
||||
</data>
|
||||
</root>
|
165
Oqtane.Client/Resources/Modules/Admin/UrlMappings/Index.resx
Normal file
165
Oqtane.Client/Resources/Modules/Admin/UrlMappings/Index.resx
Normal file
@ -0,0 +1,165 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Confirm.DeleteUrlMapping" xml:space="preserve">
|
||||
<value>Are You Sure You Wish To Delete {0}?</value>
|
||||
</data>
|
||||
<data name="AddUrlMapping.Text" xml:space="preserve">
|
||||
<value>Add Url Mapping</value>
|
||||
</data>
|
||||
<data name="DeleteUrlMapping.Header" xml:space="preserve">
|
||||
<value>Delete Url Mapping</value>
|
||||
</data>
|
||||
<data name="IP" xml:space="preserve">
|
||||
<value>IP</value>
|
||||
</data>
|
||||
<data name="User" xml:space="preserve">
|
||||
<value>User</value>
|
||||
</data>
|
||||
<data name="Visited" xml:space="preserve">
|
||||
<value>Visited</value>
|
||||
</data>
|
||||
<data name="Visits" xml:space="preserve">
|
||||
<value>Visits</value>
|
||||
</data>
|
||||
<data name="Mapped" xml:space="preserve">
|
||||
<value>Mapped Urls</value>
|
||||
</data>
|
||||
<data name="Broken" xml:space="preserve">
|
||||
<value>Broken Urls</value>
|
||||
</data>
|
||||
<data name="CaptureBrokenUrls.HelpText" xml:space="preserve">
|
||||
<value>Specify if broken Urls should be captured automatically and saved in Url Mappings</value>
|
||||
</data>
|
||||
<data name="CaptureBrokenUrls.Text" xml:space="preserve">
|
||||
<value>Capture Broken Urls?</value>
|
||||
</data>
|
||||
<data name="Error.SaveSiteSettings" xml:space="preserve">
|
||||
<value>Error Saving Settings</value>
|
||||
</data>
|
||||
<data name="Settings.Heading" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
<data name="Success.SaveSiteSettings" xml:space="preserve">
|
||||
<value>Settings Saved Successfully</value>
|
||||
</data>
|
||||
<data name="Urls.Heading" xml:space="preserve">
|
||||
<value>Urls</value>
|
||||
</data>
|
||||
</root>
|
@ -126,4 +126,22 @@
|
||||
<data name="DeleteUser.Header" xml:space="preserve">
|
||||
<value>Delete User</value>
|
||||
</data>
|
||||
<data name="AllowRegistration.HelpText" xml:space="preserve">
|
||||
<value>Do you want the users to be able to register for an account on the site</value>
|
||||
</data>
|
||||
<data name="AllowRegistration.Text" xml:space="preserve">
|
||||
<value>Allow User Registration? </value>
|
||||
</data>
|
||||
<data name="Error.SaveSiteSettings" xml:space="preserve">
|
||||
<value>Error Saving Settings</value>
|
||||
</data>
|
||||
<data name="Settings.Heading" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
<data name="Success.SaveSiteSettings" xml:space="preserve">
|
||||
<value>Settings Saved Successfully</value>
|
||||
</data>
|
||||
<data name="Users.Heading" xml:space="preserve">
|
||||
<value>Users</value>
|
||||
</data>
|
||||
</root>
|
165
Oqtane.Client/Resources/Modules/Admin/Visitors/Index.resx
Normal file
165
Oqtane.Client/Resources/Modules/Admin/Visitors/Index.resx
Normal file
@ -0,0 +1,165 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Url" xml:space="preserve">
|
||||
<value>Url</value>
|
||||
</data>
|
||||
<data name="Requested" xml:space="preserve">
|
||||
<value>Requested</value>
|
||||
</data>
|
||||
<data name="Requests" xml:space="preserve">
|
||||
<value>Requests</value>
|
||||
</data>
|
||||
<data name="AllVisitors" xml:space="preserve">
|
||||
<value>All Visitors</value>
|
||||
</data>
|
||||
<data name="PastDay" xml:space="preserve">
|
||||
<value>Past Day</value>
|
||||
</data>
|
||||
<data name="PastMonth" xml:space="preserve">
|
||||
<value>Past Month</value>
|
||||
</data>
|
||||
<data name="PastWeek" xml:space="preserve">
|
||||
<value>Past Week</value>
|
||||
</data>
|
||||
<data name="UsersOnly" xml:space="preserve">
|
||||
<value>Users Only</value>
|
||||
</data>
|
||||
<data name="Language" xml:space="preserve">
|
||||
<value>Language</value>
|
||||
</data>
|
||||
<data name="Error.SaveSiteSettings" xml:space="preserve">
|
||||
<value>Error Saving Settings</value>
|
||||
</data>
|
||||
<data name="Settings.Heading" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
<data name="Success.SaveSiteSettings" xml:space="preserve">
|
||||
<value>Settings Saved Successfully</value>
|
||||
</data>
|
||||
<data name="Visitors.Heading" xml:space="preserve">
|
||||
<value>Visitors</value>
|
||||
</data>
|
||||
<data name="VisitorTracking.HelpText" xml:space="preserve">
|
||||
<value>Specify if visitor tracking is enabled</value>
|
||||
</data>
|
||||
<data name="VisitorTracking.Text" xml:space="preserve">
|
||||
<value>Visitor Tracking Enabled?</value>
|
||||
</data>
|
||||
</root>
|
48
Oqtane.Client/Services/Interfaces/IUrlMappingService.cs
Normal file
48
Oqtane.Client/Services/Interfaces/IUrlMappingService.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using Oqtane.Models;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Oqtane.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Service to manage <see cref="UrlMapping"/>s on a <see cref="Site"/>
|
||||
/// </summary>
|
||||
public interface IUrlMappingService
|
||||
{
|
||||
/// <summary>
|
||||
/// Get all <see cref="UrlMapping"/>s of this <see cref="Site"/>.
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="siteId">ID-reference of a <see cref="Site"/></param>
|
||||
/// <returns></returns>
|
||||
Task<List<UrlMapping>> GetUrlMappingsAsync(int siteId, bool isMapped);
|
||||
|
||||
/// <summary>
|
||||
/// Get one specific <see cref="UrlMapping"/>
|
||||
/// </summary>
|
||||
/// <param name="urlMappingId">ID-reference of a <see cref="UrlMapping"/></param>
|
||||
/// <returns></returns>
|
||||
Task<UrlMapping> GetUrlMappingAsync(int urlMappingId);
|
||||
|
||||
/// <summary>
|
||||
/// Add / save a new <see cref="UrlMapping"/> to the database.
|
||||
/// </summary>
|
||||
/// <param name="urlMapping"></param>
|
||||
/// <returns></returns>
|
||||
Task<UrlMapping> AddUrlMappingAsync(UrlMapping urlMapping);
|
||||
|
||||
/// <summary>
|
||||
/// Update a <see cref="UrlMapping"/> in the database.
|
||||
/// </summary>
|
||||
/// <param name="urlMapping"></param>
|
||||
/// <returns></returns>
|
||||
Task<UrlMapping> UpdateUrlMappingAsync(UrlMapping urlMapping);
|
||||
|
||||
/// <summary>
|
||||
/// Delete a <see cref="UrlMapping"/> in the database.
|
||||
/// </summary>
|
||||
/// <param name="urlMappingId">ID-reference of a <see cref="UrlMapping"/></param>
|
||||
/// <returns></returns>
|
||||
Task DeleteUrlMappingAsync(int urlMappingId);
|
||||
}
|
||||
}
|
21
Oqtane.Client/Services/Interfaces/IVisitorService.cs
Normal file
21
Oqtane.Client/Services/Interfaces/IVisitorService.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using Oqtane.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Oqtane.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Service to manage <see cref="Visitor"/>s on a <see cref="Site"/>
|
||||
/// </summary>
|
||||
public interface IVisitorService
|
||||
{
|
||||
/// <summary>
|
||||
/// Get all <see cref="Visitor"/>s of this <see cref="Site"/>.
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="siteId">ID-reference of a <see cref="Site"/></param>
|
||||
/// <returns></returns>
|
||||
Task<List<Visitor>> GetVisitorsAsync(int siteId, DateTime fromDate);
|
||||
}
|
||||
}
|
51
Oqtane.Client/Services/UrlMappingService.cs
Normal file
51
Oqtane.Client/Services/UrlMappingService.cs
Normal file
@ -0,0 +1,51 @@
|
||||
using Oqtane.Models;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.Http;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Oqtane.Documentation;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace Oqtane.Services
|
||||
{
|
||||
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
|
||||
public class UrlMappingService : ServiceBase, IUrlMappingService
|
||||
{
|
||||
|
||||
private readonly SiteState _siteState;
|
||||
|
||||
public UrlMappingService(HttpClient http, SiteState siteState) : base(http)
|
||||
{
|
||||
|
||||
_siteState = siteState;
|
||||
}
|
||||
|
||||
private string Apiurl => CreateApiUrl("UrlMapping", _siteState.Alias);
|
||||
|
||||
public async Task<List<UrlMapping>> GetUrlMappingsAsync(int siteId, bool isMapped)
|
||||
{
|
||||
List<UrlMapping> urlMappings = await GetJsonAsync<List<UrlMapping>>($"{Apiurl}?siteid={siteId}&ismapped={isMapped}");
|
||||
return urlMappings.OrderByDescending(item => item.RequestedOn).ToList();
|
||||
}
|
||||
|
||||
public async Task<UrlMapping> GetUrlMappingAsync(int urlMappingId)
|
||||
{
|
||||
return await GetJsonAsync<UrlMapping>($"{Apiurl}/{urlMappingId}");
|
||||
}
|
||||
|
||||
public async Task<UrlMapping> AddUrlMappingAsync(UrlMapping role)
|
||||
{
|
||||
return await PostJsonAsync<UrlMapping>(Apiurl, role);
|
||||
}
|
||||
|
||||
public async Task<UrlMapping> UpdateUrlMappingAsync(UrlMapping role)
|
||||
{
|
||||
return await PutJsonAsync<UrlMapping>($"{Apiurl}/{role.UrlMappingId}", role);
|
||||
}
|
||||
|
||||
public async Task DeleteUrlMappingAsync(int urlMappingId)
|
||||
{
|
||||
await DeleteAsync($"{Apiurl}/{urlMappingId}");
|
||||
}
|
||||
}
|
||||
}
|
32
Oqtane.Client/Services/VisitorService.cs
Normal file
32
Oqtane.Client/Services/VisitorService.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using Oqtane.Models;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.Http;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Oqtane.Documentation;
|
||||
using Oqtane.Shared;
|
||||
using System;
|
||||
|
||||
namespace Oqtane.Services
|
||||
{
|
||||
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
|
||||
public class VisitorService : ServiceBase, IVisitorService
|
||||
{
|
||||
|
||||
private readonly SiteState _siteState;
|
||||
|
||||
public VisitorService(HttpClient http, SiteState siteState) : base(http)
|
||||
{
|
||||
|
||||
_siteState = siteState;
|
||||
}
|
||||
|
||||
private string Apiurl => CreateApiUrl("Visitor", _siteState.Alias);
|
||||
|
||||
public async Task<List<Visitor>> GetVisitorsAsync(int siteId, DateTime fromDate)
|
||||
{
|
||||
List<Visitor> visitors = await GetJsonAsync<List<Visitor>>($"{Apiurl}?siteid={siteId}&fromdate={fromDate.ToString("dd-MMM-yyyy")}");
|
||||
return visitors.OrderByDescending(item => item.VisitedOn).ToList();
|
||||
}
|
||||
}
|
||||
}
|
@ -20,5 +20,6 @@ namespace Oqtane.UI
|
||||
public bool EditMode { get; set; }
|
||||
public DateTime LastSyncDate { get; set; }
|
||||
public Oqtane.Shared.Runtime Runtime { get; set; }
|
||||
public int VisitorId { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,9 @@
|
||||
[Parameter]
|
||||
public string RenderMode { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public int VisitorId { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
PageState PageState { get; set; }
|
||||
|
||||
@ -221,7 +224,8 @@
|
||||
Action = action,
|
||||
EditMode = editmode,
|
||||
LastSyncDate = lastsyncdate,
|
||||
Runtime = runtime
|
||||
Runtime = runtime,
|
||||
VisitorId = VisitorId
|
||||
};
|
||||
|
||||
OnStateChange?.Invoke(_pagestate);
|
||||
|
@ -164,6 +164,14 @@ namespace Oqtane.Controllers
|
||||
authorized = User.IsInRole(RoleNames.Admin) || (_userPermissions.GetUser(User).UserId == entityId);
|
||||
}
|
||||
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);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return authorized;
|
||||
}
|
||||
|
119
Oqtane.Server/Controllers/UrlMappingController.cs
Normal file
119
Oqtane.Server/Controllers/UrlMappingController.cs
Normal file
@ -0,0 +1,119 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Oqtane.Enums;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Shared;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Repository;
|
||||
using System.Net;
|
||||
|
||||
namespace Oqtane.Controllers
|
||||
{
|
||||
[Route(ControllerRoutes.ApiRoute)]
|
||||
public class UrlMappingController : Controller
|
||||
{
|
||||
private readonly IUrlMappingRepository _urlMappings;
|
||||
private readonly ILogManager _logger;
|
||||
private readonly Alias _alias;
|
||||
|
||||
public UrlMappingController(IUrlMappingRepository urlMappings, ILogManager logger, ITenantManager tenantManager)
|
||||
{
|
||||
_urlMappings = urlMappings;
|
||||
_logger = logger;
|
||||
_alias = tenantManager.GetAlias();
|
||||
}
|
||||
|
||||
// GET: api/<controller>?siteid=x&ismapped=y
|
||||
[HttpGet]
|
||||
[Authorize(Roles = RoleNames.Admin)]
|
||||
public IEnumerable<UrlMapping> Get(string siteid, string ismapped)
|
||||
{
|
||||
int SiteId;
|
||||
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId)
|
||||
{
|
||||
return _urlMappings.GetUrlMappings(SiteId, bool.Parse(ismapped));
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized UrlMapping Get Attempt {SiteId}", siteid);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// GET api/<controller>/5
|
||||
[HttpGet("{id}")]
|
||||
[Authorize(Roles = RoleNames.Admin)]
|
||||
public UrlMapping Get(int id)
|
||||
{
|
||||
var urlMapping = _urlMappings.GetUrlMapping(id);
|
||||
if (urlMapping != null && (urlMapping.SiteId == _alias.SiteId))
|
||||
{
|
||||
return urlMapping;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized UrlMapping Get Attempt {UrlMappingId}", id);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// POST api/<controller>
|
||||
[HttpPost]
|
||||
[Authorize(Roles = RoleNames.Admin)]
|
||||
public UrlMapping Post([FromBody] UrlMapping urlMapping)
|
||||
{
|
||||
if (ModelState.IsValid && urlMapping.SiteId == _alias.SiteId)
|
||||
{
|
||||
urlMapping = _urlMappings.AddUrlMapping(urlMapping);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "UrlMapping Added {UrlMapping}", urlMapping);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized UrlMapping Post Attempt {Role}", urlMapping);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
urlMapping = null;
|
||||
}
|
||||
return urlMapping;
|
||||
}
|
||||
|
||||
// PUT api/<controller>/5
|
||||
[HttpPut("{id}")]
|
||||
[Authorize(Roles = RoleNames.Admin)]
|
||||
public UrlMapping Put(int id, [FromBody] UrlMapping urlMapping)
|
||||
{
|
||||
if (ModelState.IsValid && urlMapping.SiteId == _alias.SiteId && _urlMappings.GetUrlMapping(urlMapping.UrlMappingId, false) != null)
|
||||
{
|
||||
urlMapping = _urlMappings.UpdateUrlMapping(urlMapping);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "UrlMapping Updated {UrlMapping}", urlMapping);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized UrlMapping Put Attempt {UrlMapping}", urlMapping);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
urlMapping = null;
|
||||
}
|
||||
return urlMapping;
|
||||
}
|
||||
|
||||
// DELETE api/<controller>/5
|
||||
[HttpDelete("{id}")]
|
||||
[Authorize(Roles = RoleNames.Admin)]
|
||||
public void Delete(int id)
|
||||
{
|
||||
var urlMapping = _urlMappings.GetUrlMapping(id);
|
||||
if (urlMapping != null && urlMapping.SiteId == _alias.SiteId)
|
||||
{
|
||||
_urlMappings.DeleteUrlMapping(id);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "UrlMapping Deleted {UrlMappingId}", id);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized UrlMapping Delete Attempt {UrlMappingId}", id);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
46
Oqtane.Server/Controllers/VisitorController.cs
Normal file
46
Oqtane.Server/Controllers/VisitorController.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Oqtane.Enums;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Shared;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Repository;
|
||||
using System.Net;
|
||||
using System;
|
||||
|
||||
namespace Oqtane.Controllers
|
||||
{
|
||||
[Route(ControllerRoutes.ApiRoute)]
|
||||
public class VisitorController : Controller
|
||||
{
|
||||
private readonly IVisitorRepository _visitors;
|
||||
private readonly ILogManager _logger;
|
||||
private readonly Alias _alias;
|
||||
|
||||
public VisitorController(IVisitorRepository visitors, ILogManager logger, ITenantManager tenantManager)
|
||||
{
|
||||
_visitors = visitors;
|
||||
_logger = logger;
|
||||
_alias = tenantManager.GetAlias();
|
||||
}
|
||||
|
||||
// GET: api/<controller>?siteid=x&fromdate=y
|
||||
[HttpGet]
|
||||
[Authorize(Roles = RoleNames.Admin)]
|
||||
public IEnumerable<Visitor> Get(string siteid, string fromdate)
|
||||
{
|
||||
int SiteId;
|
||||
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId)
|
||||
{
|
||||
return _visitors.GetVisitors(SiteId, DateTime.Parse(fromdate));
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Visitor Get Attempt {SiteId}", siteid);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -98,6 +98,8 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
services.AddTransient<ISqlRepository, SqlRepository>();
|
||||
services.AddTransient<IUpgradeManager, UpgradeManager>();
|
||||
services.AddTransient<ILanguageRepository, LanguageRepository>();
|
||||
services.AddTransient<IVisitorRepository, VisitorRepository>();
|
||||
services.AddTransient<IUrlMappingRepository, UrlMappingRepository>();
|
||||
// obsolete - replaced by ITenantManager
|
||||
services.AddTransient<ITenantResolver, TenantResolver>();
|
||||
|
||||
|
@ -582,12 +582,19 @@ namespace Oqtane.Infrastructure
|
||||
TenantId = tenant.TenantId,
|
||||
Name = install.SiteName,
|
||||
LogoFileId = null,
|
||||
FaviconFileId = null,
|
||||
PwaIsEnabled = false,
|
||||
PwaAppIconFileId = null,
|
||||
PwaSplashIconFileId = null,
|
||||
AllowRegistration = false,
|
||||
CaptureBrokenUrls = true,
|
||||
VisitorTracking = true,
|
||||
DefaultThemeType = (!string.IsNullOrEmpty(install.DefaultTheme)) ? install.DefaultTheme : Constants.DefaultTheme,
|
||||
DefaultContainerType = (!string.IsNullOrEmpty(install.DefaultContainer)) ? install.DefaultContainer : Constants.DefaultContainer,
|
||||
AdminContainerType = (!string.IsNullOrEmpty(install.DefaultAdminContainer)) ? install.DefaultAdminContainer : Constants.DefaultAdminContainer,
|
||||
SiteTemplateType = install.SiteTemplate,
|
||||
Runtime = (!string.IsNullOrEmpty(install.Runtime)) ? install.Runtime : _configManager.GetSection("Runtime").Value,
|
||||
RenderMode = (!string.IsNullOrEmpty(install.RenderMode)) ? install.RenderMode : _configManager.GetSection("RenderMode").Value
|
||||
RenderMode = (!string.IsNullOrEmpty(install.RenderMode)) ? install.RenderMode : _configManager.GetSection("RenderMode").Value,
|
||||
};
|
||||
site = sites.AddSite(site);
|
||||
|
||||
|
@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Newtonsoft.Json;
|
||||
using Oqtane.Extensions;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Repository;
|
||||
using Oqtane.Shared;
|
||||
@ -37,9 +38,6 @@ namespace Oqtane.Infrastructure
|
||||
|
||||
switch (version)
|
||||
{
|
||||
case "1.0.0":
|
||||
Upgrade_1_0_0(tenant, scope);
|
||||
break;
|
||||
case "2.0.2":
|
||||
Upgrade_2_0_2(tenant, scope);
|
||||
break;
|
||||
@ -49,61 +47,13 @@ namespace Oqtane.Infrastructure
|
||||
case "2.2.0":
|
||||
Upgrade_2_2_0(tenant, scope);
|
||||
break;
|
||||
case "3.0.1":
|
||||
Upgrade_3_0_1(tenant, scope);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// **Note: this code is commented out on purpose - it provides an example of how to programmatically add a page to all existing sites on upgrade
|
||||
/// </summary>
|
||||
/// <param name="tenant"></param>
|
||||
/// <param name="scope"></param>
|
||||
private void Upgrade_1_0_0(Tenant tenant, IServiceScope scope)
|
||||
{
|
||||
//var pageTemplates = new List<PageTemplate>();
|
||||
//
|
||||
//pageTemplates.Add(new PageTemplate
|
||||
//{
|
||||
// Name = "Test",
|
||||
// Parent = "",
|
||||
// Order = 1,
|
||||
// Path = "test",
|
||||
// Icon = Icons.Badge,
|
||||
// IsNavigation = true,
|
||||
// IsPersonalizable = false,
|
||||
// IsClickable = true,
|
||||
// PagePermissions = new List<Permission>
|
||||
// {
|
||||
// new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||
// new Permission(PermissionNames.View, RoleNames.Everyone, true),
|
||||
// new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||
// }.EncodePermissions(),
|
||||
// PageTemplateModules = new List<PageTemplateModule>
|
||||
// {
|
||||
// new PageTemplateModule
|
||||
// {
|
||||
// ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Login.Index).ToModuleDefinitionName(), Title = "Test", Pane = "Content",
|
||||
// ModulePermissions = new List<Permission>
|
||||
// {
|
||||
// new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||
// new Permission(PermissionNames.View, RoleNames.Everyone, true),
|
||||
// new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||
// }.EncodePermissions(),
|
||||
// Content = ""
|
||||
// }
|
||||
// }
|
||||
//});
|
||||
//
|
||||
//if (pageTemplates.Count != 0)
|
||||
//{
|
||||
// var sites = scope.ServiceProvider.GetRequiredService<ISiteRepository>();
|
||||
// foreach (Site site in sites.GetSites().ToList())
|
||||
// {
|
||||
// sites.CreatePages(site, pageTemplates);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
private void Upgrade_2_0_2(Tenant tenant, IServiceScope scope)
|
||||
{
|
||||
if (tenant.Name == TenantNames.Master)
|
||||
@ -163,5 +113,74 @@ namespace Oqtane.Infrastructure
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Upgrade_3_0_1(Tenant tenant, IServiceScope scope)
|
||||
{
|
||||
var pageTemplates = new List<PageTemplate>();
|
||||
|
||||
pageTemplates.Add(new PageTemplate
|
||||
{
|
||||
Name = "Url Mappings",
|
||||
Parent = "Admin",
|
||||
Order = 33,
|
||||
Path = "admin/urlmappings",
|
||||
Icon = Icons.LinkBroken,
|
||||
IsNavigation = true,
|
||||
IsPersonalizable = false,
|
||||
PagePermissions = new List<Permission>
|
||||
{
|
||||
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||
}.EncodePermissions(),
|
||||
PageTemplateModules = new List<PageTemplateModule>
|
||||
{
|
||||
new PageTemplateModule
|
||||
{
|
||||
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.UrlMappings.Index).ToModuleDefinitionName(), Title = "Url Mappings", Pane = PaneNames.Admin,
|
||||
ModulePermissions = new List<Permission>
|
||||
{
|
||||
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||
}.EncodePermissions(),
|
||||
Content = ""
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
pageTemplates.Add(new PageTemplate
|
||||
{
|
||||
Name = "Visitor Management",
|
||||
Parent = "Admin",
|
||||
Order = 35,
|
||||
Path = "admin/visitors",
|
||||
Icon = Icons.Eye,
|
||||
IsNavigation = true,
|
||||
IsPersonalizable = false,
|
||||
PagePermissions = new List<Permission>
|
||||
{
|
||||
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||
}.EncodePermissions(),
|
||||
PageTemplateModules = new List<PageTemplateModule>
|
||||
{
|
||||
new PageTemplateModule
|
||||
{
|
||||
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Visitors.Index).ToModuleDefinitionName(), Title = "Visitor Management", Pane = PaneNames.Admin,
|
||||
ModulePermissions = new List<Permission>
|
||||
{
|
||||
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||
}.EncodePermissions(),
|
||||
Content = ""
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var sites = scope.ServiceProvider.GetRequiredService<ISiteRepository>();
|
||||
foreach (Site site in sites.GetSites().ToList())
|
||||
{
|
||||
sites.CreatePages(site, pageTemplates);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,51 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations.Operations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders;
|
||||
using Oqtane.Databases.Interfaces;
|
||||
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
// ReSharper disable UnusedAutoPropertyAccessor.Global
|
||||
|
||||
namespace Oqtane.Migrations.EntityBuilders
|
||||
{
|
||||
public class UrlMappingEntityBuilder : BaseEntityBuilder<UrlMappingEntityBuilder>
|
||||
{
|
||||
private const string _entityTableName = "UrlMapping";
|
||||
private readonly PrimaryKey<UrlMappingEntityBuilder> _primaryKey = new("PK_UrlMapping", x => x.UrlMappingId);
|
||||
private readonly ForeignKey<UrlMappingEntityBuilder> _urlMappingForeignKey = new("FK_UrlMapping_Site", x => x.SiteId, "Site", "SiteId", ReferentialAction.Cascade);
|
||||
|
||||
public UrlMappingEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database)
|
||||
{
|
||||
EntityTableName = _entityTableName;
|
||||
PrimaryKey = _primaryKey;
|
||||
ForeignKeys.Add(_urlMappingForeignKey);
|
||||
}
|
||||
|
||||
protected override UrlMappingEntityBuilder BuildTable(ColumnsBuilder table)
|
||||
{
|
||||
UrlMappingId = AddAutoIncrementColumn(table, "UrlMappingId");
|
||||
SiteId = AddIntegerColumn(table, "SiteId");
|
||||
Url = AddStringColumn(table, "Url", 500);
|
||||
MappedUrl = AddStringColumn(table, "MappedUrl", 500);
|
||||
Requests = AddIntegerColumn(table, "Requests");
|
||||
CreatedOn = AddDateTimeColumn(table, "CreatedOn");
|
||||
RequestedOn = AddDateTimeColumn(table, "RequestedOn");
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public OperationBuilder<AddColumnOperation> UrlMappingId { get; private set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> SiteId { get; private set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> Url { get; private set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> MappedUrl { get; private set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> Requests { get; private set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> CreatedOn { get; private set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> RequestedOn { get; private set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations.Operations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders;
|
||||
using Oqtane.Databases.Interfaces;
|
||||
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
// ReSharper disable UnusedAutoPropertyAccessor.Global
|
||||
|
||||
namespace Oqtane.Migrations.EntityBuilders
|
||||
{
|
||||
public class VisitorEntityBuilder : BaseEntityBuilder<VisitorEntityBuilder>
|
||||
{
|
||||
private const string _entityTableName = "Visitor";
|
||||
private readonly PrimaryKey<VisitorEntityBuilder> _primaryKey = new("PK_Visitor", x => x.VisitorId);
|
||||
private readonly ForeignKey<VisitorEntityBuilder> _visitorForeignKey = new("FK_Visitor_Site", x => x.SiteId, "Site", "SiteId", ReferentialAction.Cascade);
|
||||
|
||||
public VisitorEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database)
|
||||
{
|
||||
EntityTableName = _entityTableName;
|
||||
PrimaryKey = _primaryKey;
|
||||
ForeignKeys.Add(_visitorForeignKey);
|
||||
}
|
||||
|
||||
protected override VisitorEntityBuilder BuildTable(ColumnsBuilder table)
|
||||
{
|
||||
VisitorId = AddAutoIncrementColumn(table, "VisitorId");
|
||||
SiteId = AddIntegerColumn(table, "SiteId");
|
||||
UserId = AddIntegerColumn(table, "UserId", true);
|
||||
Visits = AddIntegerColumn(table, "Visits");
|
||||
IPAddress = AddStringColumn(table,"IPAddress", 50);
|
||||
UserAgent = AddStringColumn(table, "UserAgent", 256);
|
||||
Language = AddStringColumn(table, "Language", 50);
|
||||
CreatedOn = AddDateTimeColumn(table, "CreatedOn");
|
||||
VisitedOn = AddDateTimeColumn(table, "VisitedOn");
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public OperationBuilder<AddColumnOperation> VisitorId { get; private set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> SiteId { get; private set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> UserId { get; private set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> Visits { get; private set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> IPAddress { get; private set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> UserAgent { get; private set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> Language { get; private set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> CreatedOn { get; private set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> VisitedOn { get; private set; }
|
||||
}
|
||||
}
|
29
Oqtane.Server/Migrations/Tenant/03000102_AddVisitorTable.cs
Normal file
29
Oqtane.Server/Migrations/Tenant/03000102_AddVisitorTable.cs
Normal file
@ -0,0 +1,29 @@
|
||||
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.02")]
|
||||
public class AddVisitorTable : MultiDatabaseMigration
|
||||
{
|
||||
public AddVisitorTable(IDatabase database) : base(database)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var visitorEntityBuilder = new VisitorEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
visitorEntityBuilder.Create();
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var visitorEntityBuilder = new VisitorEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
visitorEntityBuilder.Drop();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
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.03")]
|
||||
public class AddUrlMappingTable : MultiDatabaseMigration
|
||||
{
|
||||
public AddUrlMappingTable(IDatabase database) : base(database)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var urlMappingEntityBuilder = new UrlMappingEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
urlMappingEntityBuilder.Create();
|
||||
urlMappingEntityBuilder.AddIndex("IX_UrlMapping", new[] { "SiteId", "Url" }, true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var urlMappingEntityBuilder = new UrlMappingEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
urlMappingEntityBuilder.Drop();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
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.04")]
|
||||
public class AddSiteVisitorTracking : MultiDatabaseMigration
|
||||
{
|
||||
public AddSiteVisitorTracking(IDatabase database) : base(database)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
|
||||
siteEntityBuilder.AddBooleanColumn("VisitorTracking", true);
|
||||
siteEntityBuilder.UpdateColumn("VisitorTracking", "1", "bool", "");
|
||||
siteEntityBuilder.AddBooleanColumn("CaptureBrokenUrls", true);
|
||||
siteEntityBuilder.UpdateColumn("CaptureBrokenUrls", "1", "bool", "");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
|
||||
siteEntityBuilder.DropColumn("VisitorTracking");
|
||||
siteEntityBuilder.DropColumn("CaptureBrokenUrls");
|
||||
}
|
||||
}
|
||||
}
|
@ -22,7 +22,7 @@
|
||||
<body>
|
||||
@(Html.AntiForgeryToken())
|
||||
<app>
|
||||
<component type="typeof(Oqtane.App)" render-mode="@Model.RenderMode" param-AntiForgeryToken="@Model.AntiForgeryToken" param-Runtime="@Model.Runtime" param-RenderMode="@Model.RenderMode.ToString()" />
|
||||
<component type="typeof(Oqtane.App)" render-mode="@Model.RenderMode" param-AntiForgeryToken="@Model.AntiForgeryToken" param-Runtime="@Model.Runtime" param-RenderMode="@Model.RenderMode.ToString()" param-VisitorId="@Model.VisitorId" />
|
||||
</app>
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
|
@ -14,6 +14,10 @@ using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.AspNetCore.Antiforgery;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace Oqtane.Pages
|
||||
{
|
||||
@ -26,8 +30,10 @@ namespace Oqtane.Pages
|
||||
private readonly IAntiforgery _antiforgery;
|
||||
private readonly ISiteRepository _sites;
|
||||
private readonly IPageRepository _pages;
|
||||
private readonly IUrlMappingRepository _urlMappings;
|
||||
private readonly IVisitorRepository _visitors;
|
||||
|
||||
public HostModel(IConfiguration configuration, ITenantManager tenantManager, ILocalizationManager localizationManager, ILanguageRepository languages, IAntiforgery antiforgery, ISiteRepository sites, IPageRepository pages)
|
||||
public HostModel(IConfiguration configuration, ITenantManager tenantManager, ILocalizationManager localizationManager, ILanguageRepository languages, IAntiforgery antiforgery, ISiteRepository sites, IPageRepository pages, IUrlMappingRepository urlMappings, IVisitorRepository visitors)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_tenantManager = tenantManager;
|
||||
@ -36,11 +42,14 @@ namespace Oqtane.Pages
|
||||
_antiforgery = antiforgery;
|
||||
_sites = sites;
|
||||
_pages = pages;
|
||||
_urlMappings = urlMappings;
|
||||
_visitors = visitors;
|
||||
}
|
||||
|
||||
public string AntiForgeryToken = "";
|
||||
public string Runtime = "Server";
|
||||
public RenderMode RenderMode = RenderMode.Server;
|
||||
public int VisitorId = -1;
|
||||
public string HeadResources = "";
|
||||
public string BodyResources = "";
|
||||
public string Title = "";
|
||||
@ -48,7 +57,7 @@ namespace Oqtane.Pages
|
||||
public string PWAScript = "";
|
||||
public string ThemeType = "";
|
||||
|
||||
public void OnGet()
|
||||
public IActionResult OnGet()
|
||||
{
|
||||
AntiForgeryToken = _antiforgery.GetAndStoreTokens(HttpContext).RequestToken;
|
||||
|
||||
@ -92,6 +101,11 @@ namespace Oqtane.Pages
|
||||
Title = site.Name;
|
||||
ThemeType = site.DefaultThemeType;
|
||||
|
||||
if (site.VisitorTracking)
|
||||
{
|
||||
TrackVisitor(site.SiteId);
|
||||
}
|
||||
|
||||
var page = _pages.GetPage(route.PagePath, site.SiteId);
|
||||
if (page != null)
|
||||
{
|
||||
@ -111,6 +125,37 @@ namespace Oqtane.Pages
|
||||
ThemeType = page.ThemeType;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// page does not exist
|
||||
var url = route.SiteUrl + "/" + route.PagePath;
|
||||
var urlMapping = _urlMappings.GetUrlMapping(site.SiteId, url);
|
||||
if (urlMapping == null)
|
||||
{
|
||||
if (site.CaptureBrokenUrls)
|
||||
{
|
||||
urlMapping = new UrlMapping();
|
||||
urlMapping.SiteId = site.SiteId;
|
||||
urlMapping.Url = url;
|
||||
urlMapping.MappedUrl = "";
|
||||
urlMapping.Requests = 1;
|
||||
urlMapping.CreatedOn = DateTime.UtcNow;
|
||||
urlMapping.RequestedOn = DateTime.UtcNow;
|
||||
_urlMappings.AddUrlMapping(urlMapping);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
urlMapping.Requests += 1;
|
||||
urlMapping.RequestedOn = DateTime.UtcNow;
|
||||
_urlMappings.UpdateUrlMapping(urlMapping);
|
||||
|
||||
if (!string.IsNullOrEmpty(urlMapping.MappedUrl))
|
||||
{
|
||||
return RedirectPermanent(urlMapping.MappedUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// include global resources
|
||||
@ -139,6 +184,64 @@ namespace Oqtane.Pages
|
||||
}
|
||||
}
|
||||
}
|
||||
return Page();
|
||||
}
|
||||
|
||||
private void TrackVisitor(int SiteId)
|
||||
{
|
||||
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.Visits = 1;
|
||||
visitor.CreatedOn = DateTime.UtcNow;
|
||||
visitor.VisitedOn = DateTime.UtcNow;
|
||||
visitor = _visitors.AddVisitor(visitor);
|
||||
|
||||
Response.Cookies.Append(
|
||||
VisitorCookie,
|
||||
visitor.VisitorId.ToString(),
|
||||
new CookieOptions()
|
||||
{
|
||||
Expires = DateTimeOffset.UtcNow.AddYears(1),
|
||||
IsEssential = true
|
||||
}
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
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.Language = visitor.Language.Substring(0, visitor.Language.IndexOf(","));
|
||||
}
|
||||
if (User.HasClaim(item => item.Type == ClaimTypes.PrimarySid))
|
||||
{
|
||||
visitor.UserId = int.Parse(User.Claims.First(item => item.Type == ClaimTypes.PrimarySid).Value);
|
||||
}
|
||||
visitor.Visits += 1;
|
||||
visitor.VisitedOn = DateTime.UtcNow;
|
||||
_visitors.UpdateVisitor(visitor);
|
||||
}
|
||||
else
|
||||
{
|
||||
Response.Cookies.Delete(VisitorCookie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string CreatePWAScript(Alias alias, Site site, Route route)
|
||||
|
@ -29,5 +29,7 @@ namespace Oqtane.Repository
|
||||
public virtual DbSet<Folder> Folder { get; set; }
|
||||
public virtual DbSet<File> File { get; set; }
|
||||
public virtual DbSet<Language> Language { get; set; }
|
||||
public virtual DbSet<Visitor> Visitor { get; set; }
|
||||
public virtual DbSet<UrlMapping> UrlMapping { get; set; }
|
||||
}
|
||||
}
|
||||
|
17
Oqtane.Server/Repository/Interfaces/IUrlMappingRepository.cs
Normal file
17
Oqtane.Server/Repository/Interfaces/IUrlMappingRepository.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Oqtane.Models;
|
||||
|
||||
namespace Oqtane.Repository
|
||||
{
|
||||
public interface IUrlMappingRepository
|
||||
{
|
||||
IEnumerable<UrlMapping> GetUrlMappings(int siteId, bool isMapped);
|
||||
UrlMapping AddUrlMapping(UrlMapping urlMapping);
|
||||
UrlMapping UpdateUrlMapping(UrlMapping urlMapping);
|
||||
UrlMapping GetUrlMapping(int urlMappingId);
|
||||
UrlMapping GetUrlMapping(int urlMappingId, bool tracking);
|
||||
UrlMapping GetUrlMapping(int siteId, string url);
|
||||
void DeleteUrlMapping(int urlMappingId);
|
||||
}
|
||||
}
|
15
Oqtane.Server/Repository/Interfaces/IVisitorRepository.cs
Normal file
15
Oqtane.Server/Repository/Interfaces/IVisitorRepository.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Oqtane.Models;
|
||||
|
||||
namespace Oqtane.Repository
|
||||
{
|
||||
public interface IVisitorRepository
|
||||
{
|
||||
IEnumerable<Visitor> GetVisitors(int siteId, DateTime fromDate);
|
||||
Visitor AddVisitor(Visitor visitor);
|
||||
Visitor UpdateVisitor(Visitor visitor);
|
||||
Visitor GetVisitor(int visitorId);
|
||||
void DeleteVisitor(int visitorId);
|
||||
}
|
||||
}
|
@ -615,13 +615,70 @@ namespace Oqtane.Repository
|
||||
}
|
||||
}
|
||||
});
|
||||
pageTemplates.Add(new PageTemplate
|
||||
{
|
||||
Name = "Url Mappings",
|
||||
Parent = "Admin",
|
||||
Order = 15,
|
||||
Path = "admin/urlmappings",
|
||||
Icon = Icons.LinkBroken,
|
||||
IsNavigation = true,
|
||||
IsPersonalizable = false,
|
||||
PagePermissions = new List<Permission>
|
||||
{
|
||||
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||
}.EncodePermissions(),
|
||||
PageTemplateModules = new List<PageTemplateModule>
|
||||
{
|
||||
new PageTemplateModule
|
||||
{
|
||||
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.UrlMappings.Index).ToModuleDefinitionName(), Title = "Url Mappings", Pane = PaneNames.Admin,
|
||||
ModulePermissions = new List<Permission>
|
||||
{
|
||||
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||
}.EncodePermissions(),
|
||||
Content = ""
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
pageTemplates.Add(new PageTemplate
|
||||
{
|
||||
Name = "Visitor Management",
|
||||
Parent = "Admin",
|
||||
Order = 17,
|
||||
Path = "admin/visitors",
|
||||
Icon = Icons.Eye,
|
||||
IsNavigation = true,
|
||||
IsPersonalizable = false,
|
||||
PagePermissions = new List<Permission>
|
||||
{
|
||||
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||
}.EncodePermissions(),
|
||||
PageTemplateModules = new List<PageTemplateModule>
|
||||
{
|
||||
new PageTemplateModule
|
||||
{
|
||||
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Visitors.Index).ToModuleDefinitionName(), Title = "Visitor Management", Pane = PaneNames.Admin,
|
||||
ModulePermissions = new List<Permission>
|
||||
{
|
||||
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||
}.EncodePermissions(),
|
||||
Content = ""
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// host pages
|
||||
pageTemplates.Add(new PageTemplate
|
||||
{
|
||||
Name = "Event Log",
|
||||
Parent = "Admin",
|
||||
Order = 15,
|
||||
Order = 19,
|
||||
Path = "admin/log",
|
||||
Icon = Icons.MagnifyingGlass,
|
||||
IsNavigation = false,
|
||||
@ -649,7 +706,7 @@ namespace Oqtane.Repository
|
||||
{
|
||||
Name = "Site Management",
|
||||
Parent = "Admin",
|
||||
Order = 17,
|
||||
Order = 21,
|
||||
Path = "admin/sites",
|
||||
Icon = Icons.Globe,
|
||||
IsNavigation = false,
|
||||
@ -677,7 +734,7 @@ namespace Oqtane.Repository
|
||||
{
|
||||
Name = "Module Management",
|
||||
Parent = "Admin",
|
||||
Order = 19,
|
||||
Order = 23,
|
||||
Path = "admin/modules",
|
||||
Icon = Icons.Browser,
|
||||
IsNavigation = false,
|
||||
@ -705,7 +762,7 @@ namespace Oqtane.Repository
|
||||
{
|
||||
Name = "Theme Management",
|
||||
Parent = "Admin",
|
||||
Order = 21,
|
||||
Order = 25,
|
||||
Path = "admin/themes",
|
||||
Icon = Icons.Brush,
|
||||
IsNavigation = false,
|
||||
@ -733,7 +790,7 @@ namespace Oqtane.Repository
|
||||
{
|
||||
Name = "Language Management",
|
||||
Parent = "Admin",
|
||||
Order = 23,
|
||||
Order = 27,
|
||||
Path = "admin/languages",
|
||||
Icon = Icons.Text,
|
||||
IsNavigation = false,
|
||||
@ -765,7 +822,7 @@ namespace Oqtane.Repository
|
||||
{
|
||||
Name = "Scheduled Jobs",
|
||||
Parent = "Admin",
|
||||
Order = 25,
|
||||
Order = 29,
|
||||
Path = "admin/jobs",
|
||||
Icon = Icons.Timer,
|
||||
IsNavigation = false,
|
||||
@ -793,7 +850,7 @@ namespace Oqtane.Repository
|
||||
{
|
||||
Name = "Sql Management",
|
||||
Parent = "Admin",
|
||||
Order = 27,
|
||||
Order = 31,
|
||||
Path = "admin/sql",
|
||||
Icon = Icons.Spreadsheet,
|
||||
IsNavigation = false,
|
||||
@ -821,7 +878,7 @@ namespace Oqtane.Repository
|
||||
{
|
||||
Name = "System Info",
|
||||
Parent = "Admin",
|
||||
Order = 29,
|
||||
Order = 33,
|
||||
Path = "admin/system",
|
||||
Icon = Icons.MedicalCross,
|
||||
IsNavigation = false,
|
||||
@ -849,7 +906,7 @@ namespace Oqtane.Repository
|
||||
{
|
||||
Name = "System Update",
|
||||
Parent = "Admin",
|
||||
Order = 31,
|
||||
Order = 35,
|
||||
Path = "admin/update",
|
||||
Icon = Icons.Aperture,
|
||||
IsNavigation = false,
|
||||
|
73
Oqtane.Server/Repository/UrlMappingRepository.cs
Normal file
73
Oqtane.Server/Repository/UrlMappingRepository.cs
Normal file
@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Oqtane.Models;
|
||||
|
||||
namespace Oqtane.Repository
|
||||
{
|
||||
public class UrlMappingRepository : IUrlMappingRepository
|
||||
{
|
||||
private TenantDBContext _db;
|
||||
|
||||
public UrlMappingRepository(TenantDBContext context)
|
||||
{
|
||||
_db = context;
|
||||
}
|
||||
|
||||
public IEnumerable<UrlMapping> GetUrlMappings(int siteId, bool isMapped)
|
||||
{
|
||||
if (isMapped)
|
||||
{
|
||||
return _db.UrlMapping.Where(item => item.SiteId == siteId && !string.IsNullOrEmpty(item.MappedUrl)).Take(200);
|
||||
}
|
||||
else
|
||||
{
|
||||
return _db.UrlMapping.Where(item => item.SiteId == siteId && string.IsNullOrEmpty(item.MappedUrl)).Take(200);
|
||||
}
|
||||
}
|
||||
|
||||
public UrlMapping AddUrlMapping(UrlMapping urlMapping)
|
||||
{
|
||||
_db.UrlMapping.Add(urlMapping);
|
||||
_db.SaveChanges();
|
||||
return urlMapping;
|
||||
}
|
||||
|
||||
public UrlMapping UpdateUrlMapping(UrlMapping urlMapping)
|
||||
{
|
||||
_db.Entry(urlMapping).State = EntityState.Modified;
|
||||
_db.SaveChanges();
|
||||
return urlMapping;
|
||||
}
|
||||
|
||||
public UrlMapping GetUrlMapping(int urlMappingId)
|
||||
{
|
||||
return GetUrlMapping(urlMappingId, true);
|
||||
}
|
||||
|
||||
public UrlMapping GetUrlMapping(int urlMappingId, bool tracking)
|
||||
{
|
||||
if (tracking)
|
||||
{
|
||||
return _db.UrlMapping.Find(urlMappingId);
|
||||
}
|
||||
else
|
||||
{
|
||||
return _db.UrlMapping.AsNoTracking().FirstOrDefault(item => item.UrlMappingId == urlMappingId);
|
||||
}
|
||||
}
|
||||
|
||||
public UrlMapping GetUrlMapping(int siteId, string url)
|
||||
{
|
||||
return _db.UrlMapping.Where(item => item.SiteId == siteId && item.Url == url).FirstOrDefault();
|
||||
}
|
||||
|
||||
public void DeleteUrlMapping(int urlMappingId)
|
||||
{
|
||||
UrlMapping urlMapping = _db.UrlMapping.Find(urlMappingId);
|
||||
_db.UrlMapping.Remove(urlMapping);
|
||||
_db.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
51
Oqtane.Server/Repository/VisitorRepository.cs
Normal file
51
Oqtane.Server/Repository/VisitorRepository.cs
Normal file
@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Oqtane.Models;
|
||||
|
||||
namespace Oqtane.Repository
|
||||
{
|
||||
public class VisitorRepository : IVisitorRepository
|
||||
{
|
||||
private TenantDBContext _db;
|
||||
|
||||
public VisitorRepository(TenantDBContext context)
|
||||
{
|
||||
_db = context;
|
||||
}
|
||||
|
||||
public IEnumerable<Visitor> GetVisitors(int siteId, DateTime fromDate)
|
||||
{
|
||||
return _db.Visitor.AsNoTracking()
|
||||
.Include(item => item.User) // eager load users
|
||||
.Where(item => item.SiteId == siteId && item.VisitedOn >= fromDate);
|
||||
}
|
||||
|
||||
public Visitor AddVisitor(Visitor visitor)
|
||||
{
|
||||
_db.Visitor.Add(visitor);
|
||||
_db.SaveChanges();
|
||||
return visitor;
|
||||
}
|
||||
|
||||
public Visitor UpdateVisitor(Visitor visitor)
|
||||
{
|
||||
_db.Entry(visitor).State = EntityState.Modified;
|
||||
_db.SaveChanges();
|
||||
return visitor;
|
||||
}
|
||||
|
||||
public Visitor GetVisitor(int visitorId)
|
||||
{
|
||||
return _db.Visitor.Find(visitorId);
|
||||
}
|
||||
|
||||
public void DeleteVisitor(int visitorId)
|
||||
{
|
||||
Visitor visitor = _db.Visitor.Find(visitorId);
|
||||
_db.Visitor.Remove(visitor);
|
||||
_db.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
@ -49,10 +49,20 @@ namespace Oqtane.Models
|
||||
public int? PwaSplashIconFileId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines if users may register / create accounts
|
||||
/// Determines if visitors may register / create user accounts
|
||||
/// </summary>
|
||||
public bool AllowRegistration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines if visitors will be tracked
|
||||
/// </summary>
|
||||
public bool VisitorTracking { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines if broken urls (404s) will be captured automatically
|
||||
/// </summary>
|
||||
public bool CaptureBrokenUrls { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Unique GUID to identify the Site.
|
||||
/// </summary>
|
||||
|
47
Oqtane.Shared/Models/UrlMapping.cs
Normal file
47
Oqtane.Shared/Models/UrlMapping.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Oqtane.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes a UrlMapping in Oqtane.
|
||||
/// </summary>
|
||||
public class UrlMapping
|
||||
{
|
||||
/// <summary>
|
||||
/// ID of this UrlMapping.
|
||||
/// </summary>
|
||||
public int UrlMappingId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reference to a <see cref="Site"/>
|
||||
/// </summary>
|
||||
public int SiteId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A fully quaified Url
|
||||
/// </summary>
|
||||
public string Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A Url the visitor will be redirected to
|
||||
/// </summary>
|
||||
public string MappedUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of requests all time for the url
|
||||
/// </summary>
|
||||
public int Requests { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Date when the url was first requested for the site
|
||||
/// </summary>
|
||||
public DateTime CreatedOn { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Date when the url was last requested for the site
|
||||
/// </summary>
|
||||
public DateTime RequestedOn { get; set; }
|
||||
|
||||
}
|
||||
}
|
61
Oqtane.Shared/Models/Visitor.cs
Normal file
61
Oqtane.Shared/Models/Visitor.cs
Normal file
@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Oqtane.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes a Visitor in Oqtane.
|
||||
/// </summary>
|
||||
public class Visitor
|
||||
{
|
||||
/// <summary>
|
||||
/// ID of this Visitor.
|
||||
/// </summary>
|
||||
public int VisitorId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reference to a <see cref="Site"/>
|
||||
/// </summary>
|
||||
public int SiteId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reference to a <see cref="User"/> if applicable
|
||||
/// </summary>
|
||||
public int? UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of times a visitor has visited a site
|
||||
/// </summary>
|
||||
public int Visits { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// IP Address of visitor
|
||||
/// </summary>
|
||||
public string IPAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// User agent of visitor
|
||||
/// </summary>
|
||||
public string UserAgent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Language of visitor
|
||||
/// </summary>
|
||||
public string Language { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Date the visitor first visited the site
|
||||
/// </summary>
|
||||
public DateTime CreatedOn { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Date the visitor last visited the site
|
||||
/// </summary>
|
||||
public DateTime VisitedOn { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Direct reference to the <see cref="User"/> object (if applicable)
|
||||
/// </summary>
|
||||
public User User { get; set; }
|
||||
}
|
||||
}
|
@ -3,8 +3,8 @@ using System;
|
||||
namespace Oqtane.Shared {
|
||||
|
||||
public class Constants {
|
||||
public static readonly string Version = "3.0.0";
|
||||
public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0";
|
||||
public static readonly string Version = "3.0.1";
|
||||
public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1";
|
||||
public const string PackageId = "Oqtane.Framework";
|
||||
public const string UpdaterPackageId = "Oqtane.Updater";
|
||||
public const string PackageRegistryUrl = "https://www.oqtane.net";
|
||||
|
@ -1,4 +1,4 @@
|
||||
namespace Oqtane.Shared
|
||||
namespace Oqtane.Shared
|
||||
{
|
||||
public class EntityNames
|
||||
{
|
||||
@ -10,5 +10,6 @@
|
||||
public const string Page = "Page";
|
||||
public const string Folder = "Folder";
|
||||
public const string User = "User";
|
||||
public const string Visitor = "Visitor";
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user