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"> <div style="@_display">
<CascadingAuthenticationState> <CascadingAuthenticationState>
<CascadingValue Value="@PageState"> <CascadingValue Value="@PageState">
<SiteRouter Runtime="@Runtime" RenderMode="@RenderMode" OnStateChange="@ChangeState" /> <SiteRouter Runtime="@Runtime" RenderMode="@RenderMode" VisitorId="@VisitorId" OnStateChange="@ChangeState" />
</CascadingValue> </CascadingValue>
</CascadingAuthenticationState> </CascadingAuthenticationState>
</div> </div>
@ -39,6 +39,9 @@
[Parameter] [Parameter]
public string RenderMode { get; set; } public string RenderMode { get; set; }
[Parameter]
public int VisitorId { get; set; }
private bool _initialized = false; private bool _initialized = false;
private string _display = "display: none;"; private string _display = "display: none;";
private Installation _installation = new Installation { Success = false, Message = "" }; private Installation _installation = new Installation { Success = false, Message = "" };

View File

@ -46,6 +46,8 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddScoped<ILocalizationService, LocalizationService>(); services.AddScoped<ILocalizationService, LocalizationService>();
services.AddScoped<ILanguageService, LanguageService>(); services.AddScoped<ILanguageService, LanguageService>();
services.AddScoped<IDatabaseService, DatabaseService>(); services.AddScoped<IDatabaseService, DatabaseService>();
services.AddScoped<IUrlMappingService, UrlMappingService>();
services.AddScoped<IVisitorService, VisitorService>();
services.AddScoped<ISyncService, SyncService>(); services.AddScoped<ISyncService, SyncService>();
return services; return services;

View File

@ -158,8 +158,8 @@
if (firstRender) if (firstRender)
{ {
var interop = new Interop(JSRuntime); 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.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.0.2/js/bootstrap.min.js", "sha512-a6ctI6w1kg3J4dSjknHj3aWLEbjitAXAjLDRUxo2wyYmDFRcz2RJuQr5M3Kt8O/TtUSp8n2rAyaXYy1sjoKmrQ==", "anonymous", "", "head", ""); 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"> <TabPanel Name="Upload" ResourceKey="Upload" Security="SecurityAccessLevel.Host">
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <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"> <div class="col-sm-9">
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" /> <FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" />
</div> </div>
@ -350,7 +350,7 @@ else
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)); var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360); 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 @inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="container"> <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"> <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"> <div class="col-sm-9">
<input id="dateTime" class="form-control" @bind="@_logDate" readonly /> <input id="dateTime" class="form-control" @bind="@_logDate" readonly />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <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"> <div class="col-sm-9">
<input id="level" class="form-control" @bind="@_level" readonly /> <input id="level" class="form-control" @bind="@_level" readonly />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <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"> <div class="col-sm-9">
<input id="feature" class="form-control" @bind="@_feature" readonly /> <input id="feature" class="form-control" @bind="@_feature" readonly />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <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"> <div class="col-sm-9">
<input id="function" class="form-control" @bind="@_function" readonly /> <input id="function" class="form-control" @bind="@_function" readonly />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <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"> <div class="col-sm-9">
<input id="category" class="form-control" @bind="@_category" readonly /> <input id="category" class="form-control" @bind="@_category" readonly />
</div> </div>
@ -111,7 +43,7 @@
@if (_pageName != string.Empty) @if (_pageName != string.Empty)
{ {
<div class="row mb-1 align-items-center"> <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"> <div class="col-sm-9">
<input id="page" class="form-control" @bind="@_pageName" readonly /> <input id="page" class="form-control" @bind="@_pageName" readonly />
</div> </div>
@ -120,7 +52,7 @@
@if (_moduleTitle != string.Empty) @if (_moduleTitle != string.Empty)
{ {
<div class="row mb-1 align-items-center"> <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"> <div class="col-sm-9">
<input id="module" class="form-control" @bind="@_moduleTitle" readonly /> <input id="module" class="form-control" @bind="@_moduleTitle" readonly />
</div> </div>
@ -129,26 +61,26 @@
@if (_username != string.Empty) @if (_username != string.Empty)
{ {
<div class="row mb-1 align-items-center"> <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"> <div class="col-sm-9">
<input id="user" class="form-control" @bind="@_username" readonly /> <input id="user" class="form-control" @bind="@_username" readonly />
</div> </div>
</div> </div>
} }
<div class="row mb-1 align-items-center"> <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"> <div class="col-sm-9">
<input id="url" class="form-control" @bind="@_url" readonly /> <input id="url" class="form-control" @bind="@_url" readonly />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <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"> <div class="col-sm-9">
<input id="template" class="form-control" @bind="@_template" readonly /> <input id="template" class="form-control" @bind="@_template" readonly />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <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"> <div class="col-sm-9">
<textarea id="message" class="form-control" @bind="@_message" rows="5" readonly></textarea> <textarea id="message" class="form-control" @bind="@_message" rows="5" readonly></textarea>
</div> </div>
@ -156,24 +88,25 @@
@if (!string.IsNullOrEmpty(_exception)) @if (!string.IsNullOrEmpty(_exception))
{ {
<div class="row mb-1 align-items-center"> <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> <Label Class="col-sm-3" For="exception" HelpText="The exceptions generated by the system" ResourceKey="Exception">Exception: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<textarea id="exception" class="form-control" @bind="@_exception" rows="5" readonly></textarea> <textarea id="exception" class="form-control" @bind="@_exception" rows="5" readonly></textarea>
</div> </div>
</div> </div>
} }
<div class="row mb-1 align-items-center"> <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"> <div class="col-sm-9">
<textarea id="properties" class="form-control" @bind="@_properties" rows="5" readonly></textarea> <textarea id="properties" class="form-control" @bind="@_properties" rows="5" readonly></textarea>
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <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> <Label Class="col-sm-3" For="server" HelpText="The server that was affected" ResourceKey="Server">Server: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="server" class="form-control" @bind="@_server" readonly /> <input id="server" class="form-control" @bind="@_server" readonly />
</div> </div>
</div> </div>
</div>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>

View File

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

View File

@ -22,6 +22,7 @@ else
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>
<th>@SharedLocalizer["Version"]</th> <th>@SharedLocalizer["Version"]</th>
<th>@Localizer["InUse"]</th>
<th>@SharedLocalizer["Expires"]</th> <th>@SharedLocalizer["Expires"]</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
</Header> </Header>
@ -35,6 +36,16 @@ else
</td> </td>
<td>@context.Name</td> <td>@context.Name</td>
<td>@context.Version</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> <td>
@((MarkupString)PurchaseLink(context.PackageName)) @((MarkupString)PurchaseLink(context.PackageName))
</td> </td>

View File

@ -3,13 +3,14 @@
@inherits ModuleBase @inherits ModuleBase
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IPageService PageService @inject IPageService PageService
@inject IPageModuleService PageModuleService
@inject IThemeService ThemeService @inject IThemeService ThemeService
@inject IStringLocalizer<Edit> Localizer @inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<TabStrip Refresh="@_refresh"> <TabStrip Refresh="@_refresh">
<TabPanel Name="Settings" ResourceKey="Settings"> <TabPanel Name="Settings" ResourceKey="Settings" Heading=@Localizer["Settings.Heading"]>
@if (_themeList != null) @if (_themeList != null)
{ {
<div class="container"> <div class="container">
@ -151,10 +152,27 @@
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<PermissionGrid EntityName="@EntityNames.Page" Permissions="@_permissions" @ref="_permissionGrid" /> <PermissionGrid EntityName="@EntityNames.Page" Permissions="@_permissions" @ref="_permissionGrid" />
</div> </div>
</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> </TabPanel>
@if (_themeSettingsType != null) @if (_themeSettingsType != null)
@ -170,114 +188,136 @@
</form> </form>
@code { @code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
private ElementReference form; private ElementReference form;
private bool validated = false; private bool validated = false;
private List<Theme> _themeList; private List<Theme> _themeList;
private List<ThemeControl> _themes = new List<ThemeControl>(); private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>(); private List<ThemeControl> _containers = new List<ThemeControl>();
private List<Page> _pageList; private List<Page> _pageList;
private int _pageId; private List<Module> _pageModules;
private string _name; private int _pageId;
private string _title; private string _name;
private string _path; private string _title;
private string _currentparentid; private string _path;
private string _parentid = "-1"; private string _currentparentid;
private string _insert = "="; private string _parentid = "-1";
private List<Page> _children; private string _insert = "=";
private int _childid = -1; private List<Page> _children;
private string _isnavigation; private int _childid = -1;
private string _isclickable; private string _isnavigation;
private string _url; private string _isclickable;
private string _ispersonalizable; private string _url;
private string _themetype; private string _ispersonalizable;
private string _containertype = "-"; private string _themetype;
private string _icon; private string _containertype = "-";
private string _permissions = null; private string _icon;
private string _createdby; private string _permissions = null;
private DateTime _createdon; private string _createdby;
private string _modifiedby; private DateTime _createdon;
private DateTime _modifiedon; private string _modifiedby;
private string _deletedby; private DateTime _modifiedon;
private DateTime? _deletedon; private string _deletedby;
private PermissionGrid _permissionGrid; private DateTime? _deletedon;
private Type _themeSettingsType; private PermissionGrid _permissionGrid;
private object _themeSettings; private Type _themeSettingsType;
private RenderFragment ThemeSettingsComponent { get; set; } private object _themeSettings;
private bool _refresh = false; private RenderFragment ThemeSettingsComponent { get; set; }
private bool _refresh = false;
protected Page page;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
try try
{ {
_pageList = PageState.Pages; _pageList = PageState.Pages;
_children = PageState.Pages.Where(item => item.ParentId == null).ToList(); _children = PageState.Pages.Where(item => item.ParentId == null).ToList();
_themeList = await ThemeService.GetThemesAsync();
_themes = ThemeService.GetThemeControls(_themeList);
_themeList = await ThemeService.GetThemesAsync(); _pageId = Int32.Parse(PageState.QueryString["id"]);
_themes = ThemeService.GetThemeControls(_themeList); page = PageState.Pages.FirstOrDefault(item => item.PageId == _pageId);
_pageId = Int32.Parse(PageState.QueryString["id"]); if (page != null)
var page = PageState.Pages.FirstOrDefault(item => item.PageId == _pageId); {
if (page != null) _name = page.Name;
{ _title = page.Title;
_name = page.Name; _path = page.Path;
_title = page.Title; _pageModules = PageState.Modules.Where(m => m.PageId == page.PageId && m.IsDeleted == false).ToList();
_path = page.Path;
if (string.IsNullOrEmpty(_path)) if (string.IsNullOrEmpty(_path))
{ {
_path = "/"; _path = "/";
} }
else else
{ {
if (_path.Contains("/")) if (_path.Contains("/"))
{ {
_path = _path.Substring(_path.LastIndexOf("/") + 1); _path = _path.Substring(_path.LastIndexOf("/") + 1);
} }
} }
if (page.ParentId == null) if (page.ParentId == null)
{ {
_parentid = "-1"; _parentid = "-1";
} }
else else
{ {
_parentid = page.ParentId.ToString(); _parentid = page.ParentId.ToString();
} }
_currentparentid = _parentid; _currentparentid = _parentid;
_isnavigation = page.IsNavigation.ToString(); _isnavigation = page.IsNavigation.ToString();
_isclickable = page.IsClickable.ToString(); _isclickable = page.IsClickable.ToString();
_url = page.Url; _url = page.Url;
_ispersonalizable = page.IsPersonalizable.ToString(); _ispersonalizable = page.IsPersonalizable.ToString();
_themetype = page.ThemeType; _themetype = page.ThemeType;
if (string.IsNullOrEmpty(_themetype)) if (string.IsNullOrEmpty(_themetype))
{ {
_themetype = PageState.Site.DefaultThemeType; _themetype = PageState.Site.DefaultThemeType;
} }
_containers = ThemeService.GetContainerControls(_themeList, _themetype); _containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containertype = page.DefaultContainerType; _containertype = page.DefaultContainerType;
if (string.IsNullOrEmpty(_containertype)) if (string.IsNullOrEmpty(_containertype))
{ {
_containertype = PageState.Site.DefaultContainerType; _containertype = PageState.Site.DefaultContainerType;
} }
_icon = page.Icon; _icon = page.Icon;
_permissions = page.Permissions; _permissions = page.Permissions;
_createdby = page.CreatedBy; _createdby = page.CreatedBy;
_createdon = page.CreatedOn; _createdon = page.CreatedOn;
_modifiedby = page.ModifiedBy; _modifiedby = page.ModifiedBy;
_modifiedon = page.ModifiedOn; _modifiedon = page.ModifiedOn;
_deletedby = page.DeletedBy; _deletedby = page.DeletedBy;
_deletedon = page.DeletedOn; _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) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Page {PageId} {Error}", _pageId, ex.Message); await logger.LogError(ex, "Error Deleting Module {Title} {Error}", module.Title, ex.Message);
AddModuleMessage(Localizer["Error.Page.Load"], MessageType.Error); AddModuleMessage(Localizer["Error.Module.Delete"], MessageType.Error);
} }
} }

View File

@ -25,7 +25,7 @@
<th>@Localizer["DeletedOn"]</th> <th>@Localizer["DeletedOn"]</th>
</Header> </Header>
<Row> <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><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.Name</td>
<td>@context.DeletedBy</td> <td>@context.DeletedBy</td>
@ -34,9 +34,7 @@
</Pager> </Pager>
@if (_pages.Any()) @if (_pages.Any())
{ {
<div style="text-align:right;"> <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" />
<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>
} }
} }
</TabPanel> </TabPanel>
@ -58,7 +56,7 @@
<th>@Localizer["DeletedOn"]</th> <th>@Localizer["DeletedOn"]</th>
</Header> </Header>
<Row> <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><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>@PageState.Pages.Find(item => item.PageId == context.PageId).Name</td>
<td>@context.Title</td> <td>@context.Title</td>
@ -68,9 +66,7 @@
</Pager> </Pager>
@if (_modules.Any()) @if (_modules.Any())
{ {
<div style="text-align:right;"> <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" />
<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>
} }
} }

View File

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

View File

@ -52,6 +52,11 @@ else
private void Edit(string name) private void Edit(string name)
{ {
if (name.Equals("*"))
{
var uri = new Uri(NavigationManager.Uri);
name = uri.Authority;
}
NavigationManager.NavigateTo(_scheme + name + "/admin/site/?reload"); 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 IUserRoleService UserRoleService
@inject IUserService UserService @inject IUserService UserService
@inject ISettingService SettingService @inject ISettingService SettingService
@inject ISiteService SiteService
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
@ -14,45 +15,65 @@
} }
else else
{ {
<div class="container"> <TabStrip>
<div class="row mb-1 align-items-center"> <TabPanel Name="Users" Heading="Users" ResourceKey="Users">
<div class="col-sm-4"> <div class="container">
<ActionLink Action="Add" Text="Add User" ResourceKey="AddUser" /> <div class="row mb-1 align-items-center">
</div> <div class="col-sm-4">
<div class="col-sm-4"> <ActionLink Action="Add" Text="Add User" ResourceKey="AddUser" />
<input class="form-control" @bind="@_search" /> </div>
</div> <div class="col-sm-4">
<div class="col-sm-4"> <input class="form-control" @bind="@_search" />
<button class="btn btn-secondary" @onclick="OnSearch">@SharedLocalizer["Search"]</button> </div>
</div> <div class="col-sm-4">
</div> <button type="button" class="btn btn-secondary" @onclick="OnSearch">@SharedLocalizer["Search"]</button>
</div> </div>
<Pager Items="@userroles"> </div>
<Header> </div>
<th style="width: 1px;">&nbsp;</th> <Pager Items="@userroles">
<th style="width: 1px;">&nbsp;</th> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th> <th style="width: 1px;">&nbsp;</th>
</Header> <th style="width: 1px;">&nbsp;</th>
<Row> <th>@SharedLocalizer["Name"]</th>
<td> </Header>
<ActionLink Action="Edit" Parameters="@($"id=" + context.UserId.ToString())" ResourceKey="EditUser" /> <Row>
</td> <td>
<td> <ActionLink Action="Edit" Parameters="@($"id=" + context.UserId.ToString())" ResourceKey="EditUser" />
<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> <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" />
<ActionLink Action="Roles" Parameters="@($"id=" + context.UserId.ToString())" ResourceKey="Roles" /> </td>
</td> <td>
<td>@context.User.DisplayName</td> <ActionLink Action="Roles" Parameters="@($"id=" + context.UserId.ToString())" ResourceKey="Roles" />
</Row> </td>
</Pager> <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 { @code {
private List<UserRole> allroles; private List<UserRole> allroles;
private List<UserRole> userroles; private List<UserRole> userroles;
private string _search; private string _search;
private string _allowregistration;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
@ -61,6 +82,7 @@ else
allroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId); allroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId);
await LoadSettingsAsync(); await LoadSettingsAsync();
userroles = Search(_search); userroles = Search(_search);
_allowregistration = PageState.Site.AllowRegistration.ToString();
} }
private List<UserRole> Search(string search) private List<UserRole> Search(string search)
@ -122,4 +144,20 @@ else
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId); 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"> <div class="modal-footer">
@if (!string.IsNullOrEmpty(Action)) @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> <button type="button" class="btn btn-secondary" @onclick="DisplayModal">@Localize("Cancel")</button>
</div> </div>
@ -30,16 +30,17 @@
{ {
if (Disabled) if (Disabled)
{ {
<button class="@Class" disabled>@((MarkupString)_iconSpan) @Text</button> <button type="button" class="@Class" disabled>@((MarkupString)_iconSpan) @Text</button>
} }
else else
{ {
<button class="@Class" @onclick="DisplayModal">@((MarkupString)_iconSpan) @Text</button> <button type="button" class="@Class" @onclick="DisplayModal">@((MarkupString)_iconSpan) @Text</button>
} }
} }
@code { @code {
private bool _visible = false; private bool _visible = false;
private string _permissions = string.Empty;
private bool _editmode = false; private bool _editmode = false;
private bool _authorized = false; private bool _authorized = false;
private string _iconSpan = string.Empty; private string _iconSpan = string.Empty;
@ -59,6 +60,9 @@
[Parameter] [Parameter]
public SecurityAccessLevel? Security { get; set; } // optional - can be used to explicitly specify SecurityAccessLevel 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] [Parameter]
public string Class { get; set; } // optional public string Class { get; set; } // optional
@ -105,6 +109,7 @@
Header = Localize(nameof(Header), Header); Header = Localize(nameof(Header), Header);
Message = Localize(nameof(Message), Message); Message = Localize(nameof(Message), Message);
_permissions = (string.IsNullOrEmpty(Permissions)) ? ModuleState.Permissions : Permissions;
_authorized = IsAuthorized(); _authorized = IsAuthorized();
} }
@ -138,10 +143,10 @@
authorized = true; authorized = true;
break; break;
case SecurityAccessLevel.View: case SecurityAccessLevel.View:
authorized = UserSecurity.IsAuthorized(PageState.User,PermissionNames.View, ModuleState.Permissions); authorized = UserSecurity.IsAuthorized(PageState.User,PermissionNames.View, _permissions);
break; break;
case SecurityAccessLevel.Edit: case SecurityAccessLevel.Edit:
authorized = UserSecurity.IsAuthorized(PageState.User,PermissionNames.Edit, ModuleState.Permissions); authorized = UserSecurity.IsAuthorized(PageState.User,PermissionNames.Edit, _permissions);
break; break;
case SecurityAccessLevel.Admin: case SecurityAccessLevel.Admin:
authorized = UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin); authorized = UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin);

View File

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

View File

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

View File

@ -115,24 +115,24 @@
public override List<Resource> Resources => new List<Resource>() 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-blot-formatter.min.js" },
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-interop.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 _content = Content; // raw HTML
} }
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
if (firstRender) var interop = new RichTextEditorInterop(JSRuntime);
if (firstRender)
{ {
await base.OnAfterRenderAsync(firstRender); await base.OnAfterRenderAsync(firstRender);
var interop = new RichTextEditorInterop(JSRuntime);
await interop.CreateEditor( await interop.CreateEditor(
_editorElement, _editorElement,
_toolBar, _toolBar,
@ -140,14 +140,15 @@
Placeholder, Placeholder,
Theme, Theme,
DebugLevel); 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() public void CloseFileManager()

View File

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

View File

@ -43,16 +43,12 @@
[Parameter] [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. 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")) if (PageState.QueryString.ContainsKey("tab"))
{ {
ActiveTab = PageState.QueryString["tab"]; ActiveTab = PageState.QueryString["tab"];
} }
}
protected override void OnParametersSet()
{
if (_tabPanels == null || Refresh) if (_tabPanels == null || Refresh)
{ {
_tabPanels = new List<TabPanel>(); _tabPanels = new List<TabPanel>();

View File

@ -30,8 +30,8 @@
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" }, 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.7.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.snow.css" }
}; };
private RichTextEditor RichTextEditorHtml; private RichTextEditor RichTextEditorHtml;
@ -65,8 +65,8 @@
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "An Error Occurred Loading Html/Text Content. " + ex.Message); await logger.LogError(ex, "Error Loading Content {Error}", ex.Message);
AddModuleMessage(ex.Message, MessageType.Error); AddModuleMessage(Localizer["Error.Content.Load"], MessageType.Error);
} }
} }
@ -91,7 +91,7 @@
await HtmlTextService.AddHtmlTextAsync(htmltext); await HtmlTextService.AddHtmlTextAsync(htmltext);
} }
await logger.LogInformation("Html/Text Content Saved {HtmlText}", htmltext); await logger.LogInformation("Content Saved {HtmlText}", htmltext);
NavigationManager.NavigateTo(NavigateUrl()); NavigationManager.NavigateTo(NavigateUrl());
} }
catch (Exception ex) catch (Exception ex)

View File

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

View File

@ -1,7 +1,9 @@
using Oqtane.Documentation;
using Oqtane.Models; using Oqtane.Models;
namespace Oqtane.Modules.HtmlText 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 class ModuleInfo : IModule
{ {
public ModuleDefinition ModuleDefinition => new ModuleDefinition public ModuleDefinition ModuleDefinition => new ModuleDefinition

View File

@ -1,10 +1,12 @@
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Oqtane.Documentation;
using Oqtane.Services; using Oqtane.Services;
using Oqtane.Shared; using Oqtane.Shared;
namespace Oqtane.Modules.HtmlText.Services 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 class HtmlTextService : ServiceBase, IHtmlTextService, IService
{ {
public HtmlTextService(HttpClient http, SiteState siteState) : base(http, siteState) {} 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 System.Threading.Tasks;
using Oqtane.Documentation;
using Oqtane.Modules.HtmlText.Models; using Oqtane.Modules.HtmlText.Models;
namespace Oqtane.Modules.HtmlText.Services namespace Oqtane.Modules.HtmlText.Services
{ {
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
public interface IHtmlTextService public interface IHtmlTextService
{ {
Task<Models.HtmlText> GetHtmlTextAsync(int ModuleId); 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) 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 = "") public virtual Dictionary<string, string> GetUrlParameters(string parametersTemplate = "")

View File

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

View File

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

View File

@ -159,4 +159,10 @@
<data name="Type" xml:space="preserve"> <data name="Type" xml:space="preserve">
<value>Type</value> <value>Type</value>
</data> </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> </root>

View File

@ -168,4 +168,13 @@
<data name="Stop" xml:space="preserve"> <data name="Stop" xml:space="preserve">
<value>Stop</value> <value>Stop</value>
</data> </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> </root>

View File

@ -153,4 +153,19 @@
<data name="Search.NoResults" xml:space="preserve"> <data name="Search.NoResults" xml:space="preserve">
<value>No Translations Match The Criteria Provided Or Package Service Is Disabled</value> <value>No Translations Match The Criteria Provided Or Package Service Is Disabled</value>
</data> </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> </root>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!-- <!--
Microsoft ResX Schema Microsoft ResX Schema
@ -210,7 +210,7 @@
<data name="Personalizable.Text" xml:space="preserve"> <data name="Personalizable.Text" xml:space="preserve">
<value>Personalizable? </value> <value>Personalizable? </value>
</data> </data>
<data name="Appearance.Name" xml:space="preserve"> <data name="Appearance.Heading" xml:space="preserve">
<value>Appearance</value> <value>Appearance</value>
</data> </data>
<data name="ThisLocation.Keep" xml:space="preserve"> <data name="ThisLocation.Keep" xml:space="preserve">
@ -231,4 +231,40 @@
<data name="Move.Text" xml:space="preserve"> <data name="Move.Text" xml:space="preserve">
<value>Move: </value> <value>Move: </value>
</data> </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> </root>

View File

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

View File

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

View File

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

View File

@ -129,4 +129,13 @@
<data name="DeleteRole.Header" xml:space="preserve"> <data name="DeleteRole.Header" xml:space="preserve">
<value>Delete Role</value> <value>Delete Role</value>
</data> </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> </root>

View File

@ -129,7 +129,7 @@
<data name="DefaultContainer.Text" xml:space="preserve"> <data name="DefaultContainer.Text" xml:space="preserve">
<value>Default Container: </value> <value>Default Container: </value>
</data> </data>
<data name="Appearance.Name" xml:space="preserve"> <data name="Appearance.Heading" xml:space="preserve">
<value>Appearance</value> <value>Appearance</value>
</data> </data>
<data name="DefaultAdminContainer" xml:space="preserve"> <data name="DefaultAdminContainer" xml:space="preserve">
@ -171,9 +171,6 @@
<data name="Aliases.HelpText" xml:space="preserve"> <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> <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>
<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"> <data name="IsDeleted.HelpText" xml:space="preserve">
<value>Is this site deleted?</value> <value>Is this site deleted?</value>
</data> </data>
@ -225,9 +222,6 @@
<data name="Aliases.Text" xml:space="preserve"> <data name="Aliases.Text" xml:space="preserve">
<value>Aliases: </value> <value>Aliases: </value>
</data> </data>
<data name="AllowRegistration.Text" xml:space="preserve">
<value>Allow User Registration? </value>
</data>
<data name="IsDeleted.Text" xml:space="preserve"> <data name="IsDeleted.Text" xml:space="preserve">
<value>Is Deleted? </value> <value>Is Deleted? </value>
</data> </data>
@ -282,7 +276,7 @@
<data name="Theme.Select" xml:space="preserve"> <data name="Theme.Select" xml:space="preserve">
<value>Select Theme</value> <value>Select Theme</value>
</data> </data>
<data name="Hosting" xml:space="preserve"> <data name="Hosting.Heading" xml:space="preserve">
<value>Hosting Model</value> <value>Hosting Model</value>
</data> </data>
<data name="Prerender.HelpText" xml:space="preserve"> <data name="Prerender.HelpText" xml:space="preserve">
@ -300,4 +294,28 @@
<data name="Browse" xml:space="preserve"> <data name="Browse" xml:space="preserve">
<value>Browse</value> <value>Browse</value>
</data> </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> </root>

View File

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

View File

@ -138,4 +138,10 @@
<data name="DeleteTheme.Header" xml:space="preserve"> <data name="DeleteTheme.Header" xml:space="preserve">
<value>Delete Theme</value> <value>Delete Theme</value>
</data> </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> </root>

View File

@ -135,4 +135,10 @@
<data name="Success.Framework.Download" xml:space="preserve"> <data name="Success.Framework.Download" xml:space="preserve">
<value>Framework Downloaded Successfully... Please Select Upgrade To Complete the Process</value> <value>Framework Downloaded Successfully... Please Select Upgrade To Complete the Process</value>
</data> </data>
<data name="Download.Heading" xml:space="preserve">
<value>Download</value>
</data>
<data name="Upload.Heading" xml:space="preserve">
<value>Upload</value>
</data>
</root> </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"> <data name="Username.Text" xml:space="preserve">
<value>Username:</value> <value>Username:</value>
</data> </data>
<data name="Identity.Heading" xml:space="preserve">
<value>Identity</value>
</data>
<data name="Profile.Heading" xml:space="preserve">
<value>Profile</value>
</data>
</root> </root>

View File

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

View File

@ -177,4 +177,7 @@
<data name="Expiry" xml:space="preserve"> <data name="Expiry" xml:space="preserve">
<value>Expiry</value> <value>Expiry</value>
</data> </data>
<data name="DeleteUserRole.Text" xml:space="preserve">
<value>Delete</value>
</data>
</root> </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"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </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"> <data name="Error.Content.Save" xml:space="preserve">
<value>Error Saving Content</value> <value>An Error Occurred Saving Content</value>
</data> </data>
</root> </root>

View File

@ -1,65 +1,65 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <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 mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
The primary goals of this format is to allow a simple XML format : using a System.ComponentModel.TypeConverter
that is mostly human readable. The generation and parsing of the : and then encoded with base64 encoding.
various data types are done through the TypeConverter classes -->
associated with the data types. <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
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">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true"> <xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType> <xsd:complexType>
@ -120,4 +120,7 @@
<data name="Edit.Action" xml:space="preserve"> <data name="Edit.Action" xml:space="preserve">
<value>Edit</value> <value>Edit</value>
</data> </data>
<data name="Error.Content.Load" xml:space="preserve">
<value>An Error Occurred Loading Content</value>
</data>
</root> </root>

View File

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

View File

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

View File

@ -1,4 +1,5 @@
using Oqtane.Models; using Oqtane.Models;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -70,7 +71,7 @@ namespace Oqtane.Services
/// <summary> /// <summary>
/// Returns a key-value dictionary of all module settings for the given module /// Returns a key-value dictionary of all module settings for the given module
/// </summary> /// </summary>
/// <param name="pageId"></param> /// <param name="moduleId"></param>
/// <returns></returns> /// <returns></returns>
Task<Dictionary<string, string>> GetModuleSettingsAsync(int moduleId); Task<Dictionary<string, string>> GetModuleSettingsAsync(int moduleId);
@ -82,6 +83,21 @@ namespace Oqtane.Services
/// <returns></returns> /// <returns></returns>
Task UpdateModuleSettingsAsync(Dictionary<string, string> moduleSettings, int moduleId); 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> /// <summary>
/// Returns a key-value dictionary of all user settings for the given user /// Returns a key-value dictionary of all user settings for the given user
/// </summary> /// </summary>
@ -113,6 +129,19 @@ namespace Oqtane.Services
/// <returns></returns> /// <returns></returns>
Task UpdateFolderSettingsAsync(Dictionary<string, string> folderSettings, int folderId); 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> /// <summary>
/// Returns a key-value dictionary of all settings for the given entityName /// Returns a key-value dictionary of all settings for the given entityName
/// </summary> /// </summary>
@ -135,7 +164,7 @@ namespace Oqtane.Services
/// </summary> /// </summary>
/// <param name="settingId"></param> /// <param name="settingId"></param>
/// <returns></returns> /// <returns></returns>
Task<Setting> GetSettingAsync(int settingId); Task<Setting> GetSettingAsync(string entityName, int settingId);
/// <summary> /// <summary>
@ -157,7 +186,7 @@ namespace Oqtane.Services
/// </summary> /// </summary>
/// <param name="settingId"></param> /// <param name="settingId"></param>
/// <returns></returns> /// <returns></returns>
Task DeleteSettingAsync(int settingId); Task DeleteSettingAsync(string entityName, int settingId);
/// <summary> /// <summary>
/// Gets the value of the given settingName (key) from the given key-value dictionary /// 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> SetSetting(Dictionary<string, string> settings, string settingName, string settingValue, bool isPublic);
Dictionary<string, string> MergeSettings(Dictionary<string, string> settings1, Dictionary<string, string> settings2); 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); private string Apiurl => CreateApiUrl("Setting", _siteState.Alias);
public async Task<Dictionary<string, string>> GetTenantSettingsAsync() public async Task<Dictionary<string, string>> GetTenantSettingsAsync()
{ {
return await GetSettingsAsync(EntityNames.Tenant, -1); return await GetSettingsAsync(EntityNames.Tenant, -1);
@ -71,6 +72,16 @@ namespace Oqtane.Services
await UpdateSettingsAsync(moduleSettings, EntityNames.Module, moduleId); 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) public async Task<Dictionary<string, string>> GetUserSettingsAsync(int userId)
{ {
return await GetSettingsAsync(EntityNames.User, userId); return await GetSettingsAsync(EntityNames.User, userId);
@ -91,6 +102,16 @@ namespace Oqtane.Services
await UpdateSettingsAsync(folderSettings, EntityNames.Folder, folderId); 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) public async Task<Dictionary<string, string>> GetSettingsAsync(string entityName, int entityId)
{ {
var dictionary = new Dictionary<string, string>(); 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) public async Task<Setting> AddSettingAsync(Setting setting)
@ -159,9 +180,9 @@ namespace Oqtane.Services
return await PutJsonAsync<Setting>($"{Apiurl}/{setting.SettingId}", setting); 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; 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 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 class ThemeInfo : ITheme
{ {
public Theme Theme => new Theme public Theme Theme => new Theme

View File

@ -16,7 +16,15 @@
else else
{ {
<li class="breadcrumb-item"> <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> </li>
} }
} }

View File

@ -35,7 +35,7 @@
@if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions)) @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> <span class="oi oi-cog"></span>
</button> </button>
@ -50,7 +50,7 @@
{ {
<div class="row d-flex"> <div class="row d-flex">
<div class="col"> <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>
</div> </div>
@ -63,9 +63,9 @@
</div> </div>
<div class="row d-flex"> <div class="row d-flex">
<div class="col d-flex justify-content-between"> <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 me-1" 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-secondary col" 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-danger col ms-1" @onclick="ConfirmDelete">@SharedLocalizer["Delete"]</button>
</div> </div>
</div> </div>
<br /> <br />

View File

@ -5,7 +5,7 @@
@if (MenuPages.Any()) @if (MenuPages.Any())
{ {
<span class="app-menu-toggler navbar-expand-md"> <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> <span class="navbar-toggler-icon"></span>
</button> </button>
</span> </span>

View File

@ -4,7 +4,7 @@
@if (MenuPages.Any()) @if (MenuPages.Any())
{ {
<span class="app-menu-toggler"> <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> <span class="navbar-toggler-icon"></span>
</button> </button>
</span> </span>

View File

@ -1,7 +1,9 @@
using Oqtane.Documentation;
using Oqtane.Models; using Oqtane.Models;
namespace Oqtane.Themes.OqtaneTheme 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 class ThemeInfo : ITheme
{ {
public Theme Theme => new Theme 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) 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 @namespace Oqtane.UI
@inject IStringLocalizer<ModuleInstance> Localizer @inject IStringLocalizer<ModuleInstance> Localizer
@inject ILogService LoggingService
@inherits ErrorBoundary
<ModuleMessage Message="@_message" Type="@_messagetype" /> @if (CurrentException is null)
@DynamicComponent
@if (_progressindicator)
{ {
<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 { @code {
private string _message; private string _message;
private MessageType _messagetype; private string _error;
private bool _progressindicator = false; private MessageType _messageType;
private bool _progressIndicator = false;
[CascadingParameter] private Type ModuleType { get; set; }
protected PageState PageState { get; set; } private IDictionary<string, object> ModuleParameters { get; set; }
[CascadingParameter] [CascadingParameter]
private Module ModuleState { get; set; } 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() protected override void OnParametersSet()
{ {
_message = ""; _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 => public void AddModuleMessage(string message, MessageType type)
{ {
Type moduleType = null; _message = message;
if (!string.IsNullOrEmpty(ModuleState.ModuleType)) _messageType = type;
{ _progressIndicator = false;
moduleType = Type.GetType(ModuleState.ModuleType); StateHasChanged();
}
if (moduleType != null) public void ShowProgressIndicator()
{ {
builder.OpenComponent(0, moduleType); _progressIndicator = true;
builder.AddAttribute(1, "ModuleInstance", this); StateHasChanged();
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 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 bool EditMode { get; set; }
public DateTime LastSyncDate { get; set; } public DateTime LastSyncDate { get; set; }
public Oqtane.Shared.Runtime Runtime { get; set; } public Oqtane.Shared.Runtime Runtime { get; set; }
public int VisitorId { get; set; }
} }
} }

View File

@ -15,78 +15,75 @@
@DynamicComponent @DynamicComponent
@code { @code {
private string _absoluteUri; private string _absoluteUri;
private bool _navigationInterceptionEnabled; private bool _navigationInterceptionEnabled;
private PageState _pagestate; private PageState _pagestate;
[Parameter] [Parameter]
public string Runtime { get; set; } public string Runtime { get; set; }
[Parameter] [Parameter]
public string RenderMode { get; set; } public string RenderMode { get; set; }
[CascadingParameter] [Parameter]
PageState PageState { get; set; } public int VisitorId { get; set; }
[Parameter] [CascadingParameter]
public Action<PageState> OnStateChange { get; set; } PageState PageState { get; set; }
private RenderFragment DynamicComponent { get; set; } [Parameter]
public Action<PageState> OnStateChange { get; set; }
protected override void OnInitialized() private RenderFragment DynamicComponent { get; set; }
{
_absoluteUri = NavigationManager.Uri;
NavigationManager.LocationChanged += LocationChanged;
DynamicComponent = builder => protected override void OnInitialized()
{ {
if (PageState != null) _absoluteUri = NavigationManager.Uri;
{ NavigationManager.LocationChanged += LocationChanged;
builder.OpenComponent(0, Type.GetType(Constants.PageComponent));
builder.CloseComponent();
}
};
}
public void Dispose() DynamicComponent = builder =>
{ {
NavigationManager.LocationChanged -= LocationChanged; if (PageState != null)
} {
builder.OpenComponent(0, Type.GetType(Constants.PageComponent));
builder.CloseComponent();
}
};
}
protected override async Task OnParametersSetAsync() public void Dispose()
{ {
if (PageState == null) NavigationManager.LocationChanged -= LocationChanged;
{ }
await Refresh();
}
}
[SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")] protected override async Task OnParametersSetAsync()
private async Task Refresh() {
{ if (PageState == null)
Site site; {
List<Page> pages; await Refresh();
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);
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 Route route = new Route(_absoluteUri, SiteState.Alias.Path);
var path = uri.LocalPath.Substring(1); var moduleid = (int.TryParse(route.ModuleId, out int mid)) ? mid : -1;
var action = (!string.IsNullOrEmpty(route.Action)) ? route.Action : Constants.DefaultAction;
// parse querystring var querystring = ParseQueryString(route.Query);
var querystring = ParseQueryString(uri.Query);
// reload the client application if there is a forced reload or the user navigated to a site with a different alias // 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); NavigationManager.NavigateTo(_absoluteUri.Replace("?reload", ""), true);
return; return;
@ -168,72 +165,9 @@
pages = PageState.Pages; 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) 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 else
{ {
@ -241,14 +175,13 @@
} }
// get the page if the path has changed // 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)); 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 a home page) // 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 && path == "") if (page == null && route.PagePath == "")
{ {
page = pages.FirstOrDefault(); page = pages.FirstOrDefault();
path = page.Path;
} }
editmode = false; editmode = false;
} }
@ -286,12 +219,13 @@
Modules = modules, Modules = modules,
Uri = new Uri(_absoluteUri, UriKind.Absolute), Uri = new Uri(_absoluteUri, UriKind.Absolute),
QueryString = querystring, QueryString = querystring,
UrlParameters = urlparameters, UrlParameters = route.UrlParameters,
ModuleId = moduleid, ModuleId = moduleid,
Action = action, Action = action,
EditMode = editmode, EditMode = editmode,
LastSyncDate = lastsyncdate, LastSyncDate = lastsyncdate,
Runtime = runtime Runtime = runtime,
VisitorId = VisitorId
}; };
OnStateChange?.Invoke(_pagestate); OnStateChange?.Invoke(_pagestate);
@ -302,12 +236,12 @@
if (user == null) if (user == null)
{ {
// redirect to login page // 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 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); 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 (path != "") if (route.PagePath != "")
{ {
// redirect to home page // redirect to home page
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "", "")); NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "", ""));

View File

@ -5,103 +5,55 @@
@DynamicComponent @DynamicComponent
@code { @code {
[CascadingParameter] PageState PageState { get; set; } [CascadingParameter] PageState PageState { get; set; }
RenderFragment DynamicComponent { get; set; } RenderFragment DynamicComponent { get; set; }
protected override async Task OnParametersSetAsync() protected override void OnParametersSet()
{ {
var interop = new Interop(JsRuntime); // handle page redirection
if (!string.IsNullOrEmpty(PageState.Page.Url))
{
NavigationManager.NavigateTo(PageState.Page.Url);
return;
}
// handle page redirection DynamicComponent = builder =>
if (!string.IsNullOrEmpty(PageState.Page.Url)) {
{ var themeType = Type.GetType(PageState.Page.ThemeType);
NavigationManager.NavigateTo(PageState.Page.Url); builder.OpenComponent(0, themeType);
return; builder.CloseComponent();
} };
}
// set page title protected override async Task OnAfterRenderAsync(bool firstRender)
if (!string.IsNullOrEmpty(PageState.Page.Title)) {
{ if (!firstRender)
await interop.UpdateTitle(PageState.Page.Title); {
} var interop = new Interop(JsRuntime);
else
{
await interop.UpdateTitle(PageState.Site.Name + " - " + PageState.Page.Name);
}
// manage stylesheets for this page // set page title
string batch = DateTime.Now.ToString("yyyyMMddHHmmssfff"); if (!string.IsNullOrEmpty(PageState.Page.Title))
var links = new List<object>(); {
foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet && item.Declaration != ResourceDeclaration.Global)) await interop.UpdateTitle(PageState.Page.Title);
{ }
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 = "" }); else
} {
if (links.Any()) await interop.UpdateTitle(PageState.Site.Name + " - " + PageState.Page.Name);
{ }
await interop.IncludeLinks(links.ToArray());
}
await interop.RemoveElementsById("app-stylesheet", "", "app-stylesheet-" + batch + "-00");
// add favicon // manage stylesheets for this page
if (PageState.Site.FaviconFileId != null) string batch = DateTime.Now.ToString("yyyyMMddHHmmssfff");
{ var links = new List<object>();
await interop.IncludeLink("app-favicon", "shortcut icon", Utilities.ContentUrl(PageState.Alias, PageState.Site.FaviconFileId.Value), "image/x-icon", "", "", "id"); foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet && item.Declaration != ResourceDeclaration.Global))
} {
// add PWA support 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 (PageState.Site.PwaIsEnabled && PageState.Site.PwaAppIconFileId != null && PageState.Site.PwaSplashIconFileId != null) }
{ if (links.Any())
await InitializePwa(interop); {
} await interop.IncludeLinks(links.ToArray());
}
DynamicComponent = builder => await interop.RemoveElementsById("app-stylesheet", "", "app-stylesheet-" + batch + "-00");
{ }
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");
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Updater</id> <id>Oqtane.Updater</id>
<version>3.0.0</version> <version>3.0.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <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> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </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; return System.IO.File.Exists(errorPath) ? PhysicalFile(errorPath, MimeUtilities.GetMimeType(errorPath)) : null;
} }
[HttpGet("image/{id}/{width}/{height}/{mode?}")] [HttpGet("image/{id}/{width}/{height}/{mode?}/{rotate?}")]
public IActionResult GetImage(int id, int width, int height, string mode) public IActionResult GetImage(int id, int width, int height, string mode, string rotate)
{ {
var file = _files.GetFile(id); var file = _files.GetFile(id);
if (file != null && file.Folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.View, file.Folder.Permissions)) 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)) if (System.IO.File.Exists(filepath))
{ {
mode = (string.IsNullOrEmpty(mode)) ? "crop" : mode; 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"); string imagepath = filepath.Replace(Path.GetExtension(filepath), "." + width.ToString() + "x" + height.ToString() + "." + mode.ToLower() + ".png");
if (!System.IO.File.Exists(imagepath)) 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())) !string.IsNullOrEmpty(file.Folder.ImageSizes) && file.Folder.ImageSizes.ToLower().Split(",").Contains(width.ToString() + "x" + height.ToString()))
&& Enum.TryParse(mode, true, out ResizeMode resizemode)) && 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 else
{ {
@ -568,7 +569,7 @@ namespace Oqtane.Controllers
return System.IO.File.Exists(errorPath) ? PhysicalFile(errorPath, MimeUtilities.GetMimeType(errorPath)) : null; 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 try
{ {
@ -585,6 +586,11 @@ namespace Oqtane.Controllers
}) })
.BackgroundColor(new Rgba32(255, 255, 255, 0))); .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()); image.Save(imagepath, new PngEncoder());
} }
stream.Close(); stream.Close();

View File

@ -22,11 +22,12 @@ namespace Oqtane.Controllers
private readonly IPermissionRepository _permissionRepository; private readonly IPermissionRepository _permissionRepository;
private readonly ISettingRepository _settings; private readonly ISettingRepository _settings;
private readonly IUserPermissions _userPermissions; private readonly IUserPermissions _userPermissions;
private readonly IUrlMappingRepository _urlMappings;
private readonly ISyncManager _syncManager; private readonly ISyncManager _syncManager;
private readonly ILogManager _logger; private readonly ILogManager _logger;
private readonly Alias _alias; 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; _pages = pages;
_modules = modules; _modules = modules;
@ -34,6 +35,7 @@ namespace Oqtane.Controllers
_permissionRepository = permissionRepository; _permissionRepository = permissionRepository;
_settings = settings; _settings = settings;
_userPermissions = userPermissions; _userPermissions = userPermissions;
_urlMappings = urlMappings;
_syncManager = syncManager; _syncManager = syncManager;
_logger = logger; _logger = logger;
_alias = tenantManager.GetAlias(); _alias = tenantManager.GetAlias();
@ -252,17 +254,34 @@ namespace Oqtane.Controllers
[Authorize(Roles = RoleNames.Registered)] [Authorize(Roles = RoleNames.Registered)]
public Page Put(int id, [FromBody] Page page) 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 // get current page permissions
var oldPermissions = _permissionRepository.GetPermissions(EntityNames.Page, page.PageId).ToList(); var currentPermissions = _permissionRepository.GetPermissions(EntityNames.Page, page.PageId).ToList();
page = _pages.UpdatePage(page); 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 newPermissions = _permissionRepository.DecodePermissions(page.Permissions, page.SiteId, EntityNames.Page, page.PageId).ToList();
var added = GetPermissionsDifferences(newPermissions, oldPermissions); var added = GetPermissionsDifferences(newPermissions, currentPermissions);
var removed = GetPermissionsDifferences(oldPermissions, newPermissions); var removed = GetPermissionsDifferences(currentPermissions, newPermissions);
// synchronize module permissions // synchronize module permissions
if (added.Count > 0 || removed.Count > 0) if (added.Count > 0 || removed.Count > 0)
@ -270,7 +289,6 @@ namespace Oqtane.Controllers
foreach (PageModule pageModule in _pageModules.GetPageModules(page.PageId, "").ToList()) foreach (PageModule pageModule in _pageModules.GetPageModules(page.PageId, "").ToList())
{ {
var modulePermissions = _permissionRepository.GetPermissions(EntityNames.Module, pageModule.Module.ModuleId).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 // permissions added
foreach(Permission permission in added) foreach(Permission permission in added)
{ {

View File

@ -33,36 +33,66 @@ namespace Oqtane.Controllers
// GET: api/<controller> // GET: api/<controller>
[HttpGet] [HttpGet]
public IEnumerable<Setting> Get(string entityname, int entityid) public IEnumerable<Setting> Get(string entityName, int entityid)
{ {
List<Setting> settings = new List<Setting>(); List<Setting> settings = new List<Setting>();
if (IsAuthorized(entityname, entityid, PermissionNames.View)) if (IsAuthorized(entityName, entityid, PermissionNames.View))
{ {
settings = _settings.GetSettings(entityname, entityid).ToList(); settings = _settings.GetSettings(entityName, entityid).ToList();
if (entityname == EntityNames.Site && !User.IsInRole(RoleNames.Admin))
// 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 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; HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
} }
return settings; return settings;
} }
// GET api/<controller>/5 // GET api/<controller>/5/xxx
[HttpGet("{id}")] [HttpGet("{id}/{entityName}")]
public Setting Get(int id) 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 (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; return setting;
} }
else else
@ -111,14 +141,14 @@ namespace Oqtane.Controllers
return setting; return setting;
} }
// DELETE api/<controller>/5 // DELETE api/<controller>/5/xxx
[HttpDelete("{id}")] [HttpDelete("{id}/{entityName}")]
public void Delete(int id) 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)) if (IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.Edit))
{ {
_settings.DeleteSetting(id); _settings.DeleteSetting(setting.EntityName, id);
AddSyncEvent(setting.EntityName); AddSyncEvent(setting.EntityName);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Setting Deleted {Setting}", setting); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Setting Deleted {Setting}", setting);
} }
@ -140,7 +170,16 @@ namespace Oqtane.Controllers
switch (entityName) switch (entityName)
{ {
case EntityNames.Tenant: 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; break;
case EntityNames.Site: case EntityNames.Site:
if (permissionName == PermissionNames.Edit) if (permissionName == PermissionNames.Edit)
@ -164,6 +203,17 @@ namespace Oqtane.Controllers
authorized = User.IsInRole(RoleNames.Admin) || (_userPermissions.GetUser(User).UserId == entityId); authorized = User.IsInRole(RoleNames.Admin) || (_userPermissions.GetUser(User).UserId == entityId);
} }
break; 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; 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<ISqlRepository, SqlRepository>();
services.AddTransient<IUpgradeManager, UpgradeManager>(); services.AddTransient<IUpgradeManager, UpgradeManager>();
services.AddTransient<ILanguageRepository, LanguageRepository>(); services.AddTransient<ILanguageRepository, LanguageRepository>();
services.AddTransient<IVisitorRepository, VisitorRepository>();
services.AddTransient<IUrlMappingRepository, UrlMappingRepository>();
// obsolete - replaced by ITenantManager // obsolete - replaced by ITenantManager
services.AddTransient<ITenantResolver, TenantResolver>(); services.AddTransient<ITenantResolver, TenantResolver>();

View File

@ -582,12 +582,19 @@ namespace Oqtane.Infrastructure
TenantId = tenant.TenantId, TenantId = tenant.TenantId,
Name = install.SiteName, Name = install.SiteName,
LogoFileId = null, 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, DefaultThemeType = (!string.IsNullOrEmpty(install.DefaultTheme)) ? install.DefaultTheme : Constants.DefaultTheme,
DefaultContainerType = (!string.IsNullOrEmpty(install.DefaultContainer)) ? install.DefaultContainer : Constants.DefaultContainer, DefaultContainerType = (!string.IsNullOrEmpty(install.DefaultContainer)) ? install.DefaultContainer : Constants.DefaultContainer,
AdminContainerType = (!string.IsNullOrEmpty(install.DefaultAdminContainer)) ? install.DefaultAdminContainer : Constants.DefaultAdminContainer, AdminContainerType = (!string.IsNullOrEmpty(install.DefaultAdminContainer)) ? install.DefaultAdminContainer : Constants.DefaultAdminContainer,
SiteTemplateType = install.SiteTemplate, SiteTemplateType = install.SiteTemplate,
Runtime = (!string.IsNullOrEmpty(install.Runtime)) ? install.Runtime : _configManager.GetSection("Runtime").Value, 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); site = sites.AddSite(site);

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