completed client state invalidation in multi-user environment
This commit is contained in:
44
Oqtane.Client/UI/ContainerBuilder.razor
Normal file
44
Oqtane.Client/UI/ContainerBuilder.razor
Normal file
@ -0,0 +1,44 @@
|
||||
@namespace Oqtane.UI
|
||||
|
||||
<CascadingValue Value="@ModuleState">
|
||||
@DynamicComponent
|
||||
</CascadingValue>
|
||||
|
||||
@code {
|
||||
[CascadingParameter]
|
||||
protected PageState PageState { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Module Module { get; set; }
|
||||
|
||||
RenderFragment DynamicComponent { get; set; }
|
||||
|
||||
Module ModuleState;
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
ModuleState = Module; // passed in from Pane component
|
||||
string container = ModuleState.ContainerType;
|
||||
if (PageState.ModuleId != -1 && PageState.Action != "" && ModuleState.UseAdminContainer)
|
||||
{
|
||||
container = Constants.DefaultAdminContainer;
|
||||
}
|
||||
|
||||
DynamicComponent = builder =>
|
||||
{
|
||||
Type containerType = Type.GetType(container);
|
||||
if (containerType != null)
|
||||
{
|
||||
builder.OpenComponent(0, containerType);
|
||||
builder.CloseComponent();
|
||||
}
|
||||
else
|
||||
{
|
||||
// container does not exist with type specified
|
||||
builder.OpenComponent(0, Type.GetType(Constants.ModuleMessageComponent));
|
||||
builder.AddAttribute(1, "Message", "Error Loading Module Container " + container);
|
||||
builder.CloseComponent();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
213
Oqtane.Client/UI/Installer.razor
Normal file
213
Oqtane.Client/UI/Installer.razor
Normal file
@ -0,0 +1,213 @@
|
||||
@namespace Oqtane.UI
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IInstallationService InstallationService
|
||||
@inject ISiteService SiteService
|
||||
@inject IUserService UserService
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="mx-auto text-center">
|
||||
<img src="oqtane.png" />
|
||||
</div>
|
||||
</div>
|
||||
<hr class="app-rule" />
|
||||
<h2 class="text-center">Database Configuration</h2>
|
||||
<div class="row">
|
||||
<div class="mx-auto text-center">
|
||||
<table class="form-group" cellpadding="4" cellspacing="4">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="Title" class="control-label" style="font-weight: bold">Database Type: </label>
|
||||
</td>
|
||||
<td>
|
||||
<select class="custom-select" @bind="@DatabaseType">
|
||||
<option value="LocalDB">Local Database</option>
|
||||
<option value="SQLServer">SQL Server</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="Title" class="control-label" style="font-weight: bold">Server: </label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" class="form-control" @bind="@ServerName" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="Title" class="control-label" style="font-weight: bold">Database: </label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" class="form-control" @bind="@DatabaseName" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="Title" class="control-label" style="font-weight: bold">Integrated Security: </label>
|
||||
</td>
|
||||
<td>
|
||||
<select class="custom-select" @onchange="SetIntegratedSecurity">
|
||||
<option value="true" selected>True</option>
|
||||
<option value="false">False</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="@IntegratedSecurityDisplay">
|
||||
<td>
|
||||
<label for="Title" class="control-label" style="font-weight: bold">Username: </label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" class="form-control" @bind="@Username" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="@IntegratedSecurityDisplay">
|
||||
<td>
|
||||
<label for="Title" class="control-label" style="font-weight: bold">Password: </label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="password" class="form-control" @bind="@Password" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="app-rule" />
|
||||
<h2 class="text-center">Application Administrator</h2>
|
||||
<div class="row">
|
||||
<div class="mx-auto text-center">
|
||||
<table class="form-group" cellpadding="4" cellspacing="4">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="Title" class="control-label" style="font-weight: bold">Username: </label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" class="form-control" @bind="@HostUsername" readonly />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="Title" class="control-label" style="font-weight: bold">Password: </label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="password" class="form-control" @bind="@HostPassword" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="Title" class="control-label" style="font-weight: bold">Confirm: </label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="password" class="form-control" @bind="@ConfirmPassword" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="Title" class="control-label" style="font-weight: bold">Email: </label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" class="form-control" @bind="@HostEmail" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="mx-auto text-center">
|
||||
<button type="button" class="btn btn-success" @onclick="Install">Install Now</button><br /><br />
|
||||
@((MarkupString)@Message)
|
||||
</div>
|
||||
<div class="app-progress-indicator" style="@LoadingDisplay"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private string DatabaseType = "LocalDB";
|
||||
private string ServerName = "(LocalDb)\\MSSQLLocalDB";
|
||||
private string DatabaseName = "Oqtane-" + DateTime.Now.ToString("yyyyMMddHHmm");
|
||||
private string Username = "";
|
||||
private string Password = "";
|
||||
private string HostUsername = Constants.HostUser;
|
||||
private string HostPassword = "";
|
||||
private string ConfirmPassword = "";
|
||||
private string HostEmail = "";
|
||||
private string Message = "";
|
||||
|
||||
private string IntegratedSecurityDisplay = "display: none;";
|
||||
private string LoadingDisplay = "display: none;";
|
||||
|
||||
private void SetIntegratedSecurity(ChangeEventArgs e)
|
||||
{
|
||||
if (Convert.ToBoolean((string)e.Value))
|
||||
{
|
||||
IntegratedSecurityDisplay = "display: none;";
|
||||
}
|
||||
else
|
||||
{
|
||||
IntegratedSecurityDisplay = "";
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Install()
|
||||
{
|
||||
if (HostUsername != "" && HostPassword.Length >= 6 && HostPassword == ConfirmPassword && HostEmail != "")
|
||||
{
|
||||
LoadingDisplay = "";
|
||||
StateHasChanged();
|
||||
|
||||
string connectionstring = "";
|
||||
if (DatabaseType == "LocalDB")
|
||||
{
|
||||
connectionstring = "Data Source=" + ServerName + ";AttachDbFilename=|DataDirectory|\\" + DatabaseName + ".mdf;Initial Catalog=" + DatabaseName + ";Integrated Security=SSPI;";
|
||||
}
|
||||
else
|
||||
{
|
||||
connectionstring = "Data Source=" + ServerName + ";Initial Catalog=" + DatabaseName + ";";
|
||||
if (IntegratedSecurityDisplay == "display: none;")
|
||||
{
|
||||
connectionstring += "Integrated Security=SSPI;";
|
||||
}
|
||||
else
|
||||
{
|
||||
connectionstring += "User ID=" + Username + ";Password=" + Password;
|
||||
|
||||
}
|
||||
}
|
||||
GenericResponse response = await InstallationService.Install(connectionstring);
|
||||
if (response.Success)
|
||||
{
|
||||
Site site = new Site();
|
||||
site.TenantId = -1; // will be populated on server
|
||||
site.Name = "Default Site";
|
||||
site.LogoFileId = null;
|
||||
site.DefaultThemeType = Constants.DefaultTheme;
|
||||
site.DefaultLayoutType = Constants.DefaultLayout;
|
||||
site.DefaultContainerType = Constants.DefaultContainer;
|
||||
site = await SiteService.AddSiteAsync(site, null);
|
||||
|
||||
User user = new User();
|
||||
user.SiteId = site.SiteId;
|
||||
user.Username = HostUsername;
|
||||
user.Password = HostPassword;
|
||||
user.Email = HostEmail;
|
||||
user.DisplayName = HostUsername;
|
||||
user = await UserService.AddUserAsync(user);
|
||||
|
||||
NavigationManager.NavigateTo("", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Message = "<div class=\"alert alert-danger\" role=\"alert\">" + response.Message + "</div>";
|
||||
LoadingDisplay = "display: none;";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Message = "<div class=\"alert alert-danger\" role=\"alert\">Please Enter All Fields And Ensure Passwords Match And Are Greater Than 5 Characters In Length</div>";
|
||||
}
|
||||
}
|
||||
}
|
119
Oqtane.Client/UI/Interop.cs
Normal file
119
Oqtane.Client/UI/Interop.cs
Normal file
@ -0,0 +1,119 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Oqtane.UI
|
||||
{
|
||||
public class Interop
|
||||
{
|
||||
private readonly IJSRuntime _jsRuntime;
|
||||
|
||||
public Interop(IJSRuntime jsRuntime)
|
||||
{
|
||||
_jsRuntime = jsRuntime;
|
||||
}
|
||||
|
||||
public Task SetCookie(string name, string value, int days)
|
||||
{
|
||||
try
|
||||
{
|
||||
_jsRuntime.InvokeAsync<string>(
|
||||
"interop.setCookie",
|
||||
name, value, days);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public ValueTask<string> GetCookie(string name)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _jsRuntime.InvokeAsync<string>(
|
||||
"interop.getCookie",
|
||||
name);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new ValueTask<string>(Task.FromResult(string.Empty));
|
||||
}
|
||||
}
|
||||
|
||||
public Task IncludeCSS(string id, string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
_jsRuntime.InvokeAsync<string>(
|
||||
"interop.includeCSS",
|
||||
id, url);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public ValueTask<string> GetElementByName(string name)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _jsRuntime.InvokeAsync<string>(
|
||||
"interop.getElementByName",
|
||||
name);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new ValueTask<string>(Task.FromResult(string.Empty));
|
||||
}
|
||||
}
|
||||
|
||||
public Task SubmitForm(string path, object fields)
|
||||
{
|
||||
try
|
||||
{
|
||||
_jsRuntime.InvokeAsync<string>(
|
||||
"interop.submitForm",
|
||||
path, fields);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public ValueTask<string[]> GetFiles(string id)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _jsRuntime.InvokeAsync<string[]>(
|
||||
"interop.getFiles",
|
||||
id);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new ValueTask<string[]>(Task.FromResult(new string[0]));
|
||||
}
|
||||
}
|
||||
|
||||
public Task UploadFiles(string posturl, string folder, string id)
|
||||
{
|
||||
try
|
||||
{
|
||||
_jsRuntime.InvokeAsync<string>(
|
||||
"interop.uploadFiles",
|
||||
posturl, folder, id);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
80
Oqtane.Client/UI/ModuleInstance.razor
Normal file
80
Oqtane.Client/UI/ModuleInstance.razor
Normal file
@ -0,0 +1,80 @@
|
||||
@namespace Oqtane.UI
|
||||
|
||||
<ModuleMessage Message="@message" Type="MessageType.Error" />
|
||||
<CascadingValue Value="this">
|
||||
<ModuleMessage @ref="modulemessage" />
|
||||
@DynamicComponent
|
||||
</CascadingValue>
|
||||
@if (progressindicator)
|
||||
{
|
||||
<div class="app-progress-indicator"></div>
|
||||
}
|
||||
|
||||
@code {
|
||||
[CascadingParameter]
|
||||
protected PageState PageState { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
private Module ModuleState { get; set; }
|
||||
|
||||
private ModuleMessage modulemessage { get; set; }
|
||||
string message;
|
||||
|
||||
RenderFragment DynamicComponent { get; set; }
|
||||
|
||||
bool progressindicator = false;
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
DynamicComponent = builder =>
|
||||
{
|
||||
string typename = ModuleState.ModuleType;
|
||||
// check for core module actions component
|
||||
if (Constants.DefaultModuleActions.Contains(PageState.Action))
|
||||
{
|
||||
typename = Constants.DefaultModuleActionsTemplate.Replace(Constants.ActionToken, PageState.Action);
|
||||
}
|
||||
|
||||
Type moduleType = null;
|
||||
if (typename != null)
|
||||
{
|
||||
moduleType = Type.GetType(typename);
|
||||
|
||||
if (moduleType != null)
|
||||
{
|
||||
builder.OpenComponent(0, moduleType);
|
||||
builder.CloseComponent();
|
||||
}
|
||||
else
|
||||
{
|
||||
// module does not exist with typename specified
|
||||
message = "Module Does Not Have A Component Named " + Utilities.GetTypeNameLastSegment(typename, 0) + ".razor";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
message = "Something is wrong with moduletype";
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
public void AddModuleMessage(string message, MessageType type)
|
||||
{
|
||||
progressindicator = false;
|
||||
StateHasChanged();
|
||||
modulemessage.SetModuleMessage(message, type);
|
||||
}
|
||||
|
||||
public void ShowProgressIndicator()
|
||||
{
|
||||
progressindicator = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
public void HideProgressIndicator()
|
||||
{
|
||||
progressindicator = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
22
Oqtane.Client/UI/PageState.cs
Normal file
22
Oqtane.Client/UI/PageState.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Oqtane.Models;
|
||||
|
||||
namespace Oqtane.UI
|
||||
{
|
||||
public class PageState
|
||||
{
|
||||
public Alias Alias { get; set; }
|
||||
public Site Site { get; set; }
|
||||
public List<Page> Pages { get; set; }
|
||||
public Page Page { get; set; }
|
||||
public User User { get; set; }
|
||||
public List<Module> Modules { get; set; }
|
||||
public Uri Uri { get; set; }
|
||||
public Dictionary<string, string> QueryString { get; set; }
|
||||
public int ModuleId { get; set; }
|
||||
public string Action { get; set; }
|
||||
public bool EditMode { get; set; }
|
||||
public DateTime LastSyncDate { get; set; }
|
||||
}
|
||||
}
|
135
Oqtane.Client/UI/Pane.razor
Normal file
135
Oqtane.Client/UI/Pane.razor
Normal file
@ -0,0 +1,135 @@
|
||||
@namespace Oqtane.UI
|
||||
@inject IUserService UserService
|
||||
@inject IModuleService ModuleService
|
||||
@inject IModuleDefinitionService ModuleDefinitionService
|
||||
|
||||
<div class="@paneadminborder">
|
||||
@if (panetitle != "")
|
||||
{
|
||||
@((MarkupString)panetitle)
|
||||
}
|
||||
@DynamicComponent
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[CascadingParameter]
|
||||
protected PageState PageState { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Name { get; set; }
|
||||
|
||||
RenderFragment DynamicComponent { get; set; }
|
||||
|
||||
string paneadminborder = "";
|
||||
string panetitle = "";
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
if (PageState.EditMode && !PageState.Page.EditMode && UserSecurity.IsAuthorized(PageState.User, "Edit", PageState.Page.Permissions) && Name != Constants.AdminPane)
|
||||
{
|
||||
paneadminborder = "app-pane-admin-border";
|
||||
panetitle = "<div class=\"app-pane-admin-title\">" + Name + " Pane</div>";
|
||||
}
|
||||
else
|
||||
{
|
||||
paneadminborder = "";
|
||||
panetitle = "";
|
||||
}
|
||||
|
||||
DynamicComponent = builder =>
|
||||
{
|
||||
if (PageState.ModuleId != -1 && PageState.Action != "")
|
||||
{
|
||||
if (Name.ToLower() == Constants.AdminPane.ToLower())
|
||||
{
|
||||
Module module = PageState.Modules.Where(item => item.ModuleId == PageState.ModuleId).FirstOrDefault();
|
||||
if (module != null)
|
||||
{
|
||||
string typename = module.ModuleType;
|
||||
// check for core module actions component
|
||||
if (Constants.DefaultModuleActions.Contains(PageState.Action))
|
||||
{
|
||||
typename = Constants.DefaultModuleActionsTemplate.Replace(Constants.ActionToken, PageState.Action);
|
||||
}
|
||||
Type moduleType = Type.GetType(typename);
|
||||
if (moduleType != null)
|
||||
{
|
||||
bool authorized = false;
|
||||
if (Constants.DefaultModuleActions.Contains(PageState.Action))
|
||||
{
|
||||
authorized = UserSecurity.IsAuthorized(PageState.User, "Edit", PageState.Page.Permissions);
|
||||
}
|
||||
else
|
||||
{
|
||||
// verify security access level for this module control
|
||||
switch (module.SecurityAccessLevel)
|
||||
{
|
||||
case SecurityAccessLevel.Anonymous:
|
||||
authorized = true;
|
||||
break;
|
||||
case SecurityAccessLevel.View:
|
||||
authorized = UserSecurity.IsAuthorized(PageState.User, "View", module.Permissions);
|
||||
break;
|
||||
case SecurityAccessLevel.Edit:
|
||||
authorized = UserSecurity.IsAuthorized(PageState.User, "Edit", module.Permissions);
|
||||
break;
|
||||
case SecurityAccessLevel.Admin:
|
||||
authorized = UserSecurity.IsAuthorized(PageState.User, Constants.AdminRole);
|
||||
break;
|
||||
case SecurityAccessLevel.Host:
|
||||
authorized = UserSecurity.IsAuthorized(PageState.User, Constants.HostRole);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (authorized)
|
||||
{
|
||||
if (!Constants.DefaultModuleActions.Contains(PageState.Action) && module.ControlTitle != "")
|
||||
{
|
||||
module.Title = module.ControlTitle;
|
||||
}
|
||||
builder.OpenComponent(0, Type.GetType(Constants.ContainerComponent));
|
||||
builder.AddAttribute(1, "Module", module);
|
||||
builder.CloseComponent();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// module control does not exist with name specified
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (PageState.ModuleId != -1)
|
||||
{
|
||||
Module module = PageState.Modules.Where(item => item.ModuleId == PageState.ModuleId).FirstOrDefault();
|
||||
if (module != null && module.Pane.ToLower() == Name.ToLower())
|
||||
{
|
||||
// check if user is authorized to view module
|
||||
if (UserSecurity.IsAuthorized(PageState.User, "View", module.Permissions))
|
||||
{
|
||||
builder.OpenComponent(0, Type.GetType(Constants.ContainerComponent));
|
||||
builder.AddAttribute(1, "Module", module);
|
||||
builder.CloseComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (Module module in PageState.Modules.Where(item => item.PageId == PageState.Page.PageId && item.Pane.ToLower() == Name.ToLower() && !item.IsDeleted).OrderBy(x => x.Order).ToArray())
|
||||
{
|
||||
// check if user is authorized to view module
|
||||
if (UserSecurity.IsAuthorized(PageState.User, "View", module.Permissions))
|
||||
{
|
||||
builder.OpenComponent(0, Type.GetType(Constants.ContainerComponent));
|
||||
builder.AddAttribute(1, "Module", module);
|
||||
builder.SetKey(module.PageModuleId);
|
||||
builder.CloseComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
27
Oqtane.Client/UI/PaneLayout.razor
Normal file
27
Oqtane.Client/UI/PaneLayout.razor
Normal file
@ -0,0 +1,27 @@
|
||||
@namespace Oqtane.UI
|
||||
|
||||
@DynamicComponent
|
||||
|
||||
@code {
|
||||
[CascadingParameter]
|
||||
protected PageState PageState { get; set; }
|
||||
|
||||
RenderFragment DynamicComponent { get; set; }
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
DynamicComponent = builder =>
|
||||
{
|
||||
Type layoutType = Type.GetType(PageState.Page.LayoutType);
|
||||
if (layoutType != null)
|
||||
{
|
||||
builder.OpenComponent(0, layoutType);
|
||||
builder.CloseComponent();
|
||||
}
|
||||
else
|
||||
{
|
||||
// layout does not exist with type specified
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
10
Oqtane.Client/UI/Reload.cs
Normal file
10
Oqtane.Client/UI/Reload.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace Oqtane.UI
|
||||
{
|
||||
public enum Reload
|
||||
{
|
||||
None,
|
||||
Page,
|
||||
Site,
|
||||
Application
|
||||
}
|
||||
}
|
80
Oqtane.Client/UI/RichTextEditorInterop.cs
Normal file
80
Oqtane.Client/UI/RichTextEditorInterop.cs
Normal file
@ -0,0 +1,80 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Oqtane.UI
|
||||
{
|
||||
public static class RichTextEditorInterop
|
||||
{
|
||||
internal static ValueTask<object> CreateEditor(
|
||||
IJSRuntime jsRuntime,
|
||||
ElementReference quillElement,
|
||||
ElementReference toolbar,
|
||||
bool readOnly,
|
||||
string placeholder,
|
||||
string theme,
|
||||
string debugLevel)
|
||||
{
|
||||
return jsRuntime.InvokeAsync<object>(
|
||||
"interop.createQuill",
|
||||
quillElement, toolbar, readOnly,
|
||||
placeholder, theme, debugLevel);
|
||||
}
|
||||
|
||||
internal static ValueTask<string> GetText(
|
||||
IJSRuntime jsRuntime,
|
||||
ElementReference quillElement)
|
||||
{
|
||||
return jsRuntime.InvokeAsync<string>(
|
||||
"interop.getQuillText",
|
||||
quillElement);
|
||||
}
|
||||
|
||||
internal static ValueTask<string> GetHTML(
|
||||
IJSRuntime jsRuntime,
|
||||
ElementReference quillElement)
|
||||
{
|
||||
return jsRuntime.InvokeAsync<string>(
|
||||
"interop.getQuillHTML",
|
||||
quillElement);
|
||||
}
|
||||
|
||||
internal static ValueTask<string> GetContent(
|
||||
IJSRuntime jsRuntime,
|
||||
ElementReference quillElement)
|
||||
{
|
||||
return jsRuntime.InvokeAsync<string>(
|
||||
"interop.getQuillContent",
|
||||
quillElement);
|
||||
}
|
||||
|
||||
internal static ValueTask<object> LoadEditorContent(
|
||||
IJSRuntime jsRuntime,
|
||||
ElementReference quillElement,
|
||||
string Content)
|
||||
{
|
||||
return jsRuntime.InvokeAsync<object>(
|
||||
"interop.loadQuillContent",
|
||||
quillElement, Content);
|
||||
}
|
||||
|
||||
internal static ValueTask<object> EnableEditor(
|
||||
IJSRuntime jsRuntime,
|
||||
ElementReference quillElement,
|
||||
bool mode)
|
||||
{
|
||||
return jsRuntime.InvokeAsync<object>(
|
||||
"interop.enableQuillEditor", quillElement, mode);
|
||||
}
|
||||
|
||||
internal static ValueTask<object> InsertImage(
|
||||
IJSRuntime jsRuntime,
|
||||
ElementReference quillElement,
|
||||
string ImageURL)
|
||||
{
|
||||
return jsRuntime.InvokeAsync<object>(
|
||||
"interop.insertQuillImage",
|
||||
quillElement, ImageURL);
|
||||
}
|
||||
}
|
||||
}
|
450
Oqtane.Client/UI/SiteRouter.razor
Normal file
450
Oqtane.Client/UI/SiteRouter.razor
Normal file
@ -0,0 +1,450 @@
|
||||
@namespace Oqtane.UI
|
||||
@inject AuthenticationStateProvider AuthenticationStateProvider
|
||||
@inject SiteState SiteState
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject INavigationInterception NavigationInterception
|
||||
@inject IAliasService AliasService
|
||||
@inject ITenantService TenantService
|
||||
@inject ISiteService SiteService
|
||||
@inject IPageService PageService
|
||||
@inject IUserService UserService
|
||||
@inject IModuleService ModuleService
|
||||
@inject IModuleDefinitionService ModuleDefinitionService
|
||||
@implements IHandleAfterRender
|
||||
|
||||
@DynamicComponent
|
||||
|
||||
@code {
|
||||
|
||||
[CascadingParameter]
|
||||
PageState PageState { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Action<PageState> OnStateChange { get; set; }
|
||||
|
||||
PageState pagestate;
|
||||
RenderFragment DynamicComponent { get; set; }
|
||||
|
||||
string _absoluteUri;
|
||||
bool _navigationInterceptionEnabled;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_absoluteUri = NavigationManager.Uri;
|
||||
NavigationManager.LocationChanged += LocationChanged;
|
||||
|
||||
DynamicComponent = builder =>
|
||||
{
|
||||
if (PageState != null)
|
||||
{
|
||||
builder.OpenComponent(0, Type.GetType(Constants.PageComponent));
|
||||
builder.CloseComponent();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
NavigationManager.LocationChanged -= LocationChanged;
|
||||
}
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (PageState == null)
|
||||
{
|
||||
// misconfigured api calls should not be processed through the router
|
||||
if (!_absoluteUri.Contains("~/api/"))
|
||||
{
|
||||
await Refresh();
|
||||
}
|
||||
else
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(GetType().FullName + ": Error: API call to " + _absoluteUri + " is not mapped to a Controller");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Refresh()
|
||||
{
|
||||
Alias alias = null;
|
||||
Site site;
|
||||
List<Page> pages;
|
||||
Page page;
|
||||
User user = null;
|
||||
List<Module> modules;
|
||||
int moduleid = -1;
|
||||
string action = "";
|
||||
bool editmode = false;
|
||||
Reload reload = Reload.None;
|
||||
DateTime lastsyncdate = DateTime.Now;
|
||||
|
||||
// get Url path and querystring ( and remove anchors )
|
||||
string path = new Uri(_absoluteUri).PathAndQuery.Substring(1);
|
||||
if (path.IndexOf("#") != -1)
|
||||
{
|
||||
path = path.Substring(0, path.IndexOf("#"));
|
||||
}
|
||||
|
||||
// parse querystring and remove
|
||||
Dictionary<string, string> querystring = new Dictionary<string, string>();
|
||||
if (path.IndexOf("?") != -1)
|
||||
{
|
||||
querystring = ParseQueryString(path);
|
||||
path = path.Substring(0, path.IndexOf("?"));
|
||||
}
|
||||
|
||||
if (PageState != null)
|
||||
{
|
||||
editmode = PageState.EditMode;
|
||||
lastsyncdate = PageState.LastSyncDate;
|
||||
user = PageState.User;
|
||||
}
|
||||
|
||||
alias = await AliasService.GetAliasAsync(_absoluteUri, lastsyncdate);
|
||||
SiteState.Alias = alias; // set state for services
|
||||
lastsyncdate = alias.SyncDate;
|
||||
|
||||
if (PageState == null || alias.SiteId != PageState.Alias.SiteId)
|
||||
{
|
||||
site = await SiteService.GetSiteAsync(alias.SiteId, alias);
|
||||
}
|
||||
else
|
||||
{
|
||||
site = PageState.Site;
|
||||
}
|
||||
if (site != null)
|
||||
{
|
||||
// process sync events
|
||||
if (alias.SyncEvents.Any())
|
||||
{
|
||||
if (PageState != null && alias.SyncEvents.Exists(item => item.EntityName == "Page" && item.EntityId == PageState.Page.PageId))
|
||||
{
|
||||
reload = Reload.Page;
|
||||
}
|
||||
if (alias.SyncEvents.Exists(item => item.EntityName == "Site" && item.EntityId == alias.SiteId))
|
||||
{
|
||||
reload = Reload.Site;
|
||||
}
|
||||
if (user != null && alias.SyncEvents.Exists(item => item.EntityName == "User" && item.EntityId == user.UserId))
|
||||
{
|
||||
reload = Reload.Site;
|
||||
}
|
||||
}
|
||||
|
||||
if (PageState == null || reload >= Reload.Site)
|
||||
{
|
||||
#if WASM
|
||||
ModuleDefinitionService.LoadModuleDefinitionsAsync(site.SiteId); // download assemblies to browser when running client-side
|
||||
#endif
|
||||
|
||||
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
|
||||
if (authState.User.Identity.IsAuthenticated)
|
||||
{
|
||||
user = await UserService.GetUserAsync(authState.User.Identity.Name, site.SiteId);
|
||||
}
|
||||
pages = await PageService.GetPagesAsync(site.SiteId);
|
||||
}
|
||||
else
|
||||
{
|
||||
pages = PageState.Pages;
|
||||
}
|
||||
|
||||
// format path and remove alias
|
||||
path = path.Replace("//", "/");
|
||||
if (!path.EndsWith("/")) { path += "/"; }
|
||||
if (alias.Path != "")
|
||||
{
|
||||
path = path.Replace(alias.Path + "/", "");
|
||||
}
|
||||
|
||||
// extract admin route elements from path
|
||||
string[] segments = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
int result;
|
||||
// check if path has moduleid and control specification ie. page/moduleid/control/
|
||||
if (segments.Length >= 2 && int.TryParse(segments[segments.Length - 2], out result))
|
||||
{
|
||||
action = segments[segments.Length - 1];
|
||||
moduleid = result;
|
||||
path = path.Replace(moduleid.ToString() + "/" + action + "/", "");
|
||||
}
|
||||
else
|
||||
{
|
||||
// check if path has only moduleid specification ie. page/moduleid/
|
||||
if (segments.Length >= 1 && int.TryParse(segments[segments.Length - 1], out result))
|
||||
{
|
||||
moduleid = result;
|
||||
path = path.Replace(moduleid.ToString() + "/", "");
|
||||
}
|
||||
}
|
||||
// remove trailing slash so it can be used as a key for Pages
|
||||
if (path.EndsWith("/")) path = path.Substring(0, path.Length - 1);
|
||||
|
||||
if (PageState == null || reload >= Reload.Page)
|
||||
{
|
||||
page = pages.Where(item => item.Path == path).FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
page = PageState.Page;
|
||||
}
|
||||
// failsafe in case router cannot locate the home page for the site
|
||||
if (page == null && path == "")
|
||||
{
|
||||
page = pages.FirstOrDefault();
|
||||
path = page.Path;
|
||||
}
|
||||
|
||||
// check if page has changed
|
||||
if (page != null && page.Path != path)
|
||||
{
|
||||
page = pages.Where(item => item.Path == path).FirstOrDefault();
|
||||
reload = Reload.Page;
|
||||
if (page!=null)
|
||||
{
|
||||
editmode = page.EditMode;
|
||||
}
|
||||
}
|
||||
|
||||
user = null;
|
||||
if (PageState == null || reload >= Reload.Page)
|
||||
{
|
||||
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
|
||||
if (authState.User.Identity.IsAuthenticated)
|
||||
{
|
||||
user = await UserService.GetUserAsync(authState.User.Identity.Name, site.SiteId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
user = PageState.User;
|
||||
}
|
||||
|
||||
if (page != null)
|
||||
{
|
||||
if (PageState == null)
|
||||
{
|
||||
editmode = page.EditMode;
|
||||
}
|
||||
|
||||
// check if user is authorized to view page
|
||||
if (UserSecurity.IsAuthorized(user, "View", page.Permissions))
|
||||
{
|
||||
page = await ProcessPage(page, site, user);
|
||||
|
||||
pagestate = new PageState();
|
||||
pagestate.Alias = alias;
|
||||
pagestate.Site = site;
|
||||
pagestate.Pages = pages;
|
||||
pagestate.Page = page;
|
||||
pagestate.User = user;
|
||||
pagestate.Uri = new Uri(_absoluteUri, UriKind.Absolute);
|
||||
pagestate.QueryString = querystring;
|
||||
pagestate.ModuleId = moduleid;
|
||||
pagestate.Action = action;
|
||||
|
||||
if (PageState != null && (PageState.ModuleId != pagestate.ModuleId || PageState.Action != pagestate.Action))
|
||||
{
|
||||
reload = Reload.Page;
|
||||
}
|
||||
|
||||
if (PageState == null || reload >= Reload.Page)
|
||||
{
|
||||
modules = await ModuleService.GetModulesAsync(site.SiteId);
|
||||
modules = ProcessModules(modules, page.PageId, pagestate.ModuleId, pagestate.Action, page.Panes, site.DefaultContainerType);
|
||||
}
|
||||
else
|
||||
{
|
||||
modules = PageState.Modules;
|
||||
}
|
||||
pagestate.Modules = modules;
|
||||
pagestate.EditMode = editmode;
|
||||
pagestate.LastSyncDate = lastsyncdate;
|
||||
|
||||
OnStateChange?.Invoke(pagestate);
|
||||
}
|
||||
else
|
||||
{
|
||||
// user is not authorized to view page
|
||||
if (path != "")
|
||||
{
|
||||
NavigationManager.NavigateTo("");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// page does not exist
|
||||
if (path != "")
|
||||
{
|
||||
NavigationManager.NavigateTo("");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// site does not exist
|
||||
}
|
||||
}
|
||||
|
||||
private async void LocationChanged(object sender, LocationChangedEventArgs args)
|
||||
{
|
||||
_absoluteUri = args.Location;
|
||||
await Refresh();
|
||||
}
|
||||
|
||||
Task IHandleAfterRender.OnAfterRenderAsync()
|
||||
{
|
||||
if (!_navigationInterceptionEnabled)
|
||||
{
|
||||
_navigationInterceptionEnabled = true;
|
||||
return NavigationInterception.EnableNavigationInterceptionAsync();
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Dictionary<string, string> ParseQueryString(string path)
|
||||
{
|
||||
Dictionary<string, string> querystring = new Dictionary<string, string>();
|
||||
if (path.IndexOf("?") != -1)
|
||||
{
|
||||
foreach (string kvp in path.Substring(path.IndexOf("?") + 1).Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
if (kvp != "")
|
||||
{
|
||||
if (kvp.Contains("="))
|
||||
{
|
||||
string[] pair = kvp.Split('=');
|
||||
querystring.Add(pair[0], pair[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
querystring.Add(kvp, "true"); // default querystring when no value is provided
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return querystring;
|
||||
}
|
||||
|
||||
private async Task<Page> ProcessPage(Page page, Site site, User user)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (page.IsPersonalizable && user != null)
|
||||
{
|
||||
// load the personalized page
|
||||
page = await PageService.GetPageAsync(page.PageId, user.UserId);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(page.ThemeType))
|
||||
{
|
||||
page.ThemeType = site.DefaultThemeType;
|
||||
page.LayoutType = site.DefaultLayoutType;
|
||||
}
|
||||
Type type;
|
||||
if (!string.IsNullOrEmpty(page.LayoutType))
|
||||
{
|
||||
type = Type.GetType(page.LayoutType);
|
||||
}
|
||||
else
|
||||
{
|
||||
type = Type.GetType(page.ThemeType);
|
||||
}
|
||||
System.Reflection.PropertyInfo property = type.GetProperty("Panes");
|
||||
page.Panes = (string)property.GetValue(Activator.CreateInstance(type), null);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// error loading theme or layout
|
||||
}
|
||||
return page;
|
||||
}
|
||||
|
||||
private List<Module> ProcessModules(List<Module> modules, int pageid, int moduleid, string control, string panes, string defaultcontainertype)
|
||||
{
|
||||
Dictionary<string, int> paneindex = new Dictionary<string, int>();
|
||||
foreach (Module module in modules)
|
||||
{
|
||||
if (module.PageId == pageid || module.ModuleId == moduleid)
|
||||
{
|
||||
string typename = "";
|
||||
if (module.ModuleDefinition != null)
|
||||
{
|
||||
typename = module.ModuleDefinition.ControlTypeTemplate;
|
||||
}
|
||||
else
|
||||
{
|
||||
typename = Constants.ErrorModule;
|
||||
}
|
||||
if (module.ModuleId == moduleid && control != "")
|
||||
{
|
||||
// check if the module defines custom routes
|
||||
if (module.ModuleDefinition.ControlTypeRoutes != "")
|
||||
{
|
||||
foreach (string route in module.ModuleDefinition.ControlTypeRoutes.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
if (route.StartsWith(control + "="))
|
||||
{
|
||||
typename = route.Replace(control + "=", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
module.ModuleType = typename.Replace(Constants.ActionToken, control);
|
||||
|
||||
// admin controls need to load additional metadata from the IModuleControl interface
|
||||
if (moduleid == module.ModuleId)
|
||||
{
|
||||
typename = module.ModuleType;
|
||||
// check for core module actions component
|
||||
if (Constants.DefaultModuleActions.Contains(control))
|
||||
{
|
||||
typename = Constants.DefaultModuleActionsTemplate.Replace(Constants.ActionToken, control);
|
||||
}
|
||||
Type moduletype = Type.GetType(typename);
|
||||
if (moduletype != null)
|
||||
{
|
||||
var moduleobject = Activator.CreateInstance(moduletype);
|
||||
module.SecurityAccessLevel = (SecurityAccessLevel)moduletype.GetProperty("SecurityAccessLevel").GetValue(moduleobject, null);
|
||||
module.ControlTitle = (string)moduletype.GetProperty("Title").GetValue(moduleobject);
|
||||
module.Actions = (string)moduletype.GetProperty("Actions").GetValue(moduleobject);
|
||||
module.UseAdminContainer = (bool)moduletype.GetProperty("UseAdminContainer").GetValue(moduleobject);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
module.ModuleType = typename.Replace(Constants.ActionToken, Constants.DefaultAction);
|
||||
}
|
||||
|
||||
// ensure module's pane exists in current page and if not, assign it to the Admin pane
|
||||
if (panes == null || !panes.ToLower().Contains(module.Pane.ToLower()))
|
||||
{
|
||||
module.Pane = Constants.AdminPane;
|
||||
}
|
||||
|
||||
// calculate module position within pane
|
||||
if (paneindex.ContainsKey(module.Pane))
|
||||
{
|
||||
paneindex[module.Pane] += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
paneindex.Add(module.Pane, 0);
|
||||
}
|
||||
module.PaneModuleIndex = paneindex[module.Pane];
|
||||
|
||||
if (string.IsNullOrEmpty(module.ContainerType))
|
||||
{
|
||||
module.ContainerType = defaultcontainertype;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Module module in modules.Where(item => item.PageId == pageid))
|
||||
{
|
||||
module.PaneModuleCount = paneindex[module.Pane] + 1;
|
||||
}
|
||||
return modules;
|
||||
}
|
||||
|
||||
}
|
30
Oqtane.Client/UI/ThemeBuilder.razor
Normal file
30
Oqtane.Client/UI/ThemeBuilder.razor
Normal file
@ -0,0 +1,30 @@
|
||||
@namespace Oqtane.UI
|
||||
@inject IJSRuntime jsRuntime
|
||||
|
||||
@DynamicComponent
|
||||
|
||||
@code {
|
||||
[CascadingParameter] PageState PageState { get; set; }
|
||||
|
||||
RenderFragment DynamicComponent { get; set; }
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
DynamicComponent = builder =>
|
||||
{
|
||||
Type themeType = Type.GetType(PageState.Page.ThemeType);
|
||||
if (themeType != null)
|
||||
{
|
||||
builder.OpenComponent(0, themeType);
|
||||
builder.CloseComponent();
|
||||
}
|
||||
else
|
||||
{
|
||||
// theme does not exist with type specified
|
||||
builder.OpenComponent(0, Type.GetType(Constants.ModuleMessageComponent));
|
||||
builder.AddAttribute(1, "Message", "Error Loading Page Theme " + PageState.Page.ThemeType);
|
||||
builder.CloseComponent();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user