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 IServiceCollection AddOqtaneAuthorization(this IServiceCollection services)
public static IServiceCollection AddOqtaneAuthentication(this IServiceCollection services)
{
services.AddAuthorizationCore();
services.AddCascadingAuthenticationState();
services.AddScoped<IdentityAuthenticationStateProvider>();
services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<IdentityAuthenticationStateProvider>());
return services;
}
public static IServiceCollection AddOqtaneScopedServices(this IServiceCollection services)
public static IServiceCollection AddOqtaneClientScopedServices(this IServiceCollection services)
{
services.AddScoped<SiteState>();
services.AddScoped<IInstallationService, InstallationService>();

View File

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

View File

@ -12,12 +12,12 @@
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{
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">
<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>
</p>
</div>
}
}
</div>
@ -27,6 +27,7 @@
private List<Page> _pages;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
public override string RenderMode => RenderModes.Static;
protected override void OnInitialized()
{

View File

@ -17,7 +17,6 @@ else
<Pager Items="@_jobs" SearchProperties="Name">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th>
@ -28,7 +27,6 @@ else
</Header>
<Row>
<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>@context.Name</td>
<td>@DisplayStatus(context.IsEnabled, context.IsExecuting)</td>
@ -49,17 +47,17 @@ else
}
@code {
private List<Job> _jobs;
private List<Job> _jobs;
public override SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.Host; } }
protected override async Task OnParametersSetAsync()
{
_jobs = await JobService.GetJobsAsync();
if (_jobs.Count == 0)
{
AddModuleMessage(string.Format(Localizer["Message.NoJobs"], NavigateUrl("admin/system")), MessageType.Warning);
}
protected override async Task OnInitializedAsync()
{
_jobs = await JobService.GetJobsAsync();
if (_jobs.Count == 0)
{
AddModuleMessage(string.Format(Localizer["Message.NoJobs"], NavigateUrl("admin/system")), MessageType.Warning);
}
}
private string DisplayStatus(bool isEnabled, bool isExecuting)
@ -112,22 +110,6 @@ else
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)
{
try

View File

@ -12,6 +12,9 @@
<Authorizing>
<text>...</text>
</Authorizing>
<Authorized>
<ModuleMessage Message="@Localizer["Info.SignedIn"]" Type="MessageType.Info" />
</Authorized>
<NotAuthorized>
@if (!twofactor)
{
@ -47,8 +50,13 @@
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
<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>
</form>
}
@ -84,8 +92,6 @@
private bool _alwaysremember = false;
private string _code = string.Empty;
private string _returnUrl = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
public override List<Resource> Resources => new List<Resource>()
@ -103,11 +109,6 @@
_togglepassword = SharedLocalizer["ShowPassword"];
if (PageState.QueryString.ContainsKey("returnurl"))
{
_returnUrl = PageState.QueryString["returnurl"];
}
if (PageState.QueryString.ContainsKey("name"))
{
_username = PageState.QueryString["name"];
@ -168,11 +169,14 @@
{
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
if (PageState.User != null)
if (PageState.User != null && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
NavigationManager.NavigateTo(PageState.ReturnUrl);
}
@ -208,12 +212,12 @@
// hybrid apps utilize an interactive login
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
authstateprovider.NotifyAuthenticationChanged();
NavigationManager.NavigateTo(NavigateUrl(WebUtility.UrlDecode(_returnUrl), true));
NavigationManager.NavigateTo(NavigateUrl(PageState.ReturnUrl, true));
}
else
{
// 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/");
await interop.SubmitForm(url, fields);
}
@ -255,7 +259,7 @@
private void Cancel()
{
NavigationManager.NavigateTo(_returnUrl);
NavigationManager.NavigateTo(PageState.ReturnUrl);
}
private async Task Forgot()
@ -323,7 +327,7 @@
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>
<br />
<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 />
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
@ -155,6 +155,7 @@
private DateTime modifiedon;
private DateTime? _effectivedate = null;
private DateTime? _expirydate = null;
protected override void OnInitialized()
{
_module = ModuleState.ModuleDefinition.Name;
@ -197,7 +198,8 @@
ModuleSettingsComponent = builder =>
{
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();
};
}
@ -216,7 +218,8 @@
ContainerSettingsComponent = builder =>
{
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();
};
}
@ -280,7 +283,7 @@
await containerSettingsControl.UpdateSettings();
}
NavigationManager.NavigateTo(NavigateUrl());
NavigationManager.NavigateTo(PageState.ReturnUrl);
}
else
{

View File

@ -201,7 +201,7 @@
@if (_themeSettingsType != null)
{
<TabPanel Name="ThemeSettings" Heading=@Localizer["Theme.Heading"] ResourceKey="ThemeSettings">
@ThemeSettingsComponent
@_themeSettingsComponent
</TabPanel>
}
</TabStrip>
@ -240,7 +240,7 @@
private PermissionGrid _permissionGrid;
private Type _themeSettingsType;
private object _themeSettings;
private RenderFragment ThemeSettingsComponent { get; set; }
private RenderFragment _themeSettingsComponent { get; set; }
private bool _refresh = false;
protected Page _parent = null;
protected Dictionary<string, string> _icons;
@ -337,16 +337,18 @@
private void ThemeSettings()
{
_themeSettingsType = null;
_themeSettingsComponent = null;
var theme = PageState.Site.Themes.FirstOrDefault(item => item.Themes.Any(themecontrol => themecontrol.TypeName.Equals(_themetype)));
if (theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType))
{
_themeSettingsType = Type.GetType(theme.ThemeSettingsType);
if (_themeSettingsType != null)
{
ThemeSettingsComponent = builder =>
_themeSettingsComponent = builder =>
{
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();
};
}
@ -374,36 +376,54 @@
page.SiteId = PageState.Page.SiteId;
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")
{
page.ParentId = null;
page.Path = Utilities.GetFriendlyUrl(_path);
}
else
{
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
{
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);
@ -449,23 +469,23 @@
// appearance
page.Title = _title;
page.Icon = (_icon == null ? string.Empty : _icon);
page.ThemeType = _themetype;
if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType)
{
page.ThemeType = string.Empty;
}
page.DefaultContainerType = _containertype;
if (!string.IsNullOrEmpty(page.DefaultContainerType) && page.DefaultContainerType == PageState.Site.DefaultContainerType)
{
page.DefaultContainerType = string.Empty;
}
page.ThemeType = _themetype;
if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType)
{
page.ThemeType = string.Empty;
}
page.DefaultContainerType = _containertype;
if (!string.IsNullOrEmpty(page.DefaultContainerType) && page.DefaultContainerType == PageState.Site.DefaultContainerType)
{
page.DefaultContainerType = string.Empty;
}
// page content
page.HeadContent = _headcontent;
page.BodyContent = _bodycontent;
// permissions
page.PermissionList = _permissionGrid.GetPermissionList();
page.PermissionList = _permissionGrid.GetPermissionList();
page = await PageService.AddPageAsync(page);
await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, page.ParentId);
@ -473,11 +493,18 @@
await logger.LogInformation("Page Added {Page}", page);
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
{
NavigationManager.NavigateTo(page.Path); // redirect to new page
NavigationManager.NavigateTo(PageState.ReturnUrl, true);
}
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

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>
<div class="col-sm-9">
<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>
</div>
@ -234,7 +237,7 @@
@if (_themeSettingsType != null)
{
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
@ThemeSettingsComponent
@_themeSettingsComponent
</TabPanel>
<br />
}
@ -278,7 +281,7 @@
@if (_themeSettingsType != null)
{
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
@ThemeSettingsComponent
@_themeSettingsComponent
</TabPanel>
<br />
}
@ -317,7 +320,7 @@
private string _containertype = "-";
private Type _themeSettingsType;
private object _themeSettings;
private RenderFragment ThemeSettingsComponent { get; set; }
private RenderFragment _themeSettingsComponent { get; set; }
private string _headcontent;
private string _bodycontent;
private List<Permission> _permissions = null;
@ -377,7 +380,7 @@
}
else
{
if (_path.Contains("/"))
if (_path.Contains("/") & !_path.Contains("://"))
{
_path = _path.Substring(_path.LastIndexOf("/") + 1);
}
@ -467,7 +470,7 @@
_containertype = _containers.First().TypeName;
ThemeSettings();
StateHasChanged();
// if theme chosen is different than default site theme, display warning message to user
if (ThemeService.GetTheme(PageState.Site.Themes, _themetype)?.ThemeName != ThemeService.GetTheme(PageState.Site.Themes, PageState.Site.DefaultThemeType)?.ThemeName)
{
@ -478,16 +481,19 @@
private void ThemeSettings()
{
_themeSettingsType = null;
_themeSettingsComponent = null;
var theme = PageState.Site.Themes.FirstOrDefault(item => item.Themes.Any(themecontrol => themecontrol.TypeName.Equals(_themetype)));
if (theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType))
{
_themeSettingsType = Type.GetType(theme.ThemeSettingsType);
if (_themeSettingsType != null)
{
ThemeSettingsComponent = builder =>
_themeSettingsComponent = builder =>
{
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();
};
}
@ -514,36 +520,54 @@
_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")
{
_page.ParentId = null;
_page.Path = Utilities.GetFriendlyUrl(_path);
}
else
{
_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
{
_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);
@ -630,11 +654,18 @@
await logger.LogInformation("Page Saved {Page}", _page);
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
{
NavigationManager.NavigateTo(PageState.ReturnUrl);
NavigationManager.NavigateTo(PageState.ReturnUrl, true);
}
else
{
NavigationManager.NavigateTo(NavigateUrl());
if (!_page.Path.Contains("://"))
{
NavigationManager.NavigateTo(NavigateUrl(), true); // redirect to page being edited
}
else
{
NavigationManager.NavigateTo(NavigateUrl("admin/pages"));
}
}
}
else

View File

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

View File

@ -9,60 +9,68 @@
@if (PageState.Site.AllowRegistration)
{
<AuthorizeView Roles="@RoleNames.Registered">
<Authorizing>
<text>...</text>
</Authorizing>
<Authorized>
<ModuleMessage Message="@Localizer["Info.Registration.Exists"]" Type="MessageType.Info" />
</Authorized>
<NotAuthorized>
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="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 />
if (!_userCreated)
{
<AuthorizeView Roles="@RoleNames.Registered">
<Authorizing>
<text>...</text>
</Authorizing>
<Authorized>
<ModuleMessage Message="@Localizer["Info.Registration.Exists"]" Type="MessageType.Info" />
</Authorized>
<NotAuthorized>
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="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 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>
<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>
<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>
@if (_allowsitelogin)
{
<br /><br />
<NavLink href="@NavigateUrl("login")">@Localizer["Login"]</NavLink>
}
</form>
</NotAuthorized>
</AuthorizeView>
}
}
else
{
@ -80,12 +88,15 @@ else
private string _confirm = string.Empty;
private string _email = string.Empty;
private string _displayname = string.Empty;
private bool _userCreated = false;
private bool _allowsitelogin = true;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
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()
@ -121,14 +132,8 @@ else
if (user != null)
{
await logger.LogInformation("User Created {Username} {Email}", _username, _email);
if (PageState.QueryString.ContainsKey("returnurl"))
{
NavigationManager.NavigateTo(WebUtility.UrlDecode(PageState.QueryString["returnurl"]));
}
else // legacy behavior
{
AddModuleMessage(Localizer["Info.User.AccountCreate"], MessageType.Info);
}
_userCreated = true;
AddModuleMessage(Localizer["Info.User.AccountCreate"], MessageType.Info);
}
else
{
@ -160,7 +165,7 @@ else
private void Cancel()
{
NavigationManager.NavigateTo(NavigateUrl(string.Empty));
NavigationManager.NavigateTo(PageState.ReturnUrl);
}
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>
<div class="col-sm-9">
<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>
</div>
@ -110,9 +113,12 @@
<div class="col-sm-9">
<select id="defaultAdminContainer" class="form-select" @bind="@_admincontainertype" required>
<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>
</div>
@ -311,27 +317,38 @@
<Section Name="Hosting" Heading="Hosting Model" ResourceKey="Hosting">
<div class="container">
<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">
<select id="runtime" class="form-select" @bind="@_runtime" required>
<option value="Server">@SharedLocalizer["BlazorServer"]</option>
<option value="WebAssembly">@SharedLocalizer["BlazorWebAssembly"]</option>
<option value="@Runtimes.Server">@(SharedLocalizer["Runtime" + @Runtimes.Server])</option>
<option value="@Runtimes.WebAssembly">@(SharedLocalizer["Runtime" + @Runtimes.WebAssembly])</option>
<option value="@Runtimes.Auto">@(SharedLocalizer["Runtime" + @Runtimes.Auto])</option>
</select>
</div>
</div>
<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">
<select id="prerender" class="form-select" @bind="@_prerender" required>
<option value="Prerendered">@SharedLocalizer["Yes"]</option>
<option value="">@SharedLocalizer["No"]</option>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="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">
<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="False">@SharedLocalizer["No"]</option>
</select>
@ -414,9 +431,10 @@
private int _aliasid = -1;
private string _aliasname;
private string _defaultalias;
private string _runtime = "";
private string _prerender = "";
private string _hybridenabled = "";
private string _rendermode = RenderModes.Interactive;
private string _runtime = Runtimes.Server;
private string _prerender = "True";
private string _hybrid = "False";
private string _tenant = string.Empty;
private string _database = string.Empty;
private string _connectionstring = string.Empty;
@ -500,9 +518,10 @@
await GetAliases();
// hosting model
_rendermode = site.RenderMode;
_runtime = site.Runtime;
_prerender = site.RenderMode.Replace(_runtime, "");
_hybridenabled = site.HybridEnabled.ToString();
_prerender = site.Prerender.ToString();
_hybrid = site.Hybrid.ToString();
// database
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
@ -566,9 +585,6 @@
var site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null)
{
bool refresh = false;
bool reload = false;
site.Name = _name;
site.HomePageId = (_homepageid != "-" ? int.Parse(_homepageid) : null);
site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
@ -582,7 +598,6 @@
if (logofileid != _logofileid)
{
_logofileid = logofileid;
refresh = true; // needs to be refreshed on client
}
}
int? faviconFieldId = _faviconfilemanager.GetFileId();
@ -590,17 +605,14 @@
if (site.FaviconFileId != faviconFieldId)
{
site.FaviconFileId = faviconFieldId;
reload = true; // needs to be reloaded on server
}
if (site.DefaultThemeType != _themetype)
{
site.DefaultThemeType = _themetype;
refresh = true; // needs to be refreshed on client
}
if (site.DefaultContainerType != _containertype)
{
site.DefaultContainerType = _containertype;
refresh = true; // needs to be refreshed on client
}
site.AdminContainerType = _admincontainertype;
@ -608,44 +620,39 @@
if (site.HeadContent != _headcontent)
{
site.HeadContent = _headcontent;
reload = true;
}
if (site.BodyContent != _bodycontent)
{
site.BodyContent = _bodycontent;
reload = true;
}
// PWA
if (site.PwaIsEnabled.ToString() != _pwaisenabled)
{
site.PwaIsEnabled = Boolean.Parse(_pwaisenabled);
reload = true; // needs to be reloaded on server
}
int? pwaappiconfileid = _pwaappiconfilemanager.GetFileId();
if (pwaappiconfileid == -1) pwaappiconfileid = null;
if (site.PwaAppIconFileId != pwaappiconfileid)
{
site.PwaAppIconFileId = pwaappiconfileid;
reload = true; // needs to be reloaded on server
}
int? pwasplashiconfileid = _pwasplashiconfilemanager.GetFileId();
if (pwasplashiconfileid == -1) pwasplashiconfileid = null;
if (site.PwaSplashIconFileId != pwasplashiconfileid)
{
site.PwaSplashIconFileId = pwasplashiconfileid;
reload = true; // needs to be reloaded on server
}
// hosting model
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.RenderMode = _runtime + _prerender;
site.HybridEnabled = bool.Parse(_hybridenabled);
reload = true; // needs to be reloaded on serve
site.Prerender = bool.Parse(_prerender);
site.Hybrid = bool.Parse(_hybrid);
}
}
@ -672,15 +679,7 @@
await logger.LogInformation("Site Settings Saved {Site}", site);
if (refresh || reload)
{
NavigationManager.NavigateTo(NavigateUrl(true), reload); // refresh/reload
}
else
{
AddModuleMessage(Localizer["Success.Settings.SaveSite"], MessageType.Success);
await ScrollToPageTop();
}
NavigationManager.NavigateTo(NavigateUrl(), true); // reload
}
}
else

View File

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

View File

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

View File

@ -483,9 +483,9 @@
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
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
{
@ -551,7 +551,7 @@
private void Cancel()
{
NavigationManager.NavigateTo(NavigateUrl(string.Empty));
NavigationManager.NavigateTo(PageState.ReturnUrl);
}
private void ProfileChanged(ChangeEventArgs e, string SettingName)
@ -596,7 +596,7 @@
{
try
{
ModuleInstance.ShowProgressIndicator();
ShowProgressIndicator();
foreach(var Notification in notifications)
{
if (!Notification.IsDeleted)
@ -612,7 +612,7 @@
}
await logger.LogInformation("Notifications Permanently Deleted");
await LoadNotificationsAsync();
ModuleInstance.HideProgressIndicator();
HideProgressIndicator();
StateHasChanged();
}
@ -620,7 +620,7 @@
{
await logger.LogError(ex, "Error Deleting Notifications {Error}", ex.Message);
AddModuleMessage(ex.Message, MessageType.Error);
ModuleInstance.HideProgressIndicator();
HideProgressIndicator();
}
}

View File

@ -2,47 +2,102 @@
@using System.Text.Json
@inherits LocalizableComponent
@inject IStringLocalizer<SharedResources> SharedLocalizer
@inject NavigationManager NavigationManager
@if (_visible)
@if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Interactive)
{
<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>
<button type="button" class="btn-close" aria-label="Close" @onclick="DisplayModal"></button>
</div>
<div class="modal-body">
<p>@Message</p>
</div>
<div class="modal-footer">
@if (!string.IsNullOrEmpty(Action))
{
<button type="button" class="@Class" @onclick="Confirm">@((MarkupString)_iconSpan) @Text</button>
}
<button type="button" class="btn btn-secondary" @onclick="DisplayModal">@SharedLocalizer["Cancel"]</button>
@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>
<button type="button" class="btn-close" aria-label="Close" @onclick="DisplayModal"></button>
</div>
<div class="modal-body">
<p>@Message</p>
</div>
<div class="modal-footer">
@if (!string.IsNullOrEmpty(Action))
{
<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>
}
@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 {
private bool _visible = false;
private List<Permission> _permissions;
private List<Permission> _permissions;
private bool _editmode = false;
private bool _authorized = false;
private string _iconSpan = string.Empty;
@ -62,11 +117,11 @@
[Parameter]
public SecurityAccessLevel? Security { get; set; } // optional - can be used to explicitly specify SecurityAccessLevel
[Parameter]
public string Permissions { get; set; } // deprecated - use PermissionList instead
[Parameter]
public string Permissions { get; set; } // deprecated - use PermissionList instead
[Parameter]
public List<Permission> PermissionList { get; set; } // optional - can be used to specify permissions
[Parameter]
public List<Permission> PermissionList { get; set; } // optional - can be used to specify permissions
[Parameter]
public string Class { get; set; } // optional
@ -83,15 +138,18 @@
[Parameter]
public string IconName { get; set; } // optional - specifies an icon for the link - default is no icon
protected override void OnInitialized()
{
if (!string.IsNullOrEmpty(Permissions))
{
PermissionList = JsonSerializer.Deserialize<List<Permission>>(Permissions);
}
}
protected override void OnParametersSet()
[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
protected override void OnInitialized()
{
if (!string.IsNullOrEmpty(Permissions))
{
PermissionList = JsonSerializer.Deserialize<List<Permission>>(Permissions);
}
}
protected override void OnParametersSet()
{
base.OnParametersSet();
@ -122,8 +180,13 @@
Header = Localize(nameof(Header), Header);
Message = Localize(nameof(Message), Message);
_permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList;
_permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList;
_authorized = IsAuthorized();
if (PageState.QueryString.ContainsKey("dialog"))
{
_visible = (PageState.QueryString["dialog"] == Id);
}
}
private bool IsAuthorized()
@ -175,12 +238,22 @@
private void DisplayModal()
{
_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()
{
DisplayModal();
OnClick();
DisplayModal();
}
}

View File

@ -8,7 +8,7 @@
{
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
{
@ -97,10 +97,13 @@
{
base.OnParametersSet();
_text = Action;
if (!string.IsNullOrEmpty(Text))
{
_text = Text;
_text = Localize(nameof(Text), _text);
}
else
{
_text = Localize(nameof(Action), Action);
}
if (IconOnly && !string.IsNullOrEmpty(IconName))
@ -150,7 +153,6 @@
}
_permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList;
_text = Localize(nameof(Text), _text);
_url = EditUrl(_path, _moduleId, Action, _parameters);
if (!string.IsNullOrEmpty(ReturnUrl))

View File

@ -2,21 +2,27 @@
@inherits ModuleControlBase
@inject NavigationManager NavigationManager
@if (!string.IsNullOrEmpty(_message))
@if (!string.IsNullOrEmpty(Message))
{
<div class="@_classname alert-dismissible fade show mb-3" role="alert">
@((MarkupString)_message)
@if (Type == MessageType.Error && PageState != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
@((MarkupString)Message)
@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>
}
@code {
private string _message = string.Empty;
private string _classname = string.Empty;
private string _formname = "ModuleMessageForm";
[Parameter]
public string Message { get; set; }
@ -24,10 +30,32 @@
[Parameter]
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()
{
_message = Message;
if (!string.IsNullOrEmpty(_message))
UpdateClassName();
}
private void UpdateClassName()
{
if (!string.IsNullOrEmpty(Message))
{
_classname = GetMessageType(Type);
}
@ -57,7 +85,6 @@
private void DismissModal()
{
_message = "";
StateHasChanged();
Message = "";
}
}

View File

@ -6,63 +6,132 @@
@if (ItemList != null)
{
@if (!string.IsNullOrEmpty(SearchProperties))
@if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Interactive)
{
<div class="input-group my-3">
<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>
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
</div>
@if (!string.IsNullOrEmpty(SearchProperties))
{
<form autocomplete="off">
<div class="input-group my-3">
<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)
{
<div class="table-responsive">
@ -126,53 +195,106 @@
}
</div>
}
@if ((Toolbar == "Bottom" || 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>
@if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Interactive)
{
<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>
}
<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)
@if (_pages > _displayPages && _displayPages > 1)
{
<li class="page-item app-pager-pointer active">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</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>
}
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">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
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 () => 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 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@((_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>
<li class="page-item disabled">
<a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
</li>
</ul>
}
else
{
<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]
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; }
protected override void OnInitialized()
@ -292,6 +420,11 @@
}
}
if (PageState.QueryString.ContainsKey("search"))
{
_search = PageState.QueryString["search"];
}
if (!string.IsNullOrEmpty(SearchProperties))
{
AllItems = Items; // only used in search
@ -316,13 +449,20 @@
_displayPages = int.Parse(DisplayPages);
}
if (!string.IsNullOrEmpty(CurrentPage))
if (PageState.QueryString.ContainsKey("page"))
{
_page = int.Parse(CurrentPage);
_page = int.Parse(PageState.QueryString["page"]);
}
else
{
_page = 1;
if (!string.IsNullOrEmpty(CurrentPage))
{
_page = int.Parse(CurrentPage);
}
else
{
_page = 1;
}
}
if (_page < 1) _page = 1;
@ -465,4 +605,25 @@
}
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="col">
<TabStrip>
<TabStrip ActiveTab="@_activetab">
@if (AllowRichText)
{
<TabPanel Name="Rich" Heading="Rich Text Editor" ResourceKey="RichTextEditor">
@ -17,12 +17,6 @@
<br />
}
<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)
{
<button type="button" class="btn btn-primary" @onclick="InsertRichImage">@Localizer["InsertImage"]</button>
@ -85,7 +79,6 @@
<br />
}
<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)
{
<button type="button" class="btn btn-primary" @onclick="InsertRawImage">@Localizer["InsertImage"]</button>
@ -121,6 +114,7 @@
private string _rawhtml = string.Empty;
private string _originalrawhtml = string.Empty;
private string _message = string.Empty;
private string _activetab = "Rich";
[Parameter]
public string Content { get; set; }
@ -163,6 +157,12 @@
_rawhtml = Content;
_originalrawhtml = _rawhtml; // preserve for comparison later
_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)
@ -208,19 +208,6 @@
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()
{
// evaluate raw html content as first priority

View File

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

View File

@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Oqtane.Documentation;
@ -9,7 +8,7 @@ using Oqtane.Shared;
namespace Oqtane.Modules.HtmlText.Services
{
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
public class HtmlTextService : ServiceBase, IHtmlTextService, IService
public class HtmlTextService : ServiceBase, IHtmlTextService, IClientService
{
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));
}
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)

View File

@ -13,7 +13,7 @@ namespace Oqtane.Modules.HtmlText.Services
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);
}

View File

@ -40,7 +40,7 @@
}
catch (Exception ex)
{
ModuleInstance.AddModuleMessage(ex.Message, MessageType.Error);
AddModuleMessage(ex.Message, MessageType.Error);
}
}
@ -55,7 +55,7 @@
}
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; }
[Parameter]
public ModuleInstance ModuleInstance { get; set; }
public RenderModeBoundary RenderModeBoundary { get; set; }
// optional interface properties
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 string RenderMode { get { return RenderModes.Interactive; } } // interactive by default
// url parameters
public virtual string UrlParametersTemplate { get; set; }
@ -77,7 +79,7 @@ namespace Oqtane.Modules
{
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
@ -87,22 +89,25 @@ namespace Oqtane.Modules
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 scripts = new List<object>();
var inline = 0;
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;
scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module, location = resource.Location.ToString().ToLower() });
}
else
{
inline += 1;
await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Content, resource.Location.ToString().ToLower());
if (!string.IsNullOrEmpty(resource.Url))
{
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url;
scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module, location = resource.Location.ToString().ToLower() });
}
else
{
inline += 1;
await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Content, resource.Location.ToString().ToLower());
}
}
}
if (scripts.Any())
@ -272,22 +277,22 @@ namespace Oqtane.Modules
public void AddModuleMessage(string message, MessageType type, string position)
{
ClearModuleMessage();
ModuleInstance.AddModuleMessage(message, type, position);
RenderModeBoundary.AddModuleMessage(message, type, position);
}
public void ClearModuleMessage()
{
ModuleInstance.AddModuleMessage("", MessageType.Undefined);
RenderModeBoundary.AddModuleMessage("", MessageType.Undefined);
}
public void ShowProgressIndicator()
{
ModuleInstance.ShowProgressIndicator();
RenderModeBoundary.ShowProgressIndicator();
}
public void HideProgressIndicator()
{
ModuleInstance.HideProgressIndicator();
RenderModeBoundary.HideProgressIndicator();
}
public void SetModuleTitle(string title)
@ -487,5 +492,8 @@ namespace Oqtane.Modules
{
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>
<OutputType>Exe</OutputType>
<Configurations>Debug;Release</Configurations>
<Version>5.0.2</Version>
<Version>5.1.0</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -12,21 +12,20 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/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>
<RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace>
<IsPackable>true</IsPackable>
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.1" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.3" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.3" />
<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" />
</ItemGroup>

View File

@ -41,10 +41,10 @@ namespace Oqtane.Client
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
// register auth services
builder.Services.AddOqtaneAuthorization();
builder.Services.AddOqtaneAuthentication();
// register scoped core services
builder.Services.AddOqtaneScopedServices();
builder.Services.AddOqtaneClientScopedServices();
var serviceProvider = builder.Services.BuildServiceProvider();
@ -220,6 +220,16 @@ namespace Oqtane.Client
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
{

View File

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

View File

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

View File

@ -228,4 +228,7 @@
<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>
</data>
<data name="Register" xml:space="preserve">
<value>Register as new user?</value>
</data>
</root>

View File

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

View File

@ -274,19 +274,19 @@
<value>Select Theme</value>
</data>
<data name="Hosting.Heading" xml:space="preserve">
<value>Hosting Model</value>
<value>UI Component Settings</value>
</data>
<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 name="Prerender.Text" xml:space="preserve">
<value>Prerender? </value>
</data>
<data name="Runtime.HelpText" xml:space="preserve">
<value>The Blazor runtime hosting model for the site</value>
<data name="RenderMode.HelpText" xml:space="preserve">
<value>The default render mode for the site</value>
</data>
<data name="Runtime.Text" xml:space="preserve">
<value>Runtime: </value>
<data name="RenderMode.Text" xml:space="preserve">
<value>Render Mode:</value>
</data>
<data name="Browse" xml:space="preserve">
<value>Browse</value>
@ -325,7 +325,7 @@
<value>Default Alias: </value>
</data>
<data name="Aliases.Heading" xml:space="preserve">
<value>Urls</value>
<value>Site Urls</value>
</data>
<data name="AliasName" xml:space="preserve">
<value>Url</value>
@ -423,4 +423,10 @@
<data name="HybridEnabled.Text" xml:space="preserve">
<value>Hybrid Enabled?</value>
</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>

View File

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

View File

@ -117,9 +117,6 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="SynchronizeContent" xml:space="preserve">
<value>Synchronize Content</value>
</data>
<data name="InsertImage" xml:space="preserve">
<value>Insert Image</value>
</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>
</resheader>
<data name="Edit.Action" xml:space="preserve">
<value>Edit</value>
</data>
<value>Edit Content</value>
</data>
<data name="Edit.Text" xml:space="preserve">
<value>Edit</value>
<value>Edit Content</value>
</data>
<data name="Error.Content.Load" xml:space="preserve">
<value>An Error Occurred Loading Content</value>

View File

@ -312,14 +312,14 @@
<data name="Not Specified" xml:space="preserve">
<value>Not Specified</value>
</data>
<data name="BlazorServer" xml:space="preserve">
<value>Blazor Server</value>
<data name="RuntimeServer" xml:space="preserve">
<value>Server (SignalR)</value>
</data>
<data name="BlazorWebAssembly" xml:space="preserve">
<value>Blazor WebAssembly</value>
<data name="RuntimeWebAssembly" xml:space="preserve">
<value>Client (WebAssembly)</value>
</data>
<data name="BlazorHybrid" xml:space="preserve">
<value>Blazor Hybrid</value>
<data name="StaticServer" xml:space="preserve">
<value>Static Server Rendering</value>
</data>
<data name="Settings" xml:space="preserve">
<value>Settings</value>
@ -441,4 +441,16 @@
<data name="Message.EffectiveExpiryDateError" xml:space="preserve">
<value>Effective Date cannot be after Expiry Date.</value>
</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>

View File

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

View File

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

View File

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

View File

@ -16,6 +16,7 @@ namespace Oqtane.Services
{
private readonly HttpClient _httpClient;
private readonly SiteState _siteState;
private readonly IHttpClientFactory _factory;
protected ServiceBase(HttpClient httpClient, SiteState siteState)
{
@ -23,13 +24,31 @@ namespace Oqtane.Services
_siteState = siteState;
}
protected ServiceBase(IHttpClientFactory factory, SiteState siteState)
{
_factory = factory;
_siteState = siteState;
}
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

View File

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

View File

@ -8,7 +8,10 @@
<div class="modal-content">
<div class="modal-header">
<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 class="modal-body">
<ModuleInstance />

View File

@ -31,9 +31,9 @@
public override List<Resource> Resources => new List<Resource>()
{
// 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.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
@inherits ModuleActionsBase
@inherits ContainerBase
@attribute [OqtaneIgnore]
@if (PageState.EditMode && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList) && PageState.Action == Constants.DefaultAction)
{
<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>
@if (PageState.RenderMode == RenderModes.Interactive)
{
<ModuleActionsInteractive PageState="@PageState" ModuleState="@ModuleState" />
}
else
{
<ModuleActionsInteractive PageState="@PageState" ModuleState="@ModuleState" @rendermode="@InteractiveRenderMode.GetInteractiveRenderMode(PageState.Site.Runtime, PageState.Site.Prerender)" />
}
}

View File

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

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]
@inject IStringLocalizer<SharedResources> SharedLocalizer
@inject IStringLocalizerFactory LocalizerFactory
@implements IDisposable
<span class="app-moduletitle">
@((MarkupString)title)

View File

@ -1,19 +1,8 @@
@using System.Net
@namespace Oqtane.Themes.Controls
@inherits ThemeControlBase
@inject NavigationManager NavigationManager
@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 IStringLocalizer<ControlPanel> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (ShowLanguageSwitcher)
{
@ -22,223 +11,36 @@
@if (_showEditMode || (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered)))
{
if (PageState.EditMode)
{
<button type="button" class="btn @ButtonClass active" data-bs-toggle="button" aria-pressed="true" autocomplete="off" @onclick="(async () => await ToggleEditMode(PageState.EditMode))">
<span class="oi oi-pencil"></span>
</button>
}
else
{
<button type="button" class="btn @ButtonClass" data-bs-toggle="button" aria-pressed="false" autocomplete="off" @onclick="(async () => await ToggleEditMode(PageState.EditMode))">
<span class="oi oi-pencil"></span>
</button>
}
<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" />
@if (PageState.EditMode)
{
<button type="submit" class="btn @ButtonClass active" aria-pressed="true" autocomplete="off">
<span class="oi oi-pencil"></span>
</button>
}
else
{
<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))
{
<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">
@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>
@if (PageState.RenderMode == RenderModes.Interactive)
{
<ControlPanelInteractive PageState="@PageState" SiteState="@SiteState" ButtonClass="@ButtonClass" ContainerClass="@ContainerClass" HeaderClass="@HeaderClass" BodyClass="@BodyClass" ShowLanguageSwitcher="@ShowLanguageSwitcher" LanguageDropdownAlignment="@LanguageDropdownAlignment" />
}
else
{
<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)" />
}
}
@code{
@code {
[Parameter]
public string ButtonClass { get; set; } = "btn-outline-secondary";
@ -259,84 +61,15 @@
private bool _canViewAdminDashboard = 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 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()
protected override void OnParametersSet()
{
_canViewAdminDashboard = CanViewAdminDashboard();
_showEditMode = false;
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList))
{
_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
{
@ -347,7 +80,7 @@
_showEditMode = true;
break;
}
}
}
}
}
@ -367,128 +100,7 @@
return false;
}
private void CategoryChanged(ChangeEventArgs e)
{
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)
private async Task ToggleEditMode(bool editMode)
{
Page page = null;
if (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered))
@ -498,13 +110,13 @@
if (_showEditMode)
{
if (EditMode)
PageState.EditMode = !editMode;
if (PageState.User != null)
{
PageState.EditMode = false;
}
else
{
PageState.EditMode = true;
// preserve edit mode for authenticated users
var userSettings = new Dictionary<string, string> { { "CP-editmode", (PageState.EditMode) ? PageState.Page.PageId.ToString() : "-1" } };
await SettingService.UpdateUserSettingsAsync(userSettings, PageState.User.UserId);
}
// preserve other querystring parameters
@ -512,190 +124,14 @@
PageState.QueryString.Add("edit", PageState.EditMode.ToString().ToLower());
var url = PageState.Route.AbsolutePath + Utilities.CreateQueryString(PageState.QueryString);
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
{
// 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);
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 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>
</Authorizing>
<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>
<NotAuthorized>
@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>
</AuthorizeView>

View File

@ -21,30 +21,65 @@ namespace Oqtane.Themes.Controls
[Inject] public IJSRuntime jsRuntime { 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;
var 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
}
allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false;
allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
// set login url
if (allowexternallogin && !allowsitelogin)
{
// external login
NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + returnurl), true);
loginurl = Utilities.TenantUrl(PageState.Alias, "/pages/external");
}
else
{
// 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);
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) || !Utilities.IsPageModuleVisible(PageState.Page.EffectiveDate, PageState.Page.ExpiryDate))
{
url = PageState.Alias.Path;
}
if (PageState.Runtime == Shared.Runtime.Hybrid)
if (PageState.Runtime == 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);
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
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, returnurl = url };
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, returnurl = returnurl };
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
@inherits ThemeControlBase
@inject IFileService FileService
@if (file != null)
@if (PageState.Site.LogoFileId != null)
{
<span class="app-logo">
<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>
</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}")">
@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)
{
<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="@childPage.Icon" aria-hidden="true" />
@childPage.Name <span class="visually-hidden-focusable">(current)</span>
@ -17,7 +24,7 @@
}
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="@childPage.Icon" aria-hidden="true" />
@childPage.Name
@ -32,12 +39,19 @@ else
<ul class="navbar-nav mr-auto">
@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 (childPage.PageId == PageState.Page.PageId)
{
<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="@childPage.Icon" aria-hidden="true" />
@childPage.Name <span class="visually-hidden-focusable">(current)</span>
@ -48,7 +62,7 @@ else
else
{
<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="@childPage.Icon" aria-hidden="true" />
@childPage.Name
@ -62,7 +76,7 @@ else
if (childPage.PageId == PageState.Page.PageId)
{
<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" />
@childPage.Name <span class="visually-hidden-focusable">(current)</span>
</a>
@ -72,7 +86,7 @@ else
else
{
<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" />
@childPage.Name
</a>

View File

@ -5,10 +5,17 @@
{
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)
{
<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="@childPage.Icon" aria-hidden="true" />
@childPage.Name <span class="visually-hidden-focusable">(current)</span>
@ -19,7 +26,7 @@
else
{
<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="@childPage.Icon" aria-hidden="true" />
@childPage.Name
@ -38,10 +45,17 @@ else
<ul class="nav flex-column">
@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)
{
<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="@childPage.Icon" aria-hidden="true" />
@childPage.Name <span class="visually-hidden-focusable">(current)</span>
@ -52,7 +66,7 @@ else
else
{
<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="@childPage.Icon" aria-hidden="true" />
@childPage.Name

View File

@ -11,12 +11,12 @@
<text>...</text>
</Authorizing>
<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>
<NotAuthorized>
@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>
</AuthorizeView>
@ -31,17 +31,16 @@
protected override void OnParametersSet()
{
_returnurl = WebUtility.UrlEncode(PageState.Route.PathAndQuery);
}
private void RegisterUser()
{
NavigationManager.NavigateTo(NavigateUrl("register", "returnurl=" + _returnurl));
}
private void UpdateProfile()
{
NavigationManager.NavigateTo(NavigateUrl("profile", "returnurl=" + _returnurl));
if (!PageState.QueryString.ContainsKey("returnurl"))
{
// remember current url
_returnurl = WebUtility.UrlEncode(PageState.Route.PathAndQuery);
}
else
{
// use existing value
_returnurl = PageState.QueryString["returnurl"];
}
}
}

View File

@ -83,7 +83,7 @@
}
catch (Exception ex)
{
ModuleInstance.AddModuleMessage(ex.Message, MessageType.Error);
AddModuleMessage(ex.Message, MessageType.Error);
}
}
@ -100,7 +100,7 @@
}
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",
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.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.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace Oqtane.Themes
@ -46,7 +45,7 @@ namespace Oqtane.Themes
{
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
@ -63,15 +62,18 @@ namespace Oqtane.Themes
var inline = 0;
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;
scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module, location = resource.Location.ToString().ToLower() });
}
else
{
inline += 1;
await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Content, resource.Location.ToString().ToLower());
if (!string.IsNullOrEmpty(resource.Url))
{
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url;
scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module, location = resource.Location.ToString().ToLower() });
}
else
{
inline += 1;
await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Content, resource.Location.ToString().ToLower());
}
}
}
if (scripts.Any())

View File

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

View File

@ -1,6 +1,10 @@
@namespace Oqtane.UI
@using System.ComponentModel
@using Oqtane.Shared
@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))
{
@ -15,6 +19,12 @@
private string _title = "";
private string _content = "";
[Parameter]
public string RenderMode { get; set; }
[Parameter]
public string Runtime { get; set; }
protected override void OnInitialized()
{
((INotifyPropertyChanged)SiteState.Properties).PropertyChanged += PropertyChanged;
@ -45,7 +55,7 @@
private string RemoveScripts(string headcontent)
{
if (!string.IsNullOrEmpty(headcontent))
if (!string.IsNullOrEmpty(headcontent) && RenderMode == RenderModes.Interactive)
{
var index = headcontent.IndexOf("<script");
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
@inject IStringLocalizer<ModuleInstance> Localizer
@inject ILogService LoggingService
@inherits ErrorBoundary
@inject SiteState SiteState
@if (CurrentException is null)
@if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Interactive)
{
if (_message != "" && _messagePosition == "top")
{
<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" />
}
<StreamRenderingDisabled ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" />
}
else
else
{
@if (!string.IsNullOrEmpty(_error))
{
<ModuleMessage Message="@_error" Type="@MessageType.Error"/>
}
<StreamRenderingEnabled ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" />
}
@code {
private string _message;
private string _error;
private MessageType _messageType;
private string _messagePosition;
private bool _progressIndicator = false;
private Type ModuleType { get; set; }
private IDictionary<string, object> ModuleParameters { get; set; }
// this component is on the static side of the render mode boundary
// it passes state as serializable parameters across the boundary
[CascadingParameter]
protected PageState PageState { get; set; }
@ -46,82 +20,24 @@ else
[CascadingParameter]
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)
{
AddModuleMessage(message, type, "top");
}
[Obsolete("AddModuleMessage is deprecated. Use ModuleBase.AddModuleMessage instead.", false)]
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 bool EditMode { get; set; }
public DateTime LastSyncDate { get; set; }
public string RenderMode { get; set; }
public Shared.Runtime Runtime { get; set; }
public int VisitorId { get; set; }
public string RemoteIPAddress { get; set; }
public string ReturnUrl { get; set; }
public bool IsInternalNavigation { get; set; }
public Guid RenderId { get; set; }
public bool Refresh { get; set; }
public List<Page> Pages
{

View File

@ -67,7 +67,7 @@ else
// pane matches current pane
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);
if (moduleType != null)
@ -123,7 +123,7 @@ else
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);
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.Net
@using Microsoft.AspNetCore.Http
@namespace Oqtane.UI
@inject AuthenticationStateProvider AuthenticationStateProvider
@inject SiteState SiteState
@ -11,8 +12,10 @@
@inject IUserService UserService
@inject IUrlMappingService UrlMappingService
@inject ILogService LogService
@inject ISettingService SettingService
@inject IJSRuntime JSRuntime
@implements IHandleAfterRender
@implements IDisposable
@if (!string.IsNullOrEmpty(_error))
{
@ -26,16 +29,13 @@
private bool _isInternalNavigation = false;
private bool _navigationInterceptionEnabled;
private PageState _pagestate;
private string _error = "";
[Parameter]
public string Runtime { get; set; }
private string _error = "";
[Parameter]
public string RenderMode { get; set; }
[Parameter]
public int VisitorId { get; set; }
public string Runtime { get; set; }
[CascadingParameter]
PageState PageState { get; set; }
@ -52,9 +52,9 @@
DynamicComponent = builder =>
{
if (PageState != null)
if (PageState != null && !PageState.Refresh)
{
builder.OpenComponent(0, Type.GetType(Constants.PageComponent));
builder.OpenComponent(0, typeof(ThemeBuilder));
builder.CloseComponent();
}
};
@ -67,7 +67,7 @@
protected override async Task OnParametersSetAsync()
{
if (PageState == null)
if (PageState == null || PageState.Refresh)
{
await Refresh();
}
@ -99,12 +99,12 @@
var editmode = false;
var refresh = false;
var lastsyncdate = DateTime.MinValue;
var runtime = (Shared.Runtime)Enum.Parse(typeof(Shared.Runtime), Runtime);
var visitorId = -1;
_error = "";
Route route = new Route(_absoluteUri, SiteState.Alias.Path);
int moduleid = (int.TryParse(route.ModuleId, out moduleid)) ? moduleid : -1;
var action = (!string.IsNullOrEmpty(route.Action)) ? route.Action : Constants.DefaultAction;
int moduleid = int.Parse(route.ModuleId);
var action = route.Action;
var querystring = Utilities.ParseQueryString(route.Query);
var returnurl = "";
@ -144,66 +144,43 @@
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)
{
editmode = PageState.EditMode;
lastsyncdate = PageState.LastSyncDate;
}
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
visitorId = PageState.VisitorId;
}
// get user
if (PageState == null || refresh || PageState.Alias.SiteId != SiteState.Alias.SiteId)
if (PageState.RenderMode == RenderModes.Interactive)
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
// verify user is authenticated for current site
if (authState.User.Identity.IsAuthenticated && authState.User.Claims.Any(item => item.Type == "sitekey" && item.Value == SiteState.Alias.SiteKey))
// process any sync events (for synchrozing the client application with the server)
var sync = await SyncService.GetSyncEventsAsync(lastsyncdate);
lastsyncdate = sync.SyncDate;
if (sync.SyncEvents.Any())
{
user = await UserService.GetUserAsync(authState.User.Identity.Name, SiteState.Alias.SiteId);
if (user != null)
// reload client application if server was restarted
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 (Runtime == Runtimes.Hybrid && !site.Hybrid)
{
_error = "Hybrid Integration Is Not Enabled For This Site";
return;
}
if (PageState == null || refresh || PageState.Page.Path != route.PagePath)
{
page = site.Pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase));
@ -255,6 +238,7 @@
{
// redirect to the personalized page
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, personalized.Path, ""), false);
return;
}
}
}
@ -264,6 +248,24 @@
// 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)))
{
// 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
page = ProcessPage(page, site, user, SiteState.Alias);
@ -285,16 +287,21 @@
Action = action,
EditMode = editmode,
LastSyncDate = lastsyncdate,
Runtime = runtime,
VisitorId = VisitorId,
RenderMode = RenderMode,
Runtime = (Shared.Runtime)Enum.Parse(typeof(Shared.Runtime), Runtime),
VisitorId = visitorId,
RemoteIPAddress = SiteState.RemoteIPAddress,
ReturnUrl = returnurl,
IsInternalNavigation = _isInternalNavigation,
RenderId = Guid.NewGuid()
RenderId = Guid.NewGuid(),
Refresh = false
};
OnStateChange?.Invoke(_pagestate);
await ScrollToFragment(_pagestate.Uri);
if (PageState.RenderMode == RenderModes.Interactive)
{
await ScrollToFragment(_pagestate.Uri);
}
}
else
{
@ -317,7 +324,7 @@
}
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
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "login", "?returnurl=" + WebUtility.UrlEncode(route.PathAndQuery)));
@ -357,17 +364,15 @@
page.ThemeType = site.DefaultThemeType;
}
var theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == page.ThemeType));
Type themetype = Type.GetType(page.ThemeType);
if (themetype == null || theme == null)
if (theme == null)
{
// fallback to default Oqtane theme
page.ThemeType = Constants.DefaultTheme;
themetype = Type.GetType(Constants.DefaultTheme);
theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == page.ThemeType));
}
Type themetype = Type.GetType(page.ThemeType);
string panes = "";
if (themetype != null && theme != null)
if (themetype != null)
{
// get resources for theme (ITheme)
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>();
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
module.SecurityAccessLevel = SecurityAccessLevel.Host;
module.ControlTitle = "";
@ -420,110 +427,108 @@
module.PaneModuleIndex = -1;
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
if (action == Constants.DefaultAction && !string.IsNullOrEmpty(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))
{
action = module.ModuleDefinition.DefaultAction;
}
// 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 + "="))
{
if (route.StartsWith(action + "="))
{
typename = route.Replace(action + "=", "");
}
typename = route.Replace(action + "=", "");
break;
}
}
}
}
// get module resources
page.Resources = ManagePageResources(page.Resources, module.ModuleDefinition.Resources, ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName));
}
// 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 = "";
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
}
// 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
}
// 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
var moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
page.Resources = ManagePageResources(page.Resources, moduleobject.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)
{
// settings components are embedded within a framework settings module
moduletype = Type.GetType(module.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, action), false, true);
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;
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
}
}
// validate that module's pane exists in current page
if (page.Panes.FindIndex(item => item.Equals(module.Pane, StringComparison.OrdinalIgnoreCase)) == -1)
// additional metadata needed for admin components
if (module.ModuleId == moduleid && action != "")
{
// fallback to default pane if it exists
if (page.Panes.FindIndex(item => item.Equals(PaneNames.Default, StringComparison.OrdinalIgnoreCase)) != -1)
{
module.Pane = PaneNames.Default;
}
else // otherwise admin pane (legacy)
{
module.Pane = PaneNames.Admin;
}
module.ControlTitle = moduleobject.Title;
module.SecurityAccessLevel = moduleobject.SecurityAccessLevel;
module.Actions = moduleobject.Actions;
module.UseAdminContainer = moduleobject.UseAdminContainer;
}
}
// calculate module position within pane
if (paneindex.ContainsKey(module.Pane.ToLower()))
// validate that module's pane exists in current page
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
if (string.IsNullOrEmpty(module.ContainerType))
{
module.ContainerType = defaultcontainertype;
}
module.PaneModuleIndex = paneindex[module.Pane.ToLower()];
// container fallback
if (string.IsNullOrEmpty(module.ContainerType))
{
module.ContainerType = defaultcontainertype;
}
}
@ -558,9 +563,7 @@
// ensure resource does not exist already
if (!pageresources.Exists(item => item.Url.ToLower() == resource.Url.ToLower()))
{
resource.Level = level;
resource.Namespace = name;
pageresources.Add(resource);
pageresources.Add(resource.Clone(level, 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

@ -34,14 +34,14 @@
var headcontent = "";
// favicon
var favicon = "favicon.ico";
var favicontype = "x-icon";
if (PageState.Site.FaviconFileId != null)
{
favicon = Utilities.FileUrl(PageState.Alias, PageState.Site.FaviconFileId.Value);
favicontype = favicon.Substring(favicon.LastIndexOf(".") + 1);
headcontent += $"<link id=\"app-favicon\" rel=\"icon\" href=\"{Utilities.FileUrl(PageState.Alias, PageState.Site.FaviconFileId.Value)}\" />\n";
}
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
AddHeadContent(headcontent, PageState.Site.HeadContent);
@ -57,8 +57,7 @@
DynamicComponent = builder =>
{
var themeType = Type.GetType(PageState.Page.ThemeType);
builder.OpenComponent(0, themeType);
builder.OpenComponent(0, Type.GetType(PageState.Page.ThemeType));
builder.CloseComponent();
};
}
@ -73,7 +72,7 @@
while (index >= 0)
{
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))
{
@ -145,6 +144,7 @@
string integrity = "";
string crossorigin = "";
string type = "";
var dataAttributes = new Dictionary<string, string>();
foreach (var attribute in attributes)
{
if (attribute.Contains("="))
@ -167,6 +167,12 @@
case "type":
type = value[1];
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))
{
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
{
@ -194,4 +200,67 @@
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 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

View File

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

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Version>5.0.2</Version>
<Version>5.1.0</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/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>
<RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
@ -33,9 +33,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="EFCore.NamingConventions" Version="8.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.1" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" />
<PackageReference Include="EFCore.NamingConventions" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.3" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.2" />
</ItemGroup>
<ItemGroup>

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Version>5.0.2</Version>
<Version>5.1.0</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/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>
<RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
@ -33,7 +33,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.3" />
</ItemGroup>
<ItemGroup>

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Version>5.0.2</Version>
<Version>5.1.0</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/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>
<RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
@ -33,7 +33,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.3" />
</ItemGroup>
<ItemGroup>

View File

@ -1,6 +1,16 @@
<DynamicComponent Type="@ComponentType"></DynamicComponent>
@using Oqtane.Shared;
<DynamicComponent Type="@ComponentType" Parameters="@Parameters"></DynamicComponent>
@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.Nodes;
@using Oqtane.Shared;
@if (string.IsNullOrEmpty(message))
{
@ -11,19 +12,16 @@ else
}
@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 string message = "";
protected override void OnInitialized()
{
Parameters = new Dictionary<string, object>();
Parameters.Add(new KeyValuePair<string, object>("AntiForgeryToken", ""));
Parameters.Add(new KeyValuePair<string, object>("Runtime", "Hybrid"));
Parameters.Add(new KeyValuePair<string, object>("RenderMode", "Hybrid"));
Parameters.Add(new KeyValuePair<string, object>("VisitorId", -1));
Parameters.Add(new KeyValuePair<string, object>("RemoteIPAddress", ""));
Parameters.Add(new KeyValuePair<string, object>("AuthorizationToken", ""));
Parameters.Add(new KeyValuePair<string, object>("RenderMode", RenderModes.Interactive));
Parameters.Add(new KeyValuePair<string, object>("Runtime", Runtimes.Hybrid));
Parameters.Add(new KeyValuePair<string, object>("Platform", DeviceInfo.Current.Platform.ToString()));
if (MauiConstants.UseAppSettings)
{

View File

@ -24,7 +24,7 @@ public static class MauiProgram
builder.Services.AddMauiBlazorWebView();
#if DEBUG
builder.Services.AddBlazorWebViewDeveloperTools();
#endif
#endif
var apiurl = LoadAppSettings();
@ -44,10 +44,10 @@ public static class MauiProgram
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
// register auth services
builder.Services.AddOqtaneAuthorization();
builder.Services.AddOqtaneAuthentication();
// register scoped core services
builder.Services.AddOqtaneScopedServices();
builder.Services.AddOqtaneClientScopedServices();
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
foreach (var assembly in assemblies)
@ -255,6 +255,16 @@ public static class MauiProgram
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
{

View File

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

View File

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

View File

@ -6,8 +6,6 @@
<title>Oqtane Maui</title>
<base 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 id="app-stylesheet-page" />
<link id="app-stylesheet-module" />
@ -25,6 +23,8 @@
<a class="dismiss">🗙</a>
</div>
<script src="js/app.js"></script>
<script src="js/loadjs.min.js"></script>
<script src="js/interop.js"></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">
<metadata>
<id>Oqtane.Client</id>
<version>5.0.2</version>
<version>5.1.0</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.2</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.0</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Updater</id>
<version>5.0.2</version>
<version>5.1.0</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.2</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.0</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</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)
{
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);
}
else
@ -79,7 +79,7 @@ namespace Oqtane.Controllers
if (ModelState.IsValid && alias.AliasId == id && _aliases.GetAlias(alias.AliasId, false) != null)
{
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);
}
else
@ -100,7 +100,7 @@ namespace Oqtane.Controllers
if (alias != null)
{
_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);
var aliases = _aliases.GetAliases();

View File

@ -176,7 +176,7 @@ namespace Oqtane.Controllers
{
file = CreateFile(file.Name, folder.FolderId, filepath);
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);
}
else
@ -234,7 +234,7 @@ namespace Oqtane.Controllers
}
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);
}
else
@ -266,7 +266,7 @@ namespace Oqtane.Controllers
}
_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);
}
else
@ -341,7 +341,7 @@ namespace Oqtane.Controllers
if (file != null)
{
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)
@ -429,7 +429,7 @@ namespace Oqtane.Controllers
file = _files.UpdateFile(file);
}
_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)
{
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.File, file.FileId, "Download");
_syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, "Download");
return PhysicalFile(filepath, file.GetMimeType(), file.Name);
}
else

View File

@ -173,7 +173,7 @@ namespace Oqtane.Controllers
folder.Path = folder.Path + "/";
}
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);
}
else
@ -225,7 +225,7 @@ namespace Oqtane.Controllers
}
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);
}
else
@ -259,7 +259,7 @@ namespace Oqtane.Controllers
{
folder.Order = order;
_folders.UpdateFolder(folder);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Folder, folder.FolderId, SyncEventActions.Update);
_syncManager.AddSyncEvent(_alias, EntityNames.Folder, folder.FolderId, SyncEventActions.Update);
}
order += 2;
}
@ -285,7 +285,7 @@ namespace Oqtane.Controllers
Directory.Delete(_folders.GetFolderPath(folder));
}
_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);
}
else

View File

@ -119,7 +119,7 @@ namespace Oqtane.Controllers
var assemblyList = new List<ClientAssembly>();
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);
@ -201,7 +201,7 @@ namespace Oqtane.Controllers
private byte[] GetZIP(string list, Alias alias)
{
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);

View File

@ -109,8 +109,8 @@ namespace Oqtane.Controllers
if (ModelState.IsValid && language.SiteId == _alias.SiteId)
{
_languages.UpdateLanguage(language);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Language, language.LanguageId, SyncEventActions.Update);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh);
_syncManager.AddSyncEvent(_alias, EntityNames.Language, language.LanguageId, SyncEventActions.Update);
_syncManager.AddSyncEvent(_alias, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Language Updated {Language}", language);
}
else
@ -127,8 +127,8 @@ namespace Oqtane.Controllers
if (ModelState.IsValid && language.SiteId == _alias.SiteId)
{
language = _languages.AddLanguage(language);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Language, language.LanguageId, SyncEventActions.Create);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh);
_syncManager.AddSyncEvent(_alias, EntityNames.Language, language.LanguageId, SyncEventActions.Create);
_syncManager.AddSyncEvent(_alias, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Language Added {Language}", language);
}
else
@ -148,8 +148,8 @@ namespace Oqtane.Controllers
if (language != null && language.SiteId == _alias.SiteId)
{
_languages.DeleteLanguage(id);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Language, language.LanguageId, SyncEventActions.Delete);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh);
_syncManager.AddSyncEvent(_alias, EntityNames.Language, language.LanguageId, SyncEventActions.Delete);
_syncManager.AddSyncEvent(_alias, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Language Deleted {LanguageId}", id);
}
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))
{
module = _modules.AddModule(module);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Module, module.ModuleId, SyncEventActions.Create);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh);
_syncManager.AddSyncEvent(_alias, EntityNames.Module, module.ModuleId, SyncEventActions.Create);
_syncManager.AddSyncEvent(_alias, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Module Added {Module}", module);
}
else
@ -187,8 +187,8 @@ namespace Oqtane.Controllers
}
}
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Module, module.ModuleId, SyncEventActions.Update);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh);
_syncManager.AddSyncEvent(_alias, EntityNames.Module, module.ModuleId, SyncEventActions.Update);
_syncManager.AddSyncEvent(_alias, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Module Updated {Module}", module);
}
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))
{
_modules.DeleteModule(id);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Module, module.ModuleId, SyncEventActions.Delete);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh);
_syncManager.AddSyncEvent(_alias, EntityNames.Module, module.ModuleId, SyncEventActions.Delete);
_syncManager.AddSyncEvent(_alias, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Deleted {ModuleId}", id);
}
else

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