Merge pull request #1874 from oqtane/dev

3.0.1 release
This commit is contained in:
Shaun Walker 2021-12-12 20:46:28 -05:00 committed by GitHub
commit 99b0c9c079
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
157 changed files with 6647 additions and 3044 deletions

View File

@ -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 = "" };

View File

@ -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;

View File

@ -158,8 +158,8 @@
if (firstRender)
{
var interop = new Interop(JSRuntime);
await interop.IncludeLink("", "stylesheet", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.0.2/css/bootstrap.min.css", "text/css", "sha512-usVBAd66/NpVNfBge19gws2j6JZinnca12rAe2l+d+QkLU9fiG02O1X8Q6hepIpr/EYKZvKx/I9WsnujJuOmBA==", "anonymous", "");
await interop.IncludeScript("", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.0.2/js/bootstrap.min.js", "sha512-a6ctI6w1kg3J4dSjknHj3aWLEbjitAXAjLDRUxo2wyYmDFRcz2RJuQr5M3Kt8O/TtUSp8n2rAyaXYy1sjoKmrQ==", "anonymous", "", "head", "");
await interop.IncludeLink("", "stylesheet", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/css/bootstrap.min.css", "text/css", "sha512-GQGU0fMMi238uA+a/bdWJfpUGKUkBdgfFdgBm72SUQ6BeyWjoY/ton0tEjH+OSH9iP4Dfh+7HM0I9f5eR0L/4w==", "anonymous", "");
await interop.IncludeScript("", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/js/bootstrap.bundle.min.js", "sha512-pax4MlgXjHEPfCwcJLQhigY7+N8rt6bVvWLFyUMuxShv170X53TRzGPmPkZmGBhk+jikR8WBM4yl7A9WMHHqvg==", "anonymous", "", "head", "");
}
}

View File

@ -113,7 +113,7 @@ else
<TabPanel Name="Upload" ResourceKey="Upload" Security="SecurityAccessLevel.Host">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" HelpText="Upload one or more translations. Once they are uploaded click Install to complete the installation." ResourceKey="Module">Language: </Label>
<Label Class="col-sm-3" HelpText="Upload one or more translations. Once they are uploaded click Install to complete the installation." ResourceKey="LanguageUpload">Language: </Label>
<div class="col-sm-9">
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" />
</div>
@ -350,7 +350,7 @@ else
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360);
NavigationManager.NavigateTo(NavigationManager.Uri, forceLoad: true);
NavigationManager.NavigateTo(NavigationManager.Uri, true);
}
}
}

View File

@ -10,100 +10,32 @@
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="container">
<div class="row mb-1 align-items-center">
<div class="col-sm-9">
</div>
</div>
<div class="row mb-1 align-items-center">
<div class="col-sm-9">
</div>
</div>
<div class="row mb-1 align-items-center">
<div class="col-sm-9">
</div>
</div>
<div class="row mb-1 align-items-center">
<div class="col-sm-9">
</div>
</div>
<div class="row mb-1 align-items-center">
<div class="col-sm-9">
</div>
</div>
<div class="row mb-1 align-items-center">
<div class="col-sm-9">
</div>
</div>
<div class="row mb-1 align-items-center">
<div class="col-sm-9">
</div>
</div>
<div class="row mb-1 align-items-center">
<div class="col-sm-9">
</div>
</div>
<div class="row mb-1 align-items-center">
<div class="col-sm-9">
</div>
</div>
<div class="row mb-1 align-items-center">
<div class="col-sm-9">
</div>
</div>
<div class="row mb-1 align-items-center">
<div class="col-sm-9">
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="dateTime" HelpText="The date and time of this log" ResourceKey="DateTime">Date/Time: </Label>
<Label Class="col-sm-3" For="dateTime" HelpText="The date and time of this log" ResourceKey="DateTime">Date/Time: </Label>
<div class="col-sm-9">
<input id="dateTime" class="form-control" @bind="@_logDate" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="level" HelpText="The level of this log" ResourceKey="Level">Level: </Label>
<Label Class="col-sm-3" For="level" HelpText="The level of this log" ResourceKey="Level">Level: </Label>
<div class="col-sm-9">
<input id="level" class="form-control" @bind="@_level" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="feature" HelpText="The feature that was affected" ResourceKey="Feature">Feature: </Label>
<Label Class="col-sm-3" For="feature" HelpText="The feature that was affected" ResourceKey="Feature">Feature: </Label>
<div class="col-sm-9">
<input id="feature" class="form-control" @bind="@_feature" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="function" HelpText="The function that was performed" ResourceKey="Function">Function: </Label>
<Label Class="col-sm-3" For="function" HelpText="The function that was performed" ResourceKey="Function">Function: </Label>
<div class="col-sm-9">
<input id="function" class="form-control" @bind="@_function" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="category" HelpText="The categories that were affected" ResourceKey="Category">Category: </Label>
<Label Class="col-sm-3" For="category" HelpText="The categories that were affected" ResourceKey="Category">Category: </Label>
<div class="col-sm-9">
<input id="category" class="form-control" @bind="@_category" readonly />
</div>
@ -111,7 +43,7 @@
@if (_pageName != string.Empty)
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="page" HelpText="The page that was affected" ResourceKey="Page">Page: </Label>
<Label Class="col-sm-3" For="page" HelpText="The page that was affected" ResourceKey="Page">Page: </Label>
<div class="col-sm-9">
<input id="page" class="form-control" @bind="@_pageName" readonly />
</div>
@ -120,7 +52,7 @@
@if (_moduleTitle != string.Empty)
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="module" HelpText="The module that was affected" ResourceKey="Module">Module: </Label>
<Label Class="col-sm-3" For="module" HelpText="The module that was affected" ResourceKey="Module">Module: </Label>
<div class="col-sm-9">
<input id="module" class="form-control" @bind="@_moduleTitle" readonly />
</div>
@ -129,26 +61,26 @@
@if (_username != string.Empty)
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="user" HelpText="The user that caused this log" ResourceKey="User">User: </Label>
<Label Class="col-sm-3" For="user" HelpText="The user that caused this log" ResourceKey="User">User: </Label>
<div class="col-sm-9">
<input id="user" class="form-control" @bind="@_username" readonly />
</div>
</div>
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="url" HelpText="The url the log comes from" ResourceKey="Url">Url: </Label>
<Label Class="col-sm-3" For="url" HelpText="The url the log comes from" ResourceKey="Url">Url: </Label>
<div class="col-sm-9">
<input id="url" class="form-control" @bind="@_url" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="template" HelpText="What the log is about" ResourceKey="Template">Template: </Label>
<Label Class="col-sm-3" For="template" HelpText="What the log is about" ResourceKey="Template">Template: </Label>
<div class="col-sm-9">
<input id="template" class="form-control" @bind="@_template" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="message" HelpText="The message that the system generated" ResourceKey="Message">Message: </Label>
<Label Class="col-sm-3" For="message" HelpText="The message that the system generated" ResourceKey="Message">Message: </Label>
<div class="col-sm-9">
<textarea id="message" class="form-control" @bind="@_message" rows="5" readonly></textarea>
</div>
@ -156,24 +88,25 @@
@if (!string.IsNullOrEmpty(_exception))
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="exception" HelpText="The exceptions generated by the system" ResourceKey="Exception">Exception: </Label>
<div class="col-sm-9">
<Label Class="col-sm-3" For="exception" HelpText="The exceptions generated by the system" ResourceKey="Exception">Exception: </Label>
<div class="col-sm-9">
<textarea id="exception" class="form-control" @bind="@_exception" rows="5" readonly></textarea>
</div>
</div>
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="properties" HelpText="The properties that were affected" ResourceKey="Properties">Properties: </Label>
<Label Class="col-sm-3" For="properties" HelpText="The properties that were affected" ResourceKey="Properties">Properties: </Label>
<div class="col-sm-9">
<textarea id="properties" class="form-control" @bind="@_properties" rows="5" readonly></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="server" HelpText="The server that was affected" ResourceKey="Server">Server: </Label>
<div class="col-sm-9">
<Label Class="col-sm-3" For="server" HelpText="The server that was affected" ResourceKey="Server">Server: </Label>
<div class="col-sm-9">
<input id="server" class="form-control" @bind="@_server" readonly />
</div>
</div>
</div>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>

View File

@ -51,11 +51,11 @@ else
{
<Pager Items="@_logs">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Date"]</th>
<th>@Localizer["Level"]</th>
<th>@Localizer["Feature"]</th>
<th>@Localizer["Function"]</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Date"]</th>
<th>@Localizer["Level"]</th>
<th>@Localizer["Feature"]</th>
<th>@Localizer["Function"]</th>
</Header>
<Row>
<td class="@GetClass(context.Function)"><ActionLink Action="Detail" Parameters="@($"id=" + context.LogId.ToString())" ResourceKey="LogDetails" /></td>

View File

@ -1,7 +1,9 @@
using Oqtane.Models;
using Oqtane.Documentation;
using Oqtane.Models;
namespace Oqtane.Modules.Admin.ModuleCreator
{
[PrivateApi("Mark this as private, since it's not very useful in the public docs")]
public class ModuleInfo : IModule
{
public ModuleDefinition ModuleDefinition => new ModuleDefinition

View File

@ -22,6 +22,7 @@ else
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th>
<th>@SharedLocalizer["Version"]</th>
<th>@Localizer["InUse"]</th>
<th>@SharedLocalizer["Expires"]</th>
<th style="width: 1px;">&nbsp;</th>
</Header>
@ -35,6 +36,16 @@ else
</td>
<td>@context.Name</td>
<td>@context.Version</td>
<td>
@if(context.AssemblyName == "Oqtane.Client" || PageState.Modules.Where(m => m.ModuleDefinition.ModuleDefinitionId == context.ModuleDefinitionId).Count() > 0)
{
<span>@SharedLocalizer["Yes"]</span>
}
else
{
<span>@SharedLocalizer["No"]</span>
}
</td>
<td>
@((MarkupString)PurchaseLink(context.PackageName))
</td>

View File

@ -3,13 +3,14 @@
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IPageService PageService
@inject IPageModuleService PageModuleService
@inject IThemeService ThemeService
@inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<TabStrip Refresh="@_refresh">
<TabPanel Name="Settings" ResourceKey="Settings">
<TabPanel Name="Settings" ResourceKey="Settings" Heading=@Localizer["Settings.Heading"]>
@if (_themeList != null)
{
<div class="container">
@ -151,10 +152,27 @@
<div class="container">
<div class="row mb-1 align-items-center">
<PermissionGrid EntityName="@EntityNames.Page" Permissions="@_permissions" @ref="_permissionGrid" />
</div>
</div>
}
</TabPanel>
<TabPanel Name="PageModules" Heading="Modules" ResourceKey="PageModules">
@if(_pageModules != null)
{
<Pager Items="_pageModules">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["ModuleTitle"]</th>
<th>@Localizer["ModuleDefinition"]</th>
</Header>
<Row>
<td><ActionLink Action="Settings" Text="Edit" ModuleId="@context.ModuleId" Security="SecurityAccessLevel.Edit" Permissions="@context.Permissions" ResourceKey="ModuleSettings" /></td>
<td><ActionDialog Header="Delete Module" Message="Are You Sure You Wish To Delete This Module?" Action="Delete" Security="SecurityAccessLevel.Edit" Permissions="@context.Permissions" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" /></td>
<td>@context.Title</td>
<td>@context.ModuleDefinition?.Name</td>
</Row>
</Pager>
}
</TabPanel>
@if (_themeSettingsType != null)
@ -170,114 +188,136 @@
</form>
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
private ElementReference form;
private bool validated = false;
private List<Theme> _themeList;
private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private List<Page> _pageList;
private int _pageId;
private string _name;
private string _title;
private string _path;
private string _currentparentid;
private string _parentid = "-1";
private string _insert = "=";
private List<Page> _children;
private int _childid = -1;
private string _isnavigation;
private string _isclickable;
private string _url;
private string _ispersonalizable;
private string _themetype;
private string _containertype = "-";
private string _icon;
private string _permissions = null;
private string _createdby;
private DateTime _createdon;
private string _modifiedby;
private DateTime _modifiedon;
private string _deletedby;
private DateTime? _deletedon;
private PermissionGrid _permissionGrid;
private Type _themeSettingsType;
private object _themeSettings;
private RenderFragment ThemeSettingsComponent { get; set; }
private bool _refresh = false;
private ElementReference form;
private bool validated = false;
private List<Theme> _themeList;
private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private List<Page> _pageList;
private List<Module> _pageModules;
private int _pageId;
private string _name;
private string _title;
private string _path;
private string _currentparentid;
private string _parentid = "-1";
private string _insert = "=";
private List<Page> _children;
private int _childid = -1;
private string _isnavigation;
private string _isclickable;
private string _url;
private string _ispersonalizable;
private string _themetype;
private string _containertype = "-";
private string _icon;
private string _permissions = null;
private string _createdby;
private DateTime _createdon;
private string _modifiedby;
private DateTime _modifiedon;
private string _deletedby;
private DateTime? _deletedon;
private PermissionGrid _permissionGrid;
private Type _themeSettingsType;
private object _themeSettings;
private RenderFragment ThemeSettingsComponent { get; set; }
private bool _refresh = false;
protected Page page;
protected override async Task OnInitializedAsync()
{
try
{
_pageList = PageState.Pages;
_children = PageState.Pages.Where(item => item.ParentId == null).ToList();
protected override async Task OnInitializedAsync()
{
try
{
_pageList = PageState.Pages;
_children = PageState.Pages.Where(item => item.ParentId == null).ToList();
_themeList = await ThemeService.GetThemesAsync();
_themes = ThemeService.GetThemeControls(_themeList);
_themeList = await ThemeService.GetThemesAsync();
_themes = ThemeService.GetThemeControls(_themeList);
_pageId = Int32.Parse(PageState.QueryString["id"]);
page = PageState.Pages.FirstOrDefault(item => item.PageId == _pageId);
_pageId = Int32.Parse(PageState.QueryString["id"]);
var page = PageState.Pages.FirstOrDefault(item => item.PageId == _pageId);
if (page != null)
{
_name = page.Name;
_title = page.Title;
_path = page.Path;
if (page != null)
{
_name = page.Name;
_title = page.Title;
_path = page.Path;
_pageModules = PageState.Modules.Where(m => m.PageId == page.PageId && m.IsDeleted == false).ToList();
if (string.IsNullOrEmpty(_path))
{
_path = "/";
}
else
{
if (_path.Contains("/"))
{
_path = _path.Substring(_path.LastIndexOf("/") + 1);
}
}
if (string.IsNullOrEmpty(_path))
{
_path = "/";
}
else
{
if (_path.Contains("/"))
{
_path = _path.Substring(_path.LastIndexOf("/") + 1);
}
}
if (page.ParentId == null)
{
_parentid = "-1";
}
else
{
_parentid = page.ParentId.ToString();
}
if (page.ParentId == null)
{
_parentid = "-1";
}
else
{
_parentid = page.ParentId.ToString();
}
_currentparentid = _parentid;
_isnavigation = page.IsNavigation.ToString();
_isclickable = page.IsClickable.ToString();
_url = page.Url;
_ispersonalizable = page.IsPersonalizable.ToString();
_themetype = page.ThemeType;
if (string.IsNullOrEmpty(_themetype))
{
_themetype = PageState.Site.DefaultThemeType;
}
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containertype = page.DefaultContainerType;
if (string.IsNullOrEmpty(_containertype))
{
_containertype = PageState.Site.DefaultContainerType;
}
_icon = page.Icon;
_permissions = page.Permissions;
_createdby = page.CreatedBy;
_createdon = page.CreatedOn;
_modifiedby = page.ModifiedBy;
_modifiedon = page.ModifiedOn;
_deletedby = page.DeletedBy;
_deletedon = page.DeletedOn;
_currentparentid = _parentid;
_isnavigation = page.IsNavigation.ToString();
_isclickable = page.IsClickable.ToString();
_url = page.Url;
_ispersonalizable = page.IsPersonalizable.ToString();
_themetype = page.ThemeType;
if (string.IsNullOrEmpty(_themetype))
{
_themetype = PageState.Site.DefaultThemeType;
}
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containertype = page.DefaultContainerType;
if (string.IsNullOrEmpty(_containertype))
{
_containertype = PageState.Site.DefaultContainerType;
}
_icon = page.Icon;
_permissions = page.Permissions;
_createdby = page.CreatedBy;
_createdon = page.CreatedOn;
_modifiedby = page.ModifiedBy;
_modifiedon = page.ModifiedOn;
_deletedby = page.DeletedBy;
_deletedon = page.DeletedOn;
ThemeSettings();
}
ThemeSettings();
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Page {PageId} {Error}", _pageId, ex.Message);
AddModuleMessage(Localizer["Error.Page.Load"], MessageType.Error);
}
}
private async Task DeleteModule(Module module)
{
try
{
PageModule pagemodule = await PageModuleService.GetPageModuleAsync(page.PageId, module.ModuleId);
pagemodule.IsDeleted = true;
await PageModuleService.UpdatePageModuleAsync(pagemodule);
await logger.LogInformation(LogFunction.Update,"Module Deleted {Title}", module.Title);
_pageModules.RemoveAll(item => item.PageModuleId == pagemodule.PageModuleId);
StateHasChanged();
NavigationManager.NavigateTo(NavigationManager.Uri + "&tab=PageModules");
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Page {PageId} {Error}", _pageId, ex.Message);
AddModuleMessage(Localizer["Error.Page.Load"], MessageType.Error);
await logger.LogError(ex, "Error Deleting Module {Title} {Error}", module.Title, ex.Message);
AddModuleMessage(Localizer["Error.Module.Delete"], MessageType.Error);
}
}

View File

@ -25,7 +25,7 @@
<th>@Localizer["DeletedOn"]</th>
</Header>
<Row>
<td><button @onclick="@(() => RestorePage(context))" class="btn btn-info" title="Restore">Restore</button></td>
<td><button type="button" @onclick="@(() => RestorePage(context))" class="btn btn-info" title="Restore">Restore</button></td>
<td><ActionDialog Header="Delete Page" Message="@string.Format(Localizer["Confirm.Page.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
<td>@context.Name</td>
<td>@context.DeletedBy</td>
@ -34,9 +34,7 @@
</Pager>
@if (_pages.Any())
{
<div style="text-align:right;">
<ActionDialog Header="Delete All Pages" Message="Are You Sure You Wish To Permanently Delete All Pages?" Action="Delete All Pages" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllPages())" ResourceKey="DeleteAllPages" />
</div>
<br /><ActionDialog Header="Delete All Pages" Message="Are You Sure You Wish To Permanently Delete All Pages?" Action="Delete All Pages" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllPages())" ResourceKey="DeleteAllPages" />
}
}
</TabPanel>
@ -58,7 +56,7 @@
<th>@Localizer["DeletedOn"]</th>
</Header>
<Row>
<td><button @onclick="@(() => RestoreModule(context))" class="btn btn-info" title="Restore">@Localizer["Restore"]</button></td>
<td><button type="button" @onclick="@(() => RestoreModule(context))" class="btn btn-info" title="Restore">@Localizer["Restore"]</button></td>
<td><ActionDialog Header="Delete Module" Message="@string.Format(Localizer["Confirm.Module.Delete"], context.Title)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" /></td>
<td>@PageState.Pages.Find(item => item.PageId == context.PageId).Name</td>
<td>@context.Title</td>
@ -68,9 +66,7 @@
</Pager>
@if (_modules.Any())
{
<div style="text-align:right;">
<ActionDialog Header="Delete All Modules" Message="Are You Sure You Wish To Permanently Delete All Modules?" Action="Delete All Modules" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllModules())" ResourceKey="DeleteAllModules" />
</div>
<br /><ActionDialog Header="Delete All Modules" Message="Are You Sure You Wish To Permanently Delete All Modules?" Action="Delete All Modules" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllModules())" ResourceKey="DeleteAllModules" />
}
}

View File

@ -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">
@ -240,243 +231,246 @@
}
@code {
private ElementReference form;
private bool validated = false;
private bool _initialized = false;
private List<Theme> _themeList;
private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private string _name = string.Empty;
private List<Alias> _aliasList;
private string _urls = string.Empty;
private string _runtime = "";
private string _prerender = "";
private int _logofileid = -1;
private FileManager _logofilemanager;
private int _faviconfileid = -1;
private FileManager _faviconfilemanager;
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";
private string _smtpusername = string.Empty;
private string _smtppassword = string.Empty;
private string _smtpsender = string.Empty;
private string _pwaisenabled;
private int _pwaappiconfileid = -1;
private FileManager _pwaappiconfilemanager;
private int _pwasplashiconfileid = -1;
private FileManager _pwasplashiconfilemanager;
private string _tenant = string.Empty;
private string _database = string.Empty;
private string _connectionstring = string.Empty;
private string _createdby;
private DateTime _createdon;
private string _modifiedby;
private DateTime _modifiedon;
private string _deletedby;
private DateTime? _deletedon;
private string _isdeleted;
private ElementReference form;
private bool validated = false;
private bool _initialized = false;
private List<Theme> _themeList;
private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private string _name = string.Empty;
private List<Alias> _aliasList;
private string _urls = string.Empty;
private string _runtime = "";
private string _prerender = "";
private int _logofileid = -1;
private FileManager _logofilemanager;
private int _faviconfileid = -1;
private FileManager _faviconfilemanager;
private string _themetype = "-";
private string _containertype = "-";
private string _admincontainertype = "-";
private string _smtphost = string.Empty;
private string _smtpport = string.Empty;
private string _smtpssl = "False";
private string _smtpusername = string.Empty;
private string _smtppassword = string.Empty;
private string _smtpsender = string.Empty;
private string _pwaisenabled;
private int _pwaappiconfileid = -1;
private FileManager _pwaappiconfilemanager;
private int _pwasplashiconfileid = -1;
private FileManager _pwasplashiconfilemanager;
private string _tenant = string.Empty;
private string _database = string.Empty;
private string _connectionstring = string.Empty;
private string _createdby;
private DateTime _createdon;
private string _modifiedby;
private DateTime _modifiedon;
private string _deletedby;
private DateTime? _deletedon;
private string _isdeleted;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnInitializedAsync()
{
try
{
_themeList = await ThemeService.GetThemesAsync();
Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null)
{
_name = site.Name;
_runtime = site.Runtime;
_prerender = site.RenderMode.Replace(_runtime, "");
_allowregistration = site.AllowRegistration.ToString();
_isdeleted = site.IsDeleted.ToString();
protected override async Task OnInitializedAsync()
{
try
{
_themeList = await ThemeService.GetThemesAsync();
Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null)
{
_name = site.Name;
_runtime = site.Runtime;
_prerender = site.RenderMode.Replace(_runtime, "");
_isdeleted = site.IsDeleted.ToString();
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_aliasList = await AliasService.GetAliasesAsync();
foreach (Alias alias in _aliasList.Where(item => item.SiteId == site.SiteId && item.TenantId == site.TenantId).ToList())
{
_urls += alias.Name + ",";
}
_urls = _urls.Substring(0, _urls.Length - 1);
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_aliasList = await AliasService.GetAliasesAsync();
foreach (Alias alias in _aliasList.Where(item => item.SiteId == site.SiteId && item.TenantId == site.TenantId).ToList())
{
_urls += alias.Name + ",";
}
_urls = _urls.Substring(0, _urls.Length - 1);
}
}
if (site.LogoFileId != null)
{
_logofileid = site.LogoFileId.Value;
}
if (site.LogoFileId != null)
{
_logofileid = site.LogoFileId.Value;
}
if (site.FaviconFileId != null)
{
_faviconfileid = site.FaviconFileId.Value;
}
if (site.FaviconFileId != null)
{
_faviconfileid = site.FaviconFileId.Value;
}
_themes = ThemeService.GetThemeControls(_themeList);
_themetype = (!string.IsNullOrEmpty(site.DefaultThemeType)) ? site.DefaultThemeType : Constants.DefaultTheme;
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer;
_admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer;
_themes = ThemeService.GetThemeControls(_themeList);
_themetype = (!string.IsNullOrEmpty(site.DefaultThemeType)) ? site.DefaultThemeType : Constants.DefaultTheme;
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer;
_admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer;
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
_smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty);
_smtpport = SettingService.GetSetting(settings, "SMTPPort", string.Empty);
_smtpssl = SettingService.GetSetting(settings, "SMTPSSL", "False");
_smtpusername = SettingService.GetSetting(settings, "SMTPUsername", string.Empty);
_smtppassword = SettingService.GetSetting(settings, "SMTPPassword", string.Empty);
_smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty);
_pwaisenabled = site.PwaIsEnabled.ToString();
if (site.PwaAppIconFileId != null)
{
_pwaappiconfileid = site.PwaAppIconFileId.Value;
}
if (site.PwaSplashIconFileId != null)
{
_pwasplashiconfileid = site.PwaSplashIconFileId.Value;
}
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
_smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty);
_smtpport = SettingService.GetSetting(settings, "SMTPPort", string.Empty);
_smtpssl = SettingService.GetSetting(settings, "SMTPSSL", "False");
_smtpusername = SettingService.GetSetting(settings, "SMTPUsername", string.Empty);
_smtppassword = SettingService.GetSetting(settings, "SMTPPassword", string.Empty);
_smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty);
_pwaisenabled = site.PwaIsEnabled.ToString();
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
var tenants = await TenantService.GetTenantsAsync();
var _databases = await DatabaseService.GetDatabasesAsync();
var tenant = tenants.Find(item => item.TenantId == site.TenantId);
if (tenant != null)
{
_tenant = tenant.Name;
_database = _databases.Find(item => item.DBType == tenant.DBType)?.Name;
_connectionstring = tenant.DBConnectionString;
}
}
if (site.PwaAppIconFileId != null)
{
_pwaappiconfileid = site.PwaAppIconFileId.Value;
}
_createdby = site.CreatedBy;
_createdon = site.CreatedOn;
_modifiedby = site.ModifiedBy;
_modifiedon = site.ModifiedOn;
_deletedby = site.DeletedBy;
_deletedon = site.DeletedOn;
if (site.PwaSplashIconFileId != null)
{
_pwasplashiconfileid = site.PwaSplashIconFileId.Value;
}
_initialized = true;
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message);
AddModuleMessage(ex.Message, MessageType.Error);
}
}
_pwaisenabled = site.PwaIsEnabled.ToString();
if (site.PwaAppIconFileId != null)
{
_pwaappiconfileid = site.PwaAppIconFileId.Value;
}
if (site.PwaSplashIconFileId != null)
{
_pwasplashiconfileid = site.PwaSplashIconFileId.Value;
}
private async void ThemeChanged(ChangeEventArgs e)
{
try
{
_themetype = (string)e.Value;
if (_themetype != "-")
{
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
}
else
{
_containers = new List<ThemeControl>();
}
_containertype = "-";
_admincontainertype = Constants.DefaultAdminContainer;
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Pane Layouts For Theme {ThemeType} {Error}", _themetype, ex.Message);
AddModuleMessage(Localizer["Error.Theme.LoadPane"], MessageType.Error);
}
}
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
var tenants = await TenantService.GetTenantsAsync();
var _databases = await DatabaseService.GetDatabasesAsync();
var tenant = tenants.Find(item => item.TenantId == site.TenantId);
if (tenant != null)
{
_tenant = tenant.Name;
_database = _databases.Find(item => item.DBType == tenant.DBType)?.Name;
_connectionstring = tenant.DBConnectionString;
}
}
private async Task SaveSite()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
try
{
if (_name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-")
{
var unique = true;
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
foreach (string name in _urls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
if (_aliasList.Exists(item => item.Name == name && item.SiteId != PageState.Alias.SiteId && item.TenantId != PageState.Alias.TenantId))
{
unique = false;
}
}
}
_createdby = site.CreatedBy;
_createdon = site.CreatedOn;
_modifiedby = site.ModifiedBy;
_modifiedon = site.ModifiedOn;
_deletedby = site.DeletedBy;
_deletedon = site.DeletedOn;
if (unique)
{
var site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null)
{
bool refresh = false;
bool reload = false;
_initialized = true;
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message);
AddModuleMessage(ex.Message, MessageType.Error);
}
}
site.Name = _name;
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
if (site.Runtime != _runtime || site.RenderMode != _runtime + _prerender)
{
site.Runtime = _runtime;
site.RenderMode = _runtime + _prerender;
refresh = true;
reload = true; // needs to be reloaded on server
}
}
site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
private async void ThemeChanged(ChangeEventArgs e)
{
try
{
_themetype = (string)e.Value;
if (_themetype != "-")
{
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
}
else
{
_containers = new List<ThemeControl>();
}
_containertype = "-";
_admincontainertype = Constants.DefaultAdminContainer;
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Pane Layouts For Theme {ThemeType} {Error}", _themetype, ex.Message);
AddModuleMessage(Localizer["Error.Theme.LoadPane"], MessageType.Error);
}
}
site.LogoFileId = null;
var logofileid = _logofilemanager.GetFileId();
if (logofileid != -1)
{
site.LogoFileId = logofileid;
}
int? faviconFieldId = _faviconfilemanager.GetFileId();
if (faviconFieldId == -1) faviconFieldId = null;
if (site.FaviconFileId != faviconFieldId)
{
site.FaviconFileId = faviconFieldId;
reload = true; // needs to be reloaded on server
}
if (site.DefaultThemeType != _themetype)
{
site.DefaultThemeType = _themetype;
refresh = true; // needs to be refreshed on client
}
if (site.DefaultContainerType != _containertype)
{
site.DefaultContainerType = _containertype;
refresh = true; // needs to be refreshed on client
}
site.AdminContainerType = _admincontainertype;
private async Task SaveSite()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
try
{
if (_name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-")
{
var unique = true;
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
foreach (string name in _urls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
if (_aliasList.Exists(item => item.Name == name && item.SiteId != PageState.Alias.SiteId && item.TenantId != PageState.Alias.TenantId))
{
unique = false;
}
}
}
if (unique)
{
var site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null)
{
bool reload = false;
bool refresh = (site.DefaultThemeType != _themetype || site.DefaultContainerType != _containertype);
site.Name = _name;
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
if (site.Runtime != _runtime || site.RenderMode != _runtime + _prerender)
{
site.Runtime = _runtime;
site.RenderMode = _runtime + _prerender;
refresh = true;
reload = true;
}
}
site.AllowRegistration = (_allowregistration == null ? true : Boolean.Parse(_allowregistration));
site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
site.LogoFileId = null;
var logofileid = _logofilemanager.GetFileId();
if (logofileid != -1)
{
site.LogoFileId = logofileid;
}
var faviconFieldId = _faviconfilemanager.GetFileId();
if (faviconFieldId != -1)
{
site.FaviconFileId = faviconFieldId;
}
site.DefaultThemeType = _themetype;
site.DefaultContainerType = _containertype;
site.AdminContainerType = _admincontainertype;
site.PwaIsEnabled = (_pwaisenabled == null ? true : Boolean.Parse(_pwaisenabled));
var pwaappiconfileid = _pwaappiconfilemanager.GetFileId();
if (pwaappiconfileid != -1)
if (site.PwaIsEnabled.ToString() != _pwaisenabled)
{
site.PwaIsEnabled = Boolean.Parse(_pwaisenabled);
reload = true; // needs to be reloaded on server
}
int? pwaappiconfileid = _pwaappiconfilemanager.GetFileId();
if (pwaappiconfileid == -1) pwaappiconfileid = null;
if (site.PwaAppIconFileId != pwaappiconfileid)
{
site.PwaAppIconFileId = pwaappiconfileid;
reload = true; // needs to be reloaded on server
}
var pwasplashiconfileid = _pwasplashiconfilemanager.GetFileId();
if (pwasplashiconfileid != -1)
int? pwasplashiconfileid = _pwasplashiconfilemanager.GetFileId();
if (pwasplashiconfileid == -1) pwasplashiconfileid = null;
if (site.PwaSplashIconFileId != pwasplashiconfileid)
{
site.PwaSplashIconFileId = pwasplashiconfileid;
reload = true; // needs to be reloaded on server
}
site = await SiteService.UpdateSiteAsync(site);
@ -518,7 +512,7 @@
if (refresh)
{
NavigationManager.NavigateTo(NavigateUrl(), reload); // refresh to show new theme or container
NavigationManager.NavigateTo(NavigateUrl(true), reload); // refresh/reload
}
else
{

View File

@ -52,6 +52,11 @@ else
private void Edit(string name)
{
if (name.Equals("*"))
{
var uri = new Uri(NavigationManager.Uri);
name = uri.Authority;
}
NavigationManager.NavigateTo(_scheme + name + "/admin/site/?reload");
}

View 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);
}
}
}

View 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);
}
}
}

View File

@ -0,0 +1,140 @@
@namespace Oqtane.Modules.Admin.UrlMappings
@inherits ModuleBase
@inject NavigationManager NavigationManager
@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;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</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>
<a href="" onclick="@(() => BrowseUrl(context.Url))">@context.Url</a>
@if (_mapped)
{
@((MarkupString)"<br />&gt;&gt;&nbsp;")<a href="" onclick="@(() => BrowseUrl(context.MappedUrl))">@context.MappedUrl</a>
}
</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 void BrowseUrl(string url)
{
NavigationManager.NavigateTo(url, true);
}
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);
}
}
}

View File

@ -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 class="btn btn-secondary" @onclick="OnSearch">@SharedLocalizer["Search"]</button>
</div>
</div>
</div>
<Pager Items="@userroles">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</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;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</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);
}
}
}

View File

@ -0,0 +1,123 @@
@namespace Oqtane.Modules.Admin.Visitors
@using System.Globalization
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IVisitorService VisitorService
@inject IUserService UserService
@inject IStringLocalizer<Detail> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="ip" HelpText="The last recorded IP address for this visitor" ResourceKey="IP">IP Address: </Label>
<div class="col-sm-9">
<input id="ip" class="form-control" @bind="@_ip" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="language" HelpText="The last recorded language for this visitor" ResourceKey="Language">Language: </Label>
<div class="col-sm-9">
<input id="language" class="form-control" @bind="@_language" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="useragent" HelpText="The last recorded user agent for this visitor" ResourceKey="UserAgent">User Agent: </Label>
<div class="col-sm-9">
<input id="useragent" class="form-control" @bind="@_useragent" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="url" HelpText="The last recorded url for this visitor" ResourceKey="Url">Url: </Label>
<div class="col-sm-9">
<input id="url" class="form-control" @bind="@_url" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="referrer" HelpText="The last recorded referrer for this visitor" ResourceKey="Referrer">Referrer: </Label>
<div class="col-sm-9">
<input id="referrer" class="form-control" @bind="@_referrer" readonly />
</div>
</div>
@if (_user != string.Empty)
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="user" HelpText="The last recorded user associated with this visitor" ResourceKey="User">User: </Label>
<div class="col-sm-9">
<input id="user" class="form-control" @bind="@_user" readonly />
</div>
</div>
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="visits" HelpText="The total number of visits by this visitor all time" ResourceKey="Visits">Visits: </Label>
<div class="col-sm-9">
<input id="visits" class="form-control" @bind="@_visits" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="visited" HelpText="The last recorded date/time when the visitor visited the site" ResourceKey="Visited">Visited: </Label>
<div class="col-sm-9">
<input id="visited" class="form-control" @bind="@_visited" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="created" HelpText="The first recorded date/time when this visitor visited the site" ResourceKey="Created">Created: </Label>
<div class="col-sm-9">
<input id="created" class="form-control" @bind="@_created" readonly />
</div>
</div>
</div>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@code {
private int _visitorId;
private string _ip = string.Empty;
private string _language = string.Empty;
private string _useragent = string.Empty;
private string _url = string.Empty;
private string _referrer = string.Empty;
private string _user = string.Empty;
private string _visits = string.Empty;
private string _visited = string.Empty;
private string _created = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnInitializedAsync()
{
try
{
_visitorId = Int32.Parse(PageState.QueryString["id"]);
var visitor = await VisitorService.GetVisitorAsync(_visitorId);
if (visitor != null)
{
_ip = visitor.IPAddress;
_language = visitor.Language;
_useragent = visitor.UserAgent;
_url = visitor.Url;
_referrer = visitor.Referrer;
_visits = visitor.Visits.ToString();
_visited = visitor.VisitedOn.ToString(CultureInfo.CurrentCulture);
_created = visitor.CreatedOn.ToString(CultureInfo.CurrentCulture);
if (visitor.UserId != null)
{
var user = await UserService.GetUserAsync(visitor.UserId.Value, PageState.Site.SiteId);
if (user != null)
{
_user = user.DisplayName;
}
}
}
else
{
AddModuleMessage(Localizer["Error.LoadVisitor"], MessageType.Error);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Visitor {VisitorId} {Error}", _visitorId, ex.Message);
AddModuleMessage(Localizer["Error.LoadVisitor"], MessageType.Error);
}
}
}

View File

@ -0,0 +1,144 @@
@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 style="width: 1px;">&nbsp;</th>
<th>@Localizer["IP"]</th>
<th>@Localizer["User"]</th>
<th>@Localizer["Language"]</th>
<th>@Localizer["Visits"]</th>
<th>@Localizer["Visited"]</th>
<th>@Localizer["Created"]</th>
</Header>
<Row>
<td><ActionLink Action="Detail" Parameters="@($"id=" + context.VisitorId.ToString())" ResourceKey="Details" /></td>
<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>
<td>@context.CreatedOn</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);
}
}
}

View File

@ -17,7 +17,7 @@
<div class="modal-footer">
@if (!string.IsNullOrEmpty(Action))
{
<button type="button" class="@Class" @onclick="Confirm">@((MarkupString)_iconSpan) @Localize(Action)</button>
<button type="button" class="@Class" @onclick="Confirm">@((MarkupString)_iconSpan) @Text</button>
}
<button type="button" class="btn btn-secondary" @onclick="DisplayModal">@Localize("Cancel")</button>
</div>
@ -30,16 +30,17 @@
{
if (Disabled)
{
<button class="@Class" disabled>@((MarkupString)_iconSpan) @Text</button>
<button type="button" class="@Class" disabled>@((MarkupString)_iconSpan) @Text</button>
}
else
{
<button class="@Class" @onclick="DisplayModal">@((MarkupString)_iconSpan) @Text</button>
<button type="button" class="@Class" @onclick="DisplayModal">@((MarkupString)_iconSpan) @Text</button>
}
}
@code {
private bool _visible = false;
private string _permissions = string.Empty;
private bool _editmode = false;
private bool _authorized = false;
private string _iconSpan = string.Empty;
@ -59,6 +60,9 @@
[Parameter]
public SecurityAccessLevel? Security { get; set; } // optional - can be used to explicitly specify SecurityAccessLevel
[Parameter]
public string Permissions { get; set; } // optional - can be used to specify a permission string
[Parameter]
public string Class { get; set; } // optional
@ -105,6 +109,7 @@
Header = Localize(nameof(Header), Header);
Message = Localize(nameof(Message), Message);
_permissions = (string.IsNullOrEmpty(Permissions)) ? ModuleState.Permissions : Permissions;
_authorized = IsAuthorized();
}
@ -138,10 +143,10 @@
authorized = true;
break;
case SecurityAccessLevel.View:
authorized = UserSecurity.IsAuthorized(PageState.User,PermissionNames.View, ModuleState.Permissions);
authorized = UserSecurity.IsAuthorized(PageState.User,PermissionNames.View, _permissions);
break;
case SecurityAccessLevel.Edit:
authorized = UserSecurity.IsAuthorized(PageState.User,PermissionNames.Edit, ModuleState.Permissions);
authorized = UserSecurity.IsAuthorized(PageState.User,PermissionNames.Edit, _permissions);
break;
case SecurityAccessLevel.Admin:
authorized = UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin);

View File

@ -6,101 +6,118 @@
{
if (Disabled)
{
<button class="@_classname" style="@_style" disabled>@((MarkupString)_iconSpan) @_text</button>
}
else
{
<NavLink class="@_classname" href="@_url" style="@_style">@((MarkupString)_iconSpan) @_text</NavLink>
}
<button type="button" class="@_classname" style="@_style" disabled>@((MarkupString)_iconSpan) @_text</button>
}
else
{
if (OnClick == null)
{
<NavLink class="@_classname" href="@_url" style="@_style">@((MarkupString)_iconSpan) @_text</NavLink>
}
else
{
<button type="button" class="@_classname" style="@_style" onclick="@OnClick">@((MarkupString)_iconSpan) @_text</button>
}
}
}
@code {
private string _text = string.Empty;
private string _url = string.Empty;
private string _parameters = string.Empty;
private string _classname = "btn btn-primary";
private string _style = string.Empty;
private bool _editmode = false;
private bool _authorized = false;
private string _iconSpan = string.Empty;
private string _text = string.Empty;
private string _parameters = string.Empty;
private string _url = string.Empty;
private string _permissions = string.Empty;
private bool _editmode = false;
private bool _authorized = false;
private string _classname = "btn btn-primary";
private string _style = string.Empty;
private string _iconSpan = string.Empty;
[Parameter]
public string Action { get; set; } // required
[Parameter]
public string Action { get; set; } // required
[Parameter]
public SecurityAccessLevel? Security { get; set; } // optional - can be used to explicitly specify SecurityAccessLevel
[Parameter]
public string Text { get; set; } // optional - defaults to Action if not specified
[Parameter]
public string Text { get; set; } // optional - defaults to Action if not specified
[Parameter]
public string Parameters { get; set; } // optional - querystring parameters should be in the form of "id=x&name=y"
[Parameter]
public string Parameters { get; set; } // optional - querystring parameter should be in the form of "id=x&name=y"
[Parameter]
public int ModuleId { get; set; } = -1; // optional - allows the link to target a specific moduleid
[Parameter]
public string Class { get; set; } // optional - defaults to primary if not specified
[Parameter]
public Action OnClick { get; set; } = null; // optional - executes a method in the calling component
[Parameter]
public string Style { get; set; } // optional
[Parameter]
public SecurityAccessLevel? Security { get; set; } // optional - can be used to explicitly specify SecurityAccessLevel
[Parameter]
public bool Disabled { get; set; } // optional
[Parameter]
public string Permissions { get; set; } // optional - can be used to specify a permission string
[Parameter]
public string EditMode { get; set; } // optional - specifies if an authorized user must be in edit mode to see the action - default is false.
[Parameter]
public bool Disabled { get; set; } // optional
[Parameter]
public string IconName { get; set; } // optional - specifies an icon for the link - default is no icon
[Parameter]
public string EditMode { get; set; } // optional - specifies if an authorized user must be in edit mode to see the action - default is false.
[Parameter]
public bool IconOnly { get; set; } // optional - specifies only icon in link
[Parameter]
public string Class { get; set; } // optional - defaults to primary if not specified
protected override void OnParametersSet()
{
base.OnParametersSet();
[Parameter]
public string Style { get; set; } // optional
_text = Action;
if (!string.IsNullOrEmpty(Text))
{
_text = Text;
}
[Parameter]
public string IconName { get; set; } // optional - specifies an icon for the link - default is no icon
if (IconOnly && !string.IsNullOrEmpty(IconName))
{
_text = string.Empty;
}
[Parameter]
public bool IconOnly { get; set; } // optional - specifies only icon in link
if (!string.IsNullOrEmpty(Parameters))
{
_parameters = Parameters;
}
protected override void OnParametersSet()
{
base.OnParametersSet();
if (!string.IsNullOrEmpty(Class))
{
_classname = Class;
}
_text = Action;
if (!string.IsNullOrEmpty(Text))
{
_text = Text;
}
if (!string.IsNullOrEmpty(Style))
{
_style = Style;
}
if (IconOnly && !string.IsNullOrEmpty(IconName))
{
_text = string.Empty;
}
if (!string.IsNullOrEmpty(EditMode))
{
_editmode = bool.Parse(EditMode);
}
if (!string.IsNullOrEmpty(Parameters))
{
_parameters = Parameters;
}
if (!string.IsNullOrEmpty(IconName))
{
if (!IconName.Contains(" "))
{
IconName = "oi oi-" + IconName;
}
_iconSpan = $"<span class=\"{IconName}\"></span>{(IconOnly ? "" : "&nbsp")}";
if (!string.IsNullOrEmpty(Class))
{
_classname = Class;
}
}
if (!string.IsNullOrEmpty(Style))
{
_style = Style;
}
_text = Localize(nameof(Text), _text);
_url = EditUrl(Action, _parameters);
if (!string.IsNullOrEmpty(EditMode))
{
_editmode = bool.Parse(EditMode);
}
if (!string.IsNullOrEmpty(IconName))
{
if (!IconName.Contains(" "))
{
IconName = "oi oi-" + IconName;
}
_iconSpan = $"<span class=\"{IconName}\"></span>{(IconOnly ? "" : "&nbsp")}";
}
_permissions = (string.IsNullOrEmpty(Permissions)) ? ModuleState.Permissions : Permissions;
_text = Localize(nameof(Text), _text);
_url = (ModuleId == -1) ? EditUrl(Action, _parameters) : EditUrl(ModuleId, Action, _parameters);
_authorized = IsAuthorized();
}
@ -136,10 +153,10 @@
authorized = true;
break;
case SecurityAccessLevel.View:
authorized = UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, ModuleState.Permissions);
authorized = UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, _permissions);
break;
case SecurityAccessLevel.Edit:
authorized = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, ModuleState.Permissions);
authorized = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, _permissions);
break;
case SecurityAccessLevel.Admin:
authorized = UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin);

View File

@ -4,7 +4,7 @@
@if (ItemList != null)
{
@if (Toolbar == "Top" && _pages > 0 && Items.Count() > _maxItems)
@if ((Toolbar == "Top" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
{
<ul class="pagination justify-content-center my-2">
<li class="page-item@((_page > 1) ? "" : " disabled")">
@ -101,7 +101,7 @@
}
</div>
}
@if (Toolbar == "Bottom" && _pages > 0 && Items.Count() > _maxItems)
@if ((Toolbar == "Bottom" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
{
<ul class="pagination justify-content-center my-2">
<li class="page-item@((_page > 1) ? "" : " disabled")">
@ -164,7 +164,7 @@
public string Format { get; set; } // Table or Grid
[Parameter]
public string Toolbar { get; set; } // Top or Bottom
public string Toolbar { get; set; } // Top, Bottom or Both
[Parameter]
public RenderFragment Header { get; set; } = null;

View File

@ -115,24 +115,24 @@
public override List<Resource> Resources => new List<Resource>()
{
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill1.3.6.min.js" },
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill1.3.7.min.js" },
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-blot-formatter.min.js" },
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-interop.js" }
};
protected override void OnInitialized()
protected override void OnParametersSet()
{
_content = Content; // raw HTML
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
var interop = new RichTextEditorInterop(JSRuntime);
if (firstRender)
{
await base.OnAfterRenderAsync(firstRender);
var interop = new RichTextEditorInterop(JSRuntime);
await interop.CreateEditor(
_editorElement,
_toolBar,
@ -140,14 +140,15 @@
Placeholder,
Theme,
DebugLevel);
await interop.LoadEditorContent(_editorElement, Content);
_content = Content; // raw HTML
// preserve a copy of the rich text content ( Quill sanitizes content so we need to retrieve it from the editor )
_original = await interop.GetHtml(_editorElement);
}
await interop.LoadEditorContent(_editorElement, Content);
_content = Content; // raw HTML
// preserve a copy of the rich text content ( Quill sanitizes content so we need to retrieve it from the editor )
_original = await interop.GetHtml(_editorElement);
}
public void CloseFileManager()

View File

@ -40,6 +40,10 @@ else
{
Heading = Localize(nameof(Name), Name);
}
else
{
Heading = Localize(nameof(Heading), Heading);
}
}
public string DisplayHeading()

View File

@ -43,16 +43,12 @@
[Parameter]
public bool Refresh { get; set; } // optional - used in scenarios where TabPanels are added/removed dynamically within a parent form. ActiveTab may need to be reset as well when this property is used.
protected override void OnInitialized()
protected override void OnParametersSet()
{
if (PageState.QueryString.ContainsKey("tab"))
{
ActiveTab = PageState.QueryString["tab"];
}
}
protected override void OnParametersSet()
{
if (_tabPanels == null || Refresh)
{
_tabPanels = new List<TabPanel>();

View File

@ -30,8 +30,8 @@
public override List<Resource> Resources => new List<Resource>()
{
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill1.3.6.bubble.css" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill1.3.6.snow.css" }
new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill1.3.7.bubble.css" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill1.3.7.snow.css" }
};
private RichTextEditor RichTextEditorHtml;
@ -65,8 +65,8 @@
}
catch (Exception ex)
{
await logger.LogError(ex, "An Error Occurred Loading Html/Text Content. " + ex.Message);
AddModuleMessage(ex.Message, MessageType.Error);
await logger.LogError(ex, "Error Loading Content {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Content.Load"], MessageType.Error);
}
}
@ -91,7 +91,7 @@
await HtmlTextService.AddHtmlTextAsync(htmltext);
}
await logger.LogInformation("Html/Text Content Saved {HtmlText}", htmltext);
await logger.LogInformation("Content Saved {HtmlText}", htmltext);
NavigationManager.NavigateTo(NavigateUrl());
}
catch (Exception ex)

View File

@ -15,16 +15,16 @@
}
@code {
public override List<Resource> Resources => new List<Resource>()
public override List<Resource> Resources => new List<Resource>()
{
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }
};
private string content = "";
private string content = "";
protected override async Task OnParametersSetAsync()
{
try
protected override async Task OnParametersSetAsync()
{
try
{
var htmltext = await HtmlTextService.GetHtmlTextAsync(ModuleState.ModuleId);
if (htmltext != null)
@ -35,8 +35,8 @@
}
catch (Exception ex)
{
await logger.LogError(ex, "An Error Occurred Loading Html/Text Content. " + ex.Message);
AddModuleMessage(ex.Message, MessageType.Error);
await logger.LogError(ex, "Error Loading Content {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Content.Load"], MessageType.Error);
}
}
}

View File

@ -1,7 +1,9 @@
using Oqtane.Documentation;
using Oqtane.Models;
namespace Oqtane.Modules.HtmlText
{
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
public class ModuleInfo : IModule
{
public ModuleDefinition ModuleDefinition => new ModuleDefinition

View File

@ -1,10 +1,12 @@
using System.Net.Http;
using System.Threading.Tasks;
using Oqtane.Documentation;
using Oqtane.Services;
using Oqtane.Shared;
namespace Oqtane.Modules.HtmlText.Services
{
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
public class HtmlTextService : ServiceBase, IHtmlTextService, IService
{
public HtmlTextService(HttpClient http, SiteState siteState) : base(http, siteState) {}

View File

@ -1,9 +1,11 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Threading.Tasks;
using Oqtane.Documentation;
using Oqtane.Modules.HtmlText.Models;
namespace Oqtane.Modules.HtmlText.Services
{
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
public interface IHtmlTextService
{
Task<Models.HtmlText> GetHtmlTextAsync(int ModuleId);

View File

@ -136,7 +136,12 @@ namespace Oqtane.Modules
public string ImageUrl(int fileid, int width, int height, string mode)
{
return Utilities.ImageUrl(PageState.Alias, fileid, width, height, mode);
return ImageUrl(fileid, width, height, mode, 0);
}
public string ImageUrl(int fileid, int width, int height, string mode, int rotate)
{
return Utilities.ImageUrl(PageState.Alias, fileid, width, height, mode, rotate);
}
public virtual Dictionary<string, string> GetUrlParameters(string parametersTemplate = "")

View File

@ -5,7 +5,7 @@
<OutputType>Exe</OutputType>
<RazorLangVersion>3.0</RazorLangVersion>
<Configurations>Debug;Release</Configurations>
<Version>3.0.0</Version>
<Version>3.0.1</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -13,7 +13,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.0</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.1</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace>
@ -34,11 +34,6 @@
<ProjectReference Include="..\Oqtane.Shared\Oqtane.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Resources\" />
<Folder Include="Resources\Themes\Controls\Theme\" />
</ItemGroup>
<ItemGroup>
<TrimmerRootAssembly Include="System.Runtime" />
<TrimmerRootAssembly Include="System.Linq.Parallel" />

View File

@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.JSInterop;
using Oqtane.Documentation;
using Oqtane.Modules;
using Oqtane.Services;
using Oqtane.Shared;
@ -19,6 +20,7 @@ using Oqtane.UI;
namespace Oqtane.Client
{
[PrivateApi("Mark Entry-Program as private, since it's not very useful in the public docs")]
public class Program
{
public static async Task Main(string[] args)

View File

@ -159,4 +159,10 @@
<data name="Type" xml:space="preserve">
<value>Type</value>
</data>
<data name="DeleteFile.Text" xml:space="preserve">
<value>Delete</value>
</data>
<data name="UploadFiles.Text" xml:space="preserve">
<value>Upload Files</value>
</data>
</root>

View File

@ -168,4 +168,13 @@
<data name="Stop" xml:space="preserve">
<value>Stop</value>
</data>
<data name="DeleteJob.Text" xml:space="preserve">
<value>Delete</value>
</data>
<data name="EditJob.Text" xml:space="preserve">
<value>Edit</value>
</data>
<data name="JobLog.Text" xml:space="preserve">
<value>Log</value>
</data>
</root>

View File

@ -153,4 +153,19 @@
<data name="Search.NoResults" xml:space="preserve">
<value>No Translations Match The Criteria Provided Or Package Service Is Disabled</value>
</data>
<data name="Download.Heading" xml:space="preserve">
<value>Download</value>
</data>
<data name="LanguageUpload.HelpText" xml:space="preserve">
<value>Upload one or more translations. Once they are uploaded click Install to complete the installation.</value>
</data>
<data name="LanguageUpload.Text" xml:space="preserve">
<value>Upload Language</value>
</data>
<data name="Manage.Heading" xml:space="preserve">
<value>Manage</value>
</data>
<data name="Upload.Heading" xml:space="preserve">
<value>Upload</value>
</data>
</root>

View File

@ -141,4 +141,7 @@
<data name="Default" xml:space="preserve">
<value>Default</value>
</data>
<data name="DeleteLanguage.Text" xml:space="preserve">
<value>Delete</value>
</data>
</root>

View File

@ -189,4 +189,7 @@
<data name="Create" xml:space="preserve">
<value>Create</value>
</data>
<data name="LogDetails.Text" xml:space="preserve">
<value>Details</value>
</data>
</root>

View File

@ -138,4 +138,10 @@
<data name="Search.NoResults" xml:space="preserve">
<value>No Modules Match The Criteria Provided Or Package Service Is Disabled</value>
</data>
<data name="Download.Heading" xml:space="preserve">
<value>Download</value>
</data>
<data name="Upload.Heading" xml:space="preserve">
<value>Upload</value>
</data>
</root>

View File

@ -183,7 +183,16 @@
<data name="Runtimes.Text" xml:space="preserve">
<value>Runtimes: </value>
</data>
<data name="Definition.Name" xml:space="preserve">
<data name="Definition.Heading" xml:space="preserve">
<value>Definition</value>
</data>
<data name="Information.Heading" xml:space="preserve">
<value>Information</value>
</data>
<data name="Permissions.Heading" xml:space="preserve">
<value>Permissions</value>
</data>
<data name="Information.Text" xml:space="preserve">
<value>Information</value>
</data>
</root>

View File

@ -144,4 +144,10 @@
<data name="DeleteModule.Header" xml:space="preserve">
<value>Delete Module</value>
</data>
<data name="InUse" xml:space="preserve">
<value>In Use</value>
</data>
<data name="EditModule.Text" xml:space="preserve">
<value>Edit</value>
</data>
</root>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@ -210,7 +210,7 @@
<data name="Personalizable.Text" xml:space="preserve">
<value>Personalizable? </value>
</data>
<data name="Appearance.Name" xml:space="preserve">
<data name="Appearance.Heading" xml:space="preserve">
<value>Appearance</value>
</data>
<data name="ThisLocation.Keep" xml:space="preserve">
@ -231,4 +231,40 @@
<data name="Move.Text" xml:space="preserve">
<value>Move: </value>
</data>
<data name="ModuleDefinition" xml:space="preserve">
<value>Module</value>
</data>
<data name="ModuleTitle" xml:space="preserve">
<value>Title</value>
</data>
<data name="PageModules.Heading" xml:space="preserve">
<value>Modules</value>
</data>
<data name="ModuleSettings.Text" xml:space="preserve">
<value>Edit</value>
</data>
<data name="DeleteModule.Header" xml:space="preserve">
<value>Delete Module</value>
</data>
<data name="DeleteModule.Message" xml:space="preserve">
<value>Are You Sure You Wish To Delete This Module?</value>
</data>
<data name="DeleteModule.Text" xml:space="preserve">
<value>Delete</value>
</data>
<data name="Permissions.Heading" xml:space="preserve">
<value>Permissions</value>
</data>
<data name="ThemeSettings.Heading" xml:space="preserve">
<value>Theme Settings</value>
</data>
<data name="Appearance.Hea" xml:space="preserve">
<value />
</data>
<data name="Clickable.HelpText" xml:space="preserve">
<value>Select whether the link in the site navigation is enabled or disabled</value>
</data>
<data name="Clickable.Text" xml:space="preserve">
<value>Clickable?</value>
</data>
</root>

View File

@ -132,4 +132,10 @@
<data name="Browse" xml:space="preserve">
<value>Browse</value>
</data>
<data name="DeletePage.Text" xml:space="preserve">
<value>Delete</value>
</data>
<data name="EditPage.Text" xml:space="preserve">
<value>Edit</value>
</data>
</root>

View File

@ -132,4 +132,10 @@
<data name="DeleteProfile.Header" xml:space="preserve">
<value>Delete Profile</value>
</data>
<data name="DeleteProfile.Text" xml:space="preserve">
<value>Delete</value>
</data>
<data name="EditProfile.Text" xml:space="preserve">
<value>Edit</value>
</data>
</root>

View File

@ -177,7 +177,10 @@
<data name="DeleteAllModules.Message" xml:space="preserve">
<value>Are You Sure You Wish To Permanently Delete All Modules?</value>
</data>
<data name="Pages.Name" xml:space="preserve">
<data name="Pages.Heading" xml:space="preserve">
<value>Pages</value>
</data>
<data name="Modules.Heading" xml:space="preserve">
<value>Modules</value>
</data>
</root>

View File

@ -129,4 +129,13 @@
<data name="DeleteRole.Header" xml:space="preserve">
<value>Delete Role</value>
</data>
<data name="DeleteRole.Text" xml:space="preserve">
<value>Delete</value>
</data>
<data name="Edit.Text" xml:space="preserve">
<value>Edit</value>
</data>
<data name="Users.Text" xml:space="preserve">
<value>Users</value>
</data>
</root>

View File

@ -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,28 @@
<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>
<data name="ConnectionString.Text" xml:space="preserve">
<value>Connection:</value>
</data>
<data name="Database.Text" xml:space="preserve">
<value>Database:</value>
</data>
<data name="ConnectionString.HelpText" xml:space="preserve">
<value>The connection information for the database</value>
</data>
<data name="Database.HelpText" xml:space="preserve">
<value>The database for the tenant</value>
</data>
<data name="DeleteSite.Text" xml:space="preserve">
<value>Delete Site</value>
</data>
</root>

View File

@ -204,10 +204,10 @@
<data name="Critical" xml:space="preserve">
<value>Critical</value>
</data>
<data name="Info" xml:space="preserve">
<data name="Info.Heading" xml:space="preserve">
<value>Info</value>
</data>
<data name="Options" xml:space="preserve">
<data name="Options.Heading" xml:space="preserve">
<value>Options</value>
</data>
<data name="Register" xml:space="preserve">
@ -228,4 +228,7 @@
<data name="Swagger.Text" xml:space="preserve">
<value>Swagger Enabled?</value>
</data>
<data name="RestartApplication.Text" xml:space="preserve">
<value>Restart Application</value>
</data>
</root>

View File

@ -138,4 +138,10 @@
<data name="DeleteTheme.Header" xml:space="preserve">
<value>Delete Theme</value>
</data>
<data name="CreateTheme.Text" xml:space="preserve">
<value>Create Theme</value>
</data>
<data name="ViewTheme.Text" xml:space="preserve">
<value>View</value>
</data>
</root>

View File

@ -135,4 +135,10 @@
<data name="Success.Framework.Download" xml:space="preserve">
<value>Framework Downloaded Successfully... Please Select Upgrade To Complete the Process</value>
</data>
<data name="Download.Heading" xml:space="preserve">
<value>Download</value>
</data>
<data name="Upload.Heading" xml:space="preserve">
<value>Upload</value>
</data>
</root>

View 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>

View 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>

View File

@ -0,0 +1,162 @@
<?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="Requested" xml:space="preserve">
<value>Requested</value>
</data>
<data name="Requests" xml:space="preserve">
<value>Requests</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>
<data name="Url" xml:space="preserve">
<value>Url</value>
</data>
</root>

View File

@ -177,4 +177,10 @@
<data name="Username.Text" xml:space="preserve">
<value>Username:</value>
</data>
<data name="Identity.Heading" xml:space="preserve">
<value>Identity</value>
</data>
<data name="Profile.Heading" xml:space="preserve">
<value>Profile</value>
</data>
</root>

View File

@ -126,4 +126,31 @@
<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>
<data name="DeleteUser.Text" xml:space="preserve">
<value>Delete</value>
</data>
<data name="EditUser.Text" xml:space="preserve">
<value>Edit</value>
</data>
<data name="Roles.Text" xml:space="preserve">
<value>Roles</value>
</data>
</root>

View File

@ -177,4 +177,7 @@
<data name="Expiry" xml:space="preserve">
<value>Expiry</value>
</data>
<data name="DeleteUserRole.Text" xml:space="preserve">
<value>Delete</value>
</data>
</root>

View File

@ -0,0 +1,177 @@
<?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="IP.Text" xml:space="preserve">
<value>IP Address:</value>
</data>
<data name="IP.HelpText" xml:space="preserve">
<value>The last recorded IP address for this visitor</value>
</data>
<data name="Language.HelpText" xml:space="preserve">
<value>The last recorded language for this visitor</value>
</data>
<data name="Language.Text" xml:space="preserve">
<value>Language:</value>
</data>
<data name="Error.LoadVisitor" xml:space="preserve">
<value>Error Loading Visitor</value>
</data>
<data name="Created.HelpText" xml:space="preserve">
<value>The frst recorded date/time when the visitor visited the site</value>
</data>
<data name="Created.Text" xml:space="preserve">
<value>Created:</value>
</data>
<data name="Referrer.HelpText" xml:space="preserve">
<value>The last recorded referrer for this visitor</value>
</data>
<data name="Referrer.Text" xml:space="preserve">
<value>Referrer:</value>
</data>
<data name="Url.HelpText" xml:space="preserve">
<value>The last recorded Url for this visitor</value>
</data>
<data name="Url.Text" xml:space="preserve">
<value>Url:</value>
</data>
<data name="User.HelpText" xml:space="preserve">
<value>The last recorded user associated with this visitor</value>
</data>
<data name="User.Text" xml:space="preserve">
<value>User:</value>
</data>
<data name="UserAgent.HelpText" xml:space="preserve">
<value>The last recorded user agent for this visitor</value>
</data>
<data name="UserAgent.Text" xml:space="preserve">
<value>User Agent:</value>
</data>
<data name="Visited.HelpText" xml:space="preserve">
<value>The last recorded date/time when the visitor visited the site</value>
</data>
<data name="Visited.Text" xml:space="preserve">
<value>Visited:</value>
</data>
<data name="Visits.HelpText" xml:space="preserve">
<value>The total number of visits by this visitor all time</value>
</data>
<data name="Visits.Text" xml:space="preserve">
<value>Visits:</value>
</data>
</root>

View File

@ -0,0 +1,174 @@
<?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="Visited" xml:space="preserve">
<value>Visited</value>
</data>
<data name="Visits" xml:space="preserve">
<value>Visits</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>
<data name="IP" xml:space="preserve">
<value>IP</value>
</data>
<data name="User" xml:space="preserve">
<value>User</value>
</data>
<data name="Created" xml:space="preserve">
<value>Created</value>
</data>
</root>

View File

@ -117,7 +117,10 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Error.Content.Load" xml:space="preserve">
<value>An Error Occurred Loading Content</value>
</data>
<data name="Error.Content.Save" xml:space="preserve">
<value>Error Saving Content</value>
<value>An Error Occurred Saving Content</value>
</data>
</root>

View File

@ -1,65 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
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.
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 xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
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>
@ -120,4 +120,7 @@
<data name="Edit.Action" xml:space="preserve">
<value>Edit</value>
</data>
<data name="Error.Content.Load" xml:space="preserve">
<value>An Error Occurred Loading Content</value>
</data>
</root>

View File

@ -318,4 +318,7 @@
<data name="BlazorWebAssembly" xml:space="preserve">
<value>Blazor WebAssembly</value>
</data>
<data name="Settings" xml:space="preserve">
<value>Settings</value>
</data>
</root>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@ -123,4 +123,7 @@
<data name="Error.Module.InvalidType" xml:space="preserve">
<value>Module Type Is Invalid For {0}</value>
</data>
<data name="Error.Module.Exception" xml:space="preserve">
<value>An Unexpected Error Has Occurred</value>
</data>
</root>

View File

@ -1,4 +1,5 @@
using Oqtane.Models;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
@ -70,7 +71,7 @@ namespace Oqtane.Services
/// <summary>
/// Returns a key-value dictionary of all module settings for the given module
/// </summary>
/// <param name="pageId"></param>
/// <param name="moduleId"></param>
/// <returns></returns>
Task<Dictionary<string, string>> GetModuleSettingsAsync(int moduleId);
@ -82,6 +83,21 @@ namespace Oqtane.Services
/// <returns></returns>
Task UpdateModuleSettingsAsync(Dictionary<string, string> moduleSettings, int moduleId);
/// <summary>
/// Returns a key-value dictionary of all module settings for the given module
/// </summary>
/// <param name="moduleDefinitionId"></param>
/// <returns></returns>
Task<Dictionary<string, string>> GetModuleDefinitionSettingsAsync(int moduleDefinitionId);
/// <summary>
/// Updates a module setting
/// </summary>
/// <param name="moduleDefinitionSettings"></param>
/// <param name="moduleDefinitionId"></param>
/// <returns></returns>
Task UpdateModuleDefinitionSettingsAsync(Dictionary<string, string> moduleDefinitionSettings, int moduleDefinitionId);
/// <summary>
/// Returns a key-value dictionary of all user settings for the given user
/// </summary>
@ -113,6 +129,19 @@ namespace Oqtane.Services
/// <returns></returns>
Task UpdateFolderSettingsAsync(Dictionary<string, string> folderSettings, int folderId);
/// <summary>
/// Returns a key-value dictionary of all tenant settings
/// </summary>
/// <returns></returns>
Task<Dictionary<string, string>> GetHostSettingsAsync();
/// <summary>
/// Updates a host setting
/// </summary>
/// <param name="hostSettings"></param>
/// <returns></returns>
Task UpdateHostSettingsAsync(Dictionary<string, string> hostSettings);
/// <summary>
/// Returns a key-value dictionary of all settings for the given entityName
/// </summary>
@ -135,7 +164,7 @@ namespace Oqtane.Services
/// </summary>
/// <param name="settingId"></param>
/// <returns></returns>
Task<Setting> GetSettingAsync(int settingId);
Task<Setting> GetSettingAsync(string entityName, int settingId);
/// <summary>
@ -157,7 +186,7 @@ namespace Oqtane.Services
/// </summary>
/// <param name="settingId"></param>
/// <returns></returns>
Task DeleteSettingAsync(int settingId);
Task DeleteSettingAsync(string entityName, int settingId);
/// <summary>
/// Gets the value of the given settingName (key) from the given key-value dictionary
@ -180,5 +209,13 @@ namespace Oqtane.Services
Dictionary<string, string> SetSetting(Dictionary<string, string> settings, string settingName, string settingValue, bool isPublic);
Dictionary<string, string> MergeSettings(Dictionary<string, string> settings1, Dictionary<string, string> settings2);
}
[Obsolete("GetSettingAsync(int settingId) is deprecated. Use GetSettingAsync(string entityName, int settingId) instead.", false)]
Task<Setting> GetSettingAsync(int settingId);
[Obsolete("DeleteSettingAsync(int settingId) is deprecated. Use DeleteSettingAsync(string entityName, int settingId) instead.", false)]
Task DeleteSettingAsync(int settingId);
}
}

View 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);
}
}

View File

@ -0,0 +1,29 @@
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);
/// <summary>
/// Get a specific <see cref="Visitor"/> of this <see cref="Site"/>.
///
/// </summary>
/// <param name="visitorId">ID-reference of a <see cref="Visitor"/></param>
/// <returns></returns>
Task<Visitor> GetVisitorAsync(int visitorId);
}
}

View File

@ -21,6 +21,7 @@ namespace Oqtane.Services
}
private string Apiurl => CreateApiUrl("Setting", _siteState.Alias);
public async Task<Dictionary<string, string>> GetTenantSettingsAsync()
{
return await GetSettingsAsync(EntityNames.Tenant, -1);
@ -71,6 +72,16 @@ namespace Oqtane.Services
await UpdateSettingsAsync(moduleSettings, EntityNames.Module, moduleId);
}
public async Task<Dictionary<string, string>> GetModuleDefinitionSettingsAsync(int moduleDefinitionId)
{
return await GetSettingsAsync(EntityNames.ModuleDefinition, moduleDefinitionId);
}
public async Task UpdateModuleDefinitionSettingsAsync(Dictionary<string, string> moduleDefinitionSettings, int moduleDefinitionId)
{
await UpdateSettingsAsync(moduleDefinitionSettings, EntityNames.ModuleDefinition, moduleDefinitionId);
}
public async Task<Dictionary<string, string>> GetUserSettingsAsync(int userId)
{
return await GetSettingsAsync(EntityNames.User, userId);
@ -91,6 +102,16 @@ namespace Oqtane.Services
await UpdateSettingsAsync(folderSettings, EntityNames.Folder, folderId);
}
public async Task<Dictionary<string, string>> GetHostSettingsAsync()
{
return await GetSettingsAsync(EntityNames.Host, -1);
}
public async Task UpdateHostSettingsAsync(Dictionary<string, string> hostSettings)
{
await UpdateSettingsAsync(hostSettings, EntityNames.Host, -1);
}
public async Task<Dictionary<string, string>> GetSettingsAsync(string entityName, int entityId)
{
var dictionary = new Dictionary<string, string>();
@ -144,9 +165,9 @@ namespace Oqtane.Services
}
public async Task<Setting> GetSettingAsync(int settingId)
public async Task<Setting> GetSettingAsync(string entityName, int settingId)
{
return await GetJsonAsync<Setting>($"{Apiurl}/{settingId}");
return await GetJsonAsync<Setting>($"{Apiurl}/{settingId}/{entityName}");
}
public async Task<Setting> AddSettingAsync(Setting setting)
@ -159,9 +180,9 @@ namespace Oqtane.Services
return await PutJsonAsync<Setting>($"{Apiurl}/{setting.SettingId}", setting);
}
public async Task DeleteSettingAsync(int settingId)
public async Task DeleteSettingAsync(string entityName, int settingId)
{
await DeleteAsync($"{Apiurl}/{settingId}");
await DeleteAsync($"{Apiurl}/{settingId}/{entityName}");
}
@ -220,5 +241,19 @@ namespace Oqtane.Services
}
return settings1;
}
[Obsolete("GetSettingAsync(int settingId) is deprecated. Use GetSettingAsync(string entityName, int settingId) instead.", false)]
public async Task<Setting> GetSettingAsync(int settingId)
{
return await GetJsonAsync<Setting>($"{Apiurl}/{settingId}/tenant");
}
[Obsolete("DeleteSettingAsync(int settingId) is deprecated. Use DeleteSettingAsync(string entityName, int settingId) instead.", false)]
public async Task DeleteSettingAsync(int settingId)
{
await DeleteAsync($"{Apiurl}/{settingId}/tenant");
}
}
}

View 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}");
}
}
}

View File

@ -0,0 +1,37 @@
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();
}
public async Task<Visitor> GetVisitorAsync(int visitorId)
{
return await GetJsonAsync<Visitor>($"{Apiurl}/{visitorId}");
}
}
}

View File

@ -1,7 +1,9 @@
using Oqtane.Models;
using Oqtane.Documentation;
using Oqtane.Models;
namespace Oqtane.Themes.BlazorTheme
{
[PrivateApi("Mark Build-In Theme-Info classes as private, since it's not very useful in the public docs")]
public class ThemeInfo : ITheme
{
public Theme Theme => new Theme

View File

@ -16,7 +16,15 @@
else
{
<li class="breadcrumb-item">
<a href="@NavigateUrl(p.Path)">@p.Name</a>
@if(p.IsClickable)
{
<a href="@NavigateUrl(p.Path)">@p.Name</a>
}
else
{
@p.Name
}
</li>
}
}

View File

@ -35,7 +35,7 @@
@if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions))
{
<button class="btn @ButtonClass" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasControlPanel" aria-controls="offcanvasControlPanel">
<button type="button" class="btn @ButtonClass" data-bs-toggle="offcanvas" data-bs-target="#offcanvasControlPanel" aria-controls="offcanvasControlPanel">
<span class="oi oi-cog"></span>
</button>
@ -50,7 +50,7 @@
{
<div class="row d-flex">
<div class="col">
<button data-bs-dismiss="offcanvas" type="button" class="btn btn-primary col-12" @onclick=@(async () => Navigate("Admin"))>@Localizer["AdminDash"]</button>
<button type="button" data-bs-dismiss="offcanvas" class="btn btn-primary col-12" @onclick=@(async () => Navigate("Admin"))>@Localizer["AdminDash"]</button>
</div>
</div>
@ -63,9 +63,9 @@
</div>
<div class="row d-flex">
<div class="col d-flex justify-content-between">
<button type="button" class="btn btn-secondary col-3" data-bs-dismiss="offcanvas" @onclick=@(async () => Navigate("Add"))>@SharedLocalizer["Add"]</button>
<button type="button" class="btn btn-secondary col-3" data-bs-dismiss="offcanvas" @onclick=@(async () => Navigate("Edit"))>@SharedLocalizer["Edit"]</button>
<button type="button" class="btn btn-danger col-3" @onclick="ConfirmDelete">@SharedLocalizer["Delete"]</button>
<button type="button" class="btn btn-secondary col me-1" data-bs-dismiss="offcanvas" @onclick=@(async () => Navigate("Add"))>@SharedLocalizer["Add"]</button>
<button type="button" class="btn btn-secondary col" data-bs-dismiss="offcanvas" @onclick=@(async () => Navigate("Edit"))>@SharedLocalizer["Edit"]</button>
<button type="button" class="btn btn-danger col ms-1" @onclick="ConfirmDelete">@SharedLocalizer["Delete"]</button>
</div>
</div>
<br />

View File

@ -5,7 +5,7 @@
@if (MenuPages.Any())
{
<span class="app-menu-toggler navbar-expand-md">
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#Menu" aria-controls="Menu" aria-expanded="false" aria-label="Toggle Navigation">
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#Menu" aria-controls="Menu" aria-expanded="false" aria-label="Toggle Navigation">
<span class="navbar-toggler-icon"></span>
</button>
</span>

View File

@ -4,7 +4,7 @@
@if (MenuPages.Any())
{
<span class="app-menu-toggler">
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#Menu" aria-controls="Menu" aria-expanded="false" aria-label="Toggle Navigation">
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#Menu" aria-controls="Menu" aria-expanded="false" aria-label="Toggle Navigation">
<span class="navbar-toggler-icon"></span>
</button>
</span>

View File

@ -1,7 +1,9 @@
using Oqtane.Documentation;
using Oqtane.Models;
namespace Oqtane.Themes.OqtaneTheme
{
[PrivateApi("Mark Build-In Theme-Info classes as private, since it's not very useful in the public docs")]
public class ThemeInfo : ITheme
{
public Theme Theme => new Theme

View File

@ -106,7 +106,12 @@ namespace Oqtane.Themes
public string ImageUrl(int fileid, int width, int height, string mode)
{
return Utilities.ImageUrl(PageState.Alias, fileid, width, height, mode);
return ImageUrl(fileid, width, height, mode, 0);
}
public string ImageUrl(int fileid, int width, int height, string mode, int rotate)
{
return Utilities.ImageUrl(PageState.Alias, fileid, width, height, mode, rotate);
}
}
}

View File

@ -1,78 +1,104 @@
@namespace Oqtane.UI
@inject IStringLocalizer<ModuleInstance> Localizer
@inject ILogService LoggingService
@inherits ErrorBoundary
<ModuleMessage Message="@_message" Type="@_messagetype" />
@DynamicComponent
@if (_progressindicator)
@if (CurrentException is null)
{
<div class="app-progress-indicator"></div>
<ModuleMessage Message="@_message" Type="@_messageType"/>
@if (ModuleType != null)
{
<DynamicComponent Type="@ModuleType" Parameters="@ModuleParameters"></DynamicComponent>
@if (_progressIndicator)
{
<div class="app-progress-indicator"></div>
}
}
}
else
{
@if (!string.IsNullOrEmpty(_error))
{
<ModuleMessage Message="@_error" Type="@MessageType.Error"/>
}
}
@code {
private string _message;
private MessageType _messagetype;
private bool _progressindicator = false;
private string _message;
private string _error;
private MessageType _messageType;
private bool _progressIndicator = false;
[CascadingParameter]
protected PageState PageState { get; set; }
private Type ModuleType { get; set; }
private IDictionary<string, object> ModuleParameters { get; set; }
[CascadingParameter]
private Module ModuleState { get; set; }
[CascadingParameter]
protected PageState PageState { get; set; }
private ModuleMessage ModuleMessage { get; set; }
[CascadingParameter]
private Module ModuleState { get; set; }
private RenderFragment DynamicComponent { get; set; }
private ModuleMessage ModuleMessage { get; set; }
protected override void OnParametersSet()
{
_message = "";
protected override void OnParametersSet()
{
_message = "";
if (!string.IsNullOrEmpty(ModuleState.ModuleType))
{
ModuleType = Type.GetType(ModuleState.ModuleType);
if (ModuleType != null)
{
ModuleParameters = new Dictionary<string, object> { { "ModuleInstance", this } };
return;
}
// module does not exist with typename specified
_message = string.Format(Localizer["Error.Module.InvalidName"], Utilities.GetTypeNameLastSegment(ModuleState.ModuleType, 0));
_messageType = MessageType.Error;
}
else
{
_message = string.Format(Localizer["Error.Module.InvalidType"], ModuleState.ModuleDefinitionName);
_messageType = MessageType.Error;
}
}
DynamicComponent = builder =>
{
Type moduleType = null;
if (!string.IsNullOrEmpty(ModuleState.ModuleType))
{
moduleType = Type.GetType(ModuleState.ModuleType);
public void AddModuleMessage(string message, MessageType type)
{
_message = message;
_messageType = type;
_progressIndicator = false;
StateHasChanged();
}
if (moduleType != null)
{
builder.OpenComponent(0, moduleType);
builder.AddAttribute(1, "ModuleInstance", this);
builder.CloseComponent();
}
else
{
// module does not exist with typename specified
_message = string.Format(Localizer["Error.Module.InvalidName"], Utilities.GetTypeNameLastSegment(ModuleState.ModuleType, 0));
_messagetype = MessageType.Error;
}
}
else
{
_message = string.Format(Localizer["Error.Module.InvalidType"], ModuleState.ModuleDefinitionName);
_messagetype = MessageType.Error;
}
public void ShowProgressIndicator()
{
_progressIndicator = true;
StateHasChanged();
}
};
public void HideProgressIndicator()
{
_progressIndicator = false;
StateHasChanged();
}
protected override async Task OnErrorAsync(Exception exception)
{
// retrieve friendly localized error
_error = Localizer["Error.Module.Exception"];
// log error
int? userId = (PageState.User != null) ? PageState.User.UserId : null;
string category = GetType().AssemblyQualifiedName;
string feature = Utilities.GetTypeNameLastSegment(category, 1);
await LoggingService.Log(null, ModuleState.PageId, ModuleState.ModuleId, userId, category, feature, LogFunction.Other, LogLevel.Error, exception, "An Unexpected Error Has Occurred In {ModuleDefinitionName}: {Error}", ModuleState.ModuleDefinitionName, exception.Message);
await base.OnErrorAsync(exception);
return;
}
public new void Recover()
{
_error = "";
base.Recover();
}
public void AddModuleMessage(string message, MessageType type)
{
_message = message;
_messagetype = type;
_progressindicator = false;
StateHasChanged();
}
public void ShowProgressIndicator()
{
_progressindicator = true;
StateHasChanged();
}
public void HideProgressIndicator()
{
_progressindicator = false;
StateHasChanged();
}
}

View File

@ -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; }
}
}

View File

@ -15,78 +15,75 @@
@DynamicComponent
@code {
private string _absoluteUri;
private bool _navigationInterceptionEnabled;
private PageState _pagestate;
private string _absoluteUri;
private bool _navigationInterceptionEnabled;
private PageState _pagestate;
[Parameter]
public string Runtime { get; set; }
[Parameter]
public string Runtime { get; set; }
[Parameter]
public string RenderMode { get; set; }
[Parameter]
public string RenderMode { get; set; }
[CascadingParameter]
PageState PageState { get; set; }
[Parameter]
public int VisitorId { get; set; }
[Parameter]
public Action<PageState> OnStateChange { get; set; }
[CascadingParameter]
PageState PageState { get; set; }
private RenderFragment DynamicComponent { get; set; }
[Parameter]
public Action<PageState> OnStateChange { get; set; }
protected override void OnInitialized()
{
_absoluteUri = NavigationManager.Uri;
NavigationManager.LocationChanged += LocationChanged;
private RenderFragment DynamicComponent { get; set; }
DynamicComponent = builder =>
{
if (PageState != null)
{
builder.OpenComponent(0, Type.GetType(Constants.PageComponent));
builder.CloseComponent();
}
};
}
protected override void OnInitialized()
{
_absoluteUri = NavigationManager.Uri;
NavigationManager.LocationChanged += LocationChanged;
public void Dispose()
{
NavigationManager.LocationChanged -= LocationChanged;
}
DynamicComponent = builder =>
{
if (PageState != null)
{
builder.OpenComponent(0, Type.GetType(Constants.PageComponent));
builder.CloseComponent();
}
};
}
protected override async Task OnParametersSetAsync()
{
if (PageState == null)
{
await Refresh();
}
}
public void Dispose()
{
NavigationManager.LocationChanged -= LocationChanged;
}
[SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")]
private async Task Refresh()
{
Site site;
List<Page> pages;
Page page;
User user = null;
List<Module> modules;
var moduleid = -1;
var action = Constants.DefaultAction;
var urlparameters = string.Empty;
var editmode = false;
var refresh = UI.Refresh.None;
var lastsyncdate = DateTime.UtcNow.AddHours(-1);
var runtime = (Shared.Runtime)Enum.Parse(typeof(Shared.Runtime), Runtime);
protected override async Task OnParametersSetAsync()
{
if (PageState == null)
{
await Refresh();
}
}
Uri uri = new Uri(_absoluteUri);
[SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")]
private async Task Refresh()
{
Site site;
List<Page> pages;
Page page;
User user = null;
List<Module> modules;
var editmode = false;
var refresh = UI.Refresh.None;
var lastsyncdate = DateTime.UtcNow.AddHours(-1);
var runtime = (Shared.Runtime)Enum.Parse(typeof(Shared.Runtime), Runtime);
// get path
var path = uri.LocalPath.Substring(1);
// parse querystring
var querystring = ParseQueryString(uri.Query);
Route route = new Route(_absoluteUri, SiteState.Alias.Path);
var moduleid = (int.TryParse(route.ModuleId, out int mid)) ? mid : -1;
var action = (!string.IsNullOrEmpty(route.Action)) ? route.Action : Constants.DefaultAction;
var querystring = ParseQueryString(route.Query);
// reload the client application if there is a forced reload or the user navigated to a site with a different alias
if (querystring.ContainsKey("reload") || (!path.ToLower().StartsWith(SiteState.Alias.Path.ToLower()) && !string.IsNullOrEmpty(SiteState.Alias.Path)))
if (querystring.ContainsKey("reload") || (!route.AbsolutePath.Substring(1).ToLower().StartsWith(SiteState.Alias.Path.ToLower()) && !string.IsNullOrEmpty(SiteState.Alias.Path)))
{
NavigationManager.NavigateTo(_absoluteUri.Replace("?reload", ""), true);
return;
@ -168,72 +165,9 @@
pages = PageState.Pages;
}
// format path and remove alias
path = path.Replace("//", "/"); // in case of doubleslash at end
path += (!path.EndsWith("/")) ? "/" : "";
if (SiteState.Alias.Path != "" && path.StartsWith(SiteState.Alias.Path))
{
path = path.Substring(SiteState.Alias.Path.Length + 1);
}
// extract admin route elements from path
var segments = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
int result;
int modIdPos = 0;
int actionPos = 0;
int urlParametersPos = 0;
for (int i = 0; i < segments.Length; i++)
{
if (segments[i] == Constants.UrlParametersDelimiter)
{
urlParametersPos = i + 1;
}
if (i >= urlParametersPos && urlParametersPos != 0)
{
urlparameters += "/" + segments[i];
}
if (segments[i] == Constants.ModuleDelimiter)
{
modIdPos = i + 1;
actionPos = modIdPos + 1;
if (actionPos <= segments.Length - 1)
{
action = segments[actionPos];
}
}
}
// check if path has moduleid and action specification ie. pagename/*/moduleid/action/
if (modIdPos > 0)
{
int.TryParse(segments[modIdPos], out result);
moduleid = result;
if (actionPos > segments.Length - 1)
{
path = path.Replace(segments[modIdPos - 1] + "/" + segments[modIdPos] + "/", "");
}
else
{
path = path.Replace(segments[modIdPos - 1] + "/" + segments[modIdPos] + "/" + segments[actionPos] + "/", "");
}
}
if (urlParametersPos > 0)
{
path = path.Replace(segments[urlParametersPos - 1] + urlparameters + "/", "");
}
// remove trailing slash so it can be used as a key for Pages
if (path.EndsWith("/")) path = path.Substring(0, path.Length - 1);
if (PageState == null || refresh == UI.Refresh.Site)
{
page = pages.FirstOrDefault(item => item.Path.Equals(path, StringComparison.OrdinalIgnoreCase));
page = pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase));
}
else
{
@ -241,14 +175,13 @@
}
// get the page if the path has changed
if (page == null || page.Path != path)
if (page == null || page.Path != route.PagePath)
{
page = pages.FirstOrDefault(item => item.Path.Equals(path, StringComparison.OrdinalIgnoreCase));
// if the home page path does not exist then use the first page in the collection (a future enhancement would allow the admin to specify a home page)
if (page == null && path == "")
page = pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase));
// if the home page path does not exist then use the first page in the collection (a future enhancement would allow the admin to specify the home page)
if (page == null && route.PagePath == "")
{
page = pages.FirstOrDefault();
path = page.Path;
}
editmode = false;
}
@ -286,12 +219,13 @@
Modules = modules,
Uri = new Uri(_absoluteUri, UriKind.Absolute),
QueryString = querystring,
UrlParameters = urlparameters,
UrlParameters = route.UrlParameters,
ModuleId = moduleid,
Action = action,
EditMode = editmode,
LastSyncDate = lastsyncdate,
Runtime = runtime
Runtime = runtime,
VisitorId = VisitorId
};
OnStateChange?.Invoke(_pagestate);
@ -302,12 +236,12 @@
if (user == null)
{
// redirect to login page
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "login", "?returnurl=" + path));
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "login", "?returnurl=" + route.AbsolutePath));
}
else
{
await LogService.Log(null, null, user.UserId, GetType().AssemblyQualifiedName, Utilities.GetTypeNameLastSegment(GetType().AssemblyQualifiedName, 1), LogFunction.Security, LogLevel.Error, null, "Page Does Not Exist Or User Is Not Authorized To View Page {Path}", path);
if (path != "")
await LogService.Log(null, null, user.UserId, GetType().AssemblyQualifiedName, Utilities.GetTypeNameLastSegment(GetType().AssemblyQualifiedName, 1), LogFunction.Security, LogLevel.Error, null, "Page Does Not Exist Or User Is Not Authorized To View Page {Path}", route.PagePath);
if (route.PagePath != "")
{
// redirect to home page
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "", ""));

View File

@ -5,103 +5,55 @@
@DynamicComponent
@code {
[CascadingParameter] PageState PageState { get; set; }
[CascadingParameter] PageState PageState { get; set; }
RenderFragment DynamicComponent { get; set; }
RenderFragment DynamicComponent { get; set; }
protected override async Task OnParametersSetAsync()
{
var interop = new Interop(JsRuntime);
protected override void OnParametersSet()
{
// handle page redirection
if (!string.IsNullOrEmpty(PageState.Page.Url))
{
NavigationManager.NavigateTo(PageState.Page.Url);
return;
}
// handle page redirection
if (!string.IsNullOrEmpty(PageState.Page.Url))
{
NavigationManager.NavigateTo(PageState.Page.Url);
return;
}
DynamicComponent = builder =>
{
var themeType = Type.GetType(PageState.Page.ThemeType);
builder.OpenComponent(0, themeType);
builder.CloseComponent();
};
}
// set page title
if (!string.IsNullOrEmpty(PageState.Page.Title))
{
await interop.UpdateTitle(PageState.Page.Title);
}
else
{
await interop.UpdateTitle(PageState.Site.Name + " - " + PageState.Page.Name);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender)
{
var interop = new Interop(JsRuntime);
// manage stylesheets for this page
string batch = DateTime.Now.ToString("yyyyMMddHHmmssfff");
var links = new List<object>();
foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet && item.Declaration != ResourceDeclaration.Global))
{
links.Add(new { id = "app-stylesheet-" + batch + "-" + (links.Count + 1).ToString("00"), rel = "stylesheet", href = resource.Url, type = "text/css", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", key = "" });
}
if (links.Any())
{
await interop.IncludeLinks(links.ToArray());
}
await interop.RemoveElementsById("app-stylesheet", "", "app-stylesheet-" + batch + "-00");
// set page title
if (!string.IsNullOrEmpty(PageState.Page.Title))
{
await interop.UpdateTitle(PageState.Page.Title);
}
else
{
await interop.UpdateTitle(PageState.Site.Name + " - " + PageState.Page.Name);
}
// add favicon
if (PageState.Site.FaviconFileId != null)
{
await interop.IncludeLink("app-favicon", "shortcut icon", Utilities.ContentUrl(PageState.Alias, PageState.Site.FaviconFileId.Value), "image/x-icon", "", "", "id");
}
// add PWA support
if (PageState.Site.PwaIsEnabled && PageState.Site.PwaAppIconFileId != null && PageState.Site.PwaSplashIconFileId != null)
{
await InitializePwa(interop);
}
DynamicComponent = builder =>
{
var themeType = Type.GetType(PageState.Page.ThemeType);
builder.OpenComponent(0, themeType);
builder.CloseComponent();
};
}
private async Task InitializePwa(Interop interop)
{
string url = NavigationManager.BaseUri;
url = url.Substring(0, url.Length - 1);
// dynamically create manifest.json and add to page
string manifest = "setTimeout(() => { " +
"var manifest = { " +
"\"name\": \"" + PageState.Site.Name + "\", " +
"\"short_name\": \"" + PageState.Site.Name + "\", " +
"\"start_url\": \"" + url + "/\", " +
"\"display\": \"standalone\", " +
"\"background_color\": \"#fff\", " +
"\"description\": \"" + PageState.Site.Name + "\", " +
"\"icons\": [{ " +
"\"src\": \"" + url + Utilities.ContentUrl(PageState.Alias, PageState.Site.PwaAppIconFileId.Value) + "\", " +
"\"sizes\": \"192x192\", " +
"\"type\": \"image/png\" " +
"}, { " +
"\"src\": \"" + url + Utilities.ContentUrl(PageState.Alias, PageState.Site.PwaSplashIconFileId.Value) + "\", " +
"\"sizes\": \"512x512\", " +
"\"type\": \"image/png\" " +
"}] " +
"}; " +
"const serialized = JSON.stringify(manifest); " +
"const blob = new Blob([serialized], {type: 'application/javascript'}); " +
"const url = URL.createObjectURL(blob); " +
"document.getElementById('app-manifest').setAttribute('href', url); " +
"} " +
", 1000);";
await interop.IncludeScript("app-pwa", "", "", "", manifest, "body", "id");
// service worker must be in root of site
string serviceworker = "if ('serviceWorker' in navigator) { " +
"navigator.serviceWorker.register('/service-worker.js').then(function(registration) { " +
"console.log('ServiceWorker Registration Successful'); " +
"}).catch (function(err) { " +
"console.log('ServiceWorker Registration Failed ', err); " +
"}); " +
"}";
await interop.IncludeScript("app-serviceworker", "", "", "", serviceworker, "body", "id");
}
// manage stylesheets for this page
string batch = DateTime.Now.ToString("yyyyMMddHHmmssfff");
var links = new List<object>();
foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet && item.Declaration != ResourceDeclaration.Global))
{
links.Add(new { id = "app-stylesheet-" + batch + "-" + (links.Count + 1).ToString("00"), rel = "stylesheet", href = resource.Url, type = "text/css", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", key = "" });
}
if (links.Any())
{
await interop.IncludeLinks(links.ToArray());
}
await interop.RemoveElementsById("app-stylesheet", "", "app-stylesheet-" + batch + "-00");
}
}
}

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Version>3.0.0</Version>
<Version>3.0.1</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.0</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.1</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Database.MySQL</id>
<version>3.0.0</version>
<version>3.0.1</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane MySQL Provider</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.0</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.1</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Version>3.0.0</Version>
<Version>3.0.1</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.0</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.1</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Database.PostgreSQL</id>
<version>3.0.0</version>
<version>3.0.1</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane PostgreSQL Provider</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.0</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.1</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Version>3.0.0</Version>
<Version>3.0.1</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.0</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.1</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Database.SqlServer</id>
<version>3.0.0</version>
<version>3.0.1</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane SQL Server Provider</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.0</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.1</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Version>3.0.0</Version>
<Version>3.0.1</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.0</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.1</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Database.Sqlite</id>
<version>3.0.0</version>
<version>3.0.1</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane SQLite Provider</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.0</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.1</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Client</id>
<version>3.0.0</version>
<version>3.0.1</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.0</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.1</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Framework</id>
<version>3.0.0</version>
<version>3.0.1</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -11,8 +11,8 @@
<copyright>.NET Foundation</copyright>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v3.0.0/Oqtane.Framework.3.0.0.Upgrade.zip</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.0</releaseNotes>
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v3.0.1/Oqtane.Framework.3.0.1.Upgrade.zip</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.1</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane framework</tags>
</metadata>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Server</id>
<version>3.0.0</version>
<version>3.0.1</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.0</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.1</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Shared</id>
<version>3.0.0</version>
<version>3.0.1</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.0</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.1</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Updater</id>
<version>3.0.0</version>
<version>3.0.1</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.0</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.1</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -1 +1 @@
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.0.0.Install.zip" -Force
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.0.1.Install.zip" -Force

View File

@ -1 +1 @@
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.0.0.Upgrade.zip" -Force
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.0.1.Upgrade.zip" -Force

View File

@ -508,8 +508,8 @@ namespace Oqtane.Controllers
return System.IO.File.Exists(errorPath) ? PhysicalFile(errorPath, MimeUtilities.GetMimeType(errorPath)) : null;
}
[HttpGet("image/{id}/{width}/{height}/{mode?}")]
public IActionResult GetImage(int id, int width, int height, string mode)
[HttpGet("image/{id}/{width}/{height}/{mode?}/{rotate?}")]
public IActionResult GetImage(int id, int width, int height, string mode, string rotate)
{
var file = _files.GetFile(id);
if (file != null && file.Folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.View, file.Folder.Permissions))
@ -520,6 +520,7 @@ namespace Oqtane.Controllers
if (System.IO.File.Exists(filepath))
{
mode = (string.IsNullOrEmpty(mode)) ? "crop" : mode;
rotate = (string.IsNullOrEmpty(rotate)) ? "0" : rotate;
string imagepath = filepath.Replace(Path.GetExtension(filepath), "." + width.ToString() + "x" + height.ToString() + "." + mode.ToLower() + ".png");
if (!System.IO.File.Exists(imagepath))
@ -528,7 +529,7 @@ namespace Oqtane.Controllers
!string.IsNullOrEmpty(file.Folder.ImageSizes) && file.Folder.ImageSizes.ToLower().Split(",").Contains(width.ToString() + "x" + height.ToString()))
&& Enum.TryParse(mode, true, out ResizeMode resizemode))
{
imagepath = CreateImage(filepath, width, height, resizemode.ToString(), imagepath);
imagepath = CreateImage(filepath, width, height, resizemode.ToString(), rotate, imagepath);
}
else
{
@ -568,7 +569,7 @@ namespace Oqtane.Controllers
return System.IO.File.Exists(errorPath) ? PhysicalFile(errorPath, MimeUtilities.GetMimeType(errorPath)) : null;
}
private string CreateImage(string filepath, int width, int height, string mode, string imagepath)
private string CreateImage(string filepath, int width, int height, string mode, string rotate, string imagepath)
{
try
{
@ -585,6 +586,11 @@ namespace Oqtane.Controllers
})
.BackgroundColor(new Rgba32(255, 255, 255, 0)));
if (rotate != "0" && int.TryParse(rotate, out int angle))
{
image.Mutate(x => x.Rotate(angle));
}
image.Save(imagepath, new PngEncoder());
}
stream.Close();

View File

@ -22,11 +22,12 @@ namespace Oqtane.Controllers
private readonly IPermissionRepository _permissionRepository;
private readonly ISettingRepository _settings;
private readonly IUserPermissions _userPermissions;
private readonly IUrlMappingRepository _urlMappings;
private readonly ISyncManager _syncManager;
private readonly ILogManager _logger;
private readonly Alias _alias;
public PageController(IPageRepository pages, IModuleRepository modules, IPageModuleRepository pageModules, IPermissionRepository permissionRepository, ISettingRepository settings, IUserPermissions userPermissions, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger)
public PageController(IPageRepository pages, IModuleRepository modules, IPageModuleRepository pageModules, IPermissionRepository permissionRepository, ISettingRepository settings, IUserPermissions userPermissions, IUrlMappingRepository urlMappings, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger)
{
_pages = pages;
_modules = modules;
@ -34,6 +35,7 @@ namespace Oqtane.Controllers
_permissionRepository = permissionRepository;
_settings = settings;
_userPermissions = userPermissions;
_urlMappings = urlMappings;
_syncManager = syncManager;
_logger = logger;
_alias = tenantManager.GetAlias();
@ -252,17 +254,34 @@ namespace Oqtane.Controllers
[Authorize(Roles = RoleNames.Registered)]
public Page Put(int id, [FromBody] Page page)
{
if (ModelState.IsValid && page.SiteId == _alias.SiteId && _pages.GetPage(page.PageId, false) != null && _userPermissions.IsAuthorized(User, EntityNames.Page, page.PageId, PermissionNames.Edit))
// get current page
var currentPage = _pages.GetPage(page.PageId, false);
if (ModelState.IsValid && page.SiteId == _alias.SiteId && currentPage != null && _userPermissions.IsAuthorized(User, EntityNames.Page, page.PageId, PermissionNames.Edit))
{
// preserve page permissions
var oldPermissions = _permissionRepository.GetPermissions(EntityNames.Page, page.PageId).ToList();
// get current page permissions
var currentPermissions = _permissionRepository.GetPermissions(EntityNames.Page, page.PageId).ToList();
page = _pages.UpdatePage(page);
// get differences between old and new page permissions
// save url mapping if page path changed
if (currentPage.Path != page.Path)
{
var url = HttpContext.Request.Scheme + "://" + _alias.Name + "/";
var urlMapping = new UrlMapping();
urlMapping.SiteId = page.SiteId;
urlMapping.Url = url + currentPage.Path;
urlMapping.MappedUrl = url + page.Path;
urlMapping.Requests = 0;
urlMapping.CreatedOn = System.DateTime.UtcNow;
urlMapping.RequestedOn = System.DateTime.UtcNow;
_urlMappings.AddUrlMapping(urlMapping);
}
// get differences between current and new page permissions
var newPermissions = _permissionRepository.DecodePermissions(page.Permissions, page.SiteId, EntityNames.Page, page.PageId).ToList();
var added = GetPermissionsDifferences(newPermissions, oldPermissions);
var removed = GetPermissionsDifferences(oldPermissions, newPermissions);
var added = GetPermissionsDifferences(newPermissions, currentPermissions);
var removed = GetPermissionsDifferences(currentPermissions, newPermissions);
// synchronize module permissions
if (added.Count > 0 || removed.Count > 0)
@ -270,7 +289,6 @@ namespace Oqtane.Controllers
foreach (PageModule pageModule in _pageModules.GetPageModules(page.PageId, "").ToList())
{
var modulePermissions = _permissionRepository.GetPermissions(EntityNames.Module, pageModule.Module.ModuleId).ToList();
//var modulePermissions = _permissionRepository.DecodePermissions(pageModule.Module.Permissions, page.SiteId, EntityNames.Module, pageModule.ModuleId).ToList();
// permissions added
foreach(Permission permission in added)
{

View File

@ -33,36 +33,66 @@ namespace Oqtane.Controllers
// GET: api/<controller>
[HttpGet]
public IEnumerable<Setting> Get(string entityname, int entityid)
public IEnumerable<Setting> Get(string entityName, int entityid)
{
List<Setting> settings = new List<Setting>();
if (IsAuthorized(entityname, entityid, PermissionNames.View))
if (IsAuthorized(entityName, entityid, PermissionNames.View))
{
settings = _settings.GetSettings(entityname, entityid).ToList();
if (entityname == EntityNames.Site && !User.IsInRole(RoleNames.Admin))
settings = _settings.GetSettings(entityName, entityid).ToList();
// ispublic filter
switch (entityName)
{
settings = settings.Where(item => item.IsPublic).ToList();
case EntityNames.Tenant:
case EntityNames.ModuleDefinition:
case EntityNames.Host:
if (!User.IsInRole(RoleNames.Host))
{
settings = settings.Where(item => item.IsPublic).ToList();
}
break;
case EntityNames.Site:
if (!User.IsInRole(RoleNames.Admin))
{
settings = settings.Where(item => item.IsPublic).ToList();
}
break;
}
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Access Settings {EntityName} {EntityId}", entityname, entityid);
_logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Access Settings {EntityName} {EntityId}", entityName, entityid);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
return settings;
}
// GET api/<controller>/5
[HttpGet("{id}")]
public Setting Get(int id)
// GET api/<controller>/5/xxx
[HttpGet("{id}/{entityName}")]
public Setting Get(int id, string entityName)
{
Setting setting = _settings.GetSetting(id);
Setting setting = _settings.GetSetting(entityName, id);
if (IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.View))
{
if (setting.EntityName == EntityNames.Site && !User.IsInRole(RoleNames.Admin) && !setting.IsPublic)
// ispublic filter
switch (entityName)
{
setting = null;
case EntityNames.Tenant:
case EntityNames.ModuleDefinition:
case EntityNames.Host:
if (!User.IsInRole(RoleNames.Host) && !setting.IsPublic)
{
setting = null;
}
break;
case EntityNames.Site:
if (!User.IsInRole(RoleNames.Admin) && !setting.IsPublic)
{
setting = null;
}
break;
}
return setting;
}
else
@ -111,14 +141,14 @@ namespace Oqtane.Controllers
return setting;
}
// DELETE api/<controller>/5
[HttpDelete("{id}")]
public void Delete(int id)
// DELETE api/<controller>/5/xxx
[HttpDelete("{id}/{entityName}")]
public void Delete(string entityName, int id)
{
Setting setting = _settings.GetSetting(id);
Setting setting = _settings.GetSetting(entityName, id);
if (IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.Edit))
{
_settings.DeleteSetting(id);
_settings.DeleteSetting(setting.EntityName, id);
AddSyncEvent(setting.EntityName);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Setting Deleted {Setting}", setting);
}
@ -140,7 +170,16 @@ namespace Oqtane.Controllers
switch (entityName)
{
case EntityNames.Tenant:
authorized = User.IsInRole(RoleNames.Host);
case EntityNames.ModuleDefinition:
case EntityNames.Host:
if (permissionName == PermissionNames.Edit)
{
authorized = User.IsInRole(RoleNames.Host);
}
else
{
authorized = true;
}
break;
case EntityNames.Site:
if (permissionName == PermissionNames.Edit)
@ -164,6 +203,17 @@ namespace Oqtane.Controllers
authorized = User.IsInRole(RoleNames.Admin) || (_userPermissions.GetUser(User).UserId == entityId);
}
break;
case EntityNames.Visitor:
var visitorCookie = "APP_VISITOR_" + _alias.SiteId.ToString();
if (int.TryParse(Request.Cookies[visitorCookie], out int visitorId))
{
authorized = (visitorId == entityId);
}
else
{
authorized = User.IsInRole(RoleNames.Admin);
}
break;
}
return authorized;
}

View 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;
}
}
}
}

View File

@ -0,0 +1,74 @@
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;
}
}
// GET api/<controller>/5
[HttpGet("{id}")]
public Visitor Get(int id)
{
bool authorized;
var visitorCookie = "APP_VISITOR_" + _alias.SiteId.ToString();
if (int.TryParse(Request.Cookies[visitorCookie], out int visitorId))
{
authorized = (visitorId == id);
}
else
{
authorized = User.IsInRole(RoleNames.Admin);
}
var visitor = _visitors.GetVisitor(id);
if (authorized && visitor != null && visitor.SiteId == _alias.SiteId)
{
return visitor;
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Visitor Get Attempt {VisitorId}", id);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null;
}
}
}
}

View File

@ -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>();

View File

@ -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);

Some files were not shown because too many files have changed in this diff Show More