Merge branch 'dev' into 3805-stop-registration-redirect-without-verification

This commit is contained in:
Mostafa 2024-02-22 00:22:21 +01:00 committed by GitHub
commit 1c586d8811
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
56 changed files with 772 additions and 351 deletions

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>
@ -53,7 +51,7 @@ else
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)
@ -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

@ -48,6 +48,11 @@
<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 +89,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 +106,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"];
@ -208,12 +206,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 +253,7 @@
private void Cancel() private void Cancel()
{ {
NavigationManager.NavigateTo(WebUtility.UrlDecode(_returnUrl)); NavigationManager.NavigateTo(PageState.ReturnUrl);
} }
private async Task Forgot() private async Task Forgot()
@ -323,7 +321,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

@ -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,13 +337,14 @@
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.AddComponentReferenceCapture(1, inst => { _themeSettings = Convert.ChangeType(inst, _themeSettingsType); });

View File

@ -234,7 +234,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 +278,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 +317,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;
@ -478,13 +478,14 @@
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.AddComponentReferenceCapture(1, inst => { _themeSettings = Convert.ChangeType(inst, _themeSettingsType); });
@ -630,11 +631,11 @@
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()); NavigationManager.NavigateTo(NavigateUrl(), true);
} }
} }
else else

View File

@ -62,6 +62,11 @@
<br /> <br />
<button type="button" class="btn btn-primary" @onclick="Register">@Localizer["Register"]</button> <button type="button" class="btn btn-primary" @onclick="Register">@Localizer["Register"]</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>
@if (_allowsitelogin)
{
<br /><br />
<NavLink href="@NavigateUrl("login")">@Localizer["Login"]</NavLink>
}
</form> </form>
</NotAuthorized> </NotAuthorized>
</AuthorizeView> </AuthorizeView>
@ -84,12 +89,14 @@ else
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 _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()
@ -158,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

@ -579,9 +579,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));
@ -595,7 +592,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();
@ -603,17 +599,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;
@ -621,33 +614,28 @@
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
@ -659,7 +647,6 @@
site.Runtime = _runtime; site.Runtime = _runtime;
site.Prerender = bool.Parse(_prerender); site.Prerender = bool.Parse(_prerender);
site.Hybrid = bool.Parse(_hybrid); site.Hybrid = bool.Parse(_hybrid);
reload = true; // needs to be reloaded on serve
} }
} }
@ -686,15 +673,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

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

View File

@ -6,20 +6,24 @@
{ {
<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="@($"ModuleMessageForm{ModuleState.PageModuleId}")" data-enhance> <form method="post" @onsubmit="DismissModal" @formname="@_formname" data-enhance>
<input type="hidden" name="__RequestVerificationToken" value="@SiteState.AntiForgeryToken" /> <input type="hidden" name="__RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<button type="submit" class="btn-close" aria-label="Close"></button> <button type="submit" class="btn-close" aria-label="Close"></button>
</form> </form>
}
</div> </div>
} }
@code { @code {
private string _message = string.Empty; 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; }
@ -27,6 +31,14 @@
[Parameter] [Parameter]
public MessageType Type { get; set; } public MessageType Type { get; set; }
protected override void OnInitialized()
{
if (ModuleState != null)
{
_formname += ModuleState.PageModuleId.ToString();
}
}
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
_message = Message; _message = Message;

View File

@ -10,7 +10,6 @@ using System.Collections.Generic;
using Microsoft.JSInterop; using Microsoft.JSInterop;
using System.Linq; using System.Linq;
using System.Dynamic; using System.Dynamic;
using System.Net.Http.Headers;
namespace Oqtane.Modules namespace Oqtane.Modules
{ {

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

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

@ -52,7 +52,7 @@ namespace Oqtane.Themes.Controls
else else
{ {
// use existing value // use existing value
loginurl = "?returnurl=" + PageState.QueryString["returnurl"]; loginurl += "?returnurl=" + PageState.QueryString["returnurl"];
} }
// set logout url // set logout url

View File

@ -31,7 +31,16 @@
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
_returnurl = WebUtility.UrlEncode(PageState.Route.PathAndQuery); if (!PageState.QueryString.ContainsKey("returnurl"))
{
// remember current url
_returnurl += WebUtility.UrlEncode(PageState.Route.PathAndQuery);
}
else
{
// use existing value
_returnurl += PageState.QueryString["returnurl"];
}
} }
} }

View File

@ -16,9 +16,9 @@ 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" }, 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" }
} }
}; };
} }

View File

@ -3,6 +3,8 @@
@using Oqtane.Shared @using Oqtane.Shared
@inject SiteState SiteState @inject SiteState SiteState
@implements IDisposable @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))
{ {
@ -17,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;
@ -47,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

@ -17,7 +17,7 @@ namespace Oqtane.UI
case Runtimes.Auto: case Runtimes.Auto:
return new InteractiveAutoRenderMode(prerender: prerender); return new InteractiveAutoRenderMode(prerender: prerender);
} }
return new InteractiveServerRenderMode(prerender: prerender); // default to interactiver server return new InteractiveServerRenderMode(prerender: prerender);
} }
} }
} }

View File

@ -1,18 +1,18 @@
@namespace Oqtane.UI @namespace Oqtane.UI
@inject SiteState SiteState @inject SiteState SiteState
@if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Static) @if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Interactive)
{ {
<RenderModeBoundary ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" /> <StreamRenderingDisabled ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" />
} }
else else
{ {
<RenderModeBoundary ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" @rendermode="@InteractiveRenderMode.GetInteractiveRenderMode(PageState.Site.Runtime, PageState.Site.Prerender)" /> <StreamRenderingEnabled ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" />
} }
@code { @code {
// this component is on the static side of the render mode boundary // this component is on the static side of the render mode boundary
// it passes state as serializable parameters across the boundary so that the state can be used by downstream interactive components // it passes state as serializable parameters across the boundary
[CascadingParameter] [CascadingParameter]
protected PageState PageState { get; set; } protected PageState PageState { get; set; }
@ -20,7 +20,8 @@ else
[CascadingParameter] [CascadingParameter]
private Module ModuleState { get; set; } private Module ModuleState { get; set; }
[Obsolete("AddModuleMessage is deprecated. Use ModuleBase.AddModuleMessage instead.", false)]
[Obsolete("AddModuleMessage is deprecated. Use AddModuleMessage in ModuleBase instead.", false)]
public void AddModuleMessage(string message, MessageType type) public void AddModuleMessage(string message, MessageType type)
{ {
} }
@ -30,12 +31,12 @@ else
{ {
} }
[Obsolete("ShowProgressIndicator is deprecated. Use ModuleBase.ShowProgressIndicator instead.", false)] [Obsolete("ShowProgressIndicator is deprecated. Use ShowProgressIndicator in ModuleBase instead.", false)]
public void ShowProgressIndicator() public void ShowProgressIndicator()
{ {
} }
[Obsolete("HideProgressIndicator is deprecated. Use ModuleBase.HideProgressIndicator instead.", false)] [Obsolete("HideProgressIndicator is deprecated. Use HideProgressIndicator in ModuleBase instead.", false)]
public void HideProgressIndicator() public void HideProgressIndicator()
{ {
} }

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)

View File

@ -4,12 +4,12 @@
@inject ILogService LoggingService @inject ILogService LoggingService
@inherits ErrorBoundary @inherits ErrorBoundary
<CascadingValue Value="@PageState">
<CascadingValue Value="@ModuleState">
@if (CurrentException is null) @if (CurrentException is null)
{ {
@if (ModuleType != null) @if (ModuleType != null)
{ {
<CascadingValue Value="@PageState">
<CascadingValue Value="@ModuleState">
@if (!string.IsNullOrEmpty(_messageContent) && _messagePosition == "top") @if (!string.IsNullOrEmpty(_messageContent) && _messagePosition == "top")
{ {
<ModuleMessage Message="@_messageContent" Type="@_messageType" /> <ModuleMessage Message="@_messageContent" Type="@_messageType" />
@ -23,8 +23,6 @@
{ {
<ModuleMessage Message="@_messageContent" Type="@_messageType" /> <ModuleMessage Message="@_messageContent" Type="@_messageType" />
} }
</CascadingValue>
</CascadingValue>
} }
} }
else else
@ -34,8 +32,13 @@ else
<ModuleMessage Message="@_error" Type="@MessageType.Error" /> <ModuleMessage Message="@_error" Type="@MessageType.Error" />
} }
} }
</CascadingValue>
</CascadingValue>
@code { @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; } private Type ModuleType { get; set; }
RenderFragment DynamicComponent { get; set; } RenderFragment DynamicComponent { get; set; }

View File

@ -3,6 +3,7 @@
@inject IInstallationService InstallationService @inject IInstallationService InstallationService
@inject IJSRuntime JSRuntime @inject IJSRuntime JSRuntime
@inject SiteState SiteState @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] @attribute [StreamRendering]
@if (_initialized) @if (_initialized)

View File

@ -50,7 +50,7 @@
DynamicComponent = builder => DynamicComponent = builder =>
{ {
if (PageState != null) if (PageState != null && !PageState.Refresh)
{ {
builder.OpenComponent(0, typeof(ThemeBuilder)); builder.OpenComponent(0, typeof(ThemeBuilder));
builder.CloseComponent(); builder.CloseComponent();
@ -101,8 +101,8 @@
_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 = "";
@ -567,9 +567,19 @@
// 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(new Resource
resource.Namespace = name; {
pageresources.Add(resource); ResourceType = resource.ResourceType,
Url = resource.Url,
Integrity = resource.Integrity,
CrossOrigin = resource.CrossOrigin,
Bundle = resource.Bundle,
Location = resource.Location,
ES6Module = resource.ES6Module,
Content = resource.Content,
Level = level,
Namespace = name
});
} }
} }
} }

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

@ -53,30 +53,6 @@
{ {
headcontent = AddHeadContent(headcontent, PageState.Page.HeadContent); headcontent = AddHeadContent(headcontent, PageState.Page.HeadContent);
} }
if (PageState.RenderMode == RenderModes.Static)
{
string batch = DateTime.UtcNow.ToString("yyyyMMddHHmmssfff");
int count = 0;
foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet && item.Level == ResourceLevel.Module))
{
// if (resource.Url.StartsWith("~"))
// {
// resource.Url = resource.Url.Replace("~", "/Themes/" + Utilities.GetTypeName(name) + "/").Replace("//", "/");
// }
if (!resource.Url.Contains("://") && PageState.Alias.BaseUrl != "" && !resource.Url.StartsWith(PageState.Alias.BaseUrl))
{
resource.Url = PageState.Alias.BaseUrl + resource.Url;
}
if (!headcontent.Contains(resource.Url, StringComparison.OrdinalIgnoreCase))
{
count++;
string id = "id=\"app-stylesheet-" + resource.Level.ToString().ToLower() + "-" + batch + "-" + count.ToString("00") + "\" ";
headcontent += "<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;
}
}
}
SiteState.Properties.HeadContent = headcontent; SiteState.Properties.HeadContent = headcontent;
DynamicComponent = builder => DynamicComponent = builder =>
@ -96,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))
{ {
@ -168,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("="))
@ -190,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;
} }
} }
} }
@ -197,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
{ {
@ -217,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

@ -35,7 +35,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="EFCore.NamingConventions" Version="8.0.3" /> <PackageReference Include="EFCore.NamingConventions" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.2" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.2" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.2" />
</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.UI.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

@ -21,17 +21,15 @@
@inject IConfigManager ConfigManager @inject IConfigManager ConfigManager
@inject ITenantManager TenantManager @inject ITenantManager TenantManager
@inject ISiteService SiteService @inject ISiteService SiteService
@inject IPageRepository PageRepository
@inject IThemeRepository ThemeRepository @inject IThemeRepository ThemeRepository
@inject ILanguageRepository LanguageRepository @inject ILanguageRepository LanguageRepository
@inject IServerStateManager ServerStateManager
@inject ILocalizationManager LocalizationManager @inject ILocalizationManager LocalizationManager
@inject IAliasRepository AliasRepository @inject IAliasRepository AliasRepository
@inject IUrlMappingRepository UrlMappingRepository @inject IUrlMappingRepository UrlMappingRepository
@inject IVisitorRepository VisitorRepository @inject IVisitorRepository VisitorRepository
@inject IJwtManager JwtManager @inject IJwtManager JwtManager
@if (_pageState != null) @if (_initialized)
{ {
<!DOCTYPE html> <!DOCTYPE html>
<html lang="@_language"> <html lang="@_language">
@ -51,11 +49,11 @@
<link id="app-stylesheet-module" /> <link id="app-stylesheet-module" />
@if (_renderMode == RenderModes.Static) @if (_renderMode == RenderModes.Static)
{ {
<Head /> <Head RenderMode="@_renderMode" Runtime="@_runtime" />
} }
else else
{ {
<Head @rendermode="InteractiveRenderMode.GetInteractiveRenderMode(_runtime, _prerender)" /> <Head RenderMode="@_renderMode" Runtime="@_runtime" @rendermode="InteractiveRenderMode.GetInteractiveRenderMode(_runtime, _prerender)" />
} }
@((MarkupString)_headResources) @((MarkupString)_headResources)
</head> </head>
@ -93,6 +91,7 @@
} }
@code { @code {
private bool _initialized = false;
private string _renderMode = RenderModes.Interactive; private string _renderMode = RenderModes.Interactive;
private string _runtime = Runtimes.Server; private string _runtime = Runtimes.Server;
private bool _prerender = true; private bool _prerender = true;
@ -139,12 +138,19 @@
_prerender = site.Prerender; _prerender = site.Prerender;
Route route = new Route(url, alias.Path); Route route = new Route(url, alias.Path);
var page = PageRepository.GetPage(route.PagePath, site.SiteId); var page = site.Pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase));
if (page == null && route.PagePath == "" && site.HomePageId != null) if (page == null && route.PagePath == "") // naked path refers to site home page
{ {
page = PageRepository.GetPage(site.HomePageId.Value); 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 || page.IsDeleted) if (page == null || page.IsDeleted)
{ {
HandlePageNotFound(site, page, route); HandlePageNotFound(site, page, route);
@ -161,34 +167,15 @@
CreateJwtToken(alias); CreateJwtToken(alias);
} }
// stylesheets // include stylesheets to prevent FOUC
var themes = ThemeRepository.GetThemes().ToList(); var resources = GetPageResources(alias, site, page, int.Parse(route.ModuleId), route.Action);
var resources = new List<Resource>(); ManageStyleSheets(resources);
if (string.IsNullOrEmpty(page.ThemeType))
{ // scripts
page.ThemeType = site.DefaultThemeType;
}
var theme = themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == page.ThemeType));
if (theme?.Resources != null)
{
resources.AddRange(theme.Resources);
}
var type = Type.GetType(page.ThemeType);
if (type != null)
{
var obj = Activator.CreateInstance(type) as IThemeControl;
if (obj?.Resources != null)
{
resources.AddRange(obj.Resources);
}
}
ManageStyleSheets(resources, alias, theme.ThemeName);
if (_renderMode == RenderModes.Static) if (_renderMode == RenderModes.Static)
{ {
ManageScripts(resources, alias); ManageScripts(resources, alias);
} }
// scripts
if (_renderMode == RenderModes.Interactive && _runtime == Runtimes.Server) if (_renderMode == RenderModes.Interactive && _runtime == Runtimes.Server)
{ {
_reconnectScript = CreateReconnectScript(); _reconnectScript = CreateReconnectScript();
@ -199,22 +186,16 @@
} }
_headResources += ParseScripts(site.HeadContent); _headResources += ParseScripts(site.HeadContent);
_bodyResources += ParseScripts(site.BodyContent); _bodyResources += ParseScripts(site.BodyContent);
var scripts = ServerStateManager.GetServerState(alias.SiteKey).Scripts;
foreach (var script in scripts)
{
AddScript(script, alias);
}
// set culture if not specified // set culture if not specified
string culture = Context.Request.Cookies[CookieRequestCultureProvider.DefaultCookieName]; string culture = Context.Request.Cookies[CookieRequestCultureProvider.DefaultCookieName];
if (culture == null) if (culture == null)
{ {
// get default language for site // get default language for site
var languages = LanguageRepository.GetLanguages(alias.SiteId); if (site.Languages.Any())
if (languages.Any())
{ {
// use default language if specified otherwise use first language in collection // use default language if specified otherwise use first language in collection
culture = (languages.Where(l => l.IsDefault).SingleOrDefault() ?? languages.First()).Code; culture = (site.Languages.Where(l => l.IsDefault).SingleOrDefault() ?? site.Languages.First()).Code;
} }
else else
{ {
@ -266,6 +247,7 @@
_message = "Site Not Configured Correctly - No Matching Alias Exists For Host Name"; _message = "Site Not Configured Correctly - No Matching Alias Exists For Host Name";
} }
} }
_initialized = true;
} }
private void HandleDefaultAliasRedirect(Alias alias, string url) private void HandleDefaultAliasRedirect(Alias alias, string url)
@ -562,24 +544,117 @@
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture))); CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)));
} }
private void ManageStyleSheets(List<Resource> resources, Alias alias, string name) 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));
}
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)
{
if (module.PageId == page.PageId || module.ModuleId == moduleid)
{
var typename = "";
if (module.ModuleDefinition != null)
{
typename = module.ModuleDefinition.ControlTypeTemplate;
resources = AddResources(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);
}
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", type.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(new Resource
{
ResourceType = resource.ResourceType,
Url = resource.Url,
Integrity = resource.Integrity,
CrossOrigin = resource.CrossOrigin,
Bundle = resource.Bundle,
Location = resource.Location,
ES6Module = resource.ES6Module,
Content = resource.Content,
Level = level,
Namespace = name
});
}
}
}
return pageresources;
}
private void ManageStyleSheets(List<Resource> resources)
{ {
if (resources != null) if (resources != null)
{ {
string batch = DateTime.UtcNow.ToString("yyyyMMddHHmmssfff"); string batch = DateTime.UtcNow.ToString("yyyyMMddHHmmssfff");
int count = 0; int count = 0;
foreach (var resource in resources.Where(item => item.ResourceType == ResourceType.Stylesheet)) foreach (var resource in resources.Where(item => item.ResourceType == ResourceType.Stylesheet))
{
if (resource.Url.StartsWith("~"))
{
resource.Url = resource.Url.Replace("~", "/Themes/" + Utilities.GetTypeName(name) + "/").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++; count++;
string id = "id=\"app-stylesheet-" + ResourceLevel.Page.ToString().ToLower() + "-" + batch + "-" + count.ToString("00") + "\" "; string id = "id=\"app-stylesheet-" + ResourceLevel.Page.ToString().ToLower() + "-" + batch + "-" + count.ToString("00") + "\" ";
@ -587,7 +662,6 @@
} }
} }
} }
}
private void ManageScripts(List<Resource> resources, Alias alias) private void ManageScripts(List<Resource> resources, Alias alias)
{ {

View File

@ -16,6 +16,7 @@ using Microsoft.Extensions.DependencyInjection;
using System.Text.Json; using System.Text.Json;
using System.Net; using System.Net;
using Oqtane.Modules; using Oqtane.Modules;
using Oqtane.Infrastructure.Interfaces;
namespace Oqtane.Controllers namespace Oqtane.Controllers
{ {
@ -319,52 +320,33 @@ namespace Oqtane.Controllers
private void ProcessTemplatesRecursively(DirectoryInfo current, string rootPath, string rootFolder, string templatePath, ModuleDefinition moduleDefinition) private void ProcessTemplatesRecursively(DirectoryInfo current, string rootPath, string rootFolder, string templatePath, ModuleDefinition moduleDefinition)
{ {
var tokenReplace = InitializeTokenReplace(rootPath, rootFolder, moduleDefinition);
// process folder // process folder
string folderPath = Utilities.PathCombine(rootPath, current.FullName.Replace(templatePath, "")); var folderPath = Utilities.PathCombine(rootPath, current.FullName.Replace(templatePath, ""));
folderPath = folderPath.Replace("[Owner]", moduleDefinition.Owner); folderPath = tokenReplace.ReplaceTokens(folderPath);
folderPath = folderPath.Replace("[Module]", moduleDefinition.Name);
if (!Directory.Exists(folderPath)) if (!Directory.Exists(folderPath))
{ {
Directory.CreateDirectory(folderPath); Directory.CreateDirectory(folderPath);
} }
FileInfo[] files = current.GetFiles("*.*"); tokenReplace.AddSource("Folder", folderPath);
var files = current.GetFiles("*.*");
if (files != null) if (files != null)
{ {
foreach (FileInfo file in files) foreach (FileInfo file in files)
{ {
// process file // process file
string filePath = Path.Combine(folderPath, file.Name); var filePath = Path.Combine(folderPath, file.Name);
filePath = filePath.Replace("[Owner]", moduleDefinition.Owner); filePath = tokenReplace.ReplaceTokens(filePath);
filePath = filePath.Replace("[Module]", moduleDefinition.Name); tokenReplace.AddSource("File", Path.GetFileName(filePath));
string text = System.IO.File.ReadAllText(file.FullName); var text = System.IO.File.ReadAllText(file.FullName);
text = text.Replace("[Owner]", moduleDefinition.Owner); text = tokenReplace.ReplaceTokens(text);
text = text.Replace("[Module]", moduleDefinition.Name);
text = text.Replace("[Description]", moduleDefinition.Description);
text = text.Replace("[RootPath]", rootPath);
text = text.Replace("[RootFolder]", rootFolder);
text = text.Replace("[ServerManagerType]", moduleDefinition.ServerManagerType);
text = text.Replace("[Folder]", folderPath);
text = text.Replace("[File]", Path.GetFileName(filePath));
if (moduleDefinition.Version == "local")
{
text = text.Replace("[FrameworkVersion]", Constants.Version);
text = text.Replace("[ClientReference]", $"<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Client.dll</HintPath></Reference>");
text = text.Replace("[ServerReference]", $"<Reference Include=\"Oqtane.Server\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Server.dll</HintPath></Reference>");
text = text.Replace("[SharedReference]", $"<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Shared.dll</HintPath></Reference>");
}
else
{
text = text.Replace("[FrameworkVersion]", moduleDefinition.Version);
text = text.Replace("[ClientReference]", "<PackageReference Include=\"Oqtane.Client\" Version=\"" + moduleDefinition.Version + "\" />");
text = text.Replace("[ServerReference]", "<PackageReference Include=\"Oqtane.Server\" Version=\"" + moduleDefinition.Version + "\" />");
text = text.Replace("[SharedReference]", "<PackageReference Include=\"Oqtane.Shared\" Version=\"" + moduleDefinition.Version + "\" />");
}
System.IO.File.WriteAllText(filePath, text); System.IO.File.WriteAllText(filePath, text);
} }
DirectoryInfo[] folders = current.GetDirectories(); var folders = current.GetDirectories();
foreach (DirectoryInfo folder in folders.Reverse()) foreach (DirectoryInfo folder in folders.Reverse())
{ {
@ -372,5 +354,51 @@ namespace Oqtane.Controllers
} }
} }
} }
private ITokenReplace InitializeTokenReplace(string rootPath, string rootFolder, ModuleDefinition moduleDefinition)
{
var tokenReplace = _serviceProvider.GetService<ITokenReplace>();
tokenReplace.AddSource(() =>
{
return new Dictionary<string, object>
{
{ "RootPath", rootPath },
{ "RootFolder", rootFolder },
{ "Owner", moduleDefinition.Owner },
{ "Module", moduleDefinition.Name },
{ "Description", moduleDefinition.Description },
{ "ServerManagerType", moduleDefinition.ServerManagerType }
};
});
if (moduleDefinition.Version == "local")
{
tokenReplace.AddSource(() =>
{
return new Dictionary<string, object>()
{
{ "FrameworkVersion", Constants.Version },
{ "ClientReference", $"<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Client.dll</HintPath></Reference>" },
{ "ServerReference", $"<Reference Include=\"Oqtane.Server\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Server.dll</HintPath></Reference>" },
{ "SharedReference", $"<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Shared.dll</HintPath></Reference>" },
};
});
}
else
{
tokenReplace.AddSource(() =>
{
return new Dictionary<string, object>()
{
{ "FrameworkVersion", moduleDefinition.Version },
{ "ClientReference", $"<PackageReference Include=\"Oqtane.Client\" Version=\"{moduleDefinition.Version}\" />" },
{ "ServerReference", $"<PackageReference Include=\"Oqtane.Client\" Version=\"{moduleDefinition.Version}\" />" },
{ "SharedReference", $"<PackageReference Include=\"Oqtane.Client\" Version=\"{moduleDefinition.Version}\" />" },
};
});
}
return tokenReplace;
}
} }
} }

View File

@ -14,6 +14,8 @@ using System.Text.Json;
using System.Net; using System.Net;
using System.Reflection.Metadata; using System.Reflection.Metadata;
using System; using System;
using Microsoft.Extensions.DependencyInjection;
using Oqtane.Infrastructure.Interfaces;
// ReSharper disable StringIndexOfIsCultureSpecific.1 // ReSharper disable StringIndexOfIsCultureSpecific.1
@ -29,8 +31,9 @@ namespace Oqtane.Controllers
private readonly ISyncManager _syncManager; private readonly ISyncManager _syncManager;
private readonly ILogManager _logger; private readonly ILogManager _logger;
private readonly Alias _alias; private readonly Alias _alias;
private readonly IServiceProvider _serviceProvider;
public ThemeController(IThemeRepository themes, IInstallationManager installationManager, IWebHostEnvironment environment, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger) public ThemeController(IThemeRepository themes, IInstallationManager installationManager, IWebHostEnvironment environment, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger, IServiceProvider serviceProvider)
{ {
_themes = themes; _themes = themes;
_installationManager = installationManager; _installationManager = installationManager;
@ -39,6 +42,7 @@ namespace Oqtane.Controllers
_syncManager = syncManager; _syncManager = syncManager;
_logger = logger; _logger = logger;
_alias = tenantManager.GetAlias(); _alias = tenantManager.GetAlias();
_serviceProvider = serviceProvider;
} }
// GET: api/<controller> // GET: api/<controller>
@ -208,54 +212,80 @@ namespace Oqtane.Controllers
private void ProcessTemplatesRecursively(DirectoryInfo current, string rootPath, string rootFolder, string templatePath, Theme theme) private void ProcessTemplatesRecursively(DirectoryInfo current, string rootPath, string rootFolder, string templatePath, Theme theme)
{ {
var tokenReplace = InitializeTokenReplace(rootPath, rootFolder, theme);
// process folder // process folder
string folderPath = Utilities.PathCombine(rootPath, current.FullName.Replace(templatePath, "")); var folderPath = Utilities.PathCombine(rootPath, current.FullName.Replace(templatePath, ""));
folderPath = folderPath.Replace("[Owner]", theme.Owner); folderPath = tokenReplace.ReplaceTokens(folderPath);
folderPath = folderPath.Replace("[Theme]", theme.Name);
if (!Directory.Exists(folderPath)) if (!Directory.Exists(folderPath))
{ {
Directory.CreateDirectory(folderPath); Directory.CreateDirectory(folderPath);
} }
FileInfo[] files = current.GetFiles("*.*"); tokenReplace.AddSource("Folder", folderPath);
var files = current.GetFiles("*.*");
if (files != null) if (files != null)
{ {
foreach (FileInfo file in files) foreach (FileInfo file in files)
{ {
// process file // process file
string filePath = Path.Combine(folderPath, file.Name); var filePath = Path.Combine(folderPath, file.Name);
filePath = filePath.Replace("[Owner]", theme.Owner); filePath = tokenReplace.ReplaceTokens(filePath);
filePath = filePath.Replace("[Theme]", theme.Name); tokenReplace.AddSource("File", Path.GetFileName(filePath));
string text = System.IO.File.ReadAllText(file.FullName); var text = System.IO.File.ReadAllText(file.FullName);
text = text.Replace("[Owner]", theme.Owner); text = tokenReplace.ReplaceTokens(text);
text = text.Replace("[Theme]", theme.Name);
text = text.Replace("[RootPath]", rootPath);
text = text.Replace("[RootFolder]", rootFolder);
text = text.Replace("[Folder]", folderPath);
text = text.Replace("[File]", Path.GetFileName(filePath));
if (theme.Version == "local")
{
text = text.Replace("[FrameworkVersion]", Constants.Version);
text = text.Replace("[ClientReference]", $"<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Client.dll</HintPath></Reference>");
text = text.Replace("[SharedReference]", $"<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Shared.dll</HintPath></Reference>");
}
else
{
text = text.Replace("[FrameworkVersion]", theme.Version);
text = text.Replace("[ClientReference]", "<PackageReference Include=\"Oqtane.Client\" Version=\"" + theme.Version + "\" />");
text = text.Replace("[SharedReference]", "<PackageReference Include=\"Oqtane.Shared\" Version=\"" + theme.Version + "\" />");
}
System.IO.File.WriteAllText(filePath, text); System.IO.File.WriteAllText(filePath, text);
} }
DirectoryInfo[] folders = current.GetDirectories(); var folders = current.GetDirectories();
foreach (DirectoryInfo folder in folders.Reverse()) foreach (DirectoryInfo folder in folders.Reverse())
{ {
ProcessTemplatesRecursively(folder, rootPath, rootFolder, templatePath, theme); ProcessTemplatesRecursively(folder, rootPath, rootFolder, templatePath, theme);
} }
} }
} }
private ITokenReplace InitializeTokenReplace(string rootPath, string rootFolder, Theme theme)
{
var tokenReplace = _serviceProvider.GetService<ITokenReplace>();
tokenReplace.AddSource(() =>
{
return new Dictionary<string, object>
{
{ "RootPath", rootPath },
{ "RootFolder", rootFolder },
{ "Owner", theme.Owner },
{ "Theme", theme.Name }
};
});
if (theme.Version == "local")
{
tokenReplace.AddSource(() =>
{
return new Dictionary<string, object>()
{
{ "FrameworkVersion", Constants.Version },
{ "ClientReference", $"<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Client.dll</HintPath></Reference>" },
{ "SharedReference", $"<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Shared.dll</HintPath></Reference>" },
};
});
}
else
{
tokenReplace.AddSource(() =>
{
return new Dictionary<string, object>()
{
{ "FrameworkVersion", theme.Version },
{ "ClientReference", $"<PackageReference Include=\"Oqtane.Client\" Version=\"{theme.Version}\" />" },
{ "SharedReference", $"<PackageReference Include=\"Oqtane.Client\" Version=\"{theme.Version}\" />" },
};
});
}
return tokenReplace;
}
} }
} }

View File

@ -19,6 +19,7 @@ using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Oqtane.Infrastructure; using Oqtane.Infrastructure;
using Oqtane.Infrastructure.Interfaces;
using Oqtane.Managers; using Oqtane.Managers;
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Modules; using Oqtane.Modules;
@ -150,6 +151,8 @@ namespace Microsoft.Extensions.DependencyInjection
// obsolete - replaced by ITenantManager // obsolete - replaced by ITenantManager
services.AddTransient<ITenantResolver, TenantResolver>(); services.AddTransient<ITenantResolver, TenantResolver>();
services.AddTransient<ITokenReplace, TokenReplace>();
return services; return services;
} }

View File

@ -21,8 +21,9 @@ namespace Oqtane.Extensions
options.Password.RequireNonAlphanumeric = bool.Parse(sitesettings.GetValue("IdentityOptions:Password:RequireNonAlphanumeric", options.Password.RequireNonAlphanumeric.ToString())); options.Password.RequireNonAlphanumeric = bool.Parse(sitesettings.GetValue("IdentityOptions:Password:RequireNonAlphanumeric", options.Password.RequireNonAlphanumeric.ToString()));
// lockout options // lockout options
options.Lockout.MaxFailedAccessAttempts = int.Parse(sitesettings.GetValue("IdentityOptions:Password:MaxFailedAccessAttempts", options.Lockout.MaxFailedAccessAttempts.ToString())); options.Lockout.MaxFailedAccessAttempts = int.Parse(sitesettings.GetValue("IdentityOptions:Lockout:MaxFailedAccessAttempts", options.Lockout.MaxFailedAccessAttempts.ToString()));
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.Parse(sitesettings.GetValue("IdentityOptions:Password:DefaultLockoutTimeSpan", options.Lockout.DefaultLockoutTimeSpan.ToString())); options.Lockout.DefaultLockoutTimeSpan = TimeZoneInfo.Local.GetUtcOffset(DateTime.UtcNow) + TimeSpan.Parse(sitesettings.GetValue("IdentityOptions:Lockout:DefaultLockoutTimeSpan", options.Lockout.DefaultLockoutTimeSpan.ToString()));
options.Lockout.AllowedForNewUsers = options.Lockout.MaxFailedAccessAttempts > 0;
}); });
return builder; return builder;

View File

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using Oqtane.Interfaces;
namespace Oqtane.Infrastructure.Interfaces
{
public interface ITokenReplace
{
void AddSource(ITokenSource source);
void AddSource(Func<IDictionary<string, object>> sourceFunc);
void AddSource(IDictionary<string, object> source);
void AddSource(string key, object value);
void AddSource(string name, ITokenSource source);
void AddSource(string name, Func<IDictionary<string, object>> sourceFunc);
void AddSource(string name, IDictionary<string, object> source);
void AddSource(string name, string key, object value);
string ReplaceTokens(string source);
}
}

View File

@ -7,7 +7,6 @@ namespace Oqtane.Infrastructure
{ {
public string SiteKey { get; set; } public string SiteKey { get; set; }
public List<string> Assemblies { get; set; } = new List<string>(); public List<string> Assemblies { get; set; } = new List<string>();
public List<Resource>Scripts { get; set; } = new List<Resource>();
public bool IsInitialized { get; set; } = false; public bool IsInitialized { get; set; } = false;
} }
} }

View File

@ -1,6 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Oqtane.Models;
namespace Oqtane.Infrastructure namespace Oqtane.Infrastructure
{ {
@ -22,7 +21,6 @@ namespace Oqtane.Infrastructure
serverState = new ServerState(); serverState = new ServerState();
serverState.SiteKey = siteKey; serverState.SiteKey = siteKey;
serverState.Assemblies = new List<string>(); serverState.Assemblies = new List<string>();
serverState.Scripts = new List<Resource>();
serverState.IsInitialized = false; serverState.IsInitialized = false;
_serverStates.Add(serverState); _serverStates.Add(serverState);
} }

View File

@ -59,7 +59,7 @@ namespace Oqtane.SiteTemplates
}, },
Content = "<p><a href=\"https://www.oqtane.org\" target=\"_new\">Oqtane</a> is an open source <b>CMS</b> and <b>Application Framework</b> that provides advanced functionality for developing web, mobile, and desktop applications on .NET. It leverages Blazor to compose a <b>fully dynamic</b> digital experience. Whether you are looking for a platform to <b>accelerate your web development</b> efforts, or simply interested in exploring the anatomy of a large-scale Blazor application, Oqtane provides a solid foundation based on proven enterprise architectural principles and patterns.</p>" + Content = "<p><a href=\"https://www.oqtane.org\" target=\"_new\">Oqtane</a> is an open source <b>CMS</b> and <b>Application Framework</b> that provides advanced functionality for developing web, mobile, and desktop applications on .NET. It leverages Blazor to compose a <b>fully dynamic</b> digital experience. Whether you are looking for a platform to <b>accelerate your web development</b> efforts, or simply interested in exploring the anatomy of a large-scale Blazor application, Oqtane provides a solid foundation based on proven enterprise architectural principles and patterns.</p>" +
"<p align=\"center\"><a href=\"https://www.oqtane.org\" target=\"_new\"><img class=\"img-fluid\" src=\"oqtane-glow.png\"></a></p><p align=\"center\"><a class=\"btn btn-primary\" href=\"https://www.oqtane.org\" target=\"_new\">Join Our Community</a>&nbsp;&nbsp;<a class=\"btn btn-primary\" href=\"https://github.com/oqtane/oqtane.framework\" target=\"_new\">Clone Our Repo</a></p>" + "<p align=\"center\"><a href=\"https://www.oqtane.org\" target=\"_new\"><img class=\"img-fluid\" src=\"oqtane-glow.png\"></a></p><p align=\"center\"><a class=\"btn btn-primary\" href=\"https://www.oqtane.org\" target=\"_new\">Join Our Community</a>&nbsp;&nbsp;<a class=\"btn btn-primary\" href=\"https://github.com/oqtane/oqtane.framework\" target=\"_new\">Clone Our Repo</a></p>" +
"<p><a href=\"https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor\" target=\"_new\">Blazor</a> is an open source and cross-platform web UI framework for building single-page applications using .NET and C#. Blazor applications can be hosted in a variety of ways. Blazor Server uses SignalR (WebSockets) to host your application on a web server and provide a responsive and robust development experience. Blazor WebAssembly relies on Wasm, an open web standard that does not require plugins in order for applications to run natively in a web browser. Blazor Hybrid is part of .NET MAUI and uses a Web View to render components natively on mobile and desktop devices. Razor components can be shared across all of the hosting models without any modification.</p>" + "<p><a href=\"https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor\" target=\"_new\">Blazor</a> is a modern front-end web framework based on HTML, CSS, and C# that helps you build web applications faster. Blazor provides a component-based architecture with server-side rendering and full client-side interactivity in a single solution, where you can switch between server-side and client-side rendering modes and even mix them in the same web page. For desktop or mobile scenarios, Blazor Hybrid is part of .NET MAUI and uses a Web View to render components natively on all modern devices.</p>" +
"<p>Blazor is a feature of <a href=\"https://dotnet.microsoft.com/apps/aspnet\" target=\"_new\">ASP.NET</a>, the popular cross platform development framework from Microsoft that provides powerful tools and libraries for building modern software applications.</p>" "<p>Blazor is a feature of <a href=\"https://dotnet.microsoft.com/apps/aspnet\" target=\"_new\">ASP.NET</a>, the popular cross platform development framework from Microsoft that provides powerful tools and libraries for building modern software applications.</p>"
}, },
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "MIT License", Pane = PaneNames.Default, new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "MIT License", Pane = PaneNames.Default,

View File

@ -0,0 +1,158 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Text;
using Oqtane.Infrastructure.Interfaces;
using Oqtane.Interfaces;
using Oqtane.Models;
namespace Oqtane.Infrastructure
{
public class TokenReplace : ITokenReplace
{
public const string GenericName = "generic";
private const string TokenExpression = "(?:(?<text>\\[\\])|\\[(?:(?<source>[^{}\\]\\[:]+):(?<key>[^\\]\\[\\|]+)|(?<key>[^\\]\\[\\|]+))(?:\\|(?:(?<format>[^\\]\\[]+)\\|(?<empty>[^\\]\\\\[]+))|\\|(?:(?<format>[^\\|\\]\\[]+)))?\\])|(?<text>\\[[^\\]\\[]+\\])|(?<text>\\[{0,1}[^\\]\\[]+\\]{0,1})";
private Regex TokenizerRegex = new Regex(TokenExpression, RegexOptions.Compiled | RegexOptions.Singleline);
private IDictionary<string, IDictionary<string, object>> _tokens;
private readonly ILogManager _logger;
public TokenReplace(ILogManager logger)
{
_tokens = new Dictionary<string, IDictionary<string, object>>();
_logger = logger;
}
public void AddSource(ITokenSource source)
{
this.AddSource(GenericName, source);
}
public void AddSource(Func<IDictionary<string, object>> sourceFunc)
{
this.AddSource(GenericName, sourceFunc);
}
public void AddSource(IDictionary<string, object> source)
{
this.AddSource(GenericName, source);
}
public void AddSource(string key, object value)
{
this.AddSource(GenericName, key, value);
}
public void AddSource(string name, ITokenSource source)
{
var tokens = source.GetTokens();
this.AddSource(name, tokens);
}
public void AddSource(string name, Func<IDictionary<string, object>> sourceFunc)
{
var tokens = sourceFunc();
this.AddSource(name, tokens);
}
public void AddSource(string name, IDictionary<string, object> source)
{
if(source != null)
{
foreach (var key in source.Keys)
{
this.AddSource(name, key, source[key]);
}
}
}
public void AddSource(string name, string key, object value)
{
if (string.IsNullOrWhiteSpace(name))
{
name = GenericName;
}
var source = _tokens.ContainsKey(name.ToLower()) ? _tokens[name.ToLower()] : null;
if(source == null)
{
source = new Dictionary<string, object>();
}
source[key] = value;
_tokens[name.ToLower()] = source;
}
public string ReplaceTokens(string source)
{
if (string.IsNullOrWhiteSpace(source))
{
return source;
}
var result = new StringBuilder();
foreach (Match match in this.TokenizerRegex.Matches(source))
{
var key = match.Result("${key}");
if (!string.IsNullOrWhiteSpace(key))
{
var sourceName = match.Result("${source}");
if (string.IsNullOrWhiteSpace(sourceName) || sourceName == "[")
{
sourceName = GenericName;
}
var format = match.Result("${format}");
var emptyReplacment = match.Result("${empty}");
var value = ReplaceTokenValue(sourceName, key, format);
if (string.IsNullOrWhiteSpace(value))
{
if(!string.IsNullOrWhiteSpace(emptyReplacment))
{
value = emptyReplacment;
}
else //keep the original content
{
value = match.Value;
}
}
result.Append(value);
}
else
{
result.Append(match.Result("${text}"));
}
}
return result.ToString();
}
private string ReplaceTokenValue(string sourceName, string key, string format)
{
if(!_tokens.ContainsKey(sourceName.ToLower()))
{
_logger.Log(Shared.LogLevel.Debug, this, Enums.LogFunction.Other, $"MissingSource:{sourceName}");
return string.Empty;
}
var tokens = _tokens[sourceName.ToLower()];
if(!tokens.ContainsKey(key))
{
_logger.Log(Shared.LogLevel.Debug, this, Enums.LogFunction.Other, $"MissingKey:{key}");
return string.Empty;
}
var value = tokens[key];
if(value == null)
{
return string.Empty;
}
//TODO: need to implement the format.
return value.ToString();
}
}
}

View File

@ -1,6 +1,5 @@
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Oqtane.Extensions;
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Repository; using Oqtane.Repository;
using Oqtane.Shared; using Oqtane.Shared;

View File

@ -17,6 +17,7 @@
<RootNamespace>Oqtane</RootNamespace> <RootNamespace>Oqtane</RootNamespace>
<IsPackable>true</IsPackable> <IsPackable>true</IsPackable>
<DefineConstants>$(DefineConstants);OQTANE;OQTANE3</DefineConstants> <DefineConstants>$(DefineConstants);OQTANE;OQTANE3</DefineConstants>
<PreserveCompilationContext>true</PreserveCompilationContext>
<SatelliteResourceLanguages>none</SatelliteResourceLanguages> <SatelliteResourceLanguages>none</SatelliteResourceLanguages>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -225,17 +225,6 @@ namespace Oqtane.Repository
} }
} }
} }
// build list of scripts for site
if (moduledefinition.Resources != null)
{
foreach (var resource in moduledefinition.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level == ResourceLevel.Site))
{
if (!serverState.Scripts.Contains(resource))
{
serverState.Scripts.Add(resource);
}
}
}
} }
if (permissions.Count == 0) if (permissions.Count == 0)

View File

@ -197,17 +197,6 @@ namespace Oqtane.Repository
} }
} }
} }
// build list of scripts for site
if (theme.Resources != null)
{
foreach (var resource in theme.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level == ResourceLevel.Site))
{
if (!serverState.Scripts.Contains(resource))
{
serverState.Scripts.Add(resource);
}
}
}
} }
} }
} }

View File

@ -27,11 +27,11 @@ namespace Oqtane.Services
private readonly ILanguageRepository _languages; private readonly ILanguageRepository _languages;
private readonly IUserPermissions _userPermissions; private readonly IUserPermissions _userPermissions;
private readonly ISettingRepository _settings; private readonly ISettingRepository _settings;
private readonly ITenantManager _tenantManager;
private readonly ISyncManager _syncManager; private readonly ISyncManager _syncManager;
private readonly ILogManager _logger; private readonly ILogManager _logger;
private readonly IMemoryCache _cache; private readonly IMemoryCache _cache;
private readonly IHttpContextAccessor _accessor; private readonly IHttpContextAccessor _accessor;
private readonly Alias _alias;
public ServerSiteService(ISiteRepository sites, IPageRepository pages, IThemeRepository themes, IPageModuleRepository pageModules, IModuleDefinitionRepository moduleDefinitions, ILanguageRepository languages, IUserPermissions userPermissions, ISettingRepository settings, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger, IMemoryCache cache, IHttpContextAccessor accessor) public ServerSiteService(ISiteRepository sites, IPageRepository pages, IThemeRepository themes, IPageModuleRepository pageModules, IModuleDefinitionRepository moduleDefinitions, ILanguageRepository languages, IUserPermissions userPermissions, ISettingRepository settings, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger, IMemoryCache cache, IHttpContextAccessor accessor)
{ {
@ -43,11 +43,11 @@ namespace Oqtane.Services
_languages = languages; _languages = languages;
_userPermissions = userPermissions; _userPermissions = userPermissions;
_settings = settings; _settings = settings;
_tenantManager = tenantManager;
_syncManager = syncManager; _syncManager = syncManager;
_logger = logger; _logger = logger;
_cache = cache; _cache = cache;
_accessor = accessor; _accessor = accessor;
_alias = tenantManager.GetAlias();
} }
public async Task<List<Site>> GetSitesAsync() public async Task<List<Site>> GetSitesAsync()
@ -80,8 +80,9 @@ namespace Oqtane.Services
private Site GetSite(int siteid) private Site GetSite(int siteid)
{ {
var alias = _tenantManager.GetAlias();
var site = _sites.GetSite(siteid); var site = _sites.GetSite(siteid);
if (site != null && site.SiteId == _alias.SiteId) if (site != null && site.SiteId == alias.SiteId)
{ {
// site settings // site settings
site.Settings = _settings.GetSettings(EntityNames.Site, site.SiteId) site.Settings = _settings.GetSettings(EntityNames.Site, site.SiteId)
@ -179,8 +180,9 @@ namespace Oqtane.Services
{ {
if (_accessor.HttpContext.User.IsInRole(RoleNames.Host)) if (_accessor.HttpContext.User.IsInRole(RoleNames.Host))
{ {
var alias = _tenantManager.GetAlias();
site = _sites.AddSite(site); site = _sites.AddSite(site);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, site.SiteId, SyncEventActions.Create); _syncManager.AddSyncEvent(alias.TenantId, EntityNames.Site, site.SiteId, SyncEventActions.Create);
_logger.Log(site.SiteId, LogLevel.Information, this, LogFunction.Create, "Site Added {Site}", site); _logger.Log(site.SiteId, LogLevel.Information, this, LogFunction.Create, "Site Added {Site}", site);
} }
else else
@ -194,17 +196,18 @@ namespace Oqtane.Services
{ {
if (_accessor.HttpContext.User.IsInRole(RoleNames.Admin)) if (_accessor.HttpContext.User.IsInRole(RoleNames.Admin))
{ {
var alias = _tenantManager.GetAlias();
var current = _sites.GetSite(site.SiteId, false); var current = _sites.GetSite(site.SiteId, false);
if (site.SiteId == _alias.SiteId && site.TenantId == _alias.TenantId && current != null) if (site.SiteId == alias.SiteId && site.TenantId == alias.TenantId && current != null)
{ {
site = _sites.UpdateSite(site); site = _sites.UpdateSite(site);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, site.SiteId, SyncEventActions.Update); _syncManager.AddSyncEvent(alias.TenantId, EntityNames.Site, site.SiteId, SyncEventActions.Update);
string action = SyncEventActions.Refresh; string action = SyncEventActions.Refresh;
if (current.RenderMode != site.RenderMode || current.Runtime != site.Runtime) if (current.RenderMode != site.RenderMode || current.Runtime != site.Runtime)
{ {
action = SyncEventActions.Reload; action = SyncEventActions.Reload;
} }
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, site.SiteId, action); _syncManager.AddSyncEvent(alias.TenantId, EntityNames.Site, site.SiteId, action);
_logger.Log(site.SiteId, LogLevel.Information, this, LogFunction.Update, "Site Updated {Site}", site); _logger.Log(site.SiteId, LogLevel.Information, this, LogFunction.Update, "Site Updated {Site}", site);
} }
else else
@ -224,11 +227,12 @@ namespace Oqtane.Services
{ {
if (_accessor.HttpContext.User.IsInRole(RoleNames.Host)) if (_accessor.HttpContext.User.IsInRole(RoleNames.Host))
{ {
var alias = _tenantManager.GetAlias();
var site = _sites.GetSite(siteId); var site = _sites.GetSite(siteId);
if (site != null && site.SiteId == _alias.SiteId) if (site != null && site.SiteId == alias.SiteId)
{ {
_sites.DeleteSite(siteId); _sites.DeleteSite(siteId);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, site.SiteId, SyncEventActions.Delete); _syncManager.AddSyncEvent(alias.TenantId, EntityNames.Site, site.SiteId, SyncEventActions.Delete);
_logger.Log(siteId, LogLevel.Information, this, LogFunction.Delete, "Site Deleted {SiteId}", siteId); _logger.Log(siteId, LogLevel.Information, this, LogFunction.Delete, "Site Deleted {SiteId}", siteId);
} }
else else

View File

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

View File

@ -197,7 +197,7 @@
} }
} }
@media (max-width: 767px) { @media (max-width: 767.98px) {
.app-logo { .app-logo {
height: 80px; height: 80px;
display: flex; display: flex;

View File

@ -96,7 +96,7 @@ div.app-moduleactions a.dropdown-toggle, div.app-moduleactions div.dropdown-menu
z-index: 1000; z-index: 1000;
} }
@media (max-width: 767px) { @media (max-width: 767.98px) {
.app-menu { .app-menu {
width: 100%; width: 100%;

View File

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

View File

@ -17,9 +17,9 @@ namespace [Owner].Theme.[Theme]
Resources = new 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 = "~/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" }
} }
}; };

View File

@ -90,7 +90,7 @@ div.app-moduleactions a.dropdown-toggle, div.app-moduleactions div.dropdown-menu
mix-blend-mode: difference; mix-blend-mode: difference;
} }
@media (max-width: 767px) { @media (max-width: 767.98px) {
.app-menu { .app-menu {
width: 100%; width: 100%;

View File

@ -206,21 +206,28 @@ Oqtane.Interop = {
returnPromise: true, returnPromise: true,
before: function (path, element) { before: function (path, element) {
for (let s = 0; s < scripts.length; s++) { for (let s = 0; s < scripts.length; s++) {
if (path === scripts[s].href && scripts[s].integrity !== '') { if (path === scripts[s].href) {
if (scripts[s].integrity !== '') {
element.integrity = scripts[s].integrity; element.integrity = scripts[s].integrity;
} }
if (path === scripts[s].href && scripts[s].crossorigin !== '') { if (scripts[s].crossorigin !== '') {
element.crossOrigin = scripts[s].crossorigin; element.crossOrigin = scripts[s].crossorigin;
} }
if (path === scripts[s].href && scripts[s].es6module === true) { if (scripts[s].es6module === true) {
element.type = "module"; element.type = "module";
} }
if (path === scripts[s].href && scripts[s].location === 'body') { if (typeof scripts[s].dataAttributes !== "undefined" && scripts[s].dataAttributes !== null) {
for (var key in scripts[s].dataAttributes) {
element.setAttribute(key, scripts[s].dataAttributes[key]);
}
}
if (scripts[s].location === 'body') {
document.body.appendChild(element); document.body.appendChild(element);
return false; // return false to bypass default DOM insertion mechanism return false; // return false to bypass default DOM insertion mechanism
} }
} }
} }
}
}) })
.then(function () { resolve(true) }) .then(function () { resolve(true) })
.catch(function (pathsNotFound) { reject(false) }); .catch(function (pathsNotFound) { reject(false) });

View File

@ -2,7 +2,7 @@ namespace Oqtane.Shared
{ {
public enum ResourceLevel public enum ResourceLevel
{ {
App, Undefined,
Site, Site,
Page, Page,
Module Module

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Oqtane.Interfaces
{
public interface ITokenSource
{
IDictionary<string, object> GetTokens();
}
}

View File

@ -32,8 +32,8 @@ namespace Oqtane.Models
PathAndQuery = uri.PathAndQuery; PathAndQuery = uri.PathAndQuery;
AliasPath = aliaspath; AliasPath = aliaspath;
PagePath = AbsolutePath; PagePath = AbsolutePath;
ModuleId = ""; ModuleId = "-1";
Action = ""; Action = Constants.DefaultAction;
UrlParameters = ""; UrlParameters = "";
if (AliasPath.Length != 0 && PagePath.StartsWith("/" + AliasPath)) if (AliasPath.Length != 0 && PagePath.StartsWith("/" + AliasPath))
@ -58,7 +58,9 @@ namespace Oqtane.Models
if (pos != -1) if (pos != -1)
{ {
Action = ModuleId.Substring(pos + 1); Action = ModuleId.Substring(pos + 1);
Action = (!string.IsNullOrEmpty(Action)) ? Action : Constants.DefaultAction;
ModuleId = ModuleId.Substring(0, pos); ModuleId = ModuleId.Substring(0, pos);
ModuleId = (int.TryParse(ModuleId, out _)) ? ModuleId : "-1";
} }
} }
if (PagePath.StartsWith("/")) if (PagePath.StartsWith("/"))

View File

@ -5,7 +5,7 @@ namespace Oqtane.Shared
public class Constants public class Constants
{ {
public static readonly string Version = "5.1.0"; public static readonly string Version = "5.1.0";
public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,5.0.0,5.0.1,5.0.2,5.1.0"; public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,5.0.0,5.0.1,5.0.2,5.0.3,5.1.0";
public const string PackageId = "Oqtane.Framework"; public const string PackageId = "Oqtane.Framework";
public const string ClientId = "Oqtane.Client"; public const string ClientId = "Oqtane.Client";
public const string UpdaterPackageId = "Oqtane.Updater"; public const string UpdaterPackageId = "Oqtane.Updater";