Merge pull request #4069 from oqtane/dev

5.1.0 release
This commit is contained in:
Shaun Walker 2024-03-27 08:43:57 -04:00 committed by GitHub
commit 0633b2876c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
224 changed files with 6549 additions and 3423 deletions

View File

@ -1,91 +0,0 @@
@using Microsoft.AspNetCore.Http
@inject IInstallationService InstallationService
@inject IJSRuntime JSRuntime
@inject SiteState SiteState
@if (_initialized)
{
@if (!_installation.Success)
{
<Installer />
}
else
{
@if (string.IsNullOrEmpty(_installation.Message))
{
<div style="@_display">
<CascadingAuthenticationState>
<CascadingValue Value="@PageState">
<SiteRouter Runtime="@Runtime" RenderMode="@RenderMode" VisitorId="@VisitorId" OnStateChange="@ChangeState" />
</CascadingValue>
</CascadingAuthenticationState>
</div>
}
else
{
<div class="app-alert">
@_installation.Message
</div>
}
}
}
@code {
[Parameter]
public string AntiForgeryToken { get; set; }
[Parameter]
public string Runtime { get; set; }
[Parameter]
public string RenderMode { get; set; }
[Parameter]
public int VisitorId { get; set; }
[Parameter]
public string RemoteIPAddress { get; set; }
[Parameter]
public string AuthorizationToken { get; set; }
[CascadingParameter]
HttpContext HttpContext { get; set; }
private bool _initialized = false;
private string _display = "display: none;";
private Installation _installation = new Installation { Success = false, Message = "" };
private PageState PageState { get; set; }
protected override async Task OnParametersSetAsync()
{
SiteState.RemoteIPAddress = RemoteIPAddress;
SiteState.AntiForgeryToken = AntiForgeryToken;
SiteState.AuthorizationToken = AuthorizationToken;
SiteState.IsPrerendering = (HttpContext != null) ? true : false;
_installation = await InstallationService.IsInstalled();
if (_installation.Alias != null)
{
SiteState.Alias = _installation.Alias;
}
_initialized = true;
}
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
// prevents flash on initial page load
_display = "";
StateHasChanged();
}
}
private void ChangeState(PageState pageState)
{
PageState = pageState;
StateHasChanged();
}
}

View File

@ -7,16 +7,17 @@ namespace Microsoft.Extensions.DependencyInjection
{ {
public static class OqtaneServiceCollectionExtensions public static class OqtaneServiceCollectionExtensions
{ {
public static IServiceCollection AddOqtaneAuthorization(this IServiceCollection services) public static IServiceCollection AddOqtaneAuthentication(this IServiceCollection services)
{ {
services.AddAuthorizationCore(); services.AddAuthorizationCore();
services.AddCascadingAuthenticationState();
services.AddScoped<IdentityAuthenticationStateProvider>(); services.AddScoped<IdentityAuthenticationStateProvider>();
services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<IdentityAuthenticationStateProvider>()); services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<IdentityAuthenticationStateProvider>());
return services; return services;
} }
public static IServiceCollection AddOqtaneScopedServices(this IServiceCollection services) public static IServiceCollection AddOqtaneClientScopedServices(this IServiceCollection services)
{ {
services.AddScoped<SiteState>(); services.AddScoped<SiteState>();
services.AddScoped<IInstallationService, InstallationService>(); services.AddScoped<IInstallationService, InstallationService>();

View File

@ -260,8 +260,10 @@
IsNewTenant = true, IsNewTenant = true,
SiteName = Constants.DefaultSite, SiteName = Constants.DefaultSite,
Register = _register, Register = _register,
SiteTemplate = _template SiteTemplate = _template,
}; RenderMode = RenderModes.Static,
Runtime = Runtimes.Server
};
var installation = await InstallationService.Install(config); var installation = await InstallationService.Install(config);
if (installation.Success) if (installation.Success)

View File

@ -12,12 +12,12 @@
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList)) if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{ {
string url = NavigateUrl(p.Path); string url = NavigateUrl(p.Path);
<p class="col-md-2 mx-auto text-center mb-3"> <div class="col-md-2 mx-auto text-center my-3">
<NavLink class="nav-link text-body" href="@url" Match="NavLinkMatch.All"> <NavLink class="nav-link text-body" href="@url" Match="NavLinkMatch.All">
<h2><span class="@p.Icon" aria-hidden="true"></span></h2> <h2><span class="@p.Icon" aria-hidden="true"></span></h2>
<p class="lead">@((MarkupString)SharedLocalizer[p.Name].ToString().Replace(" ", "<br />"))</p> <div class="lead">@((MarkupString)SharedLocalizer[p.Name].ToString().Replace(" ", "<br />"))</div>
</NavLink> </NavLink>
</p> </div>
} }
} }
</div> </div>
@ -27,6 +27,7 @@
private List<Page> _pages; private List<Page> _pages;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
public override string RenderMode => RenderModes.Static;
protected override void OnInitialized() protected override void OnInitialized()
{ {

View File

@ -17,7 +17,6 @@ else
<Pager Items="@_jobs" SearchProperties="Name"> <Pager Items="@_jobs" SearchProperties="Name">
<Header> <Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>
@ -28,7 +27,6 @@ else
</Header> </Header>
<Row> <Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.JobId.ToString())" ResourceKey="EditJob" /></td> <td><ActionLink Action="Edit" Parameters="@($"id=" + context.JobId.ToString())" ResourceKey="EditJob" /></td>
<td><ActionDialog Header="Delete Job" Message="Are You Sure You Wish To Delete This Job?" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteJob(context))" ResourceKey="DeleteJob" /></td>
<td><ActionLink Action="Log" Class="btn btn-secondary" Parameters="@($"id=" + context.JobId.ToString())" ResourceKey="JobLog" /></td> <td><ActionLink Action="Log" Class="btn btn-secondary" Parameters="@($"id=" + context.JobId.ToString())" ResourceKey="JobLog" /></td>
<td>@context.Name</td> <td>@context.Name</td>
<td>@DisplayStatus(context.IsEnabled, context.IsExecuting)</td> <td>@DisplayStatus(context.IsEnabled, context.IsExecuting)</td>
@ -49,17 +47,17 @@ else
} }
@code { @code {
private List<Job> _jobs; private List<Job> _jobs;
public override SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.Host; } } public override SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.Host; } }
protected override async Task OnParametersSetAsync() protected override async Task OnInitializedAsync()
{ {
_jobs = await JobService.GetJobsAsync(); _jobs = await JobService.GetJobsAsync();
if (_jobs.Count == 0) if (_jobs.Count == 0)
{ {
AddModuleMessage(string.Format(Localizer["Message.NoJobs"], NavigateUrl("admin/system")), MessageType.Warning); AddModuleMessage(string.Format(Localizer["Message.NoJobs"], NavigateUrl("admin/system")), MessageType.Warning);
} }
} }
private string DisplayStatus(bool isEnabled, bool isExecuting) private string DisplayStatus(bool isEnabled, bool isExecuting)
@ -112,22 +110,6 @@ else
return result; return result;
} }
private async Task DeleteJob(Job job)
{
try
{
await JobService.DeleteJobAsync(job.JobId);
await logger.LogInformation("Job Deleted {Job}", job);
_jobs = await JobService.GetJobsAsync();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting Job {Job} {Error}", job, ex.Message);
AddModuleMessage(Localizer["Error.Job.Delete"], MessageType.Error);
}
}
private async Task StartJob(int jobId) private async Task StartJob(int jobId)
{ {
try try

View File

@ -12,6 +12,9 @@
<Authorizing> <Authorizing>
<text>...</text> <text>...</text>
</Authorizing> </Authorizing>
<Authorized>
<ModuleMessage Message="@Localizer["Info.SignedIn"]" Type="MessageType.Info" />
</Authorized>
<NotAuthorized> <NotAuthorized>
@if (!twofactor) @if (!twofactor)
{ {
@ -47,8 +50,13 @@
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button> <button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button> <button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
<br /><br /> <br /><br />
<button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["ForgotPassword"]</button> <button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["ForgotPassword"]</button>
} @if (PageState.Site.AllowRegistration)
{
<br /><br />
<NavLink href="@NavigateUrl("register")">@Localizer["Register"]</NavLink>
}
}
</div> </div>
</form> </form>
} }
@ -84,8 +92,6 @@
private bool _alwaysremember = false; private bool _alwaysremember = false;
private string _code = string.Empty; private string _code = string.Empty;
private string _returnUrl = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
public override List<Resource> Resources => new List<Resource>() public override List<Resource> Resources => new List<Resource>()
@ -103,11 +109,6 @@
_togglepassword = SharedLocalizer["ShowPassword"]; _togglepassword = SharedLocalizer["ShowPassword"];
if (PageState.QueryString.ContainsKey("returnurl"))
{
_returnUrl = PageState.QueryString["returnurl"];
}
if (PageState.QueryString.ContainsKey("name")) if (PageState.QueryString.ContainsKey("name"))
{ {
_username = PageState.QueryString["name"]; _username = PageState.QueryString["name"];
@ -168,11 +169,14 @@
{ {
if (firstRender && PageState.User == null && _allowsitelogin) if (firstRender && PageState.User == null && _allowsitelogin)
{ {
await username.FocusAsync(); if (!string.IsNullOrEmpty(username.Id)) // ensure username is visible in UI
{
await username.FocusAsync();
}
} }
// redirect logged in user to specified page // redirect logged in user to specified page
if (PageState.User != null) if (PageState.User != null && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{ {
NavigationManager.NavigateTo(PageState.ReturnUrl); NavigationManager.NavigateTo(PageState.ReturnUrl);
} }
@ -208,12 +212,12 @@
// hybrid apps utilize an interactive login // hybrid apps utilize an interactive login
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider)); var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
authstateprovider.NotifyAuthenticationChanged(); authstateprovider.NotifyAuthenticationChanged();
NavigationManager.NavigateTo(NavigateUrl(WebUtility.UrlDecode(_returnUrl), true)); NavigationManager.NavigateTo(NavigateUrl(PageState.ReturnUrl, true));
} }
else else
{ {
// post back to the Login page so that the cookies are set correctly // post back to the Login page so that the cookies are set correctly
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = _returnUrl }; var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = WebUtility.UrlEncode(PageState.ReturnUrl) };
string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/"); string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/");
await interop.SubmitForm(url, fields); await interop.SubmitForm(url, fields);
} }
@ -255,7 +259,7 @@
private void Cancel() private void Cancel()
{ {
NavigationManager.NavigateTo(_returnUrl); NavigationManager.NavigateTo(PageState.ReturnUrl);
} }
private async Task Forgot() private async Task Forgot()
@ -323,7 +327,7 @@
private void ExternalLogin() private void ExternalLogin()
{ {
NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + _returnUrl), true); NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + WebUtility.UrlEncode(PageState.ReturnUrl)), true);
} }
} }

View File

@ -120,7 +120,7 @@
</TabStrip> </TabStrip>
<br /> <br />
<button type="button" class="btn btn-success" @onclick="SaveModule">@SharedLocalizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="SaveModule">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
<br /> <br />
<br /> <br />
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo> <AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
@ -155,6 +155,7 @@
private DateTime modifiedon; private DateTime modifiedon;
private DateTime? _effectivedate = null; private DateTime? _effectivedate = null;
private DateTime? _expirydate = null; private DateTime? _expirydate = null;
protected override void OnInitialized() protected override void OnInitialized()
{ {
_module = ModuleState.ModuleDefinition.Name; _module = ModuleState.ModuleDefinition.Name;
@ -197,7 +198,8 @@
ModuleSettingsComponent = builder => ModuleSettingsComponent = builder =>
{ {
builder.OpenComponent(0, _moduleSettingsType); builder.OpenComponent(0, _moduleSettingsType);
builder.AddComponentReferenceCapture(1, inst => { _moduleSettings = Convert.ChangeType(inst, _moduleSettingsType); }); builder.AddAttribute(1, "RenderModeBoundary", RenderModeBoundary);
builder.AddComponentReferenceCapture(2, inst => { _moduleSettings = Convert.ChangeType(inst, _moduleSettingsType); });
builder.CloseComponent(); builder.CloseComponent();
}; };
} }
@ -216,7 +218,8 @@
ContainerSettingsComponent = builder => ContainerSettingsComponent = builder =>
{ {
builder.OpenComponent(0, _containerSettingsType); builder.OpenComponent(0, _containerSettingsType);
builder.AddComponentReferenceCapture(1, inst => { _containerSettings = Convert.ChangeType(inst, _containerSettingsType); }); builder.AddAttribute(1, "RenderModeBoundary", RenderModeBoundary);
builder.AddComponentReferenceCapture(2, inst => { _containerSettings = Convert.ChangeType(inst, _containerSettingsType); });
builder.CloseComponent(); builder.CloseComponent();
}; };
} }
@ -280,7 +283,7 @@
await containerSettingsControl.UpdateSettings(); await containerSettingsControl.UpdateSettings();
} }
NavigationManager.NavigateTo(NavigateUrl()); NavigationManager.NavigateTo(PageState.ReturnUrl);
} }
else else
{ {

View File

@ -201,7 +201,7 @@
@if (_themeSettingsType != null) @if (_themeSettingsType != null)
{ {
<TabPanel Name="ThemeSettings" Heading=@Localizer["Theme.Heading"] ResourceKey="ThemeSettings"> <TabPanel Name="ThemeSettings" Heading=@Localizer["Theme.Heading"] ResourceKey="ThemeSettings">
@ThemeSettingsComponent @_themeSettingsComponent
</TabPanel> </TabPanel>
} }
</TabStrip> </TabStrip>
@ -240,7 +240,7 @@
private PermissionGrid _permissionGrid; private PermissionGrid _permissionGrid;
private Type _themeSettingsType; private Type _themeSettingsType;
private object _themeSettings; private object _themeSettings;
private RenderFragment ThemeSettingsComponent { get; set; } private RenderFragment _themeSettingsComponent { get; set; }
private bool _refresh = false; private bool _refresh = false;
protected Page _parent = null; protected Page _parent = null;
protected Dictionary<string, string> _icons; protected Dictionary<string, string> _icons;
@ -337,16 +337,18 @@
private void ThemeSettings() private void ThemeSettings()
{ {
_themeSettingsType = null; _themeSettingsType = null;
_themeSettingsComponent = null;
var theme = PageState.Site.Themes.FirstOrDefault(item => item.Themes.Any(themecontrol => themecontrol.TypeName.Equals(_themetype))); var theme = PageState.Site.Themes.FirstOrDefault(item => item.Themes.Any(themecontrol => themecontrol.TypeName.Equals(_themetype)));
if (theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType)) if (theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType))
{ {
_themeSettingsType = Type.GetType(theme.ThemeSettingsType); _themeSettingsType = Type.GetType(theme.ThemeSettingsType);
if (_themeSettingsType != null) if (_themeSettingsType != null)
{ {
ThemeSettingsComponent = builder => _themeSettingsComponent = builder =>
{ {
builder.OpenComponent(0, _themeSettingsType); builder.OpenComponent(0, _themeSettingsType);
builder.AddComponentReferenceCapture(1, inst => { _themeSettings = Convert.ChangeType(inst, _themeSettingsType); }); builder.AddAttribute(1, "RenderModeBoundary", RenderModeBoundary);
builder.AddComponentReferenceCapture(2, inst => { _themeSettings = Convert.ChangeType(inst, _themeSettingsType); });
builder.CloseComponent(); builder.CloseComponent();
}; };
} }
@ -374,36 +376,54 @@
page.SiteId = PageState.Page.SiteId; page.SiteId = PageState.Page.SiteId;
page.Name = _name; page.Name = _name;
if (string.IsNullOrEmpty(_path))
{
_path = _name;
}
if (_path.Contains("/"))
{
if (_path.EndsWith("/") && _path != "/")
{
_path = _path.Substring(0, _path.Length - 1);
}
_path = _path.Substring(_path.LastIndexOf("/") + 1);
}
if (_parentid == "-1") if (_parentid == "-1")
{ {
page.ParentId = null; page.ParentId = null;
page.Path = Utilities.GetFriendlyUrl(_path);
} }
else else
{ {
page.ParentId = Int32.Parse(_parentid); page.ParentId = Int32.Parse(_parentid);
var parent = PageState.Pages.Where(item => item.PageId == page.ParentId).FirstOrDefault(); }
if (parent.Path == string.Empty)
// path can be a link to an external url
if (!_path.Contains("://"))
{
if (string.IsNullOrEmpty(_path))
{ {
page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path); _path = _name;
}
(_path, string parameters) = Utilities.ParsePath(_path);
if (_path.Contains("/"))
{
if (_path.EndsWith("/") && _path != "/")
{
_path = _path.Substring(0, _path.Length - 1);
}
_path = _path.Substring(_path.LastIndexOf("/") + 1);
}
if (_parentid == "-1")
{
page.Path = Utilities.GetFriendlyUrl(_path);
} }
else else
{ {
page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path); Page parent = PageState.Pages.FirstOrDefault(item => item.PageId == page.ParentId);
if (parent.Path == string.Empty)
{
page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
}
else
{
page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path);
}
} }
page.Path += parameters;
}
else
{
page.Path = _path;
} }
var _pages = await PageService.GetPagesAsync(PageState.Site.SiteId); var _pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
@ -449,23 +469,23 @@
// appearance // appearance
page.Title = _title; page.Title = _title;
page.Icon = (_icon == null ? string.Empty : _icon); page.Icon = (_icon == null ? string.Empty : _icon);
page.ThemeType = _themetype; page.ThemeType = _themetype;
if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType) if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType)
{ {
page.ThemeType = string.Empty; page.ThemeType = string.Empty;
} }
page.DefaultContainerType = _containertype; page.DefaultContainerType = _containertype;
if (!string.IsNullOrEmpty(page.DefaultContainerType) && page.DefaultContainerType == PageState.Site.DefaultContainerType) if (!string.IsNullOrEmpty(page.DefaultContainerType) && page.DefaultContainerType == PageState.Site.DefaultContainerType)
{ {
page.DefaultContainerType = string.Empty; page.DefaultContainerType = string.Empty;
} }
// page content // page content
page.HeadContent = _headcontent; page.HeadContent = _headcontent;
page.BodyContent = _bodycontent; page.BodyContent = _bodycontent;
// permissions // permissions
page.PermissionList = _permissionGrid.GetPermissionList(); page.PermissionList = _permissionGrid.GetPermissionList();
page = await PageService.AddPageAsync(page); page = await PageService.AddPageAsync(page);
await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, page.ParentId); await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, page.ParentId);
@ -473,11 +493,18 @@
await logger.LogInformation("Page Added {Page}", page); await logger.LogInformation("Page Added {Page}", page);
if (!string.IsNullOrEmpty(PageState.ReturnUrl)) if (!string.IsNullOrEmpty(PageState.ReturnUrl))
{ {
NavigationManager.NavigateTo(page.Path); // redirect to new page NavigationManager.NavigateTo(PageState.ReturnUrl, true);
} }
else else
{ {
NavigationManager.NavigateTo(NavigateUrl()); // redirect to page management if (!page.Path.Contains("://"))
{
NavigationManager.NavigateTo(page.Path); // redirect to new page created
}
else
{
NavigationManager.NavigateTo(NavigateUrl("admin/pages"));
}
} }
} }
else else

View File

@ -179,9 +179,12 @@
<Label Class="col-sm-3" For="container" HelpText="Select the default container for the page" ResourceKey="DefaultContainer">Default Container: </Label> <Label Class="col-sm-3" For="container" HelpText="Select the default container for the page" ResourceKey="DefaultContainer">Default Container: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="container" class="form-select" @bind="@_containertype" required> <select id="container" class="form-select" @bind="@_containertype" required>
@foreach (var container in _containers) @if (_containers != null)
{ {
<option value="@container.TypeName">@container.Name</option> foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
} }
</select> </select>
</div> </div>
@ -234,7 +237,7 @@
@if (_themeSettingsType != null) @if (_themeSettingsType != null)
{ {
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings"> <TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
@ThemeSettingsComponent @_themeSettingsComponent
</TabPanel> </TabPanel>
<br /> <br />
} }
@ -278,7 +281,7 @@
@if (_themeSettingsType != null) @if (_themeSettingsType != null)
{ {
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings"> <TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
@ThemeSettingsComponent @_themeSettingsComponent
</TabPanel> </TabPanel>
<br /> <br />
} }
@ -317,7 +320,7 @@
private string _containertype = "-"; private string _containertype = "-";
private Type _themeSettingsType; private Type _themeSettingsType;
private object _themeSettings; private object _themeSettings;
private RenderFragment ThemeSettingsComponent { get; set; } private RenderFragment _themeSettingsComponent { get; set; }
private string _headcontent; private string _headcontent;
private string _bodycontent; private string _bodycontent;
private List<Permission> _permissions = null; private List<Permission> _permissions = null;
@ -377,7 +380,7 @@
} }
else else
{ {
if (_path.Contains("/")) if (_path.Contains("/") & !_path.Contains("://"))
{ {
_path = _path.Substring(_path.LastIndexOf("/") + 1); _path = _path.Substring(_path.LastIndexOf("/") + 1);
} }
@ -478,16 +481,19 @@
private void ThemeSettings() private void ThemeSettings()
{ {
_themeSettingsType = null; _themeSettingsType = null;
_themeSettingsComponent = null;
var theme = PageState.Site.Themes.FirstOrDefault(item => item.Themes.Any(themecontrol => themecontrol.TypeName.Equals(_themetype))); var theme = PageState.Site.Themes.FirstOrDefault(item => item.Themes.Any(themecontrol => themecontrol.TypeName.Equals(_themetype)));
if (theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType)) if (theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType))
{ {
_themeSettingsType = Type.GetType(theme.ThemeSettingsType); _themeSettingsType = Type.GetType(theme.ThemeSettingsType);
if (_themeSettingsType != null) if (_themeSettingsType != null)
{ {
ThemeSettingsComponent = builder => _themeSettingsComponent = builder =>
{ {
builder.OpenComponent(0, _themeSettingsType); builder.OpenComponent(0, _themeSettingsType);
builder.AddComponentReferenceCapture(1, inst => { _themeSettings = Convert.ChangeType(inst, _themeSettingsType); }); builder.AddAttribute(1, "RenderModeBoundary", RenderModeBoundary);
builder.AddComponentReferenceCapture(2, inst => { _themeSettings = Convert.ChangeType(inst, _themeSettingsType); });
builder.CloseComponent(); builder.CloseComponent();
}; };
} }
@ -514,36 +520,54 @@
_page.Name = _name; _page.Name = _name;
if (string.IsNullOrEmpty(_path))
{
_path = _name;
}
if (_path.Contains("/"))
{
if (_path.EndsWith("/") && _path != "/")
{
_path = _path.Substring(0, _path.Length - 1);
}
_path = _path.Substring(_path.LastIndexOf("/") + 1);
}
if (_parentid == "-1") if (_parentid == "-1")
{ {
_page.ParentId = null; _page.ParentId = null;
_page.Path = Utilities.GetFriendlyUrl(_path);
} }
else else
{ {
_page.ParentId = Int32.Parse(_parentid); _page.ParentId = Int32.Parse(_parentid);
Page parent = PageState.Pages.FirstOrDefault(item => item.PageId == _page.ParentId); }
if (parent.Path == string.Empty)
// path can be a link to an external url
if (!_path.Contains("://"))
{
if (string.IsNullOrEmpty(_path))
{ {
_page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path); _path = _name;
}
(_path, string parameters) = Utilities.ParsePath(_path);
if (_path.Contains("/"))
{
if (_path.EndsWith("/") && _path != "/")
{
_path = _path.Substring(0, _path.Length - 1);
}
_path = _path.Substring(_path.LastIndexOf("/") + 1);
}
if (_parentid == "-1")
{
_page.Path = Utilities.GetFriendlyUrl(_path);
} }
else else
{ {
_page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path); Page parent = PageState.Pages.FirstOrDefault(item => item.PageId == _page.ParentId);
if (parent.Path == string.Empty)
{
_page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
}
else
{
_page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path);
}
} }
_page.Path += parameters;
}
else
{
_page.Path = _path;
} }
var _pages = await PageService.GetPagesAsync(PageState.Site.SiteId); var _pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
@ -630,11 +654,18 @@
await logger.LogInformation("Page Saved {Page}", _page); await logger.LogInformation("Page Saved {Page}", _page);
if (!string.IsNullOrEmpty(PageState.ReturnUrl)) if (!string.IsNullOrEmpty(PageState.ReturnUrl))
{ {
NavigationManager.NavigateTo(PageState.ReturnUrl); NavigationManager.NavigateTo(PageState.ReturnUrl, true);
} }
else else
{ {
NavigationManager.NavigateTo(NavigateUrl()); if (!_page.Path.Contains("://"))
{
NavigationManager.NavigateTo(NavigateUrl(), true); // redirect to page being edited
}
else
{
NavigationManager.NavigateTo(NavigateUrl("admin/pages"));
}
} }
} }
else else

View File

@ -140,7 +140,7 @@ else
{ {
try try
{ {
ModuleInstance.ShowProgressIndicator(); ShowProgressIndicator();
foreach (Page page in _pages.Where(item => item.IsDeleted)) foreach (Page page in _pages.Where(item => item.IsDeleted))
{ {
await PageService.DeletePageAsync(page.PageId); await PageService.DeletePageAsync(page.PageId);
@ -149,7 +149,7 @@ else
await logger.LogInformation("Pages Permanently Deleted"); await logger.LogInformation("Pages Permanently Deleted");
await Load(); await Load();
ModuleInstance.HideProgressIndicator(); HideProgressIndicator();
StateHasChanged(); StateHasChanged();
NavigationManager.NavigateTo(NavigateUrl()); NavigationManager.NavigateTo(NavigateUrl());
} }
@ -157,7 +157,7 @@ else
{ {
await logger.LogError(ex, "Error Permanently Deleting Pages {Error}", ex.Message); await logger.LogError(ex, "Error Permanently Deleting Pages {Error}", ex.Message);
AddModuleMessage(ex.Message, MessageType.Error); AddModuleMessage(ex.Message, MessageType.Error);
ModuleInstance.HideProgressIndicator(); HideProgressIndicator();
} }
} }
@ -199,21 +199,21 @@ else
{ {
try try
{ {
ModuleInstance.ShowProgressIndicator(); ShowProgressIndicator();
foreach (Module module in _modules.Where(item => item.IsDeleted).ToList()) foreach (Module module in _modules.Where(item => item.IsDeleted).ToList())
{ {
await PageModuleService.DeletePageModuleAsync(module.PageModuleId); await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
} }
await logger.LogInformation("Modules Permanently Deleted"); await logger.LogInformation("Modules Permanently Deleted");
await Load(); await Load();
ModuleInstance.HideProgressIndicator(); HideProgressIndicator();
StateHasChanged(); StateHasChanged();
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Permanently Deleting Modules {Error}", ex.Message); await logger.LogError(ex, "Error Permanently Deleting Modules {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Modules.Delete"], MessageType.Error); AddModuleMessage(Localizer["Error.Modules.Delete"], MessageType.Error);
ModuleInstance.HideProgressIndicator(); HideProgressIndicator();
} }
} }
private void OnPageChangePage(int page) private void OnPageChangePage(int page)

View File

@ -9,60 +9,68 @@
@if (PageState.Site.AllowRegistration) @if (PageState.Site.AllowRegistration)
{ {
<AuthorizeView Roles="@RoleNames.Registered"> if (!_userCreated)
<Authorizing> {
<text>...</text> <AuthorizeView Roles="@RoleNames.Registered">
</Authorizing> <Authorizing>
<Authorized> <text>...</text>
<ModuleMessage Message="@Localizer["Info.Registration.Exists"]" Type="MessageType.Info" /> </Authorizing>
</Authorized> <Authorized>
<NotAuthorized> <ModuleMessage Message="@Localizer["Info.Registration.Exists"]" Type="MessageType.Info" />
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" /> </Authorized>
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate> <NotAuthorized>
<div class="container"> <ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
<div class="row mb-1 align-items-center"> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<Label Class="col-sm-3" For="username" HelpText="Your username. Note that this field can not be modified once it is saved." ResourceKey="Username"></Label> <div class="container">
<div class="col-sm-9"> <div class="row mb-1 align-items-center">
<input id="username" class="form-control" @bind="@_username" maxlength="256" required /> <Label Class="col-sm-3" For="username" HelpText="Your username. Note that this field can not be modified once it is saved." ResourceKey="Username"></Label>
<div class="col-sm-9">
<input id="username" class="form-control" @bind="@_username" maxlength="256" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="password" HelpText="Please choose a sufficiently secure password and enter it here" ResourceKey="Password"></Label>
<div class="col-sm-9">
<div class="input-group">
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="confirm" HelpText="Enter your password again to confirm it matches the value entered above" ResourceKey="Confirm"></Label>
<div class="col-sm-9">
<div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="email" HelpText="Your email address where you wish to receive notifications" ResourceKey="Email"></Label>
<div class="col-sm-9">
<input id="email" class="form-control" @bind="@_email" maxlength="256" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="displayname" HelpText="Your full name" ResourceKey="DisplayName"></Label>
<div class="col-sm-9">
<input id="displayname" class="form-control" @bind="@_displayname" maxlength="50" />
</div>
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <br />
<Label Class="col-sm-3" For="password" HelpText="Please choose a sufficiently secure password and enter it here" ResourceKey="Password"></Label> <button type="button" class="btn btn-primary" @onclick="Register">@Localizer["Register"]</button>
<div class="col-sm-9"> <button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
<div class="input-group"> @if (_allowsitelogin)
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required /> {
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button> <br /><br />
</div> <NavLink href="@NavigateUrl("login")">@Localizer["Login"]</NavLink>
</div> }
</div> </form>
<div class="row mb-1 align-items-center"> </NotAuthorized>
<Label Class="col-sm-3" For="confirm" HelpText="Enter your password again to confirm it matches the value entered above" ResourceKey="Confirm"></Label> </AuthorizeView>
<div class="col-sm-9"> }
<div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="email" HelpText="Your email address where you wish to receive notifications" ResourceKey="Email"></Label>
<div class="col-sm-9">
<input id="email" class="form-control" @bind="@_email" maxlength="256" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="displayname" HelpText="Your full name" ResourceKey="DisplayName"></Label>
<div class="col-sm-9">
<input id="displayname" class="form-control" @bind="@_displayname" maxlength="50" />
</div>
</div>
</div>
<br />
<button type="button" class="btn btn-primary" @onclick="Register">@Localizer["Register"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</form>
</NotAuthorized>
</AuthorizeView>
} }
else else
{ {
@ -80,12 +88,15 @@ else
private string _confirm = string.Empty; private string _confirm = string.Empty;
private string _email = string.Empty; private string _email = string.Empty;
private string _displayname = string.Empty; private string _displayname = string.Empty;
private bool _userCreated = false;
private bool _allowsitelogin = true;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId); _passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
} }
protected override void OnParametersSet() protected override void OnParametersSet()
@ -121,14 +132,8 @@ else
if (user != null) if (user != null)
{ {
await logger.LogInformation("User Created {Username} {Email}", _username, _email); await logger.LogInformation("User Created {Username} {Email}", _username, _email);
if (PageState.QueryString.ContainsKey("returnurl")) _userCreated = true;
{ AddModuleMessage(Localizer["Info.User.AccountCreate"], MessageType.Info);
NavigationManager.NavigateTo(WebUtility.UrlDecode(PageState.QueryString["returnurl"]));
}
else // legacy behavior
{
AddModuleMessage(Localizer["Info.User.AccountCreate"], MessageType.Info);
}
} }
else else
{ {
@ -160,7 +165,7 @@ else
private void Cancel() private void Cancel()
{ {
NavigationManager.NavigateTo(NavigateUrl(string.Empty)); NavigationManager.NavigateTo(PageState.ReturnUrl);
} }
private void TogglePassword() private void TogglePassword()

View File

@ -98,9 +98,12 @@
<Label Class="col-sm-3" For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label> <Label Class="col-sm-3" For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="defaultContainer" class="form-select" @bind="@_containertype" required> <select id="defaultContainer" class="form-select" @bind="@_containertype" required>
@foreach (var container in _containers) @if (_containers != null)
{ {
<option value="@container.TypeName">@container.Name</option> @foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
} }
</select> </select>
</div> </div>
@ -110,9 +113,12 @@
<div class="col-sm-9"> <div class="col-sm-9">
<select id="defaultAdminContainer" class="form-select" @bind="@_admincontainertype" required> <select id="defaultAdminContainer" class="form-select" @bind="@_admincontainertype" required>
<option value="@Constants.DefaultAdminContainer">&lt;@Localizer["DefaultAdminContainer"]&gt;</option> <option value="@Constants.DefaultAdminContainer">&lt;@Localizer["DefaultAdminContainer"]&gt;</option>
@foreach (var container in _containers) @if (_containers != null)
{ {
<option value="@container.TypeName">@container.Name</option> @foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
} }
</select> </select>
</div> </div>
@ -311,27 +317,38 @@
<Section Name="Hosting" Heading="Hosting Model" ResourceKey="Hosting"> <Section Name="Hosting" Heading="Hosting Model" ResourceKey="Hosting">
<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" For="runtime" HelpText="The Blazor runtime hosting model for the site" ResourceKey="Runtime">Runtime: </Label> <Label Class="col-sm-3" For="rendermode" HelpText="The default render mode for the site" ResourceKey="Rendermode">Render Mode: </Label>
<div class="col-sm-9">
<select id="rendermode" class="form-select" @bind="@_rendermode" required>
<option value="@RenderModes.Interactive">@(SharedLocalizer["RenderMode" + @RenderModes.Interactive])</option>
<option value="@RenderModes.Static">@(SharedLocalizer["RenderMode" + @RenderModes.Static])</option>
<option value="@RenderModes.Headless">@(SharedLocalizer["RenderMode" + @RenderModes.Headless])</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="runtime" HelpText="The render mode for UI components which require interactivity" ResourceKey="Runtime">Interactivity: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="runtime" class="form-select" @bind="@_runtime" required> <select id="runtime" class="form-select" @bind="@_runtime" required>
<option value="Server">@SharedLocalizer["BlazorServer"]</option> <option value="@Runtimes.Server">@(SharedLocalizer["Runtime" + @Runtimes.Server])</option>
<option value="WebAssembly">@SharedLocalizer["BlazorWebAssembly"]</option> <option value="@Runtimes.WebAssembly">@(SharedLocalizer["Runtime" + @Runtimes.WebAssembly])</option>
<option value="@Runtimes.Auto">@(SharedLocalizer["Runtime" + @Runtimes.Auto])</option>
</select> </select>
</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="prerender" HelpText="Specifies if the site should be prerendered (for search crawlers, etc...)" ResourceKey="Prerender">Prerender? </Label> <Label Class="col-sm-3" For="prerender" HelpText="Specifies if interactive components should prerender their output" ResourceKey="Prerender">Prerender? </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="prerender" class="form-select" @bind="@_prerender" required> <select id="prerender" class="form-select" @bind="@_prerender" required>
<option value="Prerendered">@SharedLocalizer["Yes"]</option> <option value="True">@SharedLocalizer["Yes"]</option>
<option value="">@SharedLocalizer["No"]</option> <option value="False">@SharedLocalizer["No"]</option>
</select> </select>
</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="hybridenabled" HelpText="Specifies if the site can be integrated with an external .NET MAUI hybrid application" ResourceKey="HybridEnabled">Hybrid Enabled? </Label> <Label Class="col-sm-3" For="hybrid" HelpText="Specifies if the site can be integrated with an external .NET MAUI hybrid application" ResourceKey="Hybrid">Hybrid? </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="hybridenabled" class="form-select" @bind="@_hybridenabled" required> <select id="hybrid" class="form-select" @bind="@_hybrid" required>
<option value="True">@SharedLocalizer["Yes"]</option> <option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option> <option value="False">@SharedLocalizer["No"]</option>
</select> </select>
@ -414,9 +431,10 @@
private int _aliasid = -1; private int _aliasid = -1;
private string _aliasname; private string _aliasname;
private string _defaultalias; private string _defaultalias;
private string _runtime = ""; private string _rendermode = RenderModes.Interactive;
private string _prerender = ""; private string _runtime = Runtimes.Server;
private string _hybridenabled = ""; private string _prerender = "True";
private string _hybrid = "False";
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;
@ -500,9 +518,10 @@
await GetAliases(); await GetAliases();
// hosting model // hosting model
_rendermode = site.RenderMode;
_runtime = site.Runtime; _runtime = site.Runtime;
_prerender = site.RenderMode.Replace(_runtime, ""); _prerender = site.Prerender.ToString();
_hybridenabled = site.HybridEnabled.ToString(); _hybrid = site.Hybrid.ToString();
// database // database
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
@ -566,9 +585,6 @@
var site = await SiteService.GetSiteAsync(PageState.Site.SiteId); var site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null) if (site != null)
{ {
bool refresh = false;
bool reload = false;
site.Name = _name; site.Name = _name;
site.HomePageId = (_homepageid != "-" ? int.Parse(_homepageid) : null); site.HomePageId = (_homepageid != "-" ? int.Parse(_homepageid) : null);
site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted)); site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
@ -582,7 +598,6 @@
if (logofileid != _logofileid) if (logofileid != _logofileid)
{ {
_logofileid = logofileid; _logofileid = logofileid;
refresh = true; // needs to be refreshed on client
} }
} }
int? faviconFieldId = _faviconfilemanager.GetFileId(); int? faviconFieldId = _faviconfilemanager.GetFileId();
@ -590,17 +605,14 @@
if (site.FaviconFileId != faviconFieldId) if (site.FaviconFileId != faviconFieldId)
{ {
site.FaviconFileId = faviconFieldId; site.FaviconFileId = faviconFieldId;
reload = true; // needs to be reloaded on server
} }
if (site.DefaultThemeType != _themetype) if (site.DefaultThemeType != _themetype)
{ {
site.DefaultThemeType = _themetype; site.DefaultThemeType = _themetype;
refresh = true; // needs to be refreshed on client
} }
if (site.DefaultContainerType != _containertype) if (site.DefaultContainerType != _containertype)
{ {
site.DefaultContainerType = _containertype; site.DefaultContainerType = _containertype;
refresh = true; // needs to be refreshed on client
} }
site.AdminContainerType = _admincontainertype; site.AdminContainerType = _admincontainertype;
@ -608,44 +620,39 @@
if (site.HeadContent != _headcontent) if (site.HeadContent != _headcontent)
{ {
site.HeadContent = _headcontent; site.HeadContent = _headcontent;
reload = true;
} }
if (site.BodyContent != _bodycontent) if (site.BodyContent != _bodycontent)
{ {
site.BodyContent = _bodycontent; site.BodyContent = _bodycontent;
reload = true;
} }
// PWA // PWA
if (site.PwaIsEnabled.ToString() != _pwaisenabled) if (site.PwaIsEnabled.ToString() != _pwaisenabled)
{ {
site.PwaIsEnabled = Boolean.Parse(_pwaisenabled); site.PwaIsEnabled = Boolean.Parse(_pwaisenabled);
reload = true; // needs to be reloaded on server
} }
int? pwaappiconfileid = _pwaappiconfilemanager.GetFileId(); int? pwaappiconfileid = _pwaappiconfilemanager.GetFileId();
if (pwaappiconfileid == -1) pwaappiconfileid = null; if (pwaappiconfileid == -1) pwaappiconfileid = null;
if (site.PwaAppIconFileId != pwaappiconfileid) if (site.PwaAppIconFileId != pwaappiconfileid)
{ {
site.PwaAppIconFileId = pwaappiconfileid; site.PwaAppIconFileId = pwaappiconfileid;
reload = true; // needs to be reloaded on server
} }
int? pwasplashiconfileid = _pwasplashiconfilemanager.GetFileId(); int? pwasplashiconfileid = _pwasplashiconfilemanager.GetFileId();
if (pwasplashiconfileid == -1) pwasplashiconfileid = null; if (pwasplashiconfileid == -1) pwasplashiconfileid = null;
if (site.PwaSplashIconFileId != pwasplashiconfileid) if (site.PwaSplashIconFileId != pwasplashiconfileid)
{ {
site.PwaSplashIconFileId = pwasplashiconfileid; site.PwaSplashIconFileId = pwasplashiconfileid;
reload = true; // needs to be reloaded on server
} }
// hosting model // hosting model
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{ {
if (site.Runtime != _runtime || site.RenderMode != _runtime + _prerender || site.HybridEnabled != bool.Parse(_hybridenabled)) if (site.RenderMode != _rendermode || site.Runtime != _runtime || site.Prerender != bool.Parse(_prerender) || site.Hybrid != bool.Parse(_hybrid))
{ {
site.RenderMode = _rendermode;
site.Runtime = _runtime; site.Runtime = _runtime;
site.RenderMode = _runtime + _prerender; site.Prerender = bool.Parse(_prerender);
site.HybridEnabled = bool.Parse(_hybridenabled); site.Hybrid = bool.Parse(_hybrid);
reload = true; // needs to be reloaded on serve
} }
} }
@ -672,15 +679,7 @@
await logger.LogInformation("Site Settings Saved {Site}", site); await logger.LogInformation("Site Settings Saved {Site}", site);
if (refresh || reload) NavigationManager.NavigateTo(NavigateUrl(), true); // reload
{
NavigationManager.NavigateTo(NavigateUrl(true), reload); // refresh/reload
}
else
{
AddModuleMessage(Localizer["Success.Settings.SaveSite"], MessageType.Success);
await ScrollToPageTop();
}
} }
} }
else else

View File

@ -71,20 +71,22 @@ else
</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="runtime" HelpText="The runtime hosting model" ResourceKey="Runtime">Runtime: </Label> <Label Class="col-sm-3" For="rendermode" HelpText="The default render mode for the site" ResourceKey="Rendermode">Render Mode: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="runtime" class="form-select" @bind="@_runtime" required> <select id="rendermode" class="form-select" @bind="@_rendermode" required>
<option value="Server">@SharedLocalizer["BlazorServer"]</option> <option value="@RenderModes.Interactive">@(SharedLocalizer["RenderMode" + @RenderModes.Interactive])</option>
<option value="WebAssembly">@SharedLocalizer["BlazorWebAssembly"]</option> <option value="@RenderModes.Static">@(SharedLocalizer["RenderMode" + @RenderModes.Static])</option>
</select> <option value="@RenderModes.Headless">@(SharedLocalizer["RenderMode" + @RenderModes.Headless])</option>
</select>
</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="prerender" HelpText="Specifies if the site should be prerendered (for search crawlers, etc...)" ResourceKey="Prerender">Prerender? </Label> <Label Class="col-sm-3" For="runtime" HelpText="The render mode for UI components which require interactivity" ResourceKey="Runtime">Interactivity: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="prerender" class="form-select" @bind="@_prerender" required> <select id="runtime" class="form-select" @bind="@_runtime" required>
<option value="Prerendered">@SharedLocalizer["Yes"]</option> <option value="@Runtimes.Server">@(SharedLocalizer["Runtime" + @Runtimes.Server])</option>
<option value="">@SharedLocalizer["No"]</option> <option value="@Runtimes.WebAssembly">@(SharedLocalizer["Runtime" + @Runtimes.WebAssembly])</option>
<option value="@Runtimes.Auto">@(SharedLocalizer["Runtime" + @Runtimes.Auto])</option>
</select> </select>
</div> </div>
</div> </div>
@ -201,8 +203,8 @@ else
private string _themetype = "-"; private string _themetype = "-";
private string _containertype = "-"; private string _containertype = "-";
private string _sitetemplatetype = "-"; private string _sitetemplatetype = "-";
private string _runtime = "Server"; private string _rendermode = RenderModes.Static;
private string _prerender = "Prerendered"; private string _runtime = Runtimes.Server;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
@ -228,137 +230,137 @@ else
_sitetemplatetype = Constants.DefaultSiteTemplate; _sitetemplatetype = Constants.DefaultSiteTemplate;
} }
_databases = await DatabaseService.GetDatabasesAsync(); _databases = await DatabaseService.GetDatabasesAsync();
if (_databases.Exists(item => item.IsDefault)) if (_databases.Exists(item => item.IsDefault))
{ {
_databaseName = _databases.Find(item => item.IsDefault).Name; _databaseName = _databases.Find(item => item.IsDefault).Name;
} }
else else
{ {
_databaseName = "LocalDB"; _databaseName = "LocalDB";
} }
LoadDatabaseConfigComponent(); LoadDatabaseConfigComponent();
} }
private void DatabaseChanged(ChangeEventArgs eventArgs) private void DatabaseChanged(ChangeEventArgs eventArgs)
{ {
try try
{ {
_databaseName = (string)eventArgs.Value; _databaseName = (string)eventArgs.Value;
_showConnectionString = false; _showConnectionString = false;
LoadDatabaseConfigComponent(); LoadDatabaseConfigComponent();
} }
catch catch
{ {
AddModuleMessage(Localizer["Error.Database.LoadConfig"], MessageType.Error); AddModuleMessage(Localizer["Error.Database.LoadConfig"], MessageType.Error);
} }
} }
private void LoadDatabaseConfigComponent() private void LoadDatabaseConfigComponent()
{ {
var database = _databases.SingleOrDefault(d => d.Name == _databaseName); var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
if (database != null) if (database != null)
{ {
_databaseConfigType = Type.GetType(database.ControlType); _databaseConfigType = Type.GetType(database.ControlType);
DatabaseConfigComponent = builder => DatabaseConfigComponent = builder =>
{ {
builder.OpenComponent(0, _databaseConfigType); builder.OpenComponent(0, _databaseConfigType);
builder.AddComponentReferenceCapture(1, inst => { _databaseConfig = Convert.ChangeType(inst, _databaseConfigType); }); builder.AddComponentReferenceCapture(1, inst => { _databaseConfig = Convert.ChangeType(inst, _databaseConfigType); });
builder.CloseComponent(); builder.CloseComponent();
}; };
} }
} }
private void TenantChanged(ChangeEventArgs e) private void TenantChanged(ChangeEventArgs e)
{ {
_tenantid = (string)e.Value; _tenantid = (string)e.Value;
if (string.IsNullOrEmpty(_tenantName)) if (string.IsNullOrEmpty(_tenantName))
{ {
_tenantName = _name; _tenantName = _name;
} }
StateHasChanged(); StateHasChanged();
} }
private async void ThemeChanged(ChangeEventArgs e) private async void ThemeChanged(ChangeEventArgs e)
{ {
try try
{ {
_themetype = (string)e.Value; _themetype = (string)e.Value;
if (_themetype != "-") if (_themetype != "-")
{ {
_containers = ThemeService.GetContainerControls(_themeList, _themetype); _containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containertype = _containers.First().TypeName; _containertype = _containers.First().TypeName;
} }
else else
{ {
_containers = new List<ThemeControl>(); _containers = new List<ThemeControl>();
_containertype = "-"; _containertype = "-";
} }
StateHasChanged(); StateHasChanged();
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Containers For Theme {ThemeType} {Error}", _themetype, ex.Message); await logger.LogError(ex, "Error Loading Containers For Theme {ThemeType} {Error}", _themetype, ex.Message);
AddModuleMessage(Localizer["Error.Theme.LoadContainers"], MessageType.Error); AddModuleMessage(Localizer["Error.Theme.LoadContainers"], MessageType.Error);
} }
} }
private async Task SaveSite() private async Task SaveSite()
{ {
validated = true; validated = true;
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
if (await interop.FormValid(form)) if (await interop.FormValid(form))
{ {
if (_tenantid != "-" && _name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-" && _sitetemplatetype != "-") if (_tenantid != "-" && _name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-" && _sitetemplatetype != "-")
{ {
_urls = Regex.Replace(_urls, @"\r\n?|\n", ","); _urls = Regex.Replace(_urls, @"\r\n?|\n", ",");
var duplicates = new List<string>(); var duplicates = new List<string>();
var aliases = await AliasService.GetAliasesAsync(); var aliases = await AliasService.GetAliasesAsync();
foreach (string name in _urls.Split(',', StringSplitOptions.RemoveEmptyEntries)) foreach (string name in _urls.Split(',', StringSplitOptions.RemoveEmptyEntries))
{ {
if (aliases.Exists(item => item.Name == name)) if (aliases.Exists(item => item.Name == name))
{ {
duplicates.Add(name); duplicates.Add(name);
} }
} }
if (duplicates.Count == 0) if (duplicates.Count == 0)
{ {
InstallConfig config = new InstallConfig(); InstallConfig config = new InstallConfig();
if (_tenantid == "+") if (_tenantid == "+")
{ {
if (!string.IsNullOrEmpty(_tenantName) && !_tenants.Exists(item => item.Name == _tenantName)) if (!string.IsNullOrEmpty(_tenantName) && !_tenants.Exists(item => item.Name == _tenantName))
{ {
// validate host credentials // validate host credentials
var user = new User(); var user = new User();
user.SiteId = PageState.Site.SiteId; user.SiteId = PageState.Site.SiteId;
user.Username = _hostusername; user.Username = _hostusername;
user.Password = _hostpassword; user.Password = _hostpassword;
user.LastIPAddress = PageState.RemoteIPAddress; user.LastIPAddress = PageState.RemoteIPAddress;
user = await UserService.LoginUserAsync(user, false, false); user = await UserService.LoginUserAsync(user, false, false);
if (user.IsAuthenticated) if (user.IsAuthenticated)
{ {
var database = _databases.SingleOrDefault(d => d.Name == _databaseName); var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
var connectionString = String.Empty; var connectionString = String.Empty;
if (_showConnectionString) if (_showConnectionString)
{ {
connectionString = _connectionString; connectionString = _connectionString;
} }
else else
{ {
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl) if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
{ {
connectionString = databaseConfigControl.GetConnectionString(); connectionString = databaseConfigControl.GetConnectionString();
} }
} }
if (connectionString != "") if (connectionString != "")
{ {
config.TenantName = _tenantName; config.TenantName = _tenantName;
config.DatabaseType = database.DBType; config.DatabaseType = database.DBType;
config.ConnectionString = connectionString; config.ConnectionString = connectionString;
config.HostUsername = _hostusername; config.HostUsername = _hostusername;
config.HostPassword = _hostpassword; config.HostPassword = _hostpassword;
config.HostEmail = user.Email; config.HostEmail = user.Email;
config.HostName = user.DisplayName; config.HostName = user.DisplayName;
@ -399,8 +401,8 @@ else
config.DefaultContainer = _containertype; config.DefaultContainer = _containertype;
config.DefaultAdminContainer = ""; config.DefaultAdminContainer = "";
config.SiteTemplate = _sitetemplatetype; config.SiteTemplate = _sitetemplatetype;
config.RenderMode = _rendermode;
config.Runtime = _runtime; config.Runtime = _runtime;
config.RenderMode = _runtime + _prerender;
ShowProgressIndicator(); ShowProgressIndicator();

View File

@ -62,7 +62,7 @@ else
{ {
if (PageState.Alias.Name == name) if (PageState.Alias.Name == name)
{ {
NavigationManager.NavigateTo("/"); NavigationManager.NavigateTo(PageState.Alias.Path + "/");
} }
else else
{ {

View File

@ -483,9 +483,9 @@
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId); await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
await logger.LogInformation("User Profile Saved"); await logger.LogInformation("User Profile Saved");
if (PageState.QueryString.ContainsKey("returnurl")) if (!string.IsNullOrEmpty(PageState.ReturnUrl))
{ {
NavigationManager.NavigateTo(WebUtility.UrlDecode(PageState.QueryString["returnurl"])); NavigationManager.NavigateTo(PageState.ReturnUrl);
} }
else // legacy behavior else // legacy behavior
{ {
@ -551,7 +551,7 @@
private void Cancel() private void Cancel()
{ {
NavigationManager.NavigateTo(NavigateUrl(string.Empty)); NavigationManager.NavigateTo(PageState.ReturnUrl);
} }
private void ProfileChanged(ChangeEventArgs e, string SettingName) private void ProfileChanged(ChangeEventArgs e, string SettingName)
@ -596,7 +596,7 @@
{ {
try try
{ {
ModuleInstance.ShowProgressIndicator(); ShowProgressIndicator();
foreach(var Notification in notifications) foreach(var Notification in notifications)
{ {
if (!Notification.IsDeleted) if (!Notification.IsDeleted)
@ -612,7 +612,7 @@
} }
await logger.LogInformation("Notifications Permanently Deleted"); await logger.LogInformation("Notifications Permanently Deleted");
await LoadNotificationsAsync(); await LoadNotificationsAsync();
ModuleInstance.HideProgressIndicator(); HideProgressIndicator();
StateHasChanged(); StateHasChanged();
} }
@ -620,7 +620,7 @@
{ {
await logger.LogError(ex, "Error Deleting Notifications {Error}", ex.Message); await logger.LogError(ex, "Error Deleting Notifications {Error}", ex.Message);
AddModuleMessage(ex.Message, MessageType.Error); AddModuleMessage(ex.Message, MessageType.Error);
ModuleInstance.HideProgressIndicator(); HideProgressIndicator();
} }
} }

View File

@ -2,47 +2,102 @@
@using System.Text.Json @using System.Text.Json
@inherits LocalizableComponent @inherits LocalizableComponent
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
@inject NavigationManager NavigationManager
@if (_visible) @if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Interactive)
{ {
<div class="app-actiondialog"> @if (_visible)
<div class="modal" tabindex="-1" role="dialog"> {
<div class="modal-dialog"> <div class="app-actiondialog">
<div class="modal-content"> <div class="modal" tabindex="-1" role="dialog">
<div class="modal-header"> <div class="modal-dialog">
<h5 class="modal-title">@Header</h5> <div class="modal-content">
<button type="button" class="btn-close" aria-label="Close" @onclick="DisplayModal"></button> <div class="modal-header">
</div> <h5 class="modal-title">@Header</h5>
<div class="modal-body"> <button type="button" class="btn-close" aria-label="Close" @onclick="DisplayModal"></button>
<p>@Message</p> </div>
</div> <div class="modal-body">
<div class="modal-footer"> <p>@Message</p>
@if (!string.IsNullOrEmpty(Action)) </div>
{ <div class="modal-footer">
<button type="button" class="@Class" @onclick="Confirm">@((MarkupString)_iconSpan) @Text</button> @if (!string.IsNullOrEmpty(Action))
} {
<button type="button" class="btn btn-secondary" @onclick="DisplayModal">@SharedLocalizer["Cancel"]</button> <button type="button" class="@Class" @onclick="Confirm">@((MarkupString)_iconSpan) @Text</button>
}
<button type="button" class="btn btn-secondary" @onclick="DisplayModal">@SharedLocalizer["Cancel"]</button>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
}
@if (_authorized)
{
if (Disabled)
{
<button type="button" class="@Class" disabled>@((MarkupString)_iconSpan) @Text</button>
} }
else @if (_authorized)
{ {
<button type="button" class="@Class" @onclick="DisplayModal">@((MarkupString)_iconSpan) @Text</button> if (Disabled)
{
<button type="button" class="@Class" disabled>@((MarkupString)_iconSpan) @Text</button>
}
else
{
<button type="button" class="@Class" @onclick="DisplayModal">@((MarkupString)_iconSpan) @Text</button>
}
}
}
else
{
@if (_visible)
{
<div class="app-actiondialog">
<div class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@Header</h5>
<form method="post" @formname="@($"ActionDialogCloseForm{Id}")" @onsubmit="DisplayModal" data-enhance>
<input type="hidden" name="__RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<button type="submit" class="btn-close" aria-label="Close"></button>
</form>
</div>
<div class="modal-body">
<p>@Message</p>
</div>
<div class="modal-footer">
@if (!string.IsNullOrEmpty(Action))
{
<form method="post" @formname="@($"ActionDialogConfirmForm{Id}")" @onsubmit="Confirm" data-enhance>
<input type="hidden" name="__RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<button type="submit" class="@Class">@((MarkupString)_iconSpan) @Text</button>
</form>
}
<form method="post" @formname="@($"ActionDialogCancelForm{Id}")" @onsubmit="DisplayModal" data-enhance>
<input type="hidden" name="__RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<button type="submit" class="btn btn-secondary">@SharedLocalizer["Cancel"]</button>
</form>
</div>
</div>
</div>
</div>
</div>
}
@if (_authorized)
{
if (Disabled)
{
<button type="button" class="@Class" disabled>@((MarkupString)_iconSpan) @Text</button>
}
else
{
<form method="post" @formname="@($"ActionDialogActionForm{Id}")" @onsubmit="DisplayModal" data-enhance>
<input type="hidden" name="__RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<button type="submit" class="@Class">@((MarkupString)_iconSpan) @Text</button>
</form>
}
} }
} }
@code { @code {
private bool _visible = false; private bool _visible = false;
private List<Permission> _permissions; private List<Permission> _permissions;
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;
@ -62,11 +117,11 @@
[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] [Parameter]
public string Permissions { get; set; } // deprecated - use PermissionList instead public string Permissions { get; set; } // deprecated - use PermissionList instead
[Parameter] [Parameter]
public List<Permission> PermissionList { get; set; } // optional - can be used to specify permissions public List<Permission> PermissionList { get; set; } // optional - can be used to specify permissions
[Parameter] [Parameter]
public string Class { get; set; } // optional public string Class { get; set; } // optional
@ -83,15 +138,18 @@
[Parameter] [Parameter]
public string IconName { get; set; } // optional - specifies an icon for the link - default is no icon public string IconName { get; set; } // optional - specifies an icon for the link - default is no icon
protected override void OnInitialized() [Parameter]
{ public string Id { get; set; } // optional - specifies a unique id for the compoment - required when there are multiple component instances on a page in static rendering
if (!string.IsNullOrEmpty(Permissions))
{
PermissionList = JsonSerializer.Deserialize<List<Permission>>(Permissions);
}
}
protected override void OnParametersSet() protected override void OnInitialized()
{
if (!string.IsNullOrEmpty(Permissions))
{
PermissionList = JsonSerializer.Deserialize<List<Permission>>(Permissions);
}
}
protected override void OnParametersSet()
{ {
base.OnParametersSet(); base.OnParametersSet();
@ -122,8 +180,13 @@
Header = Localize(nameof(Header), Header); Header = Localize(nameof(Header), Header);
Message = Localize(nameof(Message), Message); Message = Localize(nameof(Message), Message);
_permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList; _permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList;
_authorized = IsAuthorized(); _authorized = IsAuthorized();
if (PageState.QueryString.ContainsKey("dialog"))
{
_visible = (PageState.QueryString["dialog"] == Id);
}
} }
private bool IsAuthorized() private bool IsAuthorized()
@ -175,12 +238,22 @@
private void DisplayModal() private void DisplayModal()
{ {
_visible = !_visible; _visible = !_visible;
StateHasChanged(); if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Interactive)
{
StateHasChanged();
}
else
{
var parameters = new Dictionary<string, string>(PageState.QueryString);
if (parameters.ContainsKey("dialog")) parameters.Remove("dialog");
if (_visible) parameters.Add("dialog", Id);
NavigationManager.NavigateTo(PageState.Route.AbsolutePath + Utilities.CreateQueryString(parameters));
}
} }
private void Confirm() private void Confirm()
{ {
DisplayModal();
OnClick(); OnClick();
DisplayModal();
} }
} }

View File

@ -8,7 +8,7 @@
{ {
if (Disabled) if (Disabled)
{ {
<button type="button" class="@_classname" style="@_style" disabled>@((MarkupString)_iconSpan) @_text</button> <NavLink class="@($"{_classname} disabled")" href="@_url" style="@_style">@((MarkupString)_iconSpan) @_text</NavLink>
} }
else else
{ {
@ -97,10 +97,13 @@
{ {
base.OnParametersSet(); base.OnParametersSet();
_text = Action;
if (!string.IsNullOrEmpty(Text)) if (!string.IsNullOrEmpty(Text))
{ {
_text = Text; _text = Localize(nameof(Text), _text);
}
else
{
_text = Localize(nameof(Action), Action);
} }
if (IconOnly && !string.IsNullOrEmpty(IconName)) if (IconOnly && !string.IsNullOrEmpty(IconName))
@ -150,7 +153,6 @@
} }
_permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList; _permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList;
_text = Localize(nameof(Text), _text);
_url = EditUrl(_path, _moduleId, Action, _parameters); _url = EditUrl(_path, _moduleId, Action, _parameters);
if (!string.IsNullOrEmpty(ReturnUrl)) if (!string.IsNullOrEmpty(ReturnUrl))

View File

@ -2,21 +2,27 @@
@inherits ModuleControlBase @inherits ModuleControlBase
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@if (!string.IsNullOrEmpty(_message)) @if (!string.IsNullOrEmpty(Message))
{ {
<div class="@_classname alert-dismissible fade show mb-3" role="alert"> <div class="@_classname alert-dismissible fade show mb-3" role="alert">
@((MarkupString)_message) @((MarkupString)Message)
@if (Type == MessageType.Error && PageState != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) @if (PageState != null)
{ {
@((MarkupString)"&nbsp;&nbsp;")<NavLink href="@NavigateUrl("admin/log")">View Details</NavLink> @if (Type == MessageType.Error && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
<NavLink class="ms-2" href="@NavigateUrl("admin/log")">View Details</NavLink>
}
<form method="post" @onsubmit="DismissModal" @formname="@_formname" data-enhance>
<input type="hidden" name="__RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<button type="submit" class="btn-close" aria-label="Close"></button>
</form>
} }
<button type="button" class="btn-close" aria-label="Close" @onclick="DismissModal"></button>
</div> </div>
} }
@code { @code {
private string _message = string.Empty;
private string _classname = string.Empty; private string _classname = string.Empty;
private string _formname = "ModuleMessageForm";
[Parameter] [Parameter]
public string Message { get; set; } public string Message { get; set; }
@ -24,10 +30,32 @@
[Parameter] [Parameter]
public MessageType Type { get; set; } public MessageType Type { get; set; }
public void RefreshMessage(string message, MessageType type)
{
Message = message;
Type = type;
UpdateClassName();
StateHasChanged();
}
protected override void OnInitialized()
{
if (ModuleState != null)
{
_formname += ModuleState.PageModuleId.ToString();
}
}
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
_message = Message; UpdateClassName();
if (!string.IsNullOrEmpty(_message)) }
private void UpdateClassName()
{
if (!string.IsNullOrEmpty(Message))
{ {
_classname = GetMessageType(Type); _classname = GetMessageType(Type);
} }
@ -57,7 +85,6 @@
private void DismissModal() private void DismissModal()
{ {
_message = ""; Message = "";
StateHasChanged();
} }
} }

View File

@ -6,63 +6,132 @@
@if (ItemList != null) @if (ItemList != null)
{ {
@if (!string.IsNullOrEmpty(SearchProperties)) @if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Interactive)
{ {
<div class="input-group my-3"> @if (!string.IsNullOrEmpty(SearchProperties))
<input id="search" class="form-control" placeholder=@string.Format(Localizer["SearchPlaceholder"], FormatSearchProperties()) @bind="@_search" /> {
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button> <form autocomplete="off">
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button> <div class="input-group my-3">
</div> <input type="text" id="pagersearch" class="form-control" placeholder=@string.Format(Localizer["SearchPlaceholder"], FormatSearchProperties()) @bind="@_search" />
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
</div>
</form>
}
@if ((Toolbar == "Top" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
{
<ul class="pagination justify-content-center my-2">
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
</li>
}
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
</li>
@for (int i = _startPage; i <= _endPage; i++)
{
var pager = i;
if (pager == _page)
{
<li class="page-item app-pager-pointer active">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li>
}
else
{
<li class="page-item app-pager-pointer">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li>
}
}
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
</li>
}
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
</li>
<li class="page-item disabled">
<a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
</li>
</ul>
}
}
else
{
@if (!string.IsNullOrEmpty(SearchProperties))
{
<form method="post" autocomplete="off" @formname="PagerForm" @onsubmit="Search" data-enhance>
<input type="hidden" name="__RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<div class="input-group my-3">
<input type="text" id="pagersearch" name="_search" class="form-control" placeholder=@string.Format(Localizer["SearchPlaceholder"], FormatSearchProperties()) @bind="@_search" />
<button type="submit" class="btn btn-primary">@SharedLocalizer["Search"]</button>
<a class="btn btn-secondary" href="@PageUrl(1, "")">@SharedLocalizer["Reset"]</a>
</div>
</form>
}
@if ((Toolbar == "Top" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
{
<ul class="pagination justify-content-center my-2">
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(1, _search)"><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(_startPage - 1, _search)"><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
</li>
}
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(((_page > 1) ? _page - 1 : _page), _search)"><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
</li>
@for (int i = _startPage; i <= _endPage; i++)
{
var pager = i;
if (pager == _page)
{
<li class="page-item app-pager-pointer active">
<a class="page-link" href="@PageUrl(pager, _search)">@pager</a>
</li>
}
else
{
<li class="page-item app-pager-pointer">
<a class="page-link" href="@PageUrl(pager, _search)">@pager</a>
</li>
}
}
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(((_page < _pages) ? _page + 1 : _page), _search)"><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(_endPage + 1, _search)"><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
</li>
}
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(_pages, _search)"><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
</li>
<li class="page-item disabled">
<a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
</li>
</ul>
}
} }
@if ((Toolbar == "Top" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
{
<ul class="pagination justify-content-center my-2">
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
</li>
}
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
</li>
@for (int i = _startPage; i <= _endPage; i++)
{
var pager = i;
if (pager == _page)
{
<li class="page-item app-pager-pointer active">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li>
}
else
{
<li class="page-item app-pager-pointer">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li>
}
}
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
</li>
}
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
</li>
<li class="page-item disabled">
<a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
</li>
</ul>
}
@if (Format == "Table" && Row != null) @if (Format == "Table" && Row != null)
{ {
<div class="table-responsive"> <div class="table-responsive">
@ -126,53 +195,106 @@
} }
</div> </div>
} }
@if ((Toolbar == "Bottom" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems) @if ((Toolbar == "Bottom" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
{ {
<ul class="pagination justify-content-center my-2"> @if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Interactive)
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")"> {
<a class="page-link" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a> <ul class="pagination justify-content-center my-2">
</li> <li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
@if (_pages > _displayPages && _displayPages > 1) <a class="page-link" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
{
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
</li> </li>
} @if (_pages > _displayPages && _displayPages > 1)
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
</li>
@for (int i = _startPage; i <= _endPage; i++)
{
var pager = i;
if (pager == _page)
{ {
<li class="page-item app-pager-pointer active"> <li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a> <a class="page-link" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
</li> </li>
} }
else <li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
</li>
@for (int i = _startPage; i <= _endPage; i++)
{ {
<li class="page-item app-pager-pointer"> var pager = i;
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a> if (pager == _page)
{
<li class="page-item app-pager-pointer active">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li>
}
else
{
<li class="page-item app-pager-pointer">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li>
}
}
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
</li> </li>
} }
} <li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")"> <a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
<a class="page-link" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
</li> </li>
} <li class="page-item disabled">
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")"> <a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
<a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a> </li>
</li> </ul>
<li class="page-item disabled"> }
<a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a> else
</li> {
</ul> <ul class="pagination justify-content-center my-2">
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(1, _search)"><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(_startPage - 1, _search)"><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
</li>
}
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(((_page > 1) ? _page - 1 : _page), _search)"><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
</li>
@for (int i = _startPage; i <= _endPage; i++)
{
var pager = i;
if (pager == _page)
{
<li class="page-item app-pager-pointer active">
<a class="page-link" href="@PageUrl(pager, _search)">@pager</a>
</li>
}
else
{
<li class="page-item app-pager-pointer">
<a class="page-link" href="@PageUrl(pager, _search)">@pager</a>
</li>
}
}
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(((_page < _pages) ? _page + 1 : _page), _search)"><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages && _displayPages > 1)
{
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(_endPage + 1, _search)"><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
</li>
}
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" href="@PageUrl(_pages, _search)"><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
</li>
<li class="page-item disabled">
<a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
</li>
</ul>
}
} }
} }
@ -237,6 +359,12 @@
[Parameter] [Parameter]
public string SearchProperties { get; set; } // comma delimited list of property names to include in search public string SearchProperties { get; set; } // comma delimited list of property names to include in search
[Parameter]
public string Parameters { get; set; } // optional - querystring parameters in the form of "id=x&name=y" used in static render mode
[SupplyParameterFromForm(FormName = "PagerForm")]
public string _Search { get => ""; set => _search = value; }
private IEnumerable<TableItem> ItemList { get; set; } private IEnumerable<TableItem> ItemList { get; set; }
protected override void OnInitialized() protected override void OnInitialized()
@ -292,6 +420,11 @@
} }
} }
if (PageState.QueryString.ContainsKey("search"))
{
_search = PageState.QueryString["search"];
}
if (!string.IsNullOrEmpty(SearchProperties)) if (!string.IsNullOrEmpty(SearchProperties))
{ {
AllItems = Items; // only used in search AllItems = Items; // only used in search
@ -316,13 +449,20 @@
_displayPages = int.Parse(DisplayPages); _displayPages = int.Parse(DisplayPages);
} }
if (!string.IsNullOrEmpty(CurrentPage)) if (PageState.QueryString.ContainsKey("page"))
{ {
_page = int.Parse(CurrentPage); _page = int.Parse(PageState.QueryString["page"]);
} }
else else
{ {
_page = 1; if (!string.IsNullOrEmpty(CurrentPage))
{
_page = int.Parse(CurrentPage);
}
else
{
_page = 1;
}
} }
if (_page < 1) _page = 1; if (_page < 1) _page = 1;
@ -465,4 +605,25 @@
} }
return string.Join(",", properties); return string.Join(",", properties);
} }
private string PageUrl(int page, string search)
{
var parameters = new Dictionary<string, string>(PageState.QueryString);
if (parameters.ContainsKey("page")) parameters.Remove("page");
parameters.Add("page", page.ToString());
if (parameters.ContainsKey("search")) parameters.Remove("search");
if (!string.IsNullOrEmpty(search))
{
parameters.Add("search", search);
}
if (!string.IsNullOrEmpty(Parameters))
{
foreach (var parameter in Utilities.ParseQueryString(Parameters))
{
if (parameters.ContainsKey(parameter.Key)) parameters.Remove(parameter.Key);
parameters.Add(parameter.Key, parameter.Value);
}
}
return PageState.Route.AbsolutePath + Utilities.CreateQueryString(parameters);
}
} }

View File

@ -6,7 +6,7 @@
<div class="row" style="margin-bottom: 50px;"> <div class="row" style="margin-bottom: 50px;">
<div class="col"> <div class="col">
<TabStrip> <TabStrip ActiveTab="@_activetab">
@if (AllowRichText) @if (AllowRichText)
{ {
<TabPanel Name="Rich" Heading="Rich Text Editor" ResourceKey="RichTextEditor"> <TabPanel Name="Rich" Heading="Rich Text Editor" ResourceKey="RichTextEditor">
@ -17,12 +17,6 @@
<br /> <br />
} }
<div class="d-flex justify-content-center mb-2"> <div class="d-flex justify-content-center mb-2">
@if (AllowRawHtml)
{
<button type="button" class="btn btn-secondary" @onclick="RefreshRichText">@Localizer["SynchronizeContent"]</button>
@((MarkupString)"&nbsp;&nbsp;")
}
@if (AllowFileManagement) @if (AllowFileManagement)
{ {
<button type="button" class="btn btn-primary" @onclick="InsertRichImage">@Localizer["InsertImage"]</button> <button type="button" class="btn btn-primary" @onclick="InsertRichImage">@Localizer["InsertImage"]</button>
@ -85,7 +79,6 @@
<br /> <br />
} }
<div class="d-flex justify-content-center mb-2"> <div class="d-flex justify-content-center mb-2">
<button type="button" class="btn btn-secondary" @onclick="RefreshRawHtml">@Localizer["SynchronizeContent"]</button>&nbsp;&nbsp;
@if (AllowFileManagement) @if (AllowFileManagement)
{ {
<button type="button" class="btn btn-primary" @onclick="InsertRawImage">@Localizer["InsertImage"]</button> <button type="button" class="btn btn-primary" @onclick="InsertRawImage">@Localizer["InsertImage"]</button>
@ -121,6 +114,7 @@
private string _rawhtml = string.Empty; private string _rawhtml = string.Empty;
private string _originalrawhtml = string.Empty; private string _originalrawhtml = string.Empty;
private string _message = string.Empty; private string _message = string.Empty;
private string _activetab = "Rich";
[Parameter] [Parameter]
public string Content { get; set; } public string Content { get; set; }
@ -163,6 +157,12 @@
_rawhtml = Content; _rawhtml = Content;
_originalrawhtml = _rawhtml; // preserve for comparison later _originalrawhtml = _rawhtml; // preserve for comparison later
_originalrichhtml = ""; _originalrichhtml = "";
// Quill wraps content in <p> tags which can be used as a signal to set the active tab
if (!string.IsNullOrEmpty(Content) && !Content.StartsWith("<p>") && AllowRawHtml)
{
_activetab = "Raw";
}
} }
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
@ -208,19 +208,6 @@
StateHasChanged(); StateHasChanged();
} }
public void RefreshRichText()
{
_richhtml = _rawhtml;
StateHasChanged();
}
public async Task RefreshRawHtml()
{
var interop = new RichTextEditorInterop(JSRuntime);
_rawhtml = await interop.GetHtml(_editorElement);
StateHasChanged();
}
public async Task<string> GetHtml() public async Task<string> GetHtml()
{ {
// evaluate raw html content as first priority // evaluate raw html content as first priority

View File

@ -4,19 +4,27 @@
@inject IHtmlTextService HtmlTextService @inject IHtmlTextService HtmlTextService
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@((MarkupString)content)
@if (PageState.EditMode) @if (PageState.EditMode)
{ {
<br /> <div class="text-center mb-2">
<ActionLink Action="Edit" EditMode="true" ResourceKey="Edit" /> <ActionLink Action="Edit" EditMode="true" ResourceKey="Edit" />
<br /> </div>
<br /> }
@((MarkupString)content)
@if (PageState.EditMode && content.Length > 3000)
{
<div class="text-center mt-2">
<ActionLink Action="Edit" EditMode="true" ResourceKey="Edit" />
</div>
} }
@code { @code {
private string content = ""; private string content = "";
public override string RenderMode => RenderModes.Static;
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
try try

View File

@ -1,5 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Oqtane.Documentation; using Oqtane.Documentation;
@ -9,7 +8,7 @@ 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")] [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, IClientService
{ {
public HtmlTextService(HttpClient http, SiteState siteState) : base(http, siteState) {} public HtmlTextService(HttpClient http, SiteState siteState) : base(http, siteState) {}
@ -30,9 +29,9 @@ namespace Oqtane.Modules.HtmlText.Services
return await GetJsonAsync<Models.HtmlText>(CreateAuthorizationPolicyUrl($"{ApiUrl}/{htmlTextId}/{moduleId}", EntityNames.Module, moduleId)); return await GetJsonAsync<Models.HtmlText>(CreateAuthorizationPolicyUrl($"{ApiUrl}/{htmlTextId}/{moduleId}", EntityNames.Module, moduleId));
} }
public async Task AddHtmlTextAsync(Models.HtmlText htmlText) public async Task<Models.HtmlText> AddHtmlTextAsync(Models.HtmlText htmlText)
{ {
await PostJsonAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}", EntityNames.Module, htmlText.ModuleId), htmlText); return await PostJsonAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}", EntityNames.Module, htmlText.ModuleId), htmlText);
} }
public async Task DeleteHtmlTextAsync(int htmlTextId, int moduleId) public async Task DeleteHtmlTextAsync(int htmlTextId, int moduleId)

View File

@ -13,7 +13,7 @@ namespace Oqtane.Modules.HtmlText.Services
Task<Models.HtmlText> GetHtmlTextAsync(int htmlTextId, int moduleId); Task<Models.HtmlText> GetHtmlTextAsync(int htmlTextId, int moduleId);
Task AddHtmlTextAsync(Models.HtmlText htmltext); Task<Models.HtmlText> AddHtmlTextAsync(Models.HtmlText htmltext);
Task DeleteHtmlTextAsync(int htmlTextId, int moduleId); Task DeleteHtmlTextAsync(int htmlTextId, int moduleId);
} }

View File

@ -40,7 +40,7 @@
} }
catch (Exception ex) catch (Exception ex)
{ {
ModuleInstance.AddModuleMessage(ex.Message, MessageType.Error); AddModuleMessage(ex.Message, MessageType.Error);
} }
} }
@ -55,7 +55,7 @@
} }
catch (Exception ex) catch (Exception ex)
{ {
ModuleInstance.AddModuleMessage(ex.Message, MessageType.Error); AddModuleMessage(ex.Message, MessageType.Error);
} }
} }
} }

View File

@ -37,7 +37,7 @@ namespace Oqtane.Modules
protected Module ModuleState { get; set; } protected Module ModuleState { get; set; }
[Parameter] [Parameter]
public ModuleInstance ModuleInstance { get; set; } public RenderModeBoundary RenderModeBoundary { get; set; }
// optional interface properties // optional interface properties
public virtual SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.View; } set { } } // default security public virtual SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.View; } set { } } // default security
@ -50,6 +50,8 @@ namespace Oqtane.Modules
public virtual List<Resource> Resources { get; set; } public virtual List<Resource> Resources { get; set; }
public virtual string RenderMode { get { return RenderModes.Interactive; } } // interactive by default
// url parameters // url parameters
public virtual string UrlParametersTemplate { get; set; } public virtual string UrlParametersTemplate { get; set; }
@ -77,7 +79,7 @@ namespace Oqtane.Modules
{ {
if (PageState.Page.Resources != null) if (PageState.Page.Resources != null)
{ {
resources = PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level != ResourceLevel.Site && item.Namespace == type.Namespace).ToList(); resources = PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level == ResourceLevel.Module && item.Namespace == type.Namespace).ToList();
} }
} }
else // modulecontrolbase else // modulecontrolbase
@ -87,22 +89,25 @@ namespace Oqtane.Modules
resources = Resources.Where(item => item.ResourceType == ResourceType.Script).ToList(); resources = Resources.Where(item => item.ResourceType == ResourceType.Script).ToList();
} }
} }
if (resources != null &&resources.Any()) if (resources != null && resources.Any())
{ {
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
var scripts = new List<object>(); var scripts = new List<object>();
var inline = 0; var inline = 0;
foreach (Resource resource in resources) foreach (Resource resource in resources)
{ {
if (!string.IsNullOrEmpty(resource.Url)) if (string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Interactive)
{ {
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url; if (!string.IsNullOrEmpty(resource.Url))
scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module, location = resource.Location.ToString().ToLower() }); {
} var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url;
else scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module, location = resource.Location.ToString().ToLower() });
{ }
inline += 1; else
await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Content, resource.Location.ToString().ToLower()); {
inline += 1;
await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Content, resource.Location.ToString().ToLower());
}
} }
} }
if (scripts.Any()) if (scripts.Any())
@ -272,22 +277,22 @@ namespace Oqtane.Modules
public void AddModuleMessage(string message, MessageType type, string position) public void AddModuleMessage(string message, MessageType type, string position)
{ {
ClearModuleMessage(); ClearModuleMessage();
ModuleInstance.AddModuleMessage(message, type, position); RenderModeBoundary.AddModuleMessage(message, type, position);
} }
public void ClearModuleMessage() public void ClearModuleMessage()
{ {
ModuleInstance.AddModuleMessage("", MessageType.Undefined); RenderModeBoundary.AddModuleMessage("", MessageType.Undefined);
} }
public void ShowProgressIndicator() public void ShowProgressIndicator()
{ {
ModuleInstance.ShowProgressIndicator(); RenderModeBoundary.ShowProgressIndicator();
} }
public void HideProgressIndicator() public void HideProgressIndicator()
{ {
ModuleInstance.HideProgressIndicator(); RenderModeBoundary.HideProgressIndicator();
} }
public void SetModuleTitle(string title) public void SetModuleTitle(string title)
@ -487,5 +492,8 @@ namespace Oqtane.Modules
{ {
return Utilities.FileUrl(PageState.Alias, fileid, asAttachment); return Utilities.FileUrl(PageState.Alias, fileid, asAttachment);
} }
// Referencing ModuleInstance methods from ModuleBase is deprecated. Use the ModuleBase methods instead
public ModuleInstance ModuleInstance { get { return new ModuleInstance(); } }
} }
} }

View File

@ -4,7 +4,7 @@
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Version>5.0.2</Version> <Version>5.1.0</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -12,21 +12,20 @@
<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/v5.0.2</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.0</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>
<IsPackable>true</IsPackable> <IsPackable>true</IsPackable>
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData> <BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.1" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.1" PrivateAssets="all" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.1" /> <PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.3" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="System.Net.Http.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />
</ItemGroup> </ItemGroup>

View File

@ -41,10 +41,10 @@ namespace Oqtane.Client
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources"); builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
// register auth services // register auth services
builder.Services.AddOqtaneAuthorization(); builder.Services.AddOqtaneAuthentication();
// register scoped core services // register scoped core services
builder.Services.AddOqtaneScopedServices(); builder.Services.AddOqtaneClientScopedServices();
var serviceProvider = builder.Services.BuildServiceProvider(); var serviceProvider = builder.Services.BuildServiceProvider();
@ -220,6 +220,16 @@ namespace Oqtane.Client
services.AddScoped(serviceType ?? implementationType, implementationType); services.AddScoped(serviceType ?? implementationType, implementationType);
} }
} }
implementationTypes = assembly.GetInterfaces<IClientService>();
foreach (var implementationType in implementationTypes)
{
if (implementationType.AssemblyQualifiedName != null)
{
var serviceType = Type.GetType(implementationType.AssemblyQualifiedName.Replace(implementationType.Name, $"I{implementationType.Name}"));
services.AddScoped(serviceType ?? implementationType, implementationType);
}
}
} }
catch catch
{ {

View File

@ -1,10 +1,8 @@
using System; using System;
using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Json; using System.Net.Http.Json;
using System.Security.Claims; using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Oqtane.Models; using Oqtane.Models;
@ -16,12 +14,10 @@ namespace Oqtane.Providers
public class IdentityAuthenticationStateProvider : AuthenticationStateProvider public class IdentityAuthenticationStateProvider : AuthenticationStateProvider
{ {
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly NavigationManager _navigationManager;
public IdentityAuthenticationStateProvider(IServiceProvider serviceProvider, NavigationManager navigationManager) public IdentityAuthenticationStateProvider(IServiceProvider serviceProvider)
{ {
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
_navigationManager = navigationManager;
} }
public override async Task<AuthenticationState> GetAuthenticationStateAsync() public override async Task<AuthenticationState> GetAuthenticationStateAsync()

View File

@ -144,18 +144,9 @@
<data name="Month" xml:space="preserve"> <data name="Month" xml:space="preserve">
<value>Month(s)</value> <value>Month(s)</value>
</data> </data>
<data name="Error.Job.Delete" xml:space="preserve">
<value>Error Deleting Job</value>
</data>
<data name="ViewJobs.Text" xml:space="preserve"> <data name="ViewJobs.Text" xml:space="preserve">
<value>View Logs</value> <value>View Logs</value>
</data> </data>
<data name="DeleteJob.Header" xml:space="preserve">
<value>Delete Job</value>
</data>
<data name="DeleteJob.Message" xml:space="preserve">
<value>Are You Sure You Wish To Delete This Job?</value>
</data>
<data name="Frequency" xml:space="preserve"> <data name="Frequency" xml:space="preserve">
<value>Frequency</value> <value>Frequency</value>
</data> </data>
@ -165,9 +156,6 @@
<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"> <data name="EditJob.Text" xml:space="preserve">
<value>Edit</value> <value>Edit</value>
</data> </data>

View File

@ -228,4 +228,7 @@
<data name="ExternalLoginStatus.ReviewClaims" xml:space="preserve"> <data name="ExternalLoginStatus.ReviewClaims" xml:space="preserve">
<value>The Review Claims Option Was Enabled In External Login Settings. Please Visit The Event Log To View The Claims Returned By The Provider.</value> <value>The Review Claims Option Was Enabled In External Login Settings. Please Visit The Event Log To View The Claims Returned By The Provider.</value>
</data> </data>
<data name="Register" xml:space="preserve">
<value>Register as new user?</value>
</data>
</root> </root>

View File

@ -177,4 +177,7 @@
<data name="Username.Text" xml:space="preserve"> <data name="Username.Text" xml:space="preserve">
<value>Username:</value> <value>Username:</value>
</data> </data>
<data name="Login" xml:space="preserve">
<value>Already have account? Login now.</value>
</data>
</root> </root>

View File

@ -274,19 +274,19 @@
<value>Select Theme</value> <value>Select Theme</value>
</data> </data>
<data name="Hosting.Heading" xml:space="preserve"> <data name="Hosting.Heading" xml:space="preserve">
<value>Hosting Model</value> <value>UI Component Settings</value>
</data> </data>
<data name="Prerender.HelpText" xml:space="preserve"> <data name="Prerender.HelpText" xml:space="preserve">
<value>Specifies if the site should be prerendered (for search crawlers, etc...)</value> <value>Specifies if interactive components should prerender their output</value>
</data> </data>
<data name="Prerender.Text" xml:space="preserve"> <data name="Prerender.Text" xml:space="preserve">
<value>Prerender? </value> <value>Prerender? </value>
</data> </data>
<data name="Runtime.HelpText" xml:space="preserve"> <data name="RenderMode.HelpText" xml:space="preserve">
<value>The Blazor runtime hosting model for the site</value> <value>The default render mode for the site</value>
</data> </data>
<data name="Runtime.Text" xml:space="preserve"> <data name="RenderMode.Text" xml:space="preserve">
<value>Runtime: </value> <value>Render Mode:</value>
</data> </data>
<data name="Browse" xml:space="preserve"> <data name="Browse" xml:space="preserve">
<value>Browse</value> <value>Browse</value>
@ -325,7 +325,7 @@
<value>Default Alias: </value> <value>Default Alias: </value>
</data> </data>
<data name="Aliases.Heading" xml:space="preserve"> <data name="Aliases.Heading" xml:space="preserve">
<value>Urls</value> <value>Site Urls</value>
</data> </data>
<data name="AliasName" xml:space="preserve"> <data name="AliasName" xml:space="preserve">
<value>Url</value> <value>Url</value>
@ -423,4 +423,10 @@
<data name="HybridEnabled.Text" xml:space="preserve"> <data name="HybridEnabled.Text" xml:space="preserve">
<value>Hybrid Enabled?</value> <value>Hybrid Enabled?</value>
</data> </data>
<data name="Runtime.HelpText" xml:space="preserve">
<value>The render mode for UI components which require interactivity</value>
</data>
<data name="Runtime.Text" xml:space="preserve">
<value>Interactivity:</value>
</data>
</root> </root>

View File

@ -222,17 +222,11 @@
<data name="Error.Database.LoadConfig" xml:space="preserve"> <data name="Error.Database.LoadConfig" xml:space="preserve">
<value>Error loading Database Configuration Control</value> <value>Error loading Database Configuration Control</value>
</data> </data>
<data name="Prerender.HelpText" xml:space="preserve"> <data name="RenderMode.HelpText" xml:space="preserve">
<value>Specifies if the site should be prerendered (for search crawlers, etc...)</value> <value>The default render mode for the site</value>
</data> </data>
<data name="Prerender.Text" xml:space="preserve"> <data name="RenderMode.Text" xml:space="preserve">
<value>Prerender? </value> <value>Render Mode: </value>
</data>
<data name="Runtime.HelpText" xml:space="preserve">
<value>The Blazor runtime hosting model</value>
</data>
<data name="Runtime.Text" xml:space="preserve">
<value>Runtime: </value>
</data> </data>
<data name="ConnectionString.HelpText" xml:space="preserve"> <data name="ConnectionString.HelpText" xml:space="preserve">
<value>Enter a complete connection string including all parameters and delimiters</value> <value>Enter a complete connection string including all parameters and delimiters</value>
@ -246,4 +240,10 @@
<data name="EnterConnectionString" xml:space="preserve"> <data name="EnterConnectionString" xml:space="preserve">
<value>Enter Connection String</value> <value>Enter Connection String</value>
</data> </data>
<data name="Runtime.HelpText" xml:space="preserve">
<value>The render mode for UI components which require interactivity</value>
</data>
<data name="Runtime.Text" xml:space="preserve">
<value>Interactivity:</value>
</data>
</root> </root>

View File

@ -117,9 +117,6 @@
<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="SynchronizeContent" xml:space="preserve">
<value>Synchronize Content</value>
</data>
<data name="InsertImage" xml:space="preserve"> <data name="InsertImage" xml:space="preserve">
<value>Insert Image</value> <value>Insert Image</value>
</data> </data>

View File

@ -0,0 +1,126 @@
<?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="PageOfPages" xml:space="preserve">
<value>Page {0} of {1}</value>
</data>
<data name="SearchPlaceholder" xml:space="preserve">
<value>Search: {0}</value>
</data>
</root>

View File

@ -118,10 +118,10 @@
<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="Edit.Action" xml:space="preserve"> <data name="Edit.Action" xml:space="preserve">
<value>Edit</value> <value>Edit Content</value>
</data> </data>
<data name="Edit.Text" xml:space="preserve"> <data name="Edit.Text" xml:space="preserve">
<value>Edit</value> <value>Edit Content</value>
</data> </data>
<data name="Error.Content.Load" xml:space="preserve"> <data name="Error.Content.Load" xml:space="preserve">
<value>An Error Occurred Loading Content</value> <value>An Error Occurred Loading Content</value>

View File

@ -312,14 +312,14 @@
<data name="Not Specified" xml:space="preserve"> <data name="Not Specified" xml:space="preserve">
<value>Not Specified</value> <value>Not Specified</value>
</data> </data>
<data name="BlazorServer" xml:space="preserve"> <data name="RuntimeServer" xml:space="preserve">
<value>Blazor Server</value> <value>Server (SignalR)</value>
</data> </data>
<data name="BlazorWebAssembly" xml:space="preserve"> <data name="RuntimeWebAssembly" xml:space="preserve">
<value>Blazor WebAssembly</value> <value>Client (WebAssembly)</value>
</data> </data>
<data name="BlazorHybrid" xml:space="preserve"> <data name="StaticServer" xml:space="preserve">
<value>Blazor Hybrid</value> <value>Static Server Rendering</value>
</data> </data>
<data name="Settings" xml:space="preserve"> <data name="Settings" xml:space="preserve">
<value>Settings</value> <value>Settings</value>
@ -441,4 +441,16 @@
<data name="Message.EffectiveExpiryDateError" xml:space="preserve"> <data name="Message.EffectiveExpiryDateError" xml:space="preserve">
<value>Effective Date cannot be after Expiry Date.</value> <value>Effective Date cannot be after Expiry Date.</value>
</data> </data>
<data name="RuntimeAuto" xml:space="preserve">
<value>Auto</value>
</data>
<data name="RenderModeHeadless" xml:space="preserve">
<value>Headless (API Only)</value>
</data>
<data name="RenderModeInteractive" xml:space="preserve">
<value>Interactive</value>
</data>
<data name="RenderModeStatic" xml:space="preserve">
<value>Static</value>
</data>
</root> </root>

View File

@ -1,5 +1,4 @@
using Oqtane.Models; using Oqtane.Models;
using Oqtane.UI;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;

View File

@ -10,7 +10,7 @@ namespace Oqtane.Services
public interface IPageService public interface IPageService
{ {
/// <summary> /// <summary>
/// Retuns a list of pages /// Returns a list of pages
/// </summary> /// </summary>
/// <param name="siteId"></param> /// <param name="siteId"></param>
/// <returns></returns> /// <returns></returns>

View File

@ -3,11 +3,8 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using System;
using System.Reflection;
using Oqtane.Documentation; using Oqtane.Documentation;
using Oqtane.Shared; using Oqtane.Shared;
using Oqtane.UI;
namespace Oqtane.Services namespace Oqtane.Services
{ {

View File

@ -16,6 +16,7 @@ namespace Oqtane.Services
{ {
private readonly HttpClient _httpClient; private readonly HttpClient _httpClient;
private readonly SiteState _siteState; private readonly SiteState _siteState;
private readonly IHttpClientFactory _factory;
protected ServiceBase(HttpClient httpClient, SiteState siteState) protected ServiceBase(HttpClient httpClient, SiteState siteState)
{ {
@ -23,13 +24,31 @@ namespace Oqtane.Services
_siteState = siteState; _siteState = siteState;
} }
protected ServiceBase(IHttpClientFactory factory, SiteState siteState)
{
_factory = factory;
_siteState = siteState;
}
public HttpClient GetHttpClient() public HttpClient GetHttpClient()
{ {
if (!_httpClient.DefaultRequestHeaders.Contains(Constants.AntiForgeryTokenHeaderName) && _siteState != null && !string.IsNullOrEmpty(_siteState.AntiForgeryToken)) if (_factory != null)
{ {
_httpClient.DefaultRequestHeaders.Add(Constants.AntiForgeryTokenHeaderName, _siteState.AntiForgeryToken); var client = _factory.CreateClient("oqtane");
if (!client.DefaultRequestHeaders.Contains(Constants.AntiForgeryTokenHeaderName) && _siteState != null && !string.IsNullOrEmpty(_siteState.AntiForgeryToken))
{
client.DefaultRequestHeaders.Add(Constants.AntiForgeryTokenHeaderName, _siteState.AntiForgeryToken);
}
return client;
}
else
{
if (!_httpClient.DefaultRequestHeaders.Contains(Constants.AntiForgeryTokenHeaderName) && _siteState != null && !string.IsNullOrEmpty(_siteState.AntiForgeryToken))
{
_httpClient.DefaultRequestHeaders.Add(Constants.AntiForgeryTokenHeaderName, _siteState.AntiForgeryToken);
}
return _httpClient;
} }
return _httpClient;
} }
// should be used with new constructor // should be used with new constructor

View File

@ -18,8 +18,7 @@ namespace Oqtane.Services
public async Task<List<Site>> GetSitesAsync() public async Task<List<Site>> GetSitesAsync()
{ {
List<Site> sites = await GetJsonAsync<List<Site>>(Apiurl); return await GetJsonAsync<List<Site>>(Apiurl);
return sites.OrderBy(item => item.Name).ToList();
} }
public async Task<Site> GetSiteAsync(int siteId) public async Task<Site> GetSiteAsync(int siteId)

View File

@ -8,7 +8,10 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title"><ModuleTitle /></h5> <h5 class="modal-title"><ModuleTitle /></h5>
<button type="button" class="btn-close" aria-label="Close" @onclick="CloseModal"></button> <form method="post" class="app-form-inline" @formname="AdminContainerForm" @onsubmit="@CloseModal" data-enhance>
<input type="hidden" name="__RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<button type="submit" class="btn-close" aria-label="Close"></button>
</form>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<ModuleInstance /> <ModuleInstance />

View File

@ -31,9 +31,9 @@
public override List<Resource> Resources => new List<Resource>() public override List<Resource> Resources => new List<Resource>()
{ {
// obtained from https://cdnjs.com/libraries // obtained from https://cdnjs.com/libraries
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css", Integrity = "sha512-t4GWSVZO1eC8BM339Xd7Uphw5s17a86tIZIj8qRxhnKub6WoyhnrxeCIMeAqBPgdZGlCcG2PrZjMc+Wr78+5Xg==", CrossOrigin = "anonymous" }, new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/css/bootstrap.min.css", Integrity = "sha512-b2QcS5SsA8tZodcDtGRELiGv5SaKSk1vDHDaQRda0htPYWZ6046lr3kJ5bAAQdpV2mmA/4v0wQF9MyU6/pDIAg==", CrossOrigin = "anonymous" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "Theme.css" }, new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "Theme.css" },
new Resource { ResourceType = ResourceType.Script, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/js/bootstrap.bundle.min.js", Integrity = "sha512-VK2zcvntEufaimc+efOYi622VN5ZacdnufnmX7zIhCPmjhKnOi9ZDMtg1/ug5l183f19gG1/cBstPO4D8N/Img==", CrossOrigin = "anonymous" } new Resource { ResourceType = ResourceType.Script, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/js/bootstrap.bundle.min.js", Integrity = "sha512-X/YkDZyjTf4wyc2Vy16YGCPHwAY8rZJY+POgokZjQB2mhIRFJCckEGc6YyX9eNsPfn0PzThEuNs+uaomE5CO6A==", CrossOrigin = "anonymous", Location = ResourceLocation.Body }
}; };
} }

View File

@ -1,45 +1,15 @@
@namespace Oqtane.Themes.Controls @namespace Oqtane.Themes.Controls
@inherits ModuleActionsBase @inherits ContainerBase
@attribute [OqtaneIgnore] @attribute [OqtaneIgnore]
@if (PageState.EditMode && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList) && PageState.Action == Constants.DefaultAction) @if (PageState.EditMode && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList) && PageState.Action == Constants.DefaultAction)
{ {
<div class="app-moduleactions py-2 px-3"> @if (PageState.RenderMode == RenderModes.Interactive)
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"></a> {
<ul class="dropdown-menu" x-placement="bottom-start" style="position: absolute; will-change: transform; top: 0px; left: 0px; transform: translate3d(0px, 37px, 0px);" role="button"> <ModuleActionsInteractive PageState="@PageState" ModuleState="@ModuleState" />
@foreach (var action in Actions.Where(item => !item.Name.Contains("Pane"))) }
{ else
if (string.IsNullOrEmpty(action.Name)) {
{ <ModuleActionsInteractive PageState="@PageState" ModuleState="@ModuleState" @rendermode="@InteractiveRenderMode.GetInteractiveRenderMode(PageState.Site.Runtime, PageState.Site.Prerender)" />
<li class="dropdown-divider"></li> }
}
else
{
<li>
<a class="dropdown-item" @onclick="(async () => await ModuleAction(action))">
<span class="@action.Icon" aria-hidden="true"></span>&nbsp;@action.Name
</a>
</li>
}
}
@if (Actions.Where(item => item.Name.Contains("Pane")).Any())
{
<li class="dropdown-submenu">
<a class="dropdown-item" onclick="return subMenu(this)">
<span class="@Icons.AccountLogin" aria-hidden="true"></span>&nbsp;Move To &gt;
</a>
<ul class="dropdown-menu">
@foreach (var action in Actions.Where(item => item.Name.Contains("Pane")))
{
<li>
<a class="dropdown-item" @onclick="(async () => await ModuleAction(action))">
<span class="@action.Icon" aria-hidden="true"></span>&nbsp;@action.Name
</a>
</li>
}
</ul>
</li>
}
</ul>
</div>
} }

View File

@ -7,18 +7,23 @@ using Oqtane.Models;
using Oqtane.Security; using Oqtane.Security;
using Oqtane.Services; using Oqtane.Services;
using Oqtane.Shared; using Oqtane.Shared;
using Oqtane.UI;
using System.Net;
// ReSharper disable UnassignedGetOnlyAutoProperty // ReSharper disable UnassignedGetOnlyAutoProperty
// ReSharper disable MemberCanBePrivate.Global // ReSharper disable MemberCanBePrivate.Global
namespace Oqtane.Themes.Controls namespace Oqtane.Themes.Controls
{ {
public class ModuleActionsBase : ContainerBase public class ModuleActionsBase : ComponentBase
{ {
[Inject] public NavigationManager NavigationManager { get; set; } [Inject] public NavigationManager NavigationManager { get; set; }
[Inject] public IPageModuleService PageModuleService { get; set; } [Inject] public IPageModuleService PageModuleService { get; set; }
[Inject] public IModuleService ModuleService { get; set; } [Inject] public IModuleService ModuleService { get; set; }
[Parameter] public PageState PageState { get; set; }
[Parameter] public Module ModuleState { get; set; }
public List<ActionViewModel> Actions; public List<ActionViewModel> Actions;
protected override void OnParametersSet() protected override void OnParametersSet()
@ -88,7 +93,7 @@ namespace Oqtane.Themes.Controls
private async Task<string> EditUrlAsync(string url, int moduleId, string import) private async Task<string> EditUrlAsync(string url, int moduleId, string import)
{ {
await Task.Yield(); await Task.Yield();
return EditUrl(moduleId, import); return Utilities.EditUrl(PageState.Alias.Path, PageState.Page.Path, moduleId, import, "");
} }
protected async Task ModuleAction(ActionViewModel action) protected async Task ModuleAction(ActionViewModel action)
@ -97,7 +102,7 @@ namespace Oqtane.Themes.Controls
{ {
PageModule pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId); PageModule pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
string url = NavigateUrl(true); string url = Utilities.NavigateUrl(PageState.Alias.Path, PageState.Page.Path, "edit=true&refresh");
if (action.Action != null) if (action.Action != null)
{ {
@ -130,7 +135,8 @@ namespace Oqtane.Themes.Controls
private async Task<string> Settings(string url, PageModule pagemodule) private async Task<string> Settings(string url, PageModule pagemodule)
{ {
await Task.Yield(); await Task.Yield();
url = EditUrl(pagemodule.ModuleId, "Settings"); var returnurl = Utilities.NavigateUrl(PageState.Alias.Path, PageState.Page.Path, "edit=true");
url = Utilities.EditUrl(PageState.Alias.Path, PageState.Page.Path, pagemodule.ModuleId, "Settings", "returnurl=" + WebUtility.UrlEncode(returnurl));
return url; return url;
} }

View File

@ -0,0 +1,42 @@
@namespace Oqtane.Themes.Controls
@inherits ModuleActionsBase
@attribute [OqtaneIgnore]
<div class="app-moduleactions py-2 px-3">
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"></a>
<ul class="dropdown-menu" x-placement="bottom-start" style="position: absolute; will-change: transform; top: 0px; left: 0px; transform: translate3d(0px, 37px, 0px);" role="button">
@foreach (var action in Actions.Where(item => !item.Name.Contains("Pane")))
{
if (string.IsNullOrEmpty(action.Name))
{
<li class="dropdown-divider"></li>
}
else
{
<li>
<a class="dropdown-item" @onclick="(async () => await ModuleAction(action))">
<span class="@action.Icon" aria-hidden="true"></span>&nbsp;@action.Name
</a>
</li>
}
}
@if (Actions.Where(item => item.Name.Contains("Pane")).Any())
{
<li class="dropdown-submenu">
<a class="dropdown-item" onclick="return subMenu(this)">
<span class="@Icons.AccountLogin" aria-hidden="true"></span>&nbsp;Move To &gt;
</a>
<ul class="dropdown-menu">
@foreach (var action in Actions.Where(item => item.Name.Contains("Pane")))
{
<li>
<a class="dropdown-item" @onclick="(async () => await ModuleAction(action))">
<span class="@action.Icon" aria-hidden="true"></span>&nbsp;@action.Name
</a>
</li>
}
</ul>
</li>
}
</ul>
</div>

View File

@ -4,6 +4,7 @@
@attribute [OqtaneIgnore] @attribute [OqtaneIgnore]
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
@inject IStringLocalizerFactory LocalizerFactory @inject IStringLocalizerFactory LocalizerFactory
@implements IDisposable
<span class="app-moduletitle"> <span class="app-moduletitle">
@((MarkupString)title) @((MarkupString)title)

View File

@ -1,19 +1,8 @@
@using System.Net
@namespace Oqtane.Themes.Controls @namespace Oqtane.Themes.Controls
@inherits ThemeControlBase @inherits ThemeControlBase
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IUserService UserService
@inject IModuleDefinitionService ModuleDefinitionService
@inject IThemeService ThemeService
@inject IModuleService ModuleService
@inject IPageService PageService @inject IPageService PageService
@inject IPageModuleService PageModuleService
@inject ILogService logger
@inject ISettingService SettingService @inject ISettingService SettingService
@inject IJSRuntime jsRuntime
@inject IServiceProvider ServiceProvider
@inject IStringLocalizer<ControlPanel> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (ShowLanguageSwitcher) @if (ShowLanguageSwitcher)
{ {
@ -22,223 +11,36 @@
@if (_showEditMode || (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered))) @if (_showEditMode || (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered)))
{ {
if (PageState.EditMode) <form method="post" class="app-form-inline" @formname="EditModeForm" @onsubmit="@(async () => await ToggleEditMode(PageState.EditMode))" data-enhance>
{ <input type="hidden" name="__RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<button type="button" class="btn @ButtonClass active" data-bs-toggle="button" aria-pressed="true" autocomplete="off" @onclick="(async () => await ToggleEditMode(PageState.EditMode))"> @if (PageState.EditMode)
<span class="oi oi-pencil"></span> {
</button> <button type="submit" class="btn @ButtonClass active" aria-pressed="true" autocomplete="off">
} <span class="oi oi-pencil"></span>
else </button>
{ }
<button type="button" class="btn @ButtonClass" data-bs-toggle="button" aria-pressed="false" autocomplete="off" @onclick="(async () => await ToggleEditMode(PageState.EditMode))"> else
<span class="oi oi-pencil"></span> {
</button> <button type="submit" class="btn @ButtonClass" aria-pressed="false" autocomplete="off">
} <span class="oi oi-pencil"></span>
</button>
}
</form>
} }
@if (_canViewAdminDashboard || UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList)) @if (_canViewAdminDashboard || UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList))
{ {
<button type="button" class="btn @ButtonClass ms-1" data-bs-toggle="offcanvas" data-bs-target="#offcanvasControlPanel" aria-controls="offcanvasControlPanel" @onclick="ClearMessage"> @if (PageState.RenderMode == RenderModes.Interactive)
<span class="oi oi-cog"></span> {
</button> <ControlPanelInteractive PageState="@PageState" SiteState="@SiteState" ButtonClass="@ButtonClass" ContainerClass="@ContainerClass" HeaderClass="@HeaderClass" BodyClass="@BodyClass" ShowLanguageSwitcher="@ShowLanguageSwitcher" LanguageDropdownAlignment="@LanguageDropdownAlignment" />
}
<div class="@ContainerClass" tabindex="-1" data-bs-scroll="true" data-bs-backdrop="true" id="offcanvasControlPanel" aria-labelledby="offcanvasScrollingLabel"> else
<div class="@HeaderClass"> {
<h5 id="offcanvasScrollingLabel" class="offcanvas-title">@Localizer["ControlPanel"]</h5> <ControlPanelInteractive PageState="@PageState" SiteState="@SiteState" ButtonClass="@ButtonClass" ContainerClass="@ContainerClass" HeaderClass="@HeaderClass" BodyClass="@BodyClass" ShowLanguageSwitcher="@ShowLanguageSwitcher" LanguageDropdownAlignment="@LanguageDropdownAlignment" @rendermode="@InteractiveRenderMode.GetInteractiveRenderMode(PageState.Site.Runtime, PageState.Site.Prerender)" />
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="Close" @onclick="ClearMessage"></button> }
</div>
<div class="@BodyClass">
<div class="container-fluid">
@if (_canViewAdminDashboard)
{
<div class="row d-flex">
<div class="col">
<button type="button" data-bs-dismiss="offcanvas" class="btn btn-primary col-12" @onclick=@(async () => Navigate("Admin"))>@Localizer["AdminDash"]</button>
</div>
</div>
<hr class="app-rule" />
}
@if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList))
{
<div class="row">
<div class="col text-center">
<label class="control-label">@Localizer["Page.Manage"] </label>
</div>
</div>
<div class="row d-flex mb-2">
<div class="col d-flex justify-content-between">
@if (PageState.Page.UserId == null)
{
<button type="button" class="btn btn-secondary col me-1" data-bs-dismiss="offcanvas" @onclick=@(async () => Navigate("Add"))>@SharedLocalizer["Add"]</button>
}
<button type="button" class="btn btn-secondary col" data-bs-dismiss="offcanvas" @onclick=@(async () => Navigate("Edit"))>@SharedLocalizer["Edit"]</button>
<button type="button" class="btn btn-danger col ms-1" @onclick="ConfirmDelete">@SharedLocalizer["Delete"]</button>
</div>
</div>
<div class="row d-flex">
<div class="col">
@if (UserSecurity.ContainsRole(PageState.Page.PermissionList, PermissionNames.View, RoleNames.Everyone))
{
<button type="button" class="btn btn-secondary col-12" @onclick=@(async () => Publish("unpublish"))>@Localizer["Page.Unpublish"]</button>
}
else
{
<button type="button" class="btn btn-secondary col-12" @onclick=@(async () => Publish("publish"))>@Localizer["Page.Publish"]</button>
}
</div>
</div>
<hr class="app-rule" />
@if (_deleteConfirmation)
{
<div class="app-admin-modal">
<div class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@Localizer["Page.Delete"]</h5>
<button type="button" class="btn-close" aria-label="Close" @onclick="ConfirmDelete"></button>
</div>
<div class="modal-body">
<p>@Localizer["Confirm.Page.Delete"]</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" @onclick="DeletePage">@SharedLocalizer["Delete"]</button>
<button type="button" class="btn btn-secondary" @onclick="ConfirmDelete">@SharedLocalizer["Cancel"]</button>
</div>
</div>
</div>
</div>
</div>
}
<div class="row">
<div class="col text-center">
<label for="Module" class="control-label">@Localizer["Module.Manage"]</label>
<select class="form-select" @bind="@ModuleType">
<option value="new">@Localizer["Module.AddNew"]</option>
<option value="existing">@Localizer["Module.AddExisting"]</option>
</select>
@if (ModuleType == "new")
{
@if (_moduleDefinitions != null)
{
<select class="form-select mt-1" @onchange="(e => CategoryChanged(e))">
@foreach (var category in _categories)
{
if (category == Category)
{
<option value="@category" selected>@category @Localizer["Modules"]</option>
}
else
{
<option value="@category">@category @Localizer["Modules"]</option>
}
}
</select>
<select class="form-select mt-1" @onchange="(e => ModuleChanged(e))">
@if (ModuleDefinitionName == "-")
{
<option value="-" selected>&lt;@Localizer["Module.Select"]&gt;</option>
}
else
{
<option value="-">&lt;@Localizer["Module.Select"]&gt;</option>
}
@foreach (var moduledefinition in _moduleDefinitions)
{
if (moduledefinition.IsEnabled && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Utilize, moduledefinition.PermissionList))
{
if (moduledefinition.Runtimes == "" || moduledefinition.Runtimes.Contains(PageState.Runtime.ToString()))
{
<option value="@moduledefinition.ModuleDefinitionName">@moduledefinition.Name</option>
}
}
}
</select>
}
}
else
{
<select class="form-select mt-1" @onchange="(e => PageChanged(e))">
<option value="-">&lt;@Localizer["Page.Select"]&gt;</option>
@foreach (Page p in _pages)
{
<option value="@p.PageId">@p.Name</option>
}
</select>
<select class="form-select mt-1" @bind="@ModuleId">
<option value="-">&lt;@Localizer["Module.Select"]&gt;</option>
@foreach (Module module in _modules)
{
<option value="@module.ModuleId">@module.Title</option>
}
</select>
}
</div>
</div>
<div class="row">
<div class="col text-center">
<label for="Title" class="control-label">@Localizer["Title"]</label>
<input type="text" name="Title" class="form-control" @bind="@Title" />
</div>
</div>
<div class="row">
<div class="col text-center">
<label for="Pane" class="control-label">@Localizer["Pane"]</label>
<select class="form-select" @bind="@Pane">
@foreach (string pane in PageState.Page.Panes)
{
<option value="@pane">@pane Pane</option>
}
</select>
</div>
</div>
<div class="row">
<div class="col text-center">
<label for="Insert" class="control-label">@Localizer["Location"]</label>
<select class="form-select" @bind="@Location">
<option value="@int.MinValue">@Localizer["LocationTop"]</option>
<option value="@int.MaxValue">@Localizer["LocationBottom"]</option>
</select>
</div>
</div>
<div class="row">
<div class="col text-center">
<label for="Container" class="control-label">@Localizer["Container"]</label>
<select class="form-select" @bind="@ContainerType">
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</div>
</div>
<div class="row">
<div class="col text-center">
<label for="visibility" class="control-label">@Localizer["Visibility"]</label>
<select class="form-select" @bind="@Visibility">
<option value="view">@Localizer["VisibilityView"]</option>
<option value="edit">@Localizer["VisibilityEdit"]</option>
</select>
</div>
</div>
<button type="button" class="btn btn-primary col-12 mt-4" @onclick="@AddModule">@Localizer["Page.Module.Add"]</button>
@((MarkupString)Message)
<hr class="app-rule" />
}
<div class="row d-flex">
<div class="col">
<button type="button" data-bs-dismiss="offcanvas" class="btn btn-secondary col-12" @onclick=@(async () => await LogoutUser())>@Localizer["Logout"]</button>
</div>
</div>
</div>
</div>
</div>
} }
@code{ @code {
[Parameter] [Parameter]
public string ButtonClass { get; set; } = "btn-outline-secondary"; public string ButtonClass { get; set; } = "btn-outline-secondary";
@ -259,84 +61,15 @@
private bool _canViewAdminDashboard = false; private bool _canViewAdminDashboard = false;
private bool _showEditMode = false; private bool _showEditMode = false;
private bool _deleteConfirmation = false;
private List<string> _categories = new List<string>();
private List<ModuleDefinition> _allModuleDefinitions;
private List<ModuleDefinition> _moduleDefinitions;
private List<Page> _pages = new List<Page>();
private List<Module> _modules = new List<Module>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private string _category = "Common";
private string _pane = "";
protected string PageId { get; private set; } = "-"; protected override void OnParametersSet()
protected string ModuleId { get; private set; } = "-";
protected string ModuleType { get; private set; } = "new";
protected string ModuleDefinitionName { get; private set; } = "-";
protected string Title { get; private set; } = "";
protected string ContainerType { get; private set; } = "";
protected int Location { get; private set; } = int.MaxValue;
protected string Visibility { get; private set; } = "view";
protected string Message { get; private set; } = "";
private string settingCategory = "CP-category";
private string settingPane = "CP-pane";
protected string Category
{
get => _category;
private set
{
if (_category != value)
{
_category = value;
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(Category)).ToList();
ModuleDefinitionName = "-";
Message = "";
StateHasChanged();
_ = UpdateSettingsAsync();
}
}
}
protected string Pane
{
get => _pane;
private set
{
if (_pane != value)
{
_pane = value;
_ = UpdateSettingsAsync();
}
}
}
protected override async Task OnParametersSetAsync()
{ {
_canViewAdminDashboard = CanViewAdminDashboard(); _canViewAdminDashboard = CanViewAdminDashboard();
_showEditMode = false; _showEditMode = false;
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList)) if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList))
{ {
_showEditMode = true; _showEditMode = true;
LoadSettingsAsync();
_pages?.Clear();
foreach (Page p in PageState.Pages)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{
_pages.Add(p);
}
}
var themes = await ThemeService.GetThemesAsync();
_containers = ThemeService.GetContainerControls(themes, PageState.Page.ThemeType);
ContainerType = PageState.Site.DefaultContainerType;
_allModuleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(Category)).ToList();
_categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',')).Distinct().ToList();
} }
else else
{ {
@ -367,128 +100,7 @@
return false; return false;
} }
private void CategoryChanged(ChangeEventArgs e) private async Task ToggleEditMode(bool editMode)
{
Category = (string)e.Value;
}
private void ModuleChanged(ChangeEventArgs e)
{
ModuleDefinitionName = (string)e.Value;
if (ModuleDefinitionName != "-")
{
var moduleDefinition = _moduleDefinitions.FirstOrDefault(item => item.ModuleDefinitionName == ModuleDefinitionName);
Message = "<div class=\"alert alert-info mt-2 text-center\" role=\"alert\">" + moduleDefinition.Description + "</div>";
}
else
{
Message = "";
}
StateHasChanged();
}
private void PageChanged(ChangeEventArgs e)
{
PageId = (string)e.Value;
if (PageId != "-")
{
_modules = PageState.Modules
.Where(module => module.PageId == int.Parse(PageId) &&
UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, module.PermissionList))
.ToList();
}
ModuleId = "-";
StateHasChanged();
}
private async Task AddModule()
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList))
{
if ((ModuleType == "new" && ModuleDefinitionName != "-") || (ModuleType != "new" && ModuleId != "-"))
{
if (ModuleType == "new")
{
Module module = new Module();
module.SiteId = PageState.Site.SiteId;
module.PageId = PageState.Page.PageId;
module.ModuleDefinitionName = ModuleDefinitionName;
module.AllPages = false;
var permissions = new List<Permission>();
if (Visibility == "view")
{
// set module view permissions to page view permissions
permissions = SetPermissions(permissions, module.SiteId, PermissionNames.View, PermissionNames.View);
}
else
{
// set module view permissions to page edit permissions
permissions = SetPermissions(permissions, module.SiteId, PermissionNames.View, PermissionNames.Edit);
}
// set module edit permissions to page edit permissions
permissions = SetPermissions(permissions, module.SiteId, PermissionNames.Edit, PermissionNames.Edit);
module.PermissionList = permissions;
module = await ModuleService.AddModuleAsync(module);
ModuleId = module.ModuleId.ToString();
}
var pageModule = new PageModule
{
PageId = PageState.Page.PageId,
ModuleId = int.Parse(ModuleId),
Title = Title
};
if (pageModule.Title == "")
{
if (ModuleType == "new")
{
pageModule.Title = _moduleDefinitions.FirstOrDefault(item => item.ModuleDefinitionName == ModuleDefinitionName)?.Name;
}
else
{
pageModule.Title = _modules.FirstOrDefault(item => item.ModuleId == int.Parse(ModuleId))?.Title;
}
}
pageModule.Pane = Pane;
pageModule.Order = Location;
pageModule.ContainerType = ContainerType;
if (pageModule.ContainerType == PageState.Site.DefaultContainerType)
{
pageModule.ContainerType = "";
}
await PageModuleService.AddPageModuleAsync(pageModule);
await PageModuleService.UpdatePageModuleOrderAsync(pageModule.PageId, pageModule.Pane);
Message = $"<div class=\"alert alert-success mt-2 text-center\" role=\"alert\">{Localizer["Success.Page.ModuleAdd"]}</div>";
Title = "";
NavigationManager.NavigateTo(NavigateUrl());
}
else
{
Message = $"<div class=\"alert alert-warning mt-2 text-center\" role=\"alert\">{Localizer["Message.Require.ModuleSelect"]}</div>";
}
}
else
{
Message = $"<div class=\"alert alert-error mt-2 text-center\" role=\"alert\">{Localizer["Error.Authorize.No"]}</div>";
}
}
private List<Permission> SetPermissions(List<Permission> permissions, int siteId, string modulePermission, string pagePermission)
{
foreach (var permission in PageState.Page.PermissionList.Where(item => item.PermissionName == pagePermission))
{
permissions.Add(new Permission { SiteId = siteId, EntityName = EntityNames.Module, PermissionName = modulePermission, RoleId = permission.RoleId, UserId = permission.UserId, IsAuthorized = permission.IsAuthorized });
}
return permissions;
}
private async Task ToggleEditMode(bool EditMode)
{ {
Page page = null; Page page = null;
if (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered)) if (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered))
@ -498,13 +110,13 @@
if (_showEditMode) if (_showEditMode)
{ {
if (EditMode) PageState.EditMode = !editMode;
if (PageState.User != null)
{ {
PageState.EditMode = false; // preserve edit mode for authenticated users
} var userSettings = new Dictionary<string, string> { { "CP-editmode", (PageState.EditMode) ? PageState.Page.PageId.ToString() : "-1" } };
else await SettingService.UpdateUserSettingsAsync(userSettings, PageState.User.UserId);
{
PageState.EditMode = true;
} }
// preserve other querystring parameters // preserve other querystring parameters
@ -512,190 +124,14 @@
PageState.QueryString.Add("edit", PageState.EditMode.ToString().ToLower()); PageState.QueryString.Add("edit", PageState.EditMode.ToString().ToLower());
var url = PageState.Route.AbsolutePath + Utilities.CreateQueryString(PageState.QueryString); var url = PageState.Route.AbsolutePath + Utilities.CreateQueryString(PageState.QueryString);
NavigationManager.NavigateTo(url); NavigationManager.NavigateTo(url);
}
else
{
if (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered))
{
PageState.EditMode = true;
NavigationManager.NavigateTo(NavigateUrl(page.Path, "edit=" + ((PageState.EditMode) ? "true" : "false")));
}
}
}
private void Navigate(string location)
{
Module module;
switch (location)
{
case "Admin":
// get admin dashboard moduleid
module = PageState.Modules.FirstOrDefault(item => item.ModuleDefinitionName == Constants.AdminDashboardModule);
if (module != null)
{
NavigationManager.NavigateTo(EditUrl("admin", module.ModuleId, "Index", "returnurl=" + WebUtility.UrlEncode(PageState.Route.PathAndQuery)));
}
break;
case "Add":
case "Edit":
string url = "";
// get page management moduleid
module = PageState.Modules.FirstOrDefault(item => item.ModuleDefinitionName == Constants.PageManagementModule);
if (module != null)
{
switch (location)
{
case "Add":
url = EditUrl("admin/pages", module.ModuleId, location, $"id={PageState.Page.PageId}&returnurl={WebUtility.UrlEncode(PageState.Route.PathAndQuery)}");
break;
case "Edit":
url = EditUrl("admin/pages", module.ModuleId, location, $"id={PageState.Page.PageId}&returnurl={WebUtility.UrlEncode(PageState.Route.PathAndQuery)}");
break;
}
}
if (url != "")
{
NavigationManager.NavigateTo(url);
}
break;
}
}
private async void Publish(string action)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList))
{
var permissions = PageState.Page.PermissionList;
switch (action)
{
case "publish":
if (!permissions.Any(item => item.PermissionName == PermissionNames.View && item.RoleName == RoleNames.Everyone))
{
permissions.Add(new Permission(PageState.Site.SiteId, EntityNames.Page, PageState.Page.PageId, PermissionNames.View, RoleNames.Everyone, null, true));
}
if (!permissions.Any(item => item.PermissionName == PermissionNames.View && item.RoleName == RoleNames.Registered))
{
permissions.Add(new Permission(PageState.Site.SiteId, EntityNames.Page, PageState.Page.PageId, PermissionNames.View, RoleNames.Registered, null, true));
}
break;
case "unpublish":
if (permissions.Any(item => item.PermissionName == PermissionNames.View && item.RoleName == RoleNames.Everyone))
{
permissions.RemoveAll(item => item.PermissionName == PermissionNames.View && item.RoleName == RoleNames.Everyone);
}
if (permissions.Any(item => item.PermissionName == PermissionNames.View && item.RoleName == RoleNames.Registered))
{
permissions.RemoveAll(item => item.PermissionName == PermissionNames.View && item.RoleName == RoleNames.Registered);
}
break;
}
PageState.Page.PermissionList = permissions;
await PageService.UpdatePageAsync(PageState.Page);
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
}
}
private void ConfirmDelete()
{
_deleteConfirmation = !_deleteConfirmation;
StateHasChanged();
}
private async Task DeletePage()
{
ConfirmDelete();
var page = PageState.Page;
try
{
if (page.UserId == null)
{
page.IsDeleted = true;
await PageService.UpdatePageAsync(page);
await logger.Log(page.PageId, null, PageState.User?.UserId, GetType().AssemblyQualifiedName, "ControlPanel", LogFunction.Delete, LogLevel.Information, null, "Page Deleted {Page}", page);
NavigationManager.NavigateTo(NavigateUrl(""));
}
else // personalized page
{
await PageService.DeletePageAsync(page.PageId);
await logger.Log(page.PageId, null, PageState.User?.UserId, GetType().AssemblyQualifiedName, "ControlPanel", LogFunction.Delete, LogLevel.Information, null, "Page Deleted {Page}", page);
NavigationManager.NavigateTo(NavigateUrl());
}
}
catch (Exception ex)
{
await logger.Log(page.PageId, null, PageState.User?.UserId, GetType().AssemblyQualifiedName, "ControlPanel", LogFunction.Delete, LogLevel.Information, ex, "Page Deleted {Page} {Error}", page, ex.Message);
}
}
// the following code is duplicated from LoginBase
private async Task LogoutUser()
{
await LoggingService.Log(PageState.Alias, PageState.Page.PageId, null, PageState.User?.UserId, GetType().AssemblyQualifiedName, "Logout", LogFunction.Security, LogLevel.Information, null, "User Logout For Username {Username}", PageState.User?.Username);
Route route = new Route(PageState.Uri.AbsoluteUri, PageState.Alias.Path);
var url = route.PathAndQuery;
// verify if anonymous users can access page
if (!UserSecurity.IsAuthorized(null, PermissionNames.View, PageState.Page.PermissionList))
{
url = PageState.Alias.Path;
}
if (PageState.Runtime == Shared.Runtime.Hybrid)
{
// hybrid apps utilize an interactive logout
await UserService.LogoutUserAsync(PageState.User);
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
authstateprovider.NotifyAuthenticationChanged();
NavigationManager.NavigateTo(url, true);
} }
else else
{ {
// post to the Logout page to complete the logout process if (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered))
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, returnurl = url }; {
var interop = new Interop(jsRuntime); PageState.EditMode = true;
await interop.SubmitForm(Utilities.TenantUrl(PageState.Alias, "/pages/logout/"), fields); NavigationManager.NavigateTo(NavigateUrl(page.Path, "edit=" + ((PageState.EditMode) ? "true" : "false")));
}
} }
} }
private void LoadSettingsAsync()
{
_category = SettingService.GetSetting(PageState.User.Settings, settingCategory, "Common");
var pane = SettingService.GetSetting(PageState.User.Settings, settingPane, "");
if (PageState.Page.Panes.Contains(pane))
{
_pane = pane;
}
else
{
if (PageState.Page.Panes.FindIndex(item => item.Equals(PaneNames.Default, StringComparison.OrdinalIgnoreCase)) != -1)
{
_pane = PaneNames.Default;
}
else
{
_pane = PaneNames.Admin;
}
}
}
private async Task UpdateSettingsAsync()
{
Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
settings = SettingService.SetSetting(settings, settingCategory, _category);
settings = SettingService.SetSetting(settings, settingPane, _pane);
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
}
private void ClearMessage()
{
Message = "";
}
} }

View File

@ -0,0 +1,604 @@
@using System.Net
@namespace Oqtane.Themes.Controls
@inject NavigationManager NavigationManager
@inject SiteState ComponentSiteState
@inject IUserService UserService
@inject IModuleDefinitionService ModuleDefinitionService
@inject IThemeService ThemeService
@inject IModuleService ModuleService
@inject IPageService PageService
@inject IPageModuleService PageModuleService
@inject ILogService logger
@inject ISettingService SettingService
@inject IJSRuntime jsRuntime
@inject IServiceProvider ServiceProvider
@inject ILogService LoggingService
@inject IStringLocalizer<ControlPanelInteractive> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<button type="button" class="btn @ButtonClass ms-1" data-bs-toggle="offcanvas" data-bs-target="#offcanvasControlPanel" aria-controls="offcanvasControlPanel" @onclick="ClearMessage">
<span class="oi oi-cog"></span>
</button>
<div class="@ContainerClass" tabindex="-1" data-bs-scroll="true" data-bs-backdrop="true" id="offcanvasControlPanel" aria-labelledby="offcanvasScrollingLabel">
<div class="@HeaderClass">
<h5 id="offcanvasScrollingLabel" class="offcanvas-title">@Localizer["ControlPanel"]</h5>
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="Close" @onclick="ClearMessage"></button>
</div>
<div class="@BodyClass">
<div class="container-fluid">
@if (_canViewAdminDashboard)
{
<div class="row d-flex">
<div class="col">
<button type="button" data-bs-dismiss="offcanvas" class="btn btn-primary col-12" @onclick=@(async () => Navigate("Admin"))>@Localizer["AdminDash"]</button>
</div>
</div>
<hr class="app-rule" />
}
@if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList))
{
<div class="row">
<div class="col text-center">
<label class="control-label">@Localizer["Page.Manage"] </label>
</div>
</div>
<div class="row d-flex mb-2">
<div class="col d-flex justify-content-between">
@if (PageState.Page.UserId == null)
{
<button type="button" class="btn btn-secondary col me-1" data-bs-dismiss="offcanvas" @onclick=@(async () => Navigate("Add"))>@SharedLocalizer["Add"]</button>
}
<button type="button" class="btn btn-secondary col" data-bs-dismiss="offcanvas" @onclick=@(async () => Navigate("Edit"))>@SharedLocalizer["Edit"]</button>
<button type="button" class="btn btn-danger col ms-1" @onclick="ConfirmDelete">@SharedLocalizer["Delete"]</button>
</div>
</div>
<div class="row d-flex">
<div class="col">
@if (UserSecurity.ContainsRole(PageState.Page.PermissionList, PermissionNames.View, RoleNames.Everyone))
{
<button type="button" class="btn btn-secondary col-12" @onclick=@(async () => Publish("unpublish"))>@Localizer["Page.Unpublish"]</button>
}
else
{
<button type="button" class="btn btn-secondary col-12" @onclick=@(async () => Publish("publish"))>@Localizer["Page.Publish"]</button>
}
</div>
</div>
<hr class="app-rule" />
@if (_deleteConfirmation)
{
<div class="app-admin-modal">
<div class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@Localizer["Page.Delete"]</h5>
<button type="button" class="btn-close" aria-label="Close" @onclick="ConfirmDelete"></button>
</div>
<div class="modal-body">
<p>@Localizer["Confirm.Page.Delete"]</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" @onclick="DeletePage">@SharedLocalizer["Delete"]</button>
<button type="button" class="btn btn-secondary" @onclick="ConfirmDelete">@SharedLocalizer["Cancel"]</button>
</div>
</div>
</div>
</div>
</div>
}
<div class="row">
<div class="col text-center">
<label for="Module" class="control-label">@Localizer["Module.Manage"]</label>
<select class="form-select" @bind="@_moduleType">
<option value="new">@Localizer["Module.AddNew"]</option>
<option value="existing">@Localizer["Module.AddExisting"]</option>
</select>
@if (_moduleType == "new")
{
@if (_moduleDefinitions != null)
{
<select class="form-select mt-1" @onchange="(e => CategoryChanged(e))">
@foreach (var category in _categories)
{
if (category == _category)
{
<option value="@category" selected>@category @Localizer["Modules"]</option>
}
else
{
<option value="@category">@category @Localizer["Modules"]</option>
}
}
</select>
<select class="form-select mt-1" @onchange="(e => ModuleChanged(e))">
@if (_moduleDefinitionName == "-")
{
<option value="-" selected>&lt;@Localizer["Module.Select"]&gt;</option>
}
else
{
<option value="-">&lt;@Localizer["Module.Select"]&gt;</option>
}
@foreach (var moduledefinition in _moduleDefinitions)
{
if (moduledefinition.IsEnabled && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Utilize, moduledefinition.PermissionList))
{
if (moduledefinition.Runtimes == "" || moduledefinition.Runtimes.Contains(PageState.Runtime.ToString()))
{
<option value="@moduledefinition.ModuleDefinitionName">@moduledefinition.Name</option>
}
}
}
</select>
}
}
else
{
<select class="form-select mt-1" @onchange="(e => PageChanged(e))">
<option value="-">&lt;@Localizer["Page.Select"]&gt;</option>
@foreach (Page p in _pages)
{
<option value="@p.PageId">@p.Name</option>
}
</select>
<select class="form-select mt-1" @bind="@_moduleId">
<option value="-">&lt;@Localizer["Module.Select"]&gt;</option>
@foreach (Module module in _modules)
{
<option value="@module.ModuleId">@module.Title</option>
}
</select>
}
</div>
</div>
<div class="row">
<div class="col text-center">
<label for="Title" class="control-label">@Localizer["Title"]</label>
<input type="text" name="Title" class="form-control" @bind="@_title" />
</div>
</div>
<div class="row">
<div class="col text-center">
<label for="Pane" class="control-label">@Localizer["Pane"]</label>
<select class="form-select" @bind="@_pane">
@foreach (string pane in PageState.Page.Panes)
{
<option value="@pane">@pane Pane</option>
}
</select>
</div>
</div>
<div class="row">
<div class="col text-center">
<label for="Insert" class="control-label">@Localizer["Location"]</label>
<select class="form-select" @bind="@_location">
<option value="@int.MinValue">@Localizer["LocationTop"]</option>
<option value="@int.MaxValue">@Localizer["LocationBottom"]</option>
</select>
</div>
</div>
<div class="row">
<div class="col text-center">
<label for="Container" class="control-label">@Localizer["Container"]</label>
<select class="form-select" @bind="@_containerType">
@if (_containers != null)
{
foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
}
</select>
</div>
</div>
<div class="row">
<div class="col text-center">
<label for="visibility" class="control-label">@Localizer["Visibility"]</label>
<select class="form-select" @bind="@_visibility">
<option value="view">@Localizer["VisibilityView"]</option>
<option value="edit">@Localizer["VisibilityEdit"]</option>
</select>
</div>
</div>
<button type="button" class="btn btn-primary col-12 mt-4" @onclick="@AddModule">@Localizer["Page.Module.Add"]</button>
@((MarkupString)_message)
<hr class="app-rule" />
}
<div class="row d-flex">
<div class="col">
<button type="button" data-bs-dismiss="offcanvas" class="btn btn-secondary col-12" @onclick=@(async () => await LogoutUser())>@Localizer["Logout"]</button>
</div>
</div>
</div>
</div>
</div>
@code {
[Parameter]
public SiteState SiteState { get; set; }
[Parameter]
public PageState PageState { get; set; }
[Parameter]
public string ButtonClass { get; set; }
[Parameter]
public string ContainerClass { get; set; }
[Parameter]
public string HeaderClass { get; set; }
[Parameter]
public string BodyClass { get; set; }
[Parameter]
public bool ShowLanguageSwitcher { get; set; }
[Parameter]
public string LanguageDropdownAlignment { get; set; }
private bool _canViewAdminDashboard = false;
private bool _deleteConfirmation = false;
private List<string> _categories = new List<string>();
private List<ModuleDefinition> _allModuleDefinitions;
private List<ModuleDefinition> _moduleDefinitions;
private List<Page> _pages = new List<Page>();
private List<Module> _modules = new List<Module>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private string _category = "Common";
private string _pane = "";
protected string _pageId { get; private set; } = "-";
protected string _moduleId { get; private set; } = "-";
protected string _moduleType { get; private set; } = "new";
protected string _moduleDefinitionName { get; private set; } = "-";
protected string _title { get; private set; } = "";
protected string _containerType { get; private set; } = "";
protected int _location { get; private set; } = int.MaxValue;
protected string _visibility { get; private set; } = "view";
protected string _message { get; private set; } = "";
private string settingCategory = "CP-category";
private string settingPane = "CP-pane";
protected override async Task OnParametersSetAsync()
{
// repopulate the SiteState service based on the values passed in the SiteState parameter (this is how state is marshalled across the render mode boundary)
ComponentSiteState.Hydrate(SiteState);
_canViewAdminDashboard = CanViewAdminDashboard();
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList))
{
LoadSettingsAsync();
_pages?.Clear();
foreach (Page p in PageState.Pages)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{
_pages.Add(p);
}
}
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, PageState.Page.ThemeType);
_containerType = PageState.Site.DefaultContainerType;
_allModuleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(_category)).ToList();
_categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',')).Distinct().ToList();
}
}
private bool CanViewAdminDashboard()
{
var admin = PageState.Pages.FirstOrDefault(item => item.Path == "admin");
if (admin != null)
{
foreach (var page in PageState.Pages.Where(item => item.ParentId == admin?.PageId))
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, page.PermissionList))
{
return true;
}
}
}
return false;
}
private void CategoryChanged(ChangeEventArgs e)
{
_category = (string)e.Value;
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(_category)).ToList();
_moduleDefinitionName = "-";
_message = "";
}
private void ModuleChanged(ChangeEventArgs e)
{
_moduleDefinitionName = (string)e.Value;
if (_moduleDefinitionName != "-")
{
var moduleDefinition = _moduleDefinitions.FirstOrDefault(item => item.ModuleDefinitionName == _moduleDefinitionName);
_message = "<div class=\"alert alert-info mt-2 text-center\" role=\"alert\">" + moduleDefinition.Description + "</div>";
}
else
{
_message = "";
}
StateHasChanged();
}
private void PageChanged(ChangeEventArgs e)
{
_pageId = (string)e.Value;
if (_pageId != "-")
{
_modules = PageState.Modules
.Where(module => module.PageId == int.Parse(_pageId) &&
UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, module.PermissionList))
.ToList();
}
_moduleId = "-";
StateHasChanged();
}
private async Task AddModule()
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList))
{
if ((_moduleType == "new" && _moduleDefinitionName != "-") || (_moduleType != "new" && _moduleId != "-"))
{
if (_moduleType == "new")
{
Module module = new Module();
module.SiteId = PageState.Site.SiteId;
module.PageId = PageState.Page.PageId;
module.ModuleDefinitionName = _moduleDefinitionName;
module.AllPages = false;
var permissions = new List<Permission>();
if (_visibility == "view")
{
// set module view permissions to page view permissions
permissions = SetPermissions(permissions, module.SiteId, PermissionNames.View, PermissionNames.View);
}
else
{
// set module view permissions to page edit permissions
permissions = SetPermissions(permissions, module.SiteId, PermissionNames.View, PermissionNames.Edit);
}
// set module edit permissions to page edit permissions
permissions = SetPermissions(permissions, module.SiteId, PermissionNames.Edit, PermissionNames.Edit);
module.PermissionList = permissions;
module = await ModuleService.AddModuleAsync(module);
_moduleId = module.ModuleId.ToString();
}
var pageModule = new PageModule
{
PageId = PageState.Page.PageId,
ModuleId = int.Parse(_moduleId),
Title = _title
};
if (pageModule.Title == "")
{
if (_moduleType == "new")
{
pageModule.Title = _moduleDefinitions.FirstOrDefault(item => item.ModuleDefinitionName == _moduleDefinitionName)?.Name;
}
else
{
pageModule.Title = _modules.FirstOrDefault(item => item.ModuleId == int.Parse(_moduleId))?.Title;
}
}
pageModule.Pane = _pane;
pageModule.Order = _location;
pageModule.ContainerType = _containerType;
if (pageModule.ContainerType == PageState.Site.DefaultContainerType)
{
pageModule.ContainerType = "";
}
await PageModuleService.AddPageModuleAsync(pageModule);
await PageModuleService.UpdatePageModuleOrderAsync(pageModule.PageId, pageModule.Pane);
await UpdateSettingsAsync();
_message = $"<div class=\"alert alert-success mt-2 text-center\" role=\"alert\">{Localizer["Success.Page.ModuleAdd"]}</div>";
_title = "";
NavigationManager.NavigateTo(Utilities.NavigateUrl(PageState.Alias.Path, PageState.Page.Path, ""));
}
else
{
_message = $"<div class=\"alert alert-warning mt-2 text-center\" role=\"alert\">{Localizer["Message.Require.ModuleSelect"]}</div>";
}
}
else
{
_message = $"<div class=\"alert alert-error mt-2 text-center\" role=\"alert\">{Localizer["Error.Authorize.No"]}</div>";
}
}
private List<Permission> SetPermissions(List<Permission> permissions, int siteId, string modulePermission, string pagePermission)
{
foreach (var permission in PageState.Page.PermissionList.Where(item => item.PermissionName == pagePermission))
{
permissions.Add(new Permission { SiteId = siteId, EntityName = EntityNames.Module, PermissionName = modulePermission, RoleId = permission.RoleId, UserId = permission.UserId, IsAuthorized = permission.IsAuthorized });
}
return permissions;
}
private void Navigate(string location)
{
Module module;
switch (location)
{
case "Admin":
// get admin dashboard moduleid
module = PageState.Modules.FirstOrDefault(item => item.ModuleDefinitionName == Constants.AdminDashboardModule);
if (module != null)
{
NavigationManager.NavigateTo(Utilities.EditUrl(PageState.Alias.Path, "admin", module.ModuleId, "Index", "returnurl=" + WebUtility.UrlEncode(PageState.Route.PathAndQuery)));
}
break;
case "Add":
case "Edit":
string url = "";
// get page management moduleid
module = PageState.Modules.FirstOrDefault(item => item.ModuleDefinitionName == Constants.PageManagementModule);
if (module != null)
{
url = Utilities.EditUrl(PageState.Alias.Path, "admin/pages", module.ModuleId, location, $"id={PageState.Page.PageId}&returnurl={WebUtility.UrlEncode(PageState.Route.PathAndQuery)}");
NavigationManager.NavigateTo(url);
}
break;
}
}
private async void Publish(string action)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList))
{
var permissions = PageState.Page.PermissionList;
switch (action)
{
case "publish":
if (!permissions.Any(item => item.PermissionName == PermissionNames.View && item.RoleName == RoleNames.Everyone))
{
permissions.Add(new Permission(PageState.Site.SiteId, EntityNames.Page, PageState.Page.PageId, PermissionNames.View, RoleNames.Everyone, null, true));
}
if (!permissions.Any(item => item.PermissionName == PermissionNames.View && item.RoleName == RoleNames.Registered))
{
permissions.Add(new Permission(PageState.Site.SiteId, EntityNames.Page, PageState.Page.PageId, PermissionNames.View, RoleNames.Registered, null, true));
}
break;
case "unpublish":
if (permissions.Any(item => item.PermissionName == PermissionNames.View && item.RoleName == RoleNames.Everyone))
{
permissions.RemoveAll(item => item.PermissionName == PermissionNames.View && item.RoleName == RoleNames.Everyone);
}
if (permissions.Any(item => item.PermissionName == PermissionNames.View && item.RoleName == RoleNames.Registered))
{
permissions.RemoveAll(item => item.PermissionName == PermissionNames.View && item.RoleName == RoleNames.Registered);
}
break;
}
PageState.Page.PermissionList = permissions;
await PageService.UpdatePageAsync(PageState.Page);
NavigationManager.NavigateTo(Utilities.NavigateUrl(PageState.Alias.Path, PageState.Page.Path, "refresh"));
}
}
private void ConfirmDelete()
{
_deleteConfirmation = !_deleteConfirmation;
StateHasChanged();
}
private async Task DeletePage()
{
ConfirmDelete();
var page = PageState.Page;
try
{
if (page.UserId == null)
{
page.IsDeleted = true;
await PageService.UpdatePageAsync(page);
await logger.Log(page.PageId, null, PageState.User?.UserId, GetType().AssemblyQualifiedName, "ControlPanel", LogFunction.Delete, LogLevel.Information, null, "Page Deleted {Page}", page);
NavigationManager.NavigateTo(Utilities.NavigateUrl(PageState.Alias.Path, "", ""));
}
else // personalized page
{
await PageService.DeletePageAsync(page.PageId);
await logger.Log(page.PageId, null, PageState.User?.UserId, GetType().AssemblyQualifiedName, "ControlPanel", LogFunction.Delete, LogLevel.Information, null, "Page Deleted {Page}", page);
NavigationManager.NavigateTo(Utilities.NavigateUrl(PageState.Alias.Path, PageState.Page.Path, ""));
}
}
catch (Exception ex)
{
await logger.Log(page.PageId, null, PageState.User?.UserId, GetType().AssemblyQualifiedName, "ControlPanel", LogFunction.Delete, LogLevel.Information, ex, "Page Deleted {Page} {Error}", page, ex.Message);
}
}
// the following code is duplicated from LoginBase
private async Task LogoutUser()
{
await LoggingService.Log(PageState.Alias, PageState.Page.PageId, null, PageState.User?.UserId, GetType().AssemblyQualifiedName, "Logout", LogFunction.Security, LogLevel.Information, null, "User Logout For Username {Username}", PageState.User?.Username);
Route route = new Route(PageState.Uri.AbsoluteUri, PageState.Alias.Path);
var url = route.PathAndQuery;
// verify if anonymous users can access page
if (!UserSecurity.IsAuthorized(null, PermissionNames.View, PageState.Page.PermissionList))
{
url = PageState.Alias.Path;
}
if (PageState.Runtime == Shared.Runtime.Hybrid)
{
if (PageState.User != null)
{
// hybrid apps utilize an interactive logout
await UserService.LogoutUserAsync(PageState.User);
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
authstateprovider.NotifyAuthenticationChanged();
NavigationManager.NavigateTo(url, true);
}
}
else
{
// post to the Logout page to complete the logout process
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, returnurl = url };
var interop = new Interop(jsRuntime);
await interop.SubmitForm(Utilities.TenantUrl(PageState.Alias, "/pages/logout/"), fields);
}
}
private void LoadSettingsAsync()
{
_category = SettingService.GetSetting(PageState.User?.Settings, settingCategory, "Common");
var pane = SettingService.GetSetting(PageState.User?.Settings, settingPane, "");
if (PageState.Page.Panes.Contains(pane))
{
_pane = pane;
}
else
{
if (PageState.Page.Panes.FindIndex(item => item.Equals(PaneNames.Default, StringComparison.OrdinalIgnoreCase)) != -1)
{
_pane = PaneNames.Default;
}
else
{
_pane = PaneNames.Admin;
}
}
}
private async Task UpdateSettingsAsync()
{
if (PageState.User != null)
{
Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
settings = SettingService.SetSetting(settings, settingCategory, _category);
settings = SettingService.SetSetting(settings, settingPane, _pane);
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
}
}
private void ClearMessage()
{
_message = "";
}
}

View File

@ -9,12 +9,23 @@
<text>...</text> <text>...</text>
</Authorizing> </Authorizing>
<Authorized> <Authorized>
<button type="button" class="btn btn-primary" @onclick="LogoutUser">@Localizer["Logout"]</button> @if (PageState.Runtime == Runtime.Hybrid)
{
<button type="button" class="btn btn-primary" @onclick="LogoutUser">@Localizer["Logout"]</button>
}
else
{
<form method="post" class="app-form-inline" action="@logouturl" @formname="LogoutForm">
<input type="hidden" name="__RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<input type="hidden" name="returnurl" value="@returnurl" />
<button type="submit" class="btn btn-primary">@Localizer["Logout"]</button>
</form>
}
</Authorized> </Authorized>
<NotAuthorized> <NotAuthorized>
@if (ShowLogin) @if (ShowLogin)
{ {
<button type="button" class="btn btn-primary" @onclick="LoginUser">@SharedLocalizer["Login"]</button> <a href="@loginurl" class="btn btn-primary">@SharedLocalizer["Login"]</a>
} }
</NotAuthorized> </NotAuthorized>
</AuthorizeView> </AuthorizeView>

View File

@ -21,30 +21,65 @@ namespace Oqtane.Themes.Controls
[Inject] public IJSRuntime jsRuntime { get; set; } [Inject] public IJSRuntime jsRuntime { get; set; }
[Inject] public IServiceProvider ServiceProvider { get; set; } [Inject] public IServiceProvider ServiceProvider { get; set; }
protected void LoginUser() private bool allowexternallogin;
private bool allowsitelogin;
protected string loginurl;
protected string logouturl;
protected string returnurl;
protected override void OnParametersSet()
{ {
var allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false; allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false;
var allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true")); allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
var returnurl = "";
if (!PageState.QueryString.ContainsKey("returnurl"))
{
returnurl = WebUtility.UrlEncode(PageState.Route.PathAndQuery); // remember current url
}
else
{
returnurl = PageState.QueryString["returnurl"]; // use existing value
}
// set login url
if (allowexternallogin && !allowsitelogin) if (allowexternallogin && !allowsitelogin)
{ {
// external login // external login
NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + returnurl), true); loginurl = Utilities.TenantUrl(PageState.Alias, "/pages/external");
} }
else else
{ {
// local login // local login
NavigationManager.NavigateTo(NavigateUrl("login", "?returnurl=" + returnurl)); loginurl = NavigateUrl("login");
}
if (!PageState.QueryString.ContainsKey("returnurl"))
{
// remember current url
loginurl += "?returnurl=" + WebUtility.UrlEncode(PageState.Route.PathAndQuery);
}
else
{
// use existing value
loginurl += "?returnurl=" + PageState.QueryString["returnurl"];
}
// set logout url
logouturl = Utilities.TenantUrl(PageState.Alias, "/pages/logout/");
// verify anonymous users can access current page
if (UserSecurity.IsAuthorized(null, PermissionNames.View, PageState.Page.PermissionList) && Utilities.IsPageModuleVisible(PageState.Page.EffectiveDate, PageState.Page.ExpiryDate))
{
returnurl = PageState.Route.PathAndQuery;
}
else
{
returnurl = PageState.Alias.Path;
}
}
protected void LoginUser()
{
if (allowexternallogin && !allowsitelogin)
{
// external login
NavigationManager.NavigateTo(loginurl, true);
}
else
{
// local login
NavigationManager.NavigateTo(loginurl);
} }
} }
@ -52,30 +87,20 @@ namespace Oqtane.Themes.Controls
{ {
await LoggingService.Log(PageState.Alias, PageState.Page.PageId, null, PageState.User?.UserId, GetType().AssemblyQualifiedName, "Logout", LogFunction.Security, LogLevel.Information, null, "User Logout For Username {Username}", PageState.User?.Username); await LoggingService.Log(PageState.Alias, PageState.Page.PageId, null, PageState.User?.UserId, GetType().AssemblyQualifiedName, "Logout", LogFunction.Security, LogLevel.Information, null, "User Logout For Username {Username}", PageState.User?.Username);
Route route = new Route(PageState.Uri.AbsoluteUri, PageState.Alias.Path); if (PageState.Runtime == Runtime.Hybrid)
var url = route.PathAndQuery;
// verify if anonymous users can access page
if (!UserSecurity.IsAuthorized(null, PermissionNames.View, PageState.Page.PermissionList) || !Utilities.IsPageModuleVisible(PageState.Page.EffectiveDate, PageState.Page.ExpiryDate))
{
url = PageState.Alias.Path;
}
if (PageState.Runtime == Shared.Runtime.Hybrid)
{ {
// hybrid apps utilize an interactive logout // hybrid apps utilize an interactive logout
await UserService.LogoutUserAsync(PageState.User); await UserService.LogoutUserAsync(PageState.User);
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider)); var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
authstateprovider.NotifyAuthenticationChanged(); authstateprovider.NotifyAuthenticationChanged();
NavigationManager.NavigateTo(url, true); NavigationManager.NavigateTo(returnurl, true);
} }
else else // this condition is only valid for legacy Login button inheriting from LoginBase
{ {
// post to the Logout page to complete the logout process // post to the Logout page to complete the logout process
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, returnurl = url }; var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, returnurl = returnurl };
var interop = new Interop(jsRuntime); var interop = new Interop(jsRuntime);
await interop.SubmitForm(Utilities.TenantUrl(PageState.Alias, "/pages/logout/"), fields); await interop.SubmitForm(logouturl, fields);
} }
} }
} }

View File

@ -1,24 +1,11 @@
@namespace Oqtane.Themes.Controls @namespace Oqtane.Themes.Controls
@inherits ThemeControlBase @inherits ThemeControlBase
@inject IFileService FileService
@if (file != null) @if (PageState.Site.LogoFileId != null)
{ {
<span class="app-logo"> <span class="app-logo">
<a href="@PageState.Alias.Path"> <a href="@PageState.Alias.Path">
<img class="img-fluid" src="@file.Url" alt="@PageState.Site.Name" /> <img class="img-fluid" src="@Utilities.FileUrl(PageState.Alias, PageState.Site.LogoFileId.Value)" alt="@PageState.Site.Name" />
</a> </a>
</span> </span>
} }
@code {
private File file = null;
protected override async Task OnParametersSetAsync()
{
if (PageState.Site.LogoFileId != null && file?.FileId != PageState.Site.LogoFileId.Value)
{
file = await FileService.GetFileAsync(PageState.Site.LogoFileId.Value);
}
}
}

View File

@ -6,9 +6,16 @@
<div class="dropdown-menu" aria-labelledby="@($"navbarDropdown{ParentPage.PageId}")"> <div class="dropdown-menu" aria-labelledby="@($"navbarDropdown{ParentPage.PageId}")">
@foreach (var childPage in GetChildPages()) @foreach (var childPage in GetChildPages())
{ {
var _attributes = new Dictionary<string, object>();
_attributes.Add("href", GetUrl(childPage));
var _target = GetTarget(childPage);
if (!string.IsNullOrEmpty(_target))
{
_attributes.Add("target", _target);
}
if (childPage.PageId == PageState.Page.PageId) if (childPage.PageId == PageState.Page.PageId)
{ {
<a class="nav-link active px-3" href="@GetUrl(childPage)" target="@GetTarget(childPage)"> <a class="nav-link active px-3" @attributes="_attributes">
<span class="w-100" data-bs-toggle="collapse" data-bs-target=".navbar-collapse.show"> <span class="w-100" data-bs-toggle="collapse" data-bs-target=".navbar-collapse.show">
<span class="@childPage.Icon" aria-hidden="true" /> <span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name <span class="visually-hidden-focusable">(current)</span> @childPage.Name <span class="visually-hidden-focusable">(current)</span>
@ -17,7 +24,7 @@
} }
else else
{ {
<a class="nav-link px-3" href="@GetUrl(childPage)" target="@GetTarget(childPage)"> <a class="nav-link px-3" @attributes="_attributes">
<span class="w-100" data-bs-toggle="collapse" data-bs-target=".navbar-collapse.show"> <span class="w-100" data-bs-toggle="collapse" data-bs-target=".navbar-collapse.show">
<span class="@childPage.Icon" aria-hidden="true" /> <span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name @childPage.Name
@ -32,12 +39,19 @@ else
<ul class="navbar-nav mr-auto"> <ul class="navbar-nav mr-auto">
@foreach (var childPage in GetChildPages()) @foreach (var childPage in GetChildPages())
{ {
var _attributes = new Dictionary<string, object>();
_attributes.Add("href", GetUrl(childPage));
var _target = GetTarget(childPage);
if (!string.IsNullOrEmpty(_target))
{
_attributes.Add("target", _target);
}
if (!Pages.Any(e => e.ParentId == childPage.PageId)) if (!Pages.Any(e => e.ParentId == childPage.PageId))
{ {
if (childPage.PageId == PageState.Page.PageId) if (childPage.PageId == PageState.Page.PageId)
{ {
<li class="nav-item"> <li class="nav-item">
<a class="nav-link active" href="@GetUrl(childPage)" target="@GetTarget(childPage)"> <a class="nav-link active" @attributes="_attributes">
<span class="w-100" data-bs-toggle="collapse" data-bs-target=".navbar-collapse.show"> <span class="w-100" data-bs-toggle="collapse" data-bs-target=".navbar-collapse.show">
<span class="@childPage.Icon" aria-hidden="true" /> <span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name <span class="visually-hidden-focusable">(current)</span> @childPage.Name <span class="visually-hidden-focusable">(current)</span>
@ -48,7 +62,7 @@ else
else else
{ {
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="@GetUrl(childPage)" target="@GetTarget(childPage)"> <a class="nav-link" @attributes="_attributes">
<span class="w-100" data-bs-toggle="collapse" data-bs-target=".navbar-collapse.show"> <span class="w-100" data-bs-toggle="collapse" data-bs-target=".navbar-collapse.show">
<span class="@childPage.Icon" aria-hidden="true" /> <span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name @childPage.Name
@ -62,7 +76,7 @@ else
if (childPage.PageId == PageState.Page.PageId) if (childPage.PageId == PageState.Page.PageId)
{ {
<li class="nav-item dropdown active"> <li class="nav-item dropdown active">
<a class="nav-link dropdown-toggle" href="@GetUrl(childPage)" target="@GetTarget(childPage)" id="@($"navbarDropdown{childPage.PageId}")" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <a class="nav-link dropdown-toggle" id="@($"navbarDropdown{childPage.PageId}")" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" @attributes="_attributes">
<span class="@childPage.Icon" aria-hidden="true" /> <span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name <span class="visually-hidden-focusable">(current)</span> @childPage.Name <span class="visually-hidden-focusable">(current)</span>
</a> </a>
@ -72,7 +86,7 @@ else
else else
{ {
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="@GetUrl(childPage)" target="@GetTarget(childPage)" id="@($"navbarDropdown{childPage.PageId}")" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <a class="nav-link dropdown-toggle" id="@($"navbarDropdown{childPage.PageId}")" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" @attributes="_attributes">
<span class="@childPage.Icon" aria-hidden="true" /> <span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name @childPage.Name
</a> </a>

View File

@ -5,10 +5,17 @@
{ {
foreach (var childPage in GetChildPages()) foreach (var childPage in GetChildPages())
{ {
var _attributes = new Dictionary<string, object>();
_attributes.Add("href", GetUrl(childPage));
var _target = GetTarget(childPage);
if (!string.IsNullOrEmpty(_target))
{
_attributes.Add("target", _target);
}
if (childPage.PageId == PageState.Page.PageId) if (childPage.PageId == PageState.Page.PageId)
{ {
<li class="nav-item px-3" style="margin-left: @(childPage.Level * 15)px;"> <li class="nav-item px-3" style="margin-left: @(childPage.Level * 15)px;">
<a class="nav-link active" href="@GetUrl(childPage)" target="@GetTarget(childPage)"> <a class="nav-link active" @attributes="_attributes">
<span class="w-100" data-bs-toggle="collapse" data-bs-target=".navbar-collapse.show"> <span class="w-100" data-bs-toggle="collapse" data-bs-target=".navbar-collapse.show">
<span class="@childPage.Icon" aria-hidden="true" /> <span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name <span class="visually-hidden-focusable">(current)</span> @childPage.Name <span class="visually-hidden-focusable">(current)</span>
@ -19,7 +26,7 @@
else else
{ {
<li class="nav-item px-3" style="margin-left: @(childPage.Level * 15)px;"> <li class="nav-item px-3" style="margin-left: @(childPage.Level * 15)px;">
<a class="nav-link" href="@GetUrl(childPage)" target="@GetTarget(childPage)"> <a class="nav-link" @attributes="_attributes">
<span class="w-100" data-bs-toggle="collapse" data-bs-target=".navbar-collapse.show"> <span class="w-100" data-bs-toggle="collapse" data-bs-target=".navbar-collapse.show">
<span class="@childPage.Icon" aria-hidden="true" /> <span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name @childPage.Name
@ -38,10 +45,17 @@ else
<ul class="nav flex-column"> <ul class="nav flex-column">
@foreach (var childPage in GetChildPages()) @foreach (var childPage in GetChildPages())
{ {
var _attributes = new Dictionary<string, object>();
_attributes.Add("href", GetUrl(childPage));
var _target = GetTarget(childPage);
if (!string.IsNullOrEmpty(_target))
{
_attributes.Add("target", _target);
}
if (childPage.PageId == PageState.Page.PageId) if (childPage.PageId == PageState.Page.PageId)
{ {
<li class="nav-item px-3" style="margin-left: @(childPage.Level * 15)px;"> <li class="nav-item px-3" style="margin-left: @(childPage.Level * 15)px;">
<a class="nav-link active" href="@GetUrl(childPage)" target="@GetTarget(childPage)"> <a class="nav-link active" @attributes="_attributes">
<span class="w-100" data-bs-toggle="collapse" data-bs-target=".navbar-collapse.show"> <span class="w-100" data-bs-toggle="collapse" data-bs-target=".navbar-collapse.show">
<span class="@childPage.Icon" aria-hidden="true" /> <span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name <span class="visually-hidden-focusable">(current)</span> @childPage.Name <span class="visually-hidden-focusable">(current)</span>
@ -52,7 +66,7 @@ else
else else
{ {
<li class="nav-item px-3" style="margin-left: @(childPage.Level * 15)px;"> <li class="nav-item px-3" style="margin-left: @(childPage.Level * 15)px;">
<a class="nav-link" href="@GetUrl(childPage)" target="@GetTarget(childPage)"> <a class="nav-link" @attributes="_attributes">
<span class="w-100" data-bs-toggle="collapse" data-bs-target=".navbar-collapse.show"> <span class="w-100" data-bs-toggle="collapse" data-bs-target=".navbar-collapse.show">
<span class="@childPage.Icon" aria-hidden="true" /> <span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name @childPage.Name

View File

@ -11,12 +11,12 @@
<text>...</text> <text>...</text>
</Authorizing> </Authorizing>
<Authorized> <Authorized>
<button type="button" class="btn btn-primary" @onclick="UpdateProfile">@context.User.Identity.Name</button> <a href="@NavigateUrl("profile", "returnurl=" + _returnurl)" class="btn btn-primary">@context.User.Identity.Name</a>
</Authorized> </Authorized>
<NotAuthorized> <NotAuthorized>
@if (ShowRegister && PageState.Site.AllowRegistration) @if (ShowRegister && PageState.Site.AllowRegistration)
{ {
<button type="button" class="btn btn-primary" @onclick="RegisterUser">@Localizer["Register"]</button> <a href="@NavigateUrl("register", "returnurl=" + _returnurl)" class="btn btn-primary">@Localizer["Register"]</a>
} }
</NotAuthorized> </NotAuthorized>
</AuthorizeView> </AuthorizeView>
@ -31,17 +31,16 @@
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
_returnurl = WebUtility.UrlEncode(PageState.Route.PathAndQuery); if (!PageState.QueryString.ContainsKey("returnurl"))
} {
// remember current url
private void RegisterUser() _returnurl = WebUtility.UrlEncode(PageState.Route.PathAndQuery);
{ }
NavigationManager.NavigateTo(NavigateUrl("register", "returnurl=" + _returnurl)); else
} {
// use existing value
private void UpdateProfile() _returnurl = PageState.QueryString["returnurl"];
{ }
NavigationManager.NavigateTo(NavigateUrl("profile", "returnurl=" + _returnurl));
} }
} }

View File

@ -83,7 +83,7 @@
} }
catch (Exception ex) catch (Exception ex)
{ {
ModuleInstance.AddModuleMessage(ex.Message, MessageType.Error); AddModuleMessage(ex.Message, MessageType.Error);
} }
} }
@ -100,7 +100,7 @@
} }
catch (Exception ex) catch (Exception ex)
{ {
ModuleInstance.AddModuleMessage(ex.Message, MessageType.Error); AddModuleMessage(ex.Message, MessageType.Error);
} }
} }
} }

View File

@ -16,9 +16,10 @@ namespace Oqtane.Themes.OqtaneTheme
ContainerSettingsType = "Oqtane.Themes.OqtaneTheme.ContainerSettings, Oqtane.Client", ContainerSettingsType = "Oqtane.Themes.OqtaneTheme.ContainerSettings, Oqtane.Client",
Resources = new List<Resource>() Resources = new List<Resource>()
{ {
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.0/cyborg/bootstrap.min.css", Integrity = "sha512-jwIqEv8o/kTBMJVtbNCBrDqhBojl0YSUam+EFpLjVOC86Ci6t4ZciTnIkelFNOik+dEQVymKGcQLiaJZNAfWRg==", CrossOrigin = "anonymous" }, // obtained from https://cdnjs.com/libraries
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.2/cyborg/bootstrap.min.css", Integrity = "sha512-RfNxVfFNFgqk9MXO4TCKXYXn9hgc+keHCg3xFFGbnp2q7Cifda+YYzMTDHwsQtNx4DuqIMgfvZead7XOtB9CDQ==", CrossOrigin = "anonymous" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Theme.css" }, new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Theme.css" },
new Resource { ResourceType = ResourceType.Script, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/js/bootstrap.bundle.min.js", Integrity = "sha512-VK2zcvntEufaimc+efOYi622VN5ZacdnufnmX7zIhCPmjhKnOi9ZDMtg1/ug5l183f19gG1/cBstPO4D8N/Img==", CrossOrigin = "anonymous" } new Resource { ResourceType = ResourceType.Script, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/js/bootstrap.bundle.min.js", Integrity = "sha512-X/YkDZyjTf4wyc2Vy16YGCPHwAY8rZJY+POgokZjQB2mhIRFJCckEGc6YyX9eNsPfn0PzThEuNs+uaomE5CO6A==", CrossOrigin = "anonymous", Location = ResourceLocation.Body }
} }
}; };
} }

View File

@ -9,7 +9,6 @@ using Oqtane.UI;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Oqtane.Themes namespace Oqtane.Themes
@ -46,7 +45,7 @@ namespace Oqtane.Themes
{ {
if (PageState.Page.Resources != null) if (PageState.Page.Resources != null)
{ {
resources = PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level != ResourceLevel.Site && item.Namespace == type.Namespace).ToList(); resources = PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level == ResourceLevel.Page && item.Namespace == type.Namespace).ToList();
} }
} }
else // themecontrolbase, containerbase else // themecontrolbase, containerbase
@ -63,15 +62,18 @@ namespace Oqtane.Themes
var inline = 0; var inline = 0;
foreach (Resource resource in resources) foreach (Resource resource in resources)
{ {
if (!string.IsNullOrEmpty(resource.Url)) if (string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Interactive)
{ {
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url; if (!string.IsNullOrEmpty(resource.Url))
scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module, location = resource.Location.ToString().ToLower() }); {
} var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url;
else scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module, location = resource.Location.ToString().ToLower() });
{ }
inline += 1; else
await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Content, resource.Location.ToString().ToLower()); {
inline += 1;
await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Content, resource.Location.ToString().ToLower());
}
} }
} }
if (scripts.Any()) if (scripts.Any())

View File

@ -1,6 +1,7 @@
@using System.ComponentModel @using System.ComponentModel
@namespace Oqtane.UI @namespace Oqtane.UI
@inject SiteState SiteState @inject SiteState SiteState
@implements IDisposable
@if (ComponentType != null && _visible) @if (ComponentType != null && _visible)
{ {

View File

@ -1,6 +1,10 @@
@namespace Oqtane.UI
@using System.ComponentModel @using System.ComponentModel
@using Oqtane.Shared @using Oqtane.Shared
@inject SiteState SiteState @inject SiteState SiteState
@implements IDisposable
@* the following StreamRendering attribute is required - if it is removed the framework will not render the content in static rendering *@
@attribute [StreamRendering]
@if (!string.IsNullOrEmpty(_title)) @if (!string.IsNullOrEmpty(_title))
{ {
@ -15,6 +19,12 @@
private string _title = ""; private string _title = "";
private string _content = ""; private string _content = "";
[Parameter]
public string RenderMode { get; set; }
[Parameter]
public string Runtime { get; set; }
protected override void OnInitialized() protected override void OnInitialized()
{ {
((INotifyPropertyChanged)SiteState.Properties).PropertyChanged += PropertyChanged; ((INotifyPropertyChanged)SiteState.Properties).PropertyChanged += PropertyChanged;
@ -45,7 +55,7 @@
private string RemoveScripts(string headcontent) private string RemoveScripts(string headcontent)
{ {
if (!string.IsNullOrEmpty(headcontent)) if (!string.IsNullOrEmpty(headcontent) && RenderMode == RenderModes.Interactive)
{ {
var index = headcontent.IndexOf("<script"); var index = headcontent.IndexOf("<script");
while (index >= 0) while (index >= 0)

View File

@ -0,0 +1,23 @@
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components;
using Oqtane.Shared;
namespace Oqtane.UI
{
public static class InteractiveRenderMode
{
public static IComponentRenderMode GetInteractiveRenderMode(string runtime, bool prerender)
{
switch (runtime)
{
case Runtimes.Server:
return new InteractiveServerRenderMode(prerender: prerender);
case Runtimes.WebAssembly:
return new InteractiveWebAssemblyRenderMode(prerender: prerender);
case Runtimes.Auto:
return new InteractiveAutoRenderMode(prerender: prerender);
}
return new InteractiveServerRenderMode(prerender: prerender);
}
}
}

View File

@ -1,44 +1,18 @@
@namespace Oqtane.UI @namespace Oqtane.UI
@inject IStringLocalizer<ModuleInstance> Localizer @inject SiteState SiteState
@inject ILogService LoggingService
@inherits ErrorBoundary
@if (CurrentException is null) @if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Interactive)
{ {
if (_message != "" && _messagePosition == "top") <StreamRenderingDisabled ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" />
{
<ModuleMessage Message="@_message" Type="@_messageType" />
}
@if (ModuleType != null)
{
<DynamicComponent Type="@ModuleType" Parameters="@ModuleParameters"></DynamicComponent>
@if (_progressIndicator)
{
<div class="app-progress-indicator"></div>
}
}
if (_message != "" && _messagePosition == "bottom")
{
<ModuleMessage Message="@_message" Type="@_messageType" />
}
} }
else else
{ {
@if (!string.IsNullOrEmpty(_error)) <StreamRenderingEnabled ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" />
{
<ModuleMessage Message="@_error" Type="@MessageType.Error"/>
}
} }
@code { @code {
private string _message; // this component is on the static side of the render mode boundary
private string _error; // it passes state as serializable parameters across the boundary
private MessageType _messageType;
private string _messagePosition;
private bool _progressIndicator = false;
private Type ModuleType { get; set; }
private IDictionary<string, object> ModuleParameters { get; set; }
[CascadingParameter] [CascadingParameter]
protected PageState PageState { get; set; } protected PageState PageState { get; set; }
@ -46,82 +20,24 @@ else
[CascadingParameter] [CascadingParameter]
private Module ModuleState { get; set; } private Module ModuleState { get; set; }
private ModuleMessage ModuleMessage { get; set; }
protected override bool ShouldRender()
{
return PageState?.RenderId == ModuleState?.RenderId;
}
protected override void OnParametersSet()
{
_message = "";
if (ShouldRender())
{
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;
_messagePosition = "top";
}
else
{
_message = string.Format(Localizer["Error.Module.InvalidType"], ModuleState.ModuleDefinitionName);
_messageType = MessageType.Error;
_messagePosition = "top";
}
}
}
[Obsolete("AddModuleMessage is deprecated. Use AddModuleMessage in ModuleBase instead.", false)]
public void AddModuleMessage(string message, MessageType type) public void AddModuleMessage(string message, MessageType type)
{ {
AddModuleMessage(message, type, "top");
} }
[Obsolete("AddModuleMessage is deprecated. Use ModuleBase.AddModuleMessage instead.", false)]
public void AddModuleMessage(string message, MessageType type, string position) public void AddModuleMessage(string message, MessageType type, string position)
{ {
_message = message;
_messageType = type;
_messagePosition = position;
_progressIndicator = false;
StateHasChanged();
}
public void ShowProgressIndicator()
{
_progressIndicator = true;
StateHasChanged();
}
public void HideProgressIndicator()
{
_progressIndicator = false;
StateHasChanged();
}
protected override async Task OnErrorAsync(Exception exception)
{
// retrieve friendly localized error
_error = Localizer["Error.Module.Exception"];
// log error
string category = GetType().AssemblyQualifiedName;
string feature = Utilities.GetTypeNameLastSegment(category, 1);
await LoggingService.Log(null, ModuleState.PageId, ModuleState.ModuleId, PageState.User?.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();
} }
[Obsolete("ShowProgressIndicator is deprecated. Use ShowProgressIndicator in ModuleBase instead.", false)]
public void ShowProgressIndicator()
{
}
[Obsolete("HideProgressIndicator is deprecated. Use HideProgressIndicator in ModuleBase instead.", false)]
public void HideProgressIndicator()
{
}
} }

View File

@ -18,12 +18,14 @@ namespace Oqtane.UI
public string Action { get; set; } public string Action { get; set; }
public bool EditMode { get; set; } public bool EditMode { get; set; }
public DateTime LastSyncDate { get; set; } public DateTime LastSyncDate { get; set; }
public string RenderMode { get; set; }
public Shared.Runtime Runtime { get; set; } public Shared.Runtime Runtime { get; set; }
public int VisitorId { get; set; } public int VisitorId { get; set; }
public string RemoteIPAddress { get; set; } public string RemoteIPAddress { get; set; }
public string ReturnUrl { get; set; } public string ReturnUrl { get; set; }
public bool IsInternalNavigation { get; set; } public bool IsInternalNavigation { get; set; }
public Guid RenderId { get; set; } public Guid RenderId { get; set; }
public bool Refresh { get; set; }
public List<Page> Pages public List<Page> Pages
{ {

View File

@ -67,7 +67,7 @@ else
// pane matches current pane // pane matches current pane
if (Name.ToLower() == pane.ToLower()) if (Name.ToLower() == pane.ToLower())
{ {
if (module.ModuleId == PageState.ModuleId && PageState.Action != Constants.DefaultAction) if (PageState.ModuleId == module.ModuleId && PageState.Action != Constants.DefaultAction)
{ {
var moduleType = Type.GetType(module.ModuleType); var moduleType = Type.GetType(module.ModuleType);
if (moduleType != null) if (moduleType != null)
@ -123,7 +123,7 @@ else
private void CreateComponent(RenderTreeBuilder builder, Module module, int key) private void CreateComponent(RenderTreeBuilder builder, Module module, int key)
{ {
builder.OpenComponent(0, Type.GetType(Constants.ContainerComponent)); builder.OpenComponent(0, typeof(ContainerBuilder));
builder.AddAttribute(1, "ModuleState", module); builder.AddAttribute(1, "ModuleState", module);
if (key != -1) if (key != -1)
{ {

View File

@ -1,5 +0,0 @@
@namespace Oqtane.UI
@code {
// panelayouts are deprecated - this component is included for backward compatibility
}

View File

@ -0,0 +1,165 @@
@namespace Oqtane.UI
@inject SiteState ComponentSiteState
@inject IStringLocalizer<ModuleInstance> Localizer
@inject ILogService LoggingService
@inherits ErrorBoundary
<CascadingValue Value="@PageState">
<CascadingValue Value="@ModuleState">
@if (CurrentException is null)
{
@if (ModuleType != null)
{
@((MarkupString)$"<!-- rendermode: {ModuleState.RenderMode} -->")
<ModuleMessage @ref="moduleMessageTop" Message="@_messageContent" Type="@_messageType" />
@DynamicComponent
@if (_progressIndicator)
{
<div class="app-progress-indicator"></div>
}
<ModuleMessage @ref="moduleMessageBottom" Message="@_messageContent" Type="@_messageType" />
}
}
else
{
@if (!string.IsNullOrEmpty(_error))
{
<ModuleMessage Message="@_error" Type="@MessageType.Error" />
}
}
</CascadingValue>
</CascadingValue>
@code {
// this component is on the interactive side of the render mode boundary
// it receives state as serializable parameters so that the state can be made available to downstream components
private Type ModuleType { get; set; }
RenderFragment DynamicComponent { get; set; }
private string _messageContent;
private MessageType _messageType;
private string _messagePosition;
private bool _progressIndicator = false;
private string _error;
private ModuleMessage moduleMessageTop;
private ModuleMessage moduleMessageBottom;
[Parameter]
public SiteState SiteState { get; set; }
[Parameter]
public PageState PageState { get; set; }
[Parameter]
public Module ModuleState { get; set; }
protected override bool ShouldRender()
{
return PageState?.RenderId == ModuleState?.RenderId;
}
protected override void OnParametersSet()
{
_messageContent = "";
if (ShouldRender())
{
if (!string.IsNullOrEmpty(ModuleState.ModuleType))
{
ModuleType = Type.GetType(ModuleState.ModuleType);
if (ModuleType != null)
{
// repopulate the SiteState service based on the values passed in the SiteState parameter (this is how state is marshalled across the render mode boundary)
ComponentSiteState.Hydrate(SiteState);
DynamicComponent = builder =>
{
builder.OpenComponent(0, ModuleType);
builder.AddAttribute(1, "RenderModeBoundary", this);
builder.CloseComponent();
};
}
else
{
// module does not exist with typename specified
_messageContent = string.Format(Localizer["Error.Module.InvalidName"], Utilities.GetTypeNameLastSegment(ModuleState.ModuleType, 0));
_messageType = MessageType.Error;
_messagePosition = "top";
}
}
else
{
_messageContent = string.Format(Localizer["Error.Module.InvalidType"], ModuleState.ModuleDefinitionName);
_messageType = MessageType.Error;
_messagePosition = "top";
}
}
}
public void AddModuleMessage(string message, MessageType type)
{
AddModuleMessage(message, type, "top");
}
public void AddModuleMessage(string message, MessageType type, string position)
{
_messageContent = message;
_messageType = type;
_messagePosition = position;
_progressIndicator = false;
Refresh();
}
public void ShowProgressIndicator()
{
_progressIndicator = true;
StateHasChanged();
}
public void HideProgressIndicator()
{
_progressIndicator = false;
StateHasChanged();
}
private void DismissMessage()
{
_messageContent = "";
}
private void Refresh()
{
var updateTop = string.IsNullOrEmpty(_messageContent) || _messagePosition == "top";
var updateBottom = string.IsNullOrEmpty(_messageContent) || _messagePosition == "bottom";
if (updateTop && moduleMessageTop != null)
{
moduleMessageTop.RefreshMessage(_messageContent, _messageType);
}
if (updateBottom && moduleMessageBottom != null)
{
moduleMessageBottom.RefreshMessage(_messageContent, _messageType);
}
}
protected override async Task OnErrorAsync(Exception exception)
{
// retrieve friendly localized error
_error = Localizer["Error.Module.Exception"];
// log error
string category = GetType().AssemblyQualifiedName;
string feature = Utilities.GetTypeNameLastSegment(category, 1);
await LoggingService.Log(null, ModuleState.PageId, ModuleState.ModuleId, PageState.User?.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();
}
}

View File

@ -0,0 +1,106 @@
@namespace Oqtane.UI
@using Microsoft.AspNetCore.Http
@inject IInstallationService InstallationService
@inject IJSRuntime JSRuntime
@inject SiteState SiteState
@* the following StreamRendering attribute is required - if it is removed the framework will not render the content in static rendering *@
@attribute [StreamRendering]
@if (_initialized)
{
@if (!_installed)
{
<Installer />
}
else
{
if (RenderMode == RenderModes.Static)
{
<CascadingValue Value="@_pageState">
<SiteRouter RenderMode="@RenderMode" Runtime="@Runtime" OnStateChange="@ChangeState" />
</CascadingValue>
}
else
{
<div style="@_display">
<CascadingValue Value="@_pageState">
<SiteRouter RenderMode="@RenderMode" Runtime="@Runtime" OnStateChange="@ChangeState" />
</CascadingValue>
</div>
}
}
}
@code {
[Parameter]
public PageState PageState { get; set; } = null;
[Parameter]
public string RenderMode { get; set; }
[Parameter]
public string Runtime { get; set; }
[Parameter]
public string AntiForgeryToken { get; set; } = "";
[Parameter]
public string AuthorizationToken { get; set; } = "";
[Parameter]
public string Platform { get; set; } = "";
[CascadingParameter]
HttpContext HttpContext { get; set; }
private bool _initialized = false;
private bool _installed = false;
private string _display = "display: none;";
private PageState _pageState { get; set; }
protected override async Task OnParametersSetAsync()
{
SiteState.AntiForgeryToken = AntiForgeryToken;
SiteState.AuthorizationToken = AuthorizationToken;
SiteState.RemoteIPAddress = (_pageState != null) ? _pageState.RemoteIPAddress : "";
SiteState.Platform = Platform;
SiteState.IsPrerendering = (HttpContext != null) ? true : false;
if (Runtime == Runtimes.Hybrid)
{
var installation = await InstallationService.IsInstalled();
_installed = installation.Success;
if (installation.Alias != null)
{
SiteState.Alias = installation.Alias;
}
}
else
{
if (PageState != null)
{
_pageState = PageState;
SiteState.Alias = PageState.Alias;
_installed = true;
}
}
_initialized = true;
}
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
// prevents flash on initial interactive page load
_display = "";
StateHasChanged();
}
}
private void ChangeState(PageState pageState)
{
_pageState = pageState;
StateHasChanged();
}
}

View File

@ -1,11 +0,0 @@
using System;
namespace Oqtane.UI
{
[Obsolete("This enum is deprecated and will be removed in the upcoming major release, please use Oqtane.Shared.Runtime instead.")]
public enum Runtime
{
Server,
WebAssembly
}
}

View File

@ -1,5 +1,6 @@
@using System.Diagnostics.CodeAnalysis @using System.Diagnostics.CodeAnalysis
@using System.Net @using System.Net
@using Microsoft.AspNetCore.Http
@namespace Oqtane.UI @namespace Oqtane.UI
@inject AuthenticationStateProvider AuthenticationStateProvider @inject AuthenticationStateProvider AuthenticationStateProvider
@inject SiteState SiteState @inject SiteState SiteState
@ -11,8 +12,10 @@
@inject IUserService UserService @inject IUserService UserService
@inject IUrlMappingService UrlMappingService @inject IUrlMappingService UrlMappingService
@inject ILogService LogService @inject ILogService LogService
@inject ISettingService SettingService
@inject IJSRuntime JSRuntime @inject IJSRuntime JSRuntime
@implements IHandleAfterRender @implements IHandleAfterRender
@implements IDisposable
@if (!string.IsNullOrEmpty(_error)) @if (!string.IsNullOrEmpty(_error))
{ {
@ -28,14 +31,11 @@
private PageState _pagestate; private PageState _pagestate;
private string _error = ""; private string _error = "";
[Parameter]
public string Runtime { get; set; }
[Parameter] [Parameter]
public string RenderMode { get; set; } public string RenderMode { get; set; }
[Parameter] [Parameter]
public int VisitorId { get; set; } public string Runtime { get; set; }
[CascadingParameter] [CascadingParameter]
PageState PageState { get; set; } PageState PageState { get; set; }
@ -52,9 +52,9 @@
DynamicComponent = builder => DynamicComponent = builder =>
{ {
if (PageState != null) if (PageState != null && !PageState.Refresh)
{ {
builder.OpenComponent(0, Type.GetType(Constants.PageComponent)); builder.OpenComponent(0, typeof(ThemeBuilder));
builder.CloseComponent(); builder.CloseComponent();
} }
}; };
@ -67,7 +67,7 @@
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
if (PageState == null) if (PageState == null || PageState.Refresh)
{ {
await Refresh(); await Refresh();
} }
@ -99,12 +99,12 @@
var editmode = false; var editmode = false;
var refresh = false; var refresh = false;
var lastsyncdate = DateTime.MinValue; var lastsyncdate = DateTime.MinValue;
var runtime = (Shared.Runtime)Enum.Parse(typeof(Shared.Runtime), Runtime); var visitorId = -1;
_error = ""; _error = "";
Route route = new Route(_absoluteUri, SiteState.Alias.Path); Route route = new Route(_absoluteUri, SiteState.Alias.Path);
int moduleid = (int.TryParse(route.ModuleId, out moduleid)) ? moduleid : -1; int moduleid = int.Parse(route.ModuleId);
var action = (!string.IsNullOrEmpty(route.Action)) ? route.Action : Constants.DefaultAction; var action = route.Action;
var querystring = Utilities.ParseQueryString(route.Query); var querystring = Utilities.ParseQueryString(route.Query);
var returnurl = ""; var returnurl = "";
@ -144,66 +144,43 @@
refresh = true; refresh = true;
} }
// verify user is authenticated for current site
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
if (authState.User.Identity.IsAuthenticated && authState.User.Claims.Any(item => item.Type == "sitekey" && item.Value == SiteState.Alias.SiteKey))
{
// get user
user = await UserService.GetUserAsync(authState.User.Identity.Name, SiteState.Alias.SiteId);
if (user != null)
{
user.IsAuthenticated = authState.User.Identity.IsAuthenticated;
}
}
if (PageState != null) if (PageState != null)
{ {
editmode = PageState.EditMode; editmode = PageState.EditMode;
lastsyncdate = PageState.LastSyncDate; lastsyncdate = PageState.LastSyncDate;
} visitorId = PageState.VisitorId;
if (PageState?.Page.Path != route.PagePath)
{
editmode = false; // reset edit mode when navigating to different page
}
if (querystring.ContainsKey("edit") && querystring["edit"] == "true")
{
editmode = true; // querystring can set edit mode
} }
// get user if (PageState.RenderMode == RenderModes.Interactive)
if (PageState == null || refresh || PageState.Alias.SiteId != SiteState.Alias.SiteId)
{ {
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); // process any sync events (for synchrozing the client application with the server)
// verify user is authenticated for current site var sync = await SyncService.GetSyncEventsAsync(lastsyncdate);
if (authState.User.Identity.IsAuthenticated && authState.User.Claims.Any(item => item.Type == "sitekey" && item.Value == SiteState.Alias.SiteKey)) lastsyncdate = sync.SyncDate;
if (sync.SyncEvents.Any())
{ {
user = await UserService.GetUserAsync(authState.User.Identity.Name, SiteState.Alias.SiteId); // reload client application if server was restarted
if (user != null) if (sync.SyncEvents.Exists(item => item.Action == SyncEventActions.Reload && item.EntityName == EntityNames.Host))
{ {
user.IsAuthenticated = authState.User.Identity.IsAuthenticated; NavigationManager.NavigateTo(_absoluteUri, true);
return;
}
// refresh PageState when site information has changed
if (sync.SyncEvents.Exists(item => item.Action == SyncEventActions.Refresh && item.EntityName == EntityNames.Site && item.EntityId == SiteState.Alias.SiteId))
{
refresh = true;
} }
}
}
else
{
user = PageState.User;
}
// process any sync events
var sync = await SyncService.GetSyncEventsAsync(lastsyncdate);
lastsyncdate = sync.SyncDate;
if (sync.SyncEvents.Any())
{
// reload client application if server was restarted
if (sync.SyncEvents.Exists(item => item.Action == SyncEventActions.Reload && item.EntityName == EntityNames.Host))
{
NavigationManager.NavigateTo(_absoluteUri, true);
return;
}
// reload client application if site runtime/rendermode was modified
if (sync.SyncEvents.Exists(item => item.Action == SyncEventActions.Reload && item.EntityName == EntityNames.Site && item.EntityId == SiteState.Alias.SiteId))
{
NavigationManager.NavigateTo(_absoluteUri, true);
return;
}
// reload client application if current user auth information has changed
if (user != null && sync.SyncEvents.Exists(item => item.Action == SyncEventActions.Reload && item.EntityName == EntityNames.User && item.EntityId == user.UserId))
{
NavigationManager.NavigateTo(_absoluteUri, true);
return;
}
// refresh PageState when site information has changed
if (sync.SyncEvents.Exists(item => item.Action == SyncEventActions.Refresh && item.EntityName == EntityNames.Site && item.EntityId == SiteState.Alias.SiteId))
{
refresh = true;
} }
} }
@ -220,6 +197,12 @@
if (site != null) if (site != null)
{ {
if (Runtime == Runtimes.Hybrid && !site.Hybrid)
{
_error = "Hybrid Integration Is Not Enabled For This Site";
return;
}
if (PageState == null || refresh || PageState.Page.Path != route.PagePath) if (PageState == null || refresh || PageState.Page.Path != route.PagePath)
{ {
page = site.Pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase)); page = site.Pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase));
@ -255,6 +238,7 @@
{ {
// redirect to the personalized page // redirect to the personalized page
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, personalized.Path, ""), false); NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, personalized.Path, ""), false);
return;
} }
} }
} }
@ -264,6 +248,24 @@
// check if user is authorized to view page // check if user is authorized to view page
if (UserSecurity.IsAuthorized(user, PermissionNames.View, page.PermissionList) && (Utilities.IsPageModuleVisible(page.EffectiveDate, page.ExpiryDate) || UserSecurity.IsAuthorized(user, PermissionNames.Edit, page.PermissionList))) if (UserSecurity.IsAuthorized(user, PermissionNames.View, page.PermissionList) && (Utilities.IsPageModuleVisible(page.EffectiveDate, page.ExpiryDate) || UserSecurity.IsAuthorized(user, PermissionNames.Edit, page.PermissionList)))
{ {
// edit mode
if (user != null)
{
if (querystring.ContainsKey("editmode") && querystring["edit"] == "true")
{
editmode = true;
}
else
{
editmode = (page.PageId == ((user.Settings.ContainsKey("CP-editmode")) ? int.Parse(user.Settings["CP-editmode"]) : -1));
if (!editmode)
{
var userSettings = new Dictionary<string, string> { { "CP-editmode", "-1" } };
await SettingService.UpdateUserSettingsAsync(userSettings, user.UserId);
}
}
}
// load additional metadata for current page // load additional metadata for current page
page = ProcessPage(page, site, user, SiteState.Alias); page = ProcessPage(page, site, user, SiteState.Alias);
@ -285,16 +287,21 @@
Action = action, Action = action,
EditMode = editmode, EditMode = editmode,
LastSyncDate = lastsyncdate, LastSyncDate = lastsyncdate,
Runtime = runtime, RenderMode = RenderMode,
VisitorId = VisitorId, Runtime = (Shared.Runtime)Enum.Parse(typeof(Shared.Runtime), Runtime),
VisitorId = visitorId,
RemoteIPAddress = SiteState.RemoteIPAddress, RemoteIPAddress = SiteState.RemoteIPAddress,
ReturnUrl = returnurl, ReturnUrl = returnurl,
IsInternalNavigation = _isInternalNavigation, IsInternalNavigation = _isInternalNavigation,
RenderId = Guid.NewGuid() RenderId = Guid.NewGuid(),
Refresh = false
}; };
OnStateChange?.Invoke(_pagestate); OnStateChange?.Invoke(_pagestate);
await ScrollToFragment(_pagestate.Uri);
if (PageState.RenderMode == RenderModes.Interactive)
{
await ScrollToFragment(_pagestate.Uri);
}
} }
else else
{ {
@ -317,7 +324,7 @@
} }
else // not mapped else // not mapped
{ {
if (user == null && Utilities.IsPageModuleVisible(page.EffectiveDate, page.ExpiryDate)) if (user == null)
{ {
// redirect to login page if user not logged in as they may need to be authenticated // redirect to login page if user not logged in as they may need to be authenticated
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "login", "?returnurl=" + WebUtility.UrlEncode(route.PathAndQuery))); NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "login", "?returnurl=" + WebUtility.UrlEncode(route.PathAndQuery)));
@ -357,17 +364,15 @@
page.ThemeType = site.DefaultThemeType; page.ThemeType = site.DefaultThemeType;
} }
var theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == page.ThemeType)); var theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == page.ThemeType));
Type themetype = Type.GetType(page.ThemeType); if (theme == null)
if (themetype == null || theme == null)
{ {
// fallback to default Oqtane theme // fallback to default Oqtane theme
page.ThemeType = Constants.DefaultTheme; page.ThemeType = Constants.DefaultTheme;
themetype = Type.GetType(Constants.DefaultTheme);
theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == page.ThemeType)); theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == page.ThemeType));
} }
Type themetype = Type.GetType(page.ThemeType);
string panes = ""; string panes = "";
if (themetype != null && theme != null) if (themetype != null)
{ {
// get resources for theme (ITheme) // get resources for theme (ITheme)
page.Resources = ManagePageResources(page.Resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName)); page.Resources = ManagePageResources(page.Resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName));
@ -410,8 +415,10 @@
{ {
var paneindex = new Dictionary<string, int>(); var paneindex = new Dictionary<string, int>();
foreach (Module module in modules) foreach (Module module in modules.Where(item => item.PageId == page.PageId || item.ModuleId == moduleid))
{ {
var typename = Constants.ErrorModule;
// initialize module control properties // initialize module control properties
module.SecurityAccessLevel = SecurityAccessLevel.Host; module.SecurityAccessLevel = SecurityAccessLevel.Host;
module.ControlTitle = ""; module.ControlTitle = "";
@ -420,110 +427,108 @@
module.PaneModuleIndex = -1; module.PaneModuleIndex = -1;
module.PaneModuleCount = 0; module.PaneModuleCount = 0;
if (module.PageId == page.PageId || module.ModuleId == moduleid) if (module.ModuleDefinition != null && (module.ModuleDefinition.Runtimes == "" || module.ModuleDefinition.Runtimes.Contains(Runtime)))
{ {
var typename = Constants.ErrorModule; page.Resources = ManagePageResources(page.Resources, module.ModuleDefinition.Resources, ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName));
if (module.ModuleDefinition != null && (module.ModuleDefinition.Runtimes == "" || module.ModuleDefinition.Runtimes.Contains(Runtime))) // handle default action
if (action == Constants.DefaultAction && !string.IsNullOrEmpty(module.ModuleDefinition.DefaultAction))
{ {
typename = module.ModuleDefinition.ControlTypeTemplate; action = module.ModuleDefinition.DefaultAction;
}
// handle default action // get typename template
if (action == Constants.DefaultAction && !string.IsNullOrEmpty(module.ModuleDefinition.DefaultAction)) typename = module.ModuleDefinition.ControlTypeTemplate;
if (module.ModuleDefinition.ControlTypeRoutes != "")
{
// process custom action routes
foreach (string route in module.ModuleDefinition.ControlTypeRoutes.Split(';', StringSplitOptions.RemoveEmptyEntries))
{ {
action = module.ModuleDefinition.DefaultAction; if (route.StartsWith(action + "="))
}
// check if the module defines custom action routes
if (module.ModuleDefinition.ControlTypeRoutes != "")
{
foreach (string route in module.ModuleDefinition.ControlTypeRoutes.Split(';', StringSplitOptions.RemoveEmptyEntries))
{ {
if (route.StartsWith(action + "=")) typename = route.Replace(action + "=", "");
{ break;
typename = route.Replace(action + "=", "");
}
} }
} }
}
}
// get module resources // create typename
page.Resources = ManagePageResources(page.Resources, module.ModuleDefinition.Resources, ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName)); if (Constants.DefaultModuleActions.Contains(action, StringComparer.OrdinalIgnoreCase))
} {
typename = Constants.DefaultModuleActionsTemplate.Replace(Constants.ActionToken, action);
}
else
{
typename = typename.Replace(Constants.ActionToken, action);
}
// ensure component exists and implements IModuleControl // ensure component exists and implements IModuleControl
module.ModuleType = ""; module.ModuleType = "";
if (Constants.DefaultModuleActions.Contains(action, StringComparer.OrdinalIgnoreCase)) Type moduletype = Type.GetType(typename, false, true); // case insensitive
{ if (moduletype != null && moduletype.GetInterfaces().Contains(typeof(IModuleControl)))
typename = Constants.DefaultModuleActionsTemplate.Replace(Constants.ActionToken, action); {
} module.ModuleType = Utilities.GetFullTypeName(moduletype.AssemblyQualifiedName); // get actual type name
else }
{
typename = typename.Replace(Constants.ActionToken, action);
}
Type moduletype = Type.GetType(typename, false, true); // case insensitive
if (moduletype != null && moduletype.GetInterfaces().Contains(typeof(IModuleControl)))
{
module.ModuleType = Utilities.GetFullTypeName(moduletype.AssemblyQualifiedName); // get actual type name
}
// get additional metadata from IModuleControl interface if (moduletype != null && module.ModuleType != "")
if (moduletype != null && module.ModuleType != "") {
// retrieve module component resources
var moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
module.RenderMode = moduleobject.RenderMode;
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
if (action.ToLower() == "settings" && module.ModuleDefinition != null)
{ {
// retrieve module component resources // settings components are embedded within a framework settings module
var moduleobject = Activator.CreateInstance(moduletype) as IModuleControl; moduletype = Type.GetType(module.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, action), false, true);
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace); if (moduletype != null)
if (action.ToLower() == "settings" && module.ModuleDefinition != null)
{ {
// settings components are embedded within a framework settings module moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
moduletype = Type.GetType(module.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, action), false, true); page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
if (moduletype != null)
{
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
}
}
// additional metadata needed for admin components
if (module.ModuleId == moduleid && action != "")
{
module.ControlTitle = moduleobject.Title;
module.SecurityAccessLevel = moduleobject.SecurityAccessLevel;
module.Actions = moduleobject.Actions;
module.UseAdminContainer = moduleobject.UseAdminContainer;
} }
} }
// validate that module's pane exists in current page // additional metadata needed for admin components
if (page.Panes.FindIndex(item => item.Equals(module.Pane, StringComparison.OrdinalIgnoreCase)) == -1) if (module.ModuleId == moduleid && action != "")
{ {
// fallback to default pane if it exists module.ControlTitle = moduleobject.Title;
if (page.Panes.FindIndex(item => item.Equals(PaneNames.Default, StringComparison.OrdinalIgnoreCase)) != -1) module.SecurityAccessLevel = moduleobject.SecurityAccessLevel;
{ module.Actions = moduleobject.Actions;
module.Pane = PaneNames.Default; module.UseAdminContainer = moduleobject.UseAdminContainer;
}
else // otherwise admin pane (legacy)
{
module.Pane = PaneNames.Admin;
}
} }
}
// calculate module position within pane // validate that module's pane exists in current page
if (paneindex.ContainsKey(module.Pane.ToLower())) if (page.Panes.FindIndex(item => item.Equals(module.Pane, StringComparison.OrdinalIgnoreCase)) == -1)
{
// fallback to default pane if it exists
if (page.Panes.FindIndex(item => item.Equals(PaneNames.Default, StringComparison.OrdinalIgnoreCase)) != -1)
{ {
paneindex[module.Pane.ToLower()] += 1; module.Pane = PaneNames.Default;
} }
else else // otherwise admin pane (legacy)
{ {
paneindex.Add(module.Pane.ToLower(), 0); module.Pane = PaneNames.Admin;
} }
}
module.PaneModuleIndex = paneindex[module.Pane.ToLower()]; // calculate module position within pane
if (paneindex.ContainsKey(module.Pane.ToLower()))
{
paneindex[module.Pane.ToLower()] += 1;
}
else
{
paneindex.Add(module.Pane.ToLower(), 0);
}
// container fallback module.PaneModuleIndex = paneindex[module.Pane.ToLower()];
if (string.IsNullOrEmpty(module.ContainerType))
{ // container fallback
module.ContainerType = defaultcontainertype; if (string.IsNullOrEmpty(module.ContainerType))
} {
module.ContainerType = defaultcontainertype;
} }
} }
@ -558,9 +563,7 @@
// ensure resource does not exist already // ensure resource does not exist already
if (!pageresources.Exists(item => item.Url.ToLower() == resource.Url.ToLower())) if (!pageresources.Exists(item => item.Url.ToLower() == resource.Url.ToLower()))
{ {
resource.Level = level; pageresources.Add(resource.Clone(level, name));
resource.Namespace = name;
pageresources.Add(resource);
} }
} }
} }

View File

@ -0,0 +1,21 @@
@attribute [StreamRendering(false)]
@if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Static)
{
<RenderModeBoundary ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" />
}
else
{
<RenderModeBoundary ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" @rendermode="@InteractiveRenderMode.GetInteractiveRenderMode(PageState.Site.Runtime, PageState.Site.Prerender)" />
}
@code {
[Parameter]
public SiteState SiteState { get; set; }
[Parameter]
public PageState PageState { get; set; }
[Parameter]
public Module ModuleState { get; set; }
}

View File

@ -0,0 +1,21 @@
@attribute [StreamRendering(true)]
@if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Static)
{
<RenderModeBoundary ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" />
}
else
{
<RenderModeBoundary ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" @rendermode="@InteractiveRenderMode.GetInteractiveRenderMode(PageState.Site.Runtime, PageState.Site.Prerender)" />
}
@code {
[Parameter]
public SiteState SiteState { get; set; }
[Parameter]
public PageState PageState { get; set; }
[Parameter]
public Module ModuleState { get; set; }
}

View File

@ -34,14 +34,14 @@
var headcontent = ""; var headcontent = "";
// favicon // favicon
var favicon = "favicon.ico";
var favicontype = "x-icon";
if (PageState.Site.FaviconFileId != null) if (PageState.Site.FaviconFileId != null)
{ {
favicon = Utilities.FileUrl(PageState.Alias, PageState.Site.FaviconFileId.Value); headcontent += $"<link id=\"app-favicon\" rel=\"icon\" href=\"{Utilities.FileUrl(PageState.Alias, PageState.Site.FaviconFileId.Value)}\" />\n";
favicontype = favicon.Substring(favicon.LastIndexOf(".") + 1); }
else
{
headcontent += $"<link id=\"app-favicon\" rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n";
} }
headcontent += $"<link id=\"app-favicon\" rel=\"shortcut icon\" type=\"image/{favicontype}\" href=\"{favicon}\" />\n";
// head content // head content
AddHeadContent(headcontent, PageState.Site.HeadContent); AddHeadContent(headcontent, PageState.Site.HeadContent);
@ -57,8 +57,7 @@
DynamicComponent = builder => DynamicComponent = builder =>
{ {
var themeType = Type.GetType(PageState.Page.ThemeType); builder.OpenComponent(0, Type.GetType(PageState.Page.ThemeType));
builder.OpenComponent(0, themeType);
builder.CloseComponent(); builder.CloseComponent();
}; };
} }
@ -73,7 +72,7 @@
while (index >= 0) while (index >= 0)
{ {
var element = content.Substring(index, content.IndexOf(">", index) - index + 1); var element = content.Substring(index, content.IndexOf(">", index) - index + 1);
if (!string.IsNullOrEmpty(element) && !element.ToLower().StartsWith("<script") && !element.ToLower().StartsWith("</script")) if (!string.IsNullOrEmpty(element) && (PageState.RenderMode == RenderModes.Static || (!element.ToLower().StartsWith("<script") && !element.ToLower().StartsWith("</script"))))
{ {
if (!headcontent.Contains(element)) if (!headcontent.Contains(element))
{ {
@ -145,6 +144,7 @@
string integrity = ""; string integrity = "";
string crossorigin = ""; string crossorigin = "";
string type = ""; string type = "";
var dataAttributes = new Dictionary<string, string>();
foreach (var attribute in attributes) foreach (var attribute in attributes)
{ {
if (attribute.Contains("=")) if (attribute.Contains("="))
@ -167,6 +167,12 @@
case "type": case "type":
type = value[1]; type = value[1];
break; break;
default:
if(!string.IsNullOrWhiteSpace(value[0]) && value[0].StartsWith("data-"))
{
dataAttributes.Add(value[0], value[1]);
}
break;
} }
} }
} }
@ -174,7 +180,7 @@
if (!string.IsNullOrEmpty(src)) if (!string.IsNullOrEmpty(src))
{ {
src = (src.Contains("://")) ? src : PageState.Alias.BaseUrl + src; src = (src.Contains("://")) ? src : PageState.Alias.BaseUrl + src;
scripts.Add(new { href = src, bundle = "", integrity = integrity, crossorigin = crossorigin, es6module = (type == "module"), location = location.ToString().ToLower() }); scripts.Add(new { href = src, bundle = "", integrity = integrity, crossorigin = crossorigin, es6module = (type == "module"), location = location.ToString().ToLower(), dataAttributes = dataAttributes });
} }
else else
{ {
@ -194,4 +200,67 @@
await interop.IncludeScripts(scripts.ToArray()); await interop.IncludeScripts(scripts.ToArray());
} }
} }
private string ManageStyleSheets(List<Resource> resources, Alias alias)
{
var stylesheets = "";
if (resources != null)
{
string batch = DateTime.UtcNow.ToString("yyyyMMddHHmmssfff");
int count = 0;
foreach (var resource in resources.Where(item => item.ResourceType == ResourceType.Stylesheet))
{
if (resource.Url.StartsWith("~"))
{
resource.Url = resource.Url.Replace("~", "/Themes/" + Utilities.GetTypeName(resource.Namespace) + "/").Replace("//", "/");
}
if (!resource.Url.Contains("://") && alias.BaseUrl != "" && !resource.Url.StartsWith(alias.BaseUrl))
{
resource.Url = alias.BaseUrl + resource.Url;
}
if (!stylesheets.Contains(resource.Url, StringComparison.OrdinalIgnoreCase))
{
count++;
string id = "id=\"app-stylesheet-" + ResourceLevel.Page.ToString().ToLower() + "-" + batch + "-" + count.ToString("00") + "\" ";
stylesheets += "<link " + id + "rel=\"stylesheet\" href=\"" + resource.Url + "\"" + (!string.IsNullOrEmpty(resource.Integrity) ? " integrity=\"" + resource.Integrity + "\"" : "") + (!string.IsNullOrEmpty(resource.CrossOrigin) ? " crossorigin=\"" + resource.CrossOrigin + "\"" : "") + " type=\"text/css\"/>" + Environment.NewLine;
}
}
}
return stylesheets;
}
private string ManageScripts(List<Resource> resources, Alias alias)
{
var scripts = "";
if (resources != null)
{
foreach (var resource in resources.Where(item => item.ResourceType == ResourceType.Script && item.Location == ResourceLocation.Head))
{
var script = CreateScript(resource, alias);
if (!scripts.Contains(script, StringComparison.OrdinalIgnoreCase))
{
scripts += script + Environment.NewLine;
}
}
}
return scripts;
}
private string CreateScript(Resource resource, Alias alias)
{
if (!string.IsNullOrEmpty(resource.Url))
{
var url = (resource.Url.Contains("://")) ? resource.Url : alias.BaseUrl + resource.Url;
return "<script src=\"" + url + "\"" +
((!string.IsNullOrEmpty(resource.Integrity)) ? " integrity=\"" + resource.Integrity + "\"" : "") +
((!string.IsNullOrEmpty(resource.CrossOrigin)) ? " crossorigin=\"" + resource.CrossOrigin + "\"" : "") +
"></script>";
}
else
{
// inline script
return "<script>" + resource.Content + "</script>";
}
}
} }

View File

@ -5,10 +5,13 @@
@using System.Net.Http.Json @using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Authorization @using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing @using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web @using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.Extensions.Localization @using Microsoft.Extensions.Localization
@using Microsoft.JSInterop @using Microsoft.JSInterop
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Oqtane.Client @using Oqtane.Client
@using Oqtane.Models @using Oqtane.Models

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<Version>5.0.2</Version> <Version>5.1.0</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/v5.0.2</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.0</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 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<Version>5.0.2</Version> <Version>5.1.0</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/v5.0.2</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.0</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>
@ -33,9 +33,9 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="EFCore.NamingConventions" Version="8.0.2" /> <PackageReference Include="EFCore.NamingConventions" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.3" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.2" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<Version>5.0.2</Version> <Version>5.1.0</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/v5.0.2</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.0</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>
@ -33,7 +33,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<Version>5.0.2</Version> <Version>5.1.0</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/v5.0.2</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.0</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>
@ -33,7 +33,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,6 +1,16 @@
<DynamicComponent Type="@ComponentType"></DynamicComponent> @using Oqtane.Shared;
<DynamicComponent Type="@ComponentType" Parameters="@Parameters"></DynamicComponent>
@code { @code {
Type ComponentType = Type.GetType("Oqtane.Head, Oqtane.Client"); Type ComponentType = Type.GetType("Oqtane.UI.Head, Oqtane.Client");
private IDictionary<string, object> Parameters { get; set; }
protected override void OnInitialized()
{
Parameters = new Dictionary<string, object>();
Parameters.Add(new KeyValuePair<string, object>("RenderMode", RenderModes.Interactive));
Parameters.Add(new KeyValuePair<string, object>("Runtime", Runtimes.Hybrid));
}
} }

View File

@ -1,5 +1,6 @@
@using System.Text.Json; @using System.Text.Json;
@using System.Text.Json.Nodes; @using System.Text.Json.Nodes;
@using Oqtane.Shared;
@if (string.IsNullOrEmpty(message)) @if (string.IsNullOrEmpty(message))
{ {
@ -11,19 +12,16 @@ else
} }
@code { @code {
Type ComponentType = Type.GetType("Oqtane.App, Oqtane.Client"); Type ComponentType = Type.GetType("Oqtane.UI.Routes, Oqtane.Client");
private IDictionary<string, object> Parameters { get; set; } private IDictionary<string, object> Parameters { get; set; }
private string message = ""; private string message = "";
protected override void OnInitialized() protected override void OnInitialized()
{ {
Parameters = new Dictionary<string, object>(); Parameters = new Dictionary<string, object>();
Parameters.Add(new KeyValuePair<string, object>("AntiForgeryToken", "")); Parameters.Add(new KeyValuePair<string, object>("RenderMode", RenderModes.Interactive));
Parameters.Add(new KeyValuePair<string, object>("Runtime", "Hybrid")); Parameters.Add(new KeyValuePair<string, object>("Runtime", Runtimes.Hybrid));
Parameters.Add(new KeyValuePair<string, object>("RenderMode", "Hybrid")); Parameters.Add(new KeyValuePair<string, object>("Platform", DeviceInfo.Current.Platform.ToString()));
Parameters.Add(new KeyValuePair<string, object>("VisitorId", -1));
Parameters.Add(new KeyValuePair<string, object>("RemoteIPAddress", ""));
Parameters.Add(new KeyValuePair<string, object>("AuthorizationToken", ""));
if (MauiConstants.UseAppSettings) if (MauiConstants.UseAppSettings)
{ {

View File

@ -24,7 +24,7 @@ public static class MauiProgram
builder.Services.AddMauiBlazorWebView(); builder.Services.AddMauiBlazorWebView();
#if DEBUG #if DEBUG
builder.Services.AddBlazorWebViewDeveloperTools(); builder.Services.AddBlazorWebViewDeveloperTools();
#endif #endif
var apiurl = LoadAppSettings(); var apiurl = LoadAppSettings();
@ -44,10 +44,10 @@ public static class MauiProgram
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources"); builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
// register auth services // register auth services
builder.Services.AddOqtaneAuthorization(); builder.Services.AddOqtaneAuthentication();
// register scoped core services // register scoped core services
builder.Services.AddOqtaneScopedServices(); builder.Services.AddOqtaneClientScopedServices();
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies(); var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
foreach (var assembly in assemblies) foreach (var assembly in assemblies)
@ -255,6 +255,16 @@ public static class MauiProgram
services.AddScoped(serviceType ?? implementationType, implementationType); services.AddScoped(serviceType ?? implementationType, implementationType);
} }
} }
implementationTypes = assembly.GetInterfaces<IClientService>();
foreach (var implementationType in implementationTypes)
{
if (implementationType.AssemblyQualifiedName != null)
{
var serviceType = Type.GetType(implementationType.AssemblyQualifiedName.Replace(implementationType.Name, $"I{implementationType.Name}"));
services.AddScoped(serviceType ?? implementationType, implementationType);
}
}
} }
catch catch
{ {

View File

@ -6,7 +6,7 @@
<!-- <TargetFrameworks>net8.0-android;net8.0-ios;net8.0-maccatalyst</TargetFrameworks> --> <!-- <TargetFrameworks>net8.0-android;net8.0-ios;net8.0-maccatalyst</TargetFrameworks> -->
<!-- <TargetFrameworks>$(TargetFrameworks);net8.0-tizen</TargetFrameworks> --> <!-- <TargetFrameworks>$(TargetFrameworks);net8.0-tizen</TargetFrameworks> -->
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<Version>5.0.2</Version> <Version>5.1.0</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -14,7 +14,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/v5.0.2</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.0</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.Maui</RootNamespace> <RootNamespace>Oqtane.Maui</RootNamespace>
@ -31,7 +31,7 @@
<ApplicationIdGuid>0E29FC31-1B83-48ED-B6E0-9F3C67B775D4</ApplicationIdGuid> <ApplicationIdGuid>0E29FC31-1B83-48ED-B6E0-9F3C67B775D4</ApplicationIdGuid>
<!-- Versions --> <!-- Versions -->
<ApplicationDisplayVersion>5.0.2</ApplicationDisplayVersion> <ApplicationDisplayVersion>5.1.0</ApplicationDisplayVersion>
<ApplicationVersion>1</ApplicationVersion> <ApplicationVersion>1</ApplicationVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion> <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion>
@ -65,14 +65,15 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.1" /> <PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.1" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.1" /> <PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.3" />
<PackageReference Include="System.Net.Http.Json" Version="8.0.0" /> <PackageReference Include="System.Net.Http.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.Maui.Controls" Version="8.0.3" /> <PackageReference Include="Microsoft.Maui.Controls" Version="8.0.10" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="8.0.3" /> <PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.10" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="8.0.10" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -228,3 +228,7 @@ app {
.app-fas { .app-fas {
margin-left: 5px; margin-left: 5px;
} }
.app-form-inline {
display: inline-block;
}

View File

@ -6,8 +6,6 @@
<title>Oqtane Maui</title> <title>Oqtane Maui</title>
<base href="/" /> <base href="/" />
<link id="app-favicon" rel="shortcut icon" type="image/x-icon" href="" /> <link id="app-favicon" rel="shortcut icon" type="image/x-icon" href="" />
<script src="js/app.js"></script>
<script src="js/loadjs.min.js"></script>
<link rel="stylesheet" href="css/app.css" /> <link rel="stylesheet" href="css/app.css" />
<link id="app-stylesheet-page" /> <link id="app-stylesheet-page" />
<link id="app-stylesheet-module" /> <link id="app-stylesheet-module" />
@ -25,6 +23,8 @@
<a class="dismiss">🗙</a> <a class="dismiss">🗙</a>
</div> </div>
<script src="js/app.js"></script>
<script src="js/loadjs.min.js"></script>
<script src="js/interop.js"></script> <script src="js/interop.js"></script>
<script src="_framework/blazor.webview.js" autostart="false"></script> <script src="_framework/blazor.webview.js" autostart="false"></script>

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>5.0.2</version> <version>5.1.0</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/v5.0.2</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.0</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>5.0.2</version> <version>5.1.0</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/v5.0.2/Oqtane.Framework.5.0.2.Upgrade.zip</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v5.1.0/Oqtane.Framework.5.1.0.Upgrade.zip</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.2</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.0</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>5.0.2</version> <version>5.1.0</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/v5.0.2</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.0</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>5.0.2</version> <version>5.1.0</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/v5.0.2</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.0</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>5.0.2</version> <version>5.1.0</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/v5.0.2</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.0</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\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.0.2.Install.zip" -Force Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.1.0.Install.zip" -Force

View File

@ -1 +1 @@
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.0.2.Upgrade.zip" -Force Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.1.0.Upgrade.zip" -Force

View File

@ -0,0 +1,722 @@
@namespace Oqtane.Components
@using System.Net
@using System.Security.Claims
@using Microsoft.AspNetCore.Http
@using Microsoft.AspNetCore.Http.Extensions
@using Microsoft.AspNetCore.Antiforgery
@using Microsoft.AspNetCore.Localization
@using Microsoft.Net.Http.Headers
@using Microsoft.Extensions.Primitives
@using Oqtane.Client
@using Oqtane.UI
@using Oqtane.Repository
@using Oqtane.Infrastructure
@using Oqtane.Security
@using Oqtane.Models
@using Oqtane.Shared
@using Oqtane.Themes
@using Oqtane.Extensions
@inject NavigationManager NavigationManager
@inject IAntiforgery Antiforgery
@inject IConfigManager ConfigManager
@inject ITenantManager TenantManager
@inject ISiteService SiteService
@inject IPageRepository PageRepository
@inject IThemeRepository ThemeRepository
@inject ILanguageRepository LanguageRepository
@inject ILocalizationManager LocalizationManager
@inject IAliasRepository AliasRepository
@inject IUrlMappingRepository UrlMappingRepository
@inject IVisitorRepository VisitorRepository
@inject IJwtManager JwtManager
@if (_initialized)
{
<!DOCTYPE html>
<html lang="@_language">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<base href="/" />
<link rel="stylesheet" href="css/app.css" />
@if (!string.IsNullOrEmpty(_PWAScript))
{
<link id="app-manifest" rel="manifest" />
}
@((MarkupString)_styleSheets)
<link id="app-stylesheet-page" />
<link id="app-stylesheet-module" />
@if (_renderMode == RenderModes.Static)
{
<Head RenderMode="@_renderMode" Runtime="@_runtime" />
}
else
{
<Head RenderMode="@_renderMode" Runtime="@_runtime" @rendermode="InteractiveRenderMode.GetInteractiveRenderMode(_runtime, _prerender)" />
}
@((MarkupString)_headResources)
</head>
<body>
@if (string.IsNullOrEmpty(_message))
{
@if (_renderMode == RenderModes.Static)
{
<Routes PageState="@_pageState" RenderMode="@_renderMode" Runtime="@_runtime" AntiForgeryToken="@_antiForgeryToken" AuthorizationToken="@_authorizationToken" Platform="@_platform" />
}
else
{
<Routes PageState="@_pageState" RenderMode="@_renderMode" Runtime="@_runtime" AntiForgeryToken="@_antiForgeryToken" AuthorizationToken="@_authorizationToken" Platform="@_platform" @rendermode="InteractiveRenderMode.GetInteractiveRenderMode(_runtime, _prerender)" />
}
@if (!string.IsNullOrEmpty(_reconnectScript))
{
@((MarkupString)_reconnectScript)
}
@if (!string.IsNullOrEmpty(_PWAScript))
{
@((MarkupString)_PWAScript)
}
@((MarkupString)_bodyResources)
<script src="js/app.js"></script>
<script src="js/loadjs.min.js"></script>
<script src="js/interop.js"></script>
<script src="_framework/blazor.web.js"></script>
}
else
{
<div class="app-alert">@_message</div>
}
</body>
</html>
}
@code {
private bool _initialized = false;
private string _renderMode = RenderModes.Interactive;
private string _runtime = Runtimes.Server;
private bool _prerender = true;
private int _visitorId = -1;
private string _antiForgeryToken = "";
private string _remoteIPAddress = "";
private string _platform = "";
private string _authorizationToken = "";
private string _language = "en";
private string _headResources = "";
private string _bodyResources = "";
private string _styleSheets = "";
private string _PWAScript = "";
private string _reconnectScript = "";
private string _message = "";
private PageState _pageState;
// CascadingParameter is required to access HttpContext
[CascadingParameter]
HttpContext Context { get; set; }
protected override async Task OnInitializedAsync()
{
_antiForgeryToken = Antiforgery.GetAndStoreTokens(Context).RequestToken;
_remoteIPAddress = Context.Connection.RemoteIpAddress?.ToString() ?? "";
_platform = System.Runtime.InteropServices.RuntimeInformation.OSDescription;
// if framework is installed
if (ConfigManager.IsInstalled())
{
var alias = TenantManager.GetAlias();
if (alias != null)
{
var url = WebUtility.UrlDecode(Context.Request.GetEncodedUrl());
if (!alias.IsDefault)
{
HandleDefaultAliasRedirect(alias, url);
}
var site = await SiteService.GetSiteAsync(alias.SiteId);
if (site != null && (!site.IsDeleted || url.Contains("admin/site")) && site.RenderMode != RenderModes.Headless)
{
_renderMode = site.RenderMode;
_runtime = site.Runtime;
_prerender = site.Prerender;
Route route = new Route(url, alias.Path);
var page = site.Pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase));
if (page == null && route.PagePath == "") // naked path refers to site home page
{
if (site.HomePageId != null)
{
page = site.Pages.FirstOrDefault(item => item.PageId == site.HomePageId);
}
if (page == null)
{
// fallback to use the first page in the collection
page = site.Pages.FirstOrDefault();
}
}
if (page == null)
{
// personalized pages need to be retrieved using path
page = PageRepository.GetPage(route.PagePath, site.SiteId);
}
if (page == null || page.IsDeleted)
{
HandlePageNotFound(site, page, route);
}
if (site.VisitorTracking)
{
TrackVisitor(site.SiteId);
}
// get jwt token for downstream APIs
if (Context.User.Identity.IsAuthenticated)
{
CreateJwtToken(alias);
}
// include stylesheets to prevent FOUC
var resources = GetPageResources(alias, site, page, int.Parse(route.ModuleId), route.Action);
ManageStyleSheets(resources);
// scripts
if (_renderMode == RenderModes.Static)
{
ManageScripts(resources, alias);
}
if (_renderMode == RenderModes.Interactive && _runtime == Runtimes.Server)
{
_reconnectScript = CreateReconnectScript();
}
if (site.PwaIsEnabled && site.PwaAppIconFileId != null && site.PwaSplashIconFileId != null)
{
_PWAScript = CreatePWAScript(alias, site, route);
}
_headResources += ParseScripts(site.HeadContent);
_bodyResources += ParseScripts(site.BodyContent);
// set culture if not specified
string culture = Context.Request.Cookies[CookieRequestCultureProvider.DefaultCookieName];
if (culture == null)
{
// get default language for site
if (site.Languages.Any())
{
// use default language if specified otherwise use first language in collection
culture = (site.Languages.Where(l => l.IsDefault).SingleOrDefault() ?? site.Languages.First()).Code;
}
else
{
culture = LocalizationManager.GetDefaultCulture();
}
SetLocalizationCookie(culture);
}
// set language for page
if (!string.IsNullOrEmpty(culture))
{
// localization cookie value in form of c=en|uic=en
_language = culture.Split('|')[0];
_language = _language.Replace("c=", "");
}
// create initial PageState
_pageState = new PageState
{
Alias = alias,
Site = site,
Page = page,
User = null,
Uri = new Uri(url, UriKind.Absolute),
Route = route,
QueryString = Utilities.ParseQueryString(route.Query),
UrlParameters = route.UrlParameters,
ModuleId = -1,
Action = "",
EditMode = false,
LastSyncDate = DateTime.MinValue,
RenderMode = _renderMode,
Runtime = (Shared.Runtime)Enum.Parse(typeof(Shared.Runtime), _runtime),
VisitorId = _visitorId,
RemoteIPAddress = _remoteIPAddress,
ReturnUrl = "",
IsInternalNavigation = false,
RenderId = Guid.NewGuid(),
Refresh = true
};
}
else
{
_message = "Site Is Disabled";
}
}
else
{
_message = "Site Not Configured Correctly - No Matching Alias Exists For Host Name";
}
}
_initialized = true;
}
private void HandleDefaultAliasRedirect(Alias alias, string url)
{
// get aliases for site and tenant
var aliases = AliasRepository.GetAliases().Where(item => item.TenantId == alias.TenantId && item.SiteId == alias.SiteId);
// get first default alias
var defaultAlias = aliases.Where(item => item.IsDefault).FirstOrDefault();
if (defaultAlias != null)
{
// redirect to default alias
NavigationManager.NavigateTo(url.Replace(alias.Name, defaultAlias.Name), true);
}
else // no default alias specified - use first alias
{
defaultAlias = aliases.FirstOrDefault();
if (defaultAlias != null && alias.Name.Trim() != defaultAlias.Name.Trim())
{
// redirect to first alias
NavigationManager.NavigateTo(url.Replace(alias.Name, defaultAlias.Name), true);
}
}
}
private void HandlePageNotFound(Site site, Page page, Route route)
{
// page not found - look for url mapping
var urlMapping = UrlMappingRepository.GetUrlMapping(site.SiteId, route.PagePath);
if (urlMapping != null && !string.IsNullOrEmpty(urlMapping.MappedUrl))
{
// redirect to mapped url
var url = (urlMapping.MappedUrl.StartsWith("http")) ? urlMapping.MappedUrl : route.SiteUrl + "/" + urlMapping.MappedUrl;
NavigationManager.NavigateTo(url, true);
}
else // no url mapping exists
{
if (route.PagePath != "404")
{
// redirect to 404 page
NavigationManager.NavigateTo(route.SiteUrl + "/404", true);
}
}
}
private void TrackVisitor(int SiteId)
{
try
{
// get request attributes
string useragent = (Context.Request.Headers[HeaderNames.UserAgent] != StringValues.Empty) ? Context.Request.Headers[HeaderNames.UserAgent] : "(none)";
useragent = (useragent.Length > 256) ? useragent.Substring(0, 256) : useragent;
string language = (Context.Request.Headers[HeaderNames.AcceptLanguage] != StringValues.Empty) ? Context.Request.Headers[HeaderNames.AcceptLanguage] : "";
language = (language.Contains(",")) ? language.Substring(0, language.IndexOf(",")) : language;
language = (language.Contains(";")) ? language.Substring(0, language.IndexOf(";")) : language;
language = (language.Trim().Length == 0) ? "??" : language;
// filter
var settings = Context.GetSiteSettings();
var filter = settings.GetValue("VisitorFilter", Constants.DefaultVisitorFilter);
foreach (string term in filter.ToLower().Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(sValue => sValue.Trim()).ToArray())
{
if (_remoteIPAddress.ToLower().Contains(term) || useragent.ToLower().Contains(term) || language.ToLower().Contains(term))
{
return;
}
}
// get other request attributes
string url = Context.Request.GetEncodedUrl();
string referrer = (Context.Request.Headers[HeaderNames.Referer] != StringValues.Empty) ? Context.Request.Headers[HeaderNames.Referer] : "";
int? userid = Context.User.UserId();
userid = (userid == -1) ? null : userid;
// check if cookie already exists
Visitor visitor = null;
bool addcookie = false;
var VisitorCookie = Constants.VisitorCookiePrefix + SiteId.ToString();
if (!int.TryParse(Context.Request.Cookies[VisitorCookie], out _visitorId))
{
// if enabled use IP Address correlation
_visitorId = -1;
var correlate = bool.Parse(settings.GetValue("VisitorCorrelation", "true"));
if (correlate)
{
visitor = VisitorRepository.GetVisitor(SiteId, _remoteIPAddress);
if (visitor != null)
{
_visitorId = visitor.VisitorId;
addcookie = true;
}
}
}
if (_visitorId == -1)
{
// create new visitor
visitor = new Visitor();
visitor.SiteId = SiteId;
visitor.IPAddress = _remoteIPAddress;
visitor.UserAgent = useragent;
visitor.Language = language;
visitor.Url = url;
visitor.Referrer = referrer;
visitor.UserId = userid;
visitor.Visits = 1;
visitor.CreatedOn = DateTime.UtcNow;
visitor.VisitedOn = DateTime.UtcNow;
visitor = VisitorRepository.AddVisitor(visitor);
_visitorId = visitor.VisitorId;
addcookie = true;
}
else
{
if (visitor == null)
{
// get visitor if it was not previously loaded
visitor = VisitorRepository.GetVisitor(_visitorId);
}
if (visitor != null)
{
// update visitor
visitor.IPAddress = _remoteIPAddress;
visitor.UserAgent = useragent;
visitor.Language = language;
visitor.Url = url;
if (!string.IsNullOrEmpty(referrer))
{
visitor.Referrer = referrer;
}
if (userid != null)
{
visitor.UserId = userid;
}
visitor.Visits += 1;
visitor.VisitedOn = DateTime.UtcNow;
VisitorRepository.UpdateVisitor(visitor);
}
else
{
// remove cookie if VisitorId does not exist
Context.Response.Cookies.Delete(VisitorCookie);
}
}
// append cookie
if (addcookie)
{
Context.Response.Cookies.Append(
VisitorCookie,
_visitorId.ToString(),
new CookieOptions()
{
Expires = DateTimeOffset.UtcNow.AddYears(1),
IsEssential = true
}
);
}
}
catch
{
// error tracking visitor
}
}
private void CreateJwtToken(Alias alias)
{
var sitesettings = Context.GetSiteSettings();
var secret = sitesettings.GetValue("JwtOptions:Secret", "");
if (!string.IsNullOrEmpty(secret))
{
_authorizationToken = JwtManager.GenerateToken(alias, (ClaimsIdentity)Context.User.Identity, secret, sitesettings.GetValue("JwtOptions:Issuer", ""), sitesettings.GetValue("JwtOptions:Audience", ""), int.Parse(sitesettings.GetValue("JwtOptions:Lifetime", "20")));
}
}
private string CreatePWAScript(Alias alias, Site site, Route route)
{
return
"<script>" + Environment.NewLine +
" // PWA Manifest" + Environment.NewLine +
" setTimeout(() => {" + Environment.NewLine +
" var manifest = {" + Environment.NewLine +
" \"name\": \"" + site.Name + "\"," + Environment.NewLine +
" \"short_name\": \"" + site.Name + "\"," + Environment.NewLine +
" \"start_url\": \"" + route.SiteUrl + "/\"," + Environment.NewLine +
" \"display\": \"standalone\"," + Environment.NewLine +
" \"background_color\": \"#fff\"," + Environment.NewLine +
" \"description\": \"" + site.Name + "\"," + Environment.NewLine +
" \"icons\": [{" + Environment.NewLine +
" \"src\": \"" + route.RootUrl + Utilities.FileUrl(alias, site.PwaAppIconFileId.Value) + "\"," + Environment.NewLine +
" \"sizes\": \"192x192\"," + Environment.NewLine +
" \"type\": \"image/png\"" + Environment.NewLine +
" }, {" + Environment.NewLine +
" \"src\": \"" + route.RootUrl + Utilities.FileUrl(alias, site.PwaSplashIconFileId.Value) + "\"," + Environment.NewLine +
" \"sizes\": \"512x512\"," + Environment.NewLine +
" \"type\": \"image/png\"" + Environment.NewLine +
" }]" + Environment.NewLine +
" };" + Environment.NewLine +
" const serialized = JSON.stringify(manifest);" + Environment.NewLine +
" const blob = new Blob([serialized], {type: 'application/javascript'});" + Environment.NewLine +
" const url = URL.createObjectURL(blob);" + Environment.NewLine +
" document.getElementById('app-manifest').setAttribute('href', url);" + Environment.NewLine +
" }, 1000);" + Environment.NewLine +
"</script>" + Environment.NewLine +
"<script>" + Environment.NewLine +
" // PWA Service Worker" + Environment.NewLine +
" if ('serviceWorker' in navigator) {" + Environment.NewLine +
" navigator.serviceWorker.register('/service-worker.js').then(function(registration) {" + Environment.NewLine +
" console.log('ServiceWorker Registration Successful');" + Environment.NewLine +
" }).catch (function(err) {" + Environment.NewLine +
" console.log('ServiceWorker Registration Failed ', err);" + Environment.NewLine +
" });" + Environment.NewLine +
" };" + Environment.NewLine +
"</script>";
}
private string CreateReconnectScript()
{
return
"<script>" + Environment.NewLine +
" // Blazor Server Reconnect" + Environment.NewLine +
" new MutationObserver((mutations, observer) => {" + Environment.NewLine +
" if (document.querySelector('#components-reconnect-modal h5 a')) {" + Environment.NewLine +
" async function attemptReload() {" + Environment.NewLine +
" await fetch('');" + Environment.NewLine +
" location.reload();" + Environment.NewLine +
" }" + Environment.NewLine +
" observer.disconnect();" + Environment.NewLine +
" attemptReload();" + Environment.NewLine +
" setInterval(attemptReload, 5000);" + Environment.NewLine +
" }" + Environment.NewLine +
" }).observe(document.body, { childList: true, subtree: true });" + Environment.NewLine +
"</script>";
}
private string ParseScripts(string content)
{
// iterate scripts
var scripts = "";
if (!string.IsNullOrEmpty(content))
{
var index = content.IndexOf("<script");
while (index >= 0)
{
scripts += content.Substring(index, content.IndexOf("</script>", index) + 9 - index);
index = content.IndexOf("<script", index + 1);
}
}
return scripts;
}
private void AddScript(Resource resource, Alias alias)
{
var script = CreateScript(resource, alias);
if (resource.Location == Shared.ResourceLocation.Head)
{
if (!_headResources.Contains(script))
{
_headResources += script + Environment.NewLine;
}
}
else
{
if (!_bodyResources.Contains(script))
{
_bodyResources += script + Environment.NewLine;
}
}
}
private string CreateScript(Resource resource, Alias alias)
{
if (!string.IsNullOrEmpty(resource.Url))
{
if (!resource.Reload)
{
var url = (resource.Url.Contains("://")) ? resource.Url : alias.BaseUrl + resource.Url;
return "<script" +
((!string.IsNullOrEmpty(resource.Integrity)) ? " integrity=\"" + resource.Integrity + "\"" : "") +
((!string.IsNullOrEmpty(resource.CrossOrigin)) ? " crossorigin=\"" + resource.CrossOrigin + "\"" : "") +
((resource.ES6Module) ? " type=\"module\"" : "") +
" src =\"" + url + "\"></script>"; // src at end of element due to enhanced navigation patch algorithm
}
else
{
// use custom element which can execute script on every page transition
return "<page-script src=\"" + resource.Url + "\"></page-script>";
}
}
else
{
// inline script
return "<script>" + resource.Content + "</script>";
}
}
private void SetLocalizationCookie(string culture)
{
Context.Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)));
}
private List<Resource> GetPageResources(Alias alias, Site site, Page page, int moduleid, string action)
{
var resources = new List<Resource>();
var themeType = (string.IsNullOrEmpty(page.ThemeType)) ? site.DefaultThemeType : page.ThemeType;
var theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == themeType));
if (theme != null)
{
resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName));
}
else
{
// fallback to default Oqtane theme
theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == Constants.DefaultTheme));
resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName));
}
var type = Type.GetType(themeType);
if (type != null)
{
var obj = Activator.CreateInstance(type) as IThemeControl;
if (obj != null)
{
resources = AddResources(resources, obj.Resources, ResourceLevel.Page, alias, "Themes", type.Namespace);
}
}
foreach (Module module in site.Modules.Where(item => item.PageId == page.PageId || item.ModuleId == moduleid))
{
var typename = "";
if (module.ModuleDefinition != null)
{
resources = AddResources(resources, module.ModuleDefinition.Resources, ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName));
// handle default action
if (action == Constants.DefaultAction && !string.IsNullOrEmpty(module.ModuleDefinition.DefaultAction))
{
action = module.ModuleDefinition.DefaultAction;
}
// get typename template
typename = module.ModuleDefinition.ControlTypeTemplate;
if (module.ModuleDefinition.ControlTypeRoutes != "")
{
// process custom action routes
foreach (string route in module.ModuleDefinition.ControlTypeRoutes.Split(';', StringSplitOptions.RemoveEmptyEntries))
{
if (route.StartsWith(action + "="))
{
typename = route.Replace(action + "=", "");
break;
}
}
}
}
// create typename
if (Constants.DefaultModuleActions.Contains(action, StringComparer.OrdinalIgnoreCase))
{
typename = Constants.DefaultModuleActionsTemplate.Replace(Constants.ActionToken, action);
}
else
{
typename = typename.Replace(Constants.ActionToken, action);
}
// ensure component exists and implements IModuleControl
module.ModuleType = "";
Type moduletype = Type.GetType(typename, false, true); // case insensitive
if (moduletype != null && moduletype.GetInterfaces().Contains(typeof(IModuleControl)))
{
module.ModuleType = Utilities.GetFullTypeName(moduletype.AssemblyQualifiedName); // get actual type name
}
if (moduletype != null && module.ModuleType != "")
{
var obj = Activator.CreateInstance(moduletype) as IModuleControl;
if (obj != null)
{
resources = AddResources(resources, obj.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
if (action.ToLower() == "settings" && module.ModuleDefinition != null)
{
// settings components are embedded within a framework settings module
moduletype = Type.GetType(module.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, action), false, true);
if (moduletype != null)
{
obj = Activator.CreateInstance(moduletype) as IModuleControl;
resources = AddResources(resources, obj.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
}
}
}
}
}
// site level resources for modules in site
var modules = site.Modules.GroupBy(item => item.ModuleDefinition?.ModuleDefinitionName).Select(group => group.First()).ToList();
foreach (var module in modules)
{
if (module.ModuleDefinition?.Resources != null)
{
resources = AddResources(resources, module.ModuleDefinition.Resources.Where(item => item.Level == ResourceLevel.Site).ToList(), ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName));
}
}
return resources;
}
private List<Resource> AddResources(List<Resource> pageresources, List<Resource> resources, ResourceLevel level, Alias alias, string type, string name)
{
if (resources != null)
{
foreach (var resource in resources)
{
if (resource.Url.StartsWith("~"))
{
resource.Url = resource.Url.Replace("~", "/" + type + "/" + name + "/").Replace("//", "/");
}
if (!resource.Url.Contains("://") && alias.BaseUrl != "" && !resource.Url.StartsWith(alias.BaseUrl))
{
resource.Url = alias.BaseUrl + resource.Url;
}
// ensure resource does not exist already
if (!pageresources.Exists(item => item.Url.ToLower() == resource.Url.ToLower()))
{
pageresources.Add(resource.Clone(level, name));
}
}
}
return pageresources;
}
private void ManageStyleSheets(List<Resource> resources)
{
if (resources != null)
{
string batch = DateTime.UtcNow.ToString("yyyyMMddHHmmssfff");
int count = 0;
foreach (var resource in resources.Where(item => item.ResourceType == ResourceType.Stylesheet))
{
count++;
string id = "id=\"app-stylesheet-" + ResourceLevel.Page.ToString().ToLower() + "-" + batch + "-" + count.ToString("00") + "\" ";
_styleSheets += "<link " + id + "rel=\"stylesheet\"" +
(!string.IsNullOrEmpty(resource.Integrity) ? " integrity=\"" + resource.Integrity + "\"" : "") +
(!string.IsNullOrEmpty(resource.CrossOrigin) ? " crossorigin=\"" + resource.CrossOrigin + "\"" : "") +
" href=\"" + resource.Url + "\" type=\"text/css\"/>" + Environment.NewLine; // href at end of element due to enhanced navigation patch algorithm
}
}
}
private void ManageScripts(List<Resource> resources, Alias alias)
{
if (resources != null)
{
foreach (var resource in resources.Where(item => item.ResourceType == ResourceType.Script))
{
if (string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Static)
{
AddScript(resource, alias);
}
}
}
}
}

View File

@ -0,0 +1,29 @@
@using System
@using System.Linq
@using System.Collections.Generic
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.Extensions.Localization
@using Microsoft.JSInterop
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Oqtane.Client
@using Oqtane.Models
@using Oqtane.Modules
@using Oqtane.Modules.Controls
@using Oqtane.Providers
@using Oqtane.Security
@using Oqtane.Services
@using Oqtane.Shared
@using Oqtane.Themes
@using Oqtane.Themes.Controls
@using Oqtane.UI
@using Oqtane.Enums
@using Oqtane.Installer
@using Oqtane.Interfaces

View File

@ -59,7 +59,7 @@ namespace Oqtane.Controllers
if (ModelState.IsValid) if (ModelState.IsValid)
{ {
alias = _aliases.AddAlias(alias); alias = _aliases.AddAlias(alias);
_syncManager.AddSyncEvent(alias.TenantId, EntityNames.Alias, alias.AliasId, SyncEventActions.Create); _syncManager.AddSyncEvent(alias, EntityNames.Alias, alias.AliasId, SyncEventActions.Create);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Alias Added {Alias}", alias); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Alias Added {Alias}", alias);
} }
else else
@ -79,7 +79,7 @@ namespace Oqtane.Controllers
if (ModelState.IsValid && alias.AliasId == id && _aliases.GetAlias(alias.AliasId, false) != null) if (ModelState.IsValid && alias.AliasId == id && _aliases.GetAlias(alias.AliasId, false) != null)
{ {
alias = _aliases.UpdateAlias(alias); alias = _aliases.UpdateAlias(alias);
_syncManager.AddSyncEvent(alias.TenantId, EntityNames.Alias, alias.AliasId, SyncEventActions.Update); _syncManager.AddSyncEvent(alias, EntityNames.Alias, alias.AliasId, SyncEventActions.Update);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Alias Updated {Alias}", alias); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Alias Updated {Alias}", alias);
} }
else else
@ -100,7 +100,7 @@ namespace Oqtane.Controllers
if (alias != null) if (alias != null)
{ {
_aliases.DeleteAlias(id); _aliases.DeleteAlias(id);
_syncManager.AddSyncEvent(alias.TenantId, EntityNames.Alias, alias.AliasId, SyncEventActions.Delete); _syncManager.AddSyncEvent(alias, EntityNames.Alias, alias.AliasId, SyncEventActions.Delete);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Alias Deleted {AliasId}", id); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Alias Deleted {AliasId}", id);
var aliases = _aliases.GetAliases(); var aliases = _aliases.GetAliases();

View File

@ -176,7 +176,7 @@ namespace Oqtane.Controllers
{ {
file = CreateFile(file.Name, folder.FolderId, filepath); file = CreateFile(file.Name, folder.FolderId, filepath);
file = _files.AddFile(file); file = _files.AddFile(file);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.File, file.FileId, SyncEventActions.Create); _syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, SyncEventActions.Create);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "File Added {File}", file); _logger.Log(LogLevel.Information, this, LogFunction.Create, "File Added {File}", file);
} }
else else
@ -234,7 +234,7 @@ namespace Oqtane.Controllers
} }
file = _files.UpdateFile(file); file = _files.UpdateFile(file);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.File, file.FileId, SyncEventActions.Update); _syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, SyncEventActions.Update);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "File Updated {File}", file); _logger.Log(LogLevel.Information, this, LogFunction.Update, "File Updated {File}", file);
} }
else else
@ -266,7 +266,7 @@ namespace Oqtane.Controllers
} }
_files.DeleteFile(id); _files.DeleteFile(id);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.File, file.FileId, SyncEventActions.Delete); _syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, SyncEventActions.Delete);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "File Deleted {File}", file); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "File Deleted {File}", file);
} }
else else
@ -341,7 +341,7 @@ namespace Oqtane.Controllers
if (file != null) if (file != null)
{ {
file = _files.AddFile(file); file = _files.AddFile(file);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.File, file.FileId, SyncEventActions.Create); _syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, SyncEventActions.Create);
} }
} }
catch (Exception ex) catch (Exception ex)
@ -429,7 +429,7 @@ namespace Oqtane.Controllers
file = _files.UpdateFile(file); file = _files.UpdateFile(file);
} }
_logger.Log(LogLevel.Information, this, LogFunction.Create, "File Upload Succeeded {File}", Path.Combine(folderPath, upload)); _logger.Log(LogLevel.Information, this, LogFunction.Create, "File Upload Succeeded {File}", Path.Combine(folderPath, upload));
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.File, file.FileId, SyncEventActions.Create); _syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, SyncEventActions.Create);
} }
} }
} }
@ -586,7 +586,7 @@ namespace Oqtane.Controllers
{ {
if (asAttachment) if (asAttachment)
{ {
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.File, file.FileId, "Download"); _syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, "Download");
return PhysicalFile(filepath, file.GetMimeType(), file.Name); return PhysicalFile(filepath, file.GetMimeType(), file.Name);
} }
else else

View File

@ -173,7 +173,7 @@ namespace Oqtane.Controllers
folder.Path = folder.Path + "/"; folder.Path = folder.Path + "/";
} }
folder = _folders.AddFolder(folder); folder = _folders.AddFolder(folder);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Folder, folder.FolderId, SyncEventActions.Create); _syncManager.AddSyncEvent(_alias, EntityNames.Folder, folder.FolderId, SyncEventActions.Create);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Folder Added {Folder}", folder); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Folder Added {Folder}", folder);
} }
else else
@ -225,7 +225,7 @@ namespace Oqtane.Controllers
} }
folder = _folders.UpdateFolder(folder); folder = _folders.UpdateFolder(folder);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Folder, folder.FolderId, SyncEventActions.Update); _syncManager.AddSyncEvent(_alias, EntityNames.Folder, folder.FolderId, SyncEventActions.Update);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Folder Updated {Folder}", folder); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Folder Updated {Folder}", folder);
} }
else else
@ -259,7 +259,7 @@ namespace Oqtane.Controllers
{ {
folder.Order = order; folder.Order = order;
_folders.UpdateFolder(folder); _folders.UpdateFolder(folder);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Folder, folder.FolderId, SyncEventActions.Update); _syncManager.AddSyncEvent(_alias, EntityNames.Folder, folder.FolderId, SyncEventActions.Update);
} }
order += 2; order += 2;
} }
@ -285,7 +285,7 @@ namespace Oqtane.Controllers
Directory.Delete(_folders.GetFolderPath(folder)); Directory.Delete(_folders.GetFolderPath(folder));
} }
_folders.DeleteFolder(id); _folders.DeleteFolder(id);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Folder, folder.FolderId, SyncEventActions.Delete); _syncManager.AddSyncEvent(_alias, EntityNames.Folder, folder.FolderId, SyncEventActions.Delete);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Folder Deleted {FolderId}", id); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Folder Deleted {FolderId}", id);
} }
else else

View File

@ -119,7 +119,7 @@ namespace Oqtane.Controllers
var assemblyList = new List<ClientAssembly>(); var assemblyList = new List<ClientAssembly>();
var site = _sites.GetSite(alias.SiteId); var site = _sites.GetSite(alias.SiteId);
if (site != null && (site.Runtime == "WebAssembly" || site.HybridEnabled)) if (site != null && (site.Runtime == Runtimes.WebAssembly || site.Hybrid))
{ {
var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
@ -201,7 +201,7 @@ namespace Oqtane.Controllers
private byte[] GetZIP(string list, Alias alias) private byte[] GetZIP(string list, Alias alias)
{ {
var site = _sites.GetSite(alias.SiteId); var site = _sites.GetSite(alias.SiteId);
if (site != null && (site.Runtime == "WebAssembly" || site.HybridEnabled)) if (site != null && (site.Runtime == Runtimes.WebAssembly || site.Hybrid))
{ {
var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);

View File

@ -109,8 +109,8 @@ namespace Oqtane.Controllers
if (ModelState.IsValid && language.SiteId == _alias.SiteId) if (ModelState.IsValid && language.SiteId == _alias.SiteId)
{ {
_languages.UpdateLanguage(language); _languages.UpdateLanguage(language);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Language, language.LanguageId, SyncEventActions.Update); _syncManager.AddSyncEvent(_alias, EntityNames.Language, language.LanguageId, SyncEventActions.Update);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh); _syncManager.AddSyncEvent(_alias, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Language Updated {Language}", language); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Language Updated {Language}", language);
} }
else else
@ -127,8 +127,8 @@ namespace Oqtane.Controllers
if (ModelState.IsValid && language.SiteId == _alias.SiteId) if (ModelState.IsValid && language.SiteId == _alias.SiteId)
{ {
language = _languages.AddLanguage(language); language = _languages.AddLanguage(language);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Language, language.LanguageId, SyncEventActions.Create); _syncManager.AddSyncEvent(_alias, EntityNames.Language, language.LanguageId, SyncEventActions.Create);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh); _syncManager.AddSyncEvent(_alias, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Language Added {Language}", language); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Language Added {Language}", language);
} }
else else
@ -148,8 +148,8 @@ namespace Oqtane.Controllers
if (language != null && language.SiteId == _alias.SiteId) if (language != null && language.SiteId == _alias.SiteId)
{ {
_languages.DeleteLanguage(id); _languages.DeleteLanguage(id);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Language, language.LanguageId, SyncEventActions.Delete); _syncManager.AddSyncEvent(_alias, EntityNames.Language, language.LanguageId, SyncEventActions.Delete);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh); _syncManager.AddSyncEvent(_alias, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Language Deleted {LanguageId}", id); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Language Deleted {LanguageId}", id);
} }
else else

View File

@ -136,8 +136,8 @@ namespace Oqtane.Controllers
if (ModelState.IsValid && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, module.SiteId, EntityNames.Page, module.PageId, PermissionNames.Edit)) if (ModelState.IsValid && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, module.SiteId, EntityNames.Page, module.PageId, PermissionNames.Edit))
{ {
module = _modules.AddModule(module); module = _modules.AddModule(module);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Module, module.ModuleId, SyncEventActions.Create); _syncManager.AddSyncEvent(_alias, EntityNames.Module, module.ModuleId, SyncEventActions.Create);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh); _syncManager.AddSyncEvent(_alias, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Module Added {Module}", module); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Module Added {Module}", module);
} }
else else
@ -187,8 +187,8 @@ namespace Oqtane.Controllers
} }
} }
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Module, module.ModuleId, SyncEventActions.Update); _syncManager.AddSyncEvent(_alias, EntityNames.Module, module.ModuleId, SyncEventActions.Update);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh); _syncManager.AddSyncEvent(_alias, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Module Updated {Module}", module); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Module Updated {Module}", module);
} }
else else
@ -209,8 +209,8 @@ namespace Oqtane.Controllers
if (module != null && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, module.SiteId, EntityNames.Module, module.ModuleId, PermissionNames.Edit)) if (module != null && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, module.SiteId, EntityNames.Module, module.ModuleId, PermissionNames.Edit))
{ {
_modules.DeleteModule(id); _modules.DeleteModule(id);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Module, module.ModuleId, SyncEventActions.Delete); _syncManager.AddSyncEvent(_alias, EntityNames.Module, module.ModuleId, SyncEventActions.Delete);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh); _syncManager.AddSyncEvent(_alias, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Deleted {ModuleId}", id); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Deleted {ModuleId}", id);
} }
else else

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