Merge pull request #6 from oqtane/master

Sync master
This commit is contained in:
jimspillane 2020-05-12 20:52:07 -04:00 committed by GitHub
commit a886ae12cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 337 additions and 109 deletions

View File

@ -149,12 +149,18 @@
{ {
folder = await FolderService.AddFolderAsync(folder); folder = await FolderService.AddFolderAsync(folder);
} }
if (folder != null)
{
await FolderService.UpdateFolderOrderAsync(folder.SiteId, folder.FolderId, folder.ParentId); await FolderService.UpdateFolderOrderAsync(folder.SiteId, folder.FolderId, folder.ParentId);
await logger.LogInformation("Folder Saved {Folder}", folder); await logger.LogInformation("Folder Saved {Folder}", folder);
NavigationManager.NavigateTo(NavigateUrl()); NavigationManager.NavigateTo(NavigateUrl());
} }
else else
{
AddModuleMessage("An Error Was Encountered Saving The Folder", MessageType.Error);
}
}
else
{ {
AddModuleMessage("Folders Must Have A Parent And A Name", MessageType.Warning); AddModuleMessage("Folders Must Have A Parent And A Name", MessageType.Warning);
} }

View File

@ -4,9 +4,8 @@
@namespace [Owner].[Module]s.Modules @namespace [Owner].[Module]s.Modules
@inherits ModuleBase @inherits ModuleBase
@inject I[Module]Service [Module]Service
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject HttpClient http
@inject SiteState sitestate
<table class="table table-borderless"> <table class="table table-borderless">
<tr> <tr>
@ -31,7 +30,6 @@
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Actions => "Add,Edit"; public override string Actions => "Add,Edit";
I[Module]Service [Module]Service;
int _id; int _id;
string _name; string _name;
string _createdby; string _createdby;
@ -43,7 +41,6 @@
{ {
try try
{ {
[Module]Service = new [Module]Service(http, sitestate);
if (PageState.Action == "Edit") if (PageState.Action == "Edit")
{ {
_id = Int32.Parse(PageState.QueryString["id"]); _id = Int32.Parse(PageState.QueryString["id"]);

View File

@ -3,9 +3,8 @@
@namespace [Owner].[Module]s.Modules @namespace [Owner].[Module]s.Modules
@inherits ModuleBase @inherits ModuleBase
@inject I[Module]Service [Module]Service
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject HttpClient http
@inject SiteState sitestate
@if (_[Module]s == null) @if (_[Module]s == null)
{ {
@ -71,14 +70,12 @@ else
<!-- The content above is for informational purposes only and can be safely removed --> <!-- The content above is for informational purposes only and can be safely removed -->
@code { @code {
I[Module]Service [Module]Service;
List<[Module]> _[Module]s; List<[Module]> _[Module]s;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
try try
{ {
[Module]Service = new [Module]Service(http, sitestate);
_[Module]s = await [Module]Service.Get[Module]sAsync(ModuleState.ModuleId); _[Module]s = await [Module]Service.Get[Module]sAsync(ModuleState.ModuleId);
} }
catch (Exception ex) catch (Exception ex)

View File

@ -10,7 +10,6 @@ namespace [Owner].[Module]s.Modules
Name = "[Module]", Name = "[Module]",
Description = "[Module]", Description = "[Module]",
Version = "1.0.0", Version = "1.0.0",
Dependencies = "[Owner].[Module]s.Shared.Oqtane",
ServerManagerType = "[ServerManagerType]", ServerManagerType = "[ServerManagerType]",
ReleaseVersions = "1.0.0" ReleaseVersions = "1.0.0"
}; };

View File

@ -4,9 +4,8 @@
@namespace [Owner].[Module]s.Modules @namespace [Owner].[Module]s.Modules
@inherits ModuleBase @inherits ModuleBase
@inject I[Module]Service [Module]Service
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject HttpClient http
@inject SiteState sitestate
<table class="table table-borderless"> <table class="table table-borderless">
<tr> <tr>
@ -31,7 +30,6 @@
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Actions => "Add,Edit"; public override string Actions => "Add,Edit";
I[Module]Service [Module]Service;
int _id; int _id;
string _name; string _name;
string _createdby; string _createdby;
@ -43,7 +41,6 @@
{ {
try try
{ {
[Module]Service = new [Module]Service(http, sitestate);
if (PageState.Action == "Edit") if (PageState.Action == "Edit")
{ {
_id = Int32.Parse(PageState.QueryString["id"]); _id = Int32.Parse(PageState.QueryString["id"]);

View File

@ -3,9 +3,8 @@
@namespace [Owner].[Module]s.Modules @namespace [Owner].[Module]s.Modules
@inherits ModuleBase @inherits ModuleBase
@inject I[Module]Service [Module]Service
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject HttpClient http
@inject SiteState sitestate
@if (_[Module]s == null) @if (_[Module]s == null)
{ {
@ -62,14 +61,12 @@ else
<!-- The content above is for informational purposes only and can be safely removed --> <!-- The content above is for informational purposes only and can be safely removed -->
@code { @code {
I[Module]Service [Module]Service;
List<[Module]> _[Module]s; List<[Module]> _[Module]s;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
try try
{ {
[Module]Service = new [Module]Service(http, sitestate);
_[Module]s = await [Module]Service.Get[Module]sAsync(ModuleState.ModuleId); _[Module]s = await [Module]Service.Get[Module]sAsync(ModuleState.ModuleId);
} }
catch (Exception ex) catch (Exception ex)

View File

@ -10,7 +10,6 @@ namespace [Owner].[Module]s.Modules
Name = "[Module]", Name = "[Module]",
Description = "[Module]", Description = "[Module]",
Version = "1.0.0", Version = "1.0.0",
Dependencies = "[Owner].[Module]s.Module.Shared",
ServerManagerType = "[ServerManagerType]", ServerManagerType = "[ServerManagerType]",
ReleaseVersions = "1.0.0" ReleaseVersions = "1.0.0"
}; };

View File

@ -3,9 +3,8 @@
@using Oqtane.Modules.Controls @using Oqtane.Modules.Controls
@namespace Oqtane.Modules.HtmlText @namespace Oqtane.Modules.HtmlText
@inherits ModuleBase @inherits ModuleBase
@inject IHtmlTextService HtmlTextService
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject HttpClient http
@inject SiteState sitestate
@if (_content != null) @if (_content != null)
{ {
@ -14,7 +13,8 @@
<NavLink class="btn btn-secondary" href="@NavigateUrl()">Cancel</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">Cancel</NavLink>
@if (!string.IsNullOrEmpty(_content)) @if (!string.IsNullOrEmpty(_content))
{ {
<br /><br /> <br />
<br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo> <AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
} }
} }
@ -35,8 +35,7 @@
{ {
try try
{ {
var htmltextservice = new HtmlTextService(http, sitestate); var htmltext = await HtmlTextService.GetHtmlTextAsync(ModuleState.ModuleId);
var htmltext = await htmltextservice.GetHtmlTextAsync(ModuleState.ModuleId);
if (htmltext != null) if (htmltext != null)
{ {
_content = htmltext.Content; _content = htmltext.Content;
@ -65,19 +64,18 @@
try try
{ {
var htmltextservice = new HtmlTextService(http, sitestate); var htmltext = await HtmlTextService.GetHtmlTextAsync(ModuleState.ModuleId);
var htmltext = await htmltextservice.GetHtmlTextAsync(ModuleState.ModuleId);
if (htmltext != null) if (htmltext != null)
{ {
htmltext.Content = content; htmltext.Content = content;
await htmltextservice.UpdateHtmlTextAsync(htmltext); await HtmlTextService.UpdateHtmlTextAsync(htmltext);
} }
else else
{ {
htmltext = new HtmlTextInfo(); htmltext = new HtmlTextInfo();
htmltext.ModuleId = ModuleState.ModuleId; htmltext.ModuleId = ModuleState.ModuleId;
htmltext.Content = content; htmltext.Content = content;
await htmltextservice.AddHtmlTextAsync(htmltext); await HtmlTextService.AddHtmlTextAsync(htmltext);
} }
await logger.LogInformation("Html/Text Content Saved {HtmlText}", htmltext); await logger.LogInformation("Html/Text Content Saved {HtmlText}", htmltext);

View File

@ -1,21 +1,13 @@
@using Oqtane.Modules.HtmlText.Services @using Oqtane.Modules.HtmlText.Services
@using Oqtane.Modules.HtmlText.Models
@namespace Oqtane.Modules.HtmlText @namespace Oqtane.Modules.HtmlText
@inherits ModuleBase @inherits ModuleBase
@inject NavigationManager NavigationManager @inject IHtmlTextService HtmlTextService
@inject HttpClient http
@inject SiteState sitestate
@((MarkupString)content) @((MarkupString)content)
@if (PageState.EditMode) @if (PageState.EditMode)
{ {
<br /> <br /><ActionLink Action="Edit" /><br /><br />
}
<ActionLink Action="Edit" />
@if (PageState.EditMode)
{
<br /><br />
} }
@code { @code {
@ -25,8 +17,7 @@
{ {
try try
{ {
var htmltextservice = new HtmlTextService(http, sitestate); var htmltext = await HtmlTextService.GetHtmlTextAsync(ModuleState.ModuleId);
var htmltext = await htmltextservice.GetHtmlTextAsync(ModuleState.ModuleId);
if (htmltext != null) if (htmltext != null)
{ {
content = htmltext.Content; content = htmltext.Content;

View File

@ -8,7 +8,7 @@ using Oqtane.Shared;
namespace Oqtane.Modules.HtmlText.Services namespace Oqtane.Modules.HtmlText.Services
{ {
public class HtmlTextService : ServiceBase, IHtmlTextService public class HtmlTextService : ServiceBase, IHtmlTextService, IService
{ {
private readonly SiteState _siteState; private readonly SiteState _siteState;

View File

@ -9,7 +9,7 @@ using Oqtane.UI;
namespace Oqtane.Modules namespace Oqtane.Modules
{ {
public class ModuleBase : ComponentBase, IModuleControl public abstract class ModuleBase : ComponentBase, IModuleControl
{ {
private Logger _logger; private Logger _logger;

View File

@ -4,8 +4,10 @@ using System.Threading.Tasks;
using Oqtane.Services; using Oqtane.Services;
using System.Reflection; using System.Reflection;
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Json;
using Oqtane.Modules; using Oqtane.Modules;
using Oqtane.Shared; using Oqtane.Shared;
using Oqtane.Providers; using Oqtane.Providers;
@ -19,10 +21,9 @@ namespace Oqtane.Client
{ {
var builder = WebAssemblyHostBuilder.CreateDefault(args); var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app"); builder.RootComponents.Add<App>("app");
HttpClient httpClient = new HttpClient {BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)};
builder.Services.AddSingleton( builder.Services.AddSingleton(httpClient);
new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }
);
builder.Services.AddOptions(); builder.Services.AddOptions();
// register auth services // register auth services
@ -57,14 +58,16 @@ namespace Oqtane.Client
builder.Services.AddScoped<ISqlService, SqlService>(); builder.Services.AddScoped<ISqlService, SqlService>();
builder.Services.AddScoped<ISystemService, SystemService>(); builder.Services.AddScoped<ISystemService, SystemService>();
await LoadClientAssemblies(httpClient);
// dynamically register module contexts and repository services // dynamically register module contexts and repository services
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies) foreach (Assembly assembly in assemblies)
{ {
Type[] implementationtypes = assembly.GetTypes() var implementationTypes = assembly.GetTypes()
.Where(item => item.GetInterfaces().Contains(typeof(IService))) .Where(item => item.GetInterfaces().Contains(typeof(IService)));
.ToArray();
foreach (Type implementationtype in implementationtypes) foreach (Type implementationtype in implementationTypes)
{ {
Type servicetype = Type.GetType(implementationtype.AssemblyQualifiedName.Replace(implementationtype.Name, "I" + implementationtype.Name)); Type servicetype = Type.GetType(implementationtype.AssemblyQualifiedName.Replace(implementationtype.Name, "I" + implementationtype.Name));
if (servicetype != null) if (servicetype != null)
@ -76,9 +79,27 @@ namespace Oqtane.Client
builder.Services.AddScoped(implementationtype, implementationtype); // no interface defined for service builder.Services.AddScoped(implementationtype, implementationtype); // no interface defined for service
} }
} }
assembly.GetInstances<IClientStartup>()
.ToList()
.ForEach(x => x.ConfigureServices(builder.Services));
} }
await builder.Build().RunAsync(); await builder.Build().RunAsync();
} }
private static async Task LoadClientAssemblies(HttpClient http)
{
var list = await http.GetFromJsonAsync<List<string>>($"/~/api/ModuleDefinition/load");
// get list of loaded assemblies on the client ( in the client-side hosting module the browser client has its own app domain )
var assemblyList = AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetName().Name).ToList();
foreach (var name in list)
{
if (assemblyList.Contains(name)) continue;
// download assembly from server and load
var bytes = await http.GetByteArrayAsync($"/~/api/ModuleDefinition/load/{name}.dll");
Assembly.Load(bytes);
}
}
} }
} }

View File

@ -5,16 +5,21 @@ using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using System; using System;
using Oqtane.Shared;
namespace Oqtane.Services namespace Oqtane.Services
{ {
public class AliasService : ServiceBase, IAliasService public class AliasService : ServiceBase, IAliasService
{ {
public AliasService(HttpClient http) :base(http) { } private readonly SiteState _siteState;
private string Apiurl => CreateApiUrl("Alias"); public AliasService(HttpClient http, SiteState siteState) : base(http)
{
_siteState = siteState;
}
private string Apiurl => CreateApiUrl(_siteState.Alias, "Alias");
public async Task<List<Alias>> GetAliasesAsync() public async Task<List<Alias>> GetAliasesAsync()
{ {

View File

@ -3,14 +3,20 @@ using System.Threading.Tasks;
using System.Net.Http; using System.Net.Http;
using System.Linq; using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using Oqtane.Shared;
namespace Oqtane.Services namespace Oqtane.Services
{ {
public class JobLogService : ServiceBase, IJobLogService public class JobLogService : ServiceBase, IJobLogService
{ {
public JobLogService(HttpClient http) :base(http) { } private readonly SiteState _siteState;
private string Apiurl => CreateApiUrl("JobLog"); public JobLogService(HttpClient http, SiteState siteState) : base(http)
{
_siteState = siteState;
}
private string Apiurl => CreateApiUrl(_siteState.Alias, "JobLog");
public async Task<List<JobLog>> GetJobLogsAsync() public async Task<List<JobLog>> GetJobLogsAsync()
{ {

View File

@ -3,14 +3,20 @@ using System.Threading.Tasks;
using System.Net.Http; using System.Net.Http;
using System.Linq; using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using Oqtane.Shared;
namespace Oqtane.Services namespace Oqtane.Services
{ {
public class JobService : ServiceBase, IJobService public class JobService : ServiceBase, IJobService
{ {
public JobService(HttpClient http) : base(http) { } private readonly SiteState _siteState;
private string Apiurl => CreateApiUrl("Job"); public JobService(HttpClient http, SiteState siteState) : base(http)
{
_siteState = siteState;
}
private string Apiurl => CreateApiUrl(_siteState.Alias, "Job");
public async Task<List<Job>> GetJobsAsync() public async Task<List<Job>> GetJobsAsync()
{ {

View File

@ -135,13 +135,13 @@ namespace Oqtane.Services
//TODO Missing content JSON validation //TODO Missing content JSON validation
} }
// create an API Url which is tenant agnostic ( for use with entities in the MasterDB ) // create an API Url which is tenant agnostic ( for use during installation )
public string CreateApiUrl(string serviceName) public string CreateApiUrl(string serviceName)
{ {
return CreateApiUrl(null, serviceName); return CreateApiUrl(null, serviceName);
} }
// create an API Url which is tenant aware ( for use with entities in the TenantDB ) // create an API Url which is tenant aware ( for use with repositories )
public string CreateApiUrl(Alias alias, string serviceName) public string CreateApiUrl(Alias alias, string serviceName)
{ {
string apiurl = "/"; string apiurl = "/";

View File

@ -1,4 +1,5 @@
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Shared;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -6,9 +7,14 @@ namespace Oqtane.Services
{ {
public class SqlService : ServiceBase, ISqlService public class SqlService : ServiceBase, ISqlService
{ {
public SqlService(HttpClient http) : base(http) { } private readonly SiteState _siteState;
private string Apiurl => CreateApiUrl("Sql"); public SqlService(HttpClient http, SiteState siteState) : base(http)
{
_siteState = siteState;
}
private string Apiurl => CreateApiUrl(_siteState.Alias, "Sql");
public async Task<SqlQuery> ExecuteQueryAsync(SqlQuery sqlquery) public async Task<SqlQuery> ExecuteQueryAsync(SqlQuery sqlquery)
{ {

View File

@ -3,14 +3,20 @@ using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Oqtane.Shared;
namespace Oqtane.Services namespace Oqtane.Services
{ {
public class TenantService : ServiceBase, ITenantService public class TenantService : ServiceBase, ITenantService
{ {
public TenantService(HttpClient http) : base(http) { } private readonly SiteState _siteState;
private string Apiurl => CreateApiUrl("Tenant"); public TenantService(HttpClient http, SiteState siteState) : base(http)
{
_siteState = siteState;
}
private string Apiurl => CreateApiUrl(_siteState.Alias, "Tenant");
public async Task<List<Tenant>> GetTenantsAsync() public async Task<List<Tenant>> GetTenantsAsync()
{ {

View File

@ -16,6 +16,7 @@ using System.Net;
using Oqtane.Enums; using Oqtane.Enums;
using Oqtane.Infrastructure; using Oqtane.Infrastructure;
using Oqtane.Repository; using Oqtane.Repository;
using Microsoft.AspNetCore.Routing.Constraints;
// ReSharper disable StringIndexOfIsCultureSpecific.1 // ReSharper disable StringIndexOfIsCultureSpecific.1
@ -194,7 +195,7 @@ namespace Oqtane.Controllers
CreateDirectory(folderPath); CreateDirectory(folderPath);
string filename = url.Substring(url.LastIndexOf("/", StringComparison.Ordinal) + 1); string filename = url.Substring(url.LastIndexOf("/", StringComparison.Ordinal) + 1);
// check for allowable file extensions // check for allowable file extensions
if (Constants.UploadableFiles.Contains(Path.GetExtension(filename).Replace(".", ""))) if (Constants.UploadableFiles.Split(',').Contains(Path.GetExtension(filename).ToLower().Replace(".", "")))
{ {
try try
{ {
@ -317,7 +318,7 @@ namespace Oqtane.Controllers
} }
// check for allowable file extensions // check for allowable file extensions
if (!Constants.UploadableFiles.Contains(Path.GetExtension(filename)?.Replace(".", ""))) if (!Constants.UploadableFiles.Split(',').Contains(Path.GetExtension(filename)?.ToLower().Replace(".", "")))
{ {
System.IO.File.Delete(Path.Combine(folder, filename + ".tmp")); System.IO.File.Delete(Path.Combine(folder, filename + ".tmp"));
} }
@ -396,12 +397,13 @@ namespace Oqtane.Controllers
[HttpGet("download/{id}")] [HttpGet("download/{id}")]
public IActionResult Download(int id) public IActionResult Download(int id)
{ {
string errorpath = Path.Combine(GetFolderPath("images"), "error.png");
Models.File file = _files.GetFile(id); Models.File file = _files.GetFile(id);
if (file != null) if (file != null)
{ {
if (_userPermissions.IsAuthorized(User, PermissionNames.View, file.Folder.Permissions)) if (_userPermissions.IsAuthorized(User, PermissionNames.View, file.Folder.Permissions))
{ {
string filepath = Path.Combine(GetFolderPath(file.Folder) , file.Name); string filepath = Path.Combine(GetFolderPath(file.Folder), file.Name);
if (System.IO.File.Exists(filepath)) if (System.IO.File.Exists(filepath))
{ {
byte[] filebytes = System.IO.File.ReadAllBytes(filepath); byte[] filebytes = System.IO.File.ReadAllBytes(filepath);
@ -411,21 +413,24 @@ namespace Oqtane.Controllers
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Read, "File Does Not Exist {FileId} {FilePath}", id, filepath); _logger.Log(LogLevel.Error, this, LogFunction.Read, "File Does Not Exist {FileId} {FilePath}", id, filepath);
HttpContext.Response.StatusCode = 404; HttpContext.Response.StatusCode = 404;
return null; byte[] filebytes = System.IO.File.ReadAllBytes(errorpath);
return File(filebytes, "application/octet-stream", file.Name);
} }
} }
else else
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Access File {FileId}", id); _logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Access File {FileId}", id);
HttpContext.Response.StatusCode = 401; HttpContext.Response.StatusCode = 401;
return null; byte[] filebytes = System.IO.File.ReadAllBytes(errorpath);
return File(filebytes, "application/octet-stream", file.Name);
} }
} }
else else
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Read, "File Not Found {FileId}", id); _logger.Log(LogLevel.Error, this, LogFunction.Read, "File Not Found {FileId}", id);
HttpContext.Response.StatusCode = 404; HttpContext.Response.StatusCode = 404;
return null; byte[] filebytes = System.IO.File.ReadAllBytes(errorpath);
return File(filebytes, "application/octet-stream", "error.png");
} }
} }
@ -469,7 +474,7 @@ namespace Oqtane.Controllers
file.ImageHeight = 0; file.ImageHeight = 0;
file.ImageWidth = 0; file.ImageWidth = 0;
if (Constants.ImageFiles.Contains(file.Extension)) if (Constants.ImageFiles.Split(',').Contains(file.Extension.ToLower()))
{ {
FileStream stream = new FileStream(filepath, FileMode.Open, FileAccess.Read); FileStream stream = new FileStream(filepath, FileMode.Open, FileAccess.Read);
using (var image = Image.FromStream(stream)) using (var image = Image.FromStream(stream))

View File

@ -10,7 +10,6 @@ using Oqtane.Extensions;
using Oqtane.Infrastructure; using Oqtane.Infrastructure;
using Oqtane.Repository; using Oqtane.Repository;
using Oqtane.Security; using Oqtane.Security;
using System.IO;
namespace Oqtane.Controllers namespace Oqtane.Controllers
{ {
@ -105,16 +104,26 @@ namespace Oqtane.Controllers
}.EncodePermissions(); }.EncodePermissions();
} }
if (_userPermissions.IsAuthorized(User,PermissionNames.Edit, permissions)) if (_userPermissions.IsAuthorized(User,PermissionNames.Edit, permissions))
{
if (FolderPathValid(folder))
{ {
if (string.IsNullOrEmpty(folder.Path) && folder.ParentId != null) if (string.IsNullOrEmpty(folder.Path) && folder.ParentId != null)
{ {
Folder parent = _folders.GetFolder(folder.ParentId.Value); Folder parent = _folders.GetFolder(folder.ParentId.Value);
folder.Path = Utilities.PathCombine(parent.Path, folder.Name,"\\"); folder.Path = Utilities.PathCombine(parent.Path, folder.Name);
} }
folder.Path = Utilities.PathCombine(folder.Path, "\\");
folder = _folders.AddFolder(folder); folder = _folders.AddFolder(folder);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Folder Added {Folder}", folder); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Folder Added {Folder}", folder);
} }
else else
{
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Folder Name Not Valid {Folder}", folder);
HttpContext.Response.StatusCode = 401;
folder = null;
}
}
else
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Create, "User Not Authorized To Add Folder {Folder}", folder); _logger.Log(LogLevel.Error, this, LogFunction.Create, "User Not Authorized To Add Folder {Folder}", folder);
HttpContext.Response.StatusCode = 401; HttpContext.Response.StatusCode = 401;
@ -130,16 +139,26 @@ namespace Oqtane.Controllers
public Folder Put(int id, [FromBody] Folder folder) public Folder Put(int id, [FromBody] Folder folder)
{ {
if (ModelState.IsValid && _userPermissions.IsAuthorized(User, EntityNames.Folder, folder.FolderId, PermissionNames.Edit)) if (ModelState.IsValid && _userPermissions.IsAuthorized(User, EntityNames.Folder, folder.FolderId, PermissionNames.Edit))
{
if (FolderPathValid(folder))
{ {
if (string.IsNullOrEmpty(folder.Path) && folder.ParentId != null) if (string.IsNullOrEmpty(folder.Path) && folder.ParentId != null)
{ {
Folder parent = _folders.GetFolder(folder.ParentId.Value); Folder parent = _folders.GetFolder(folder.ParentId.Value);
folder.Path = Utilities.PathCombine(parent.Path, folder.Name,"\\"); folder.Path = Utilities.PathCombine(parent.Path, folder.Name);
} }
folder.Path = Utilities.PathCombine(folder.Path, "\\");
folder = _folders.UpdateFolder(folder); folder = _folders.UpdateFolder(folder);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Folder Updated {Folder}", folder); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Folder Updated {Folder}", folder);
} }
else else
{
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Folder Name Not Valid {Folder}", folder);
HttpContext.Response.StatusCode = 401;
folder = null;
}
}
else
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Update, "User Not Authorized To Update Folder {Folder}", folder); _logger.Log(LogLevel.Error, this, LogFunction.Update, "User Not Authorized To Update Folder {Folder}", folder);
HttpContext.Response.StatusCode = 401; HttpContext.Response.StatusCode = 401;
@ -191,5 +210,11 @@ namespace Oqtane.Controllers
HttpContext.Response.StatusCode = 401; HttpContext.Response.StatusCode = 401;
} }
} }
private bool FolderPathValid(Folder folder)
{
// prevent folder path traversal and reserved devices
return (!folder.Name.Contains("\\") && !folder.Name.Contains("/") && !Constants.ReservedDevices.Split(',').Contains(folder.Name.ToUpper()));
}
} }
} }

View File

@ -170,6 +170,16 @@ namespace Oqtane.Controllers
return null; return null;
} }
} }
// GET api/<controller>/load/assembyname
[HttpGet("load")]
public List<string> Load()
{
var assemblies = AppDomain.CurrentDomain.GetOqtaneClientAssemblies();
var list = AppDomain.CurrentDomain.GetOqtaneClientAssemblies().Select(a => a.GetName().Name).ToList();
var deps = assemblies.SelectMany(a => a.GetReferencedAssemblies()).Distinct();
list.AddRange(deps.Where(a=>a.Name.EndsWith(".oqtane",StringComparison.OrdinalIgnoreCase)).Select(a=>a.Name));
return list;
}
// POST api/<controller>?moduleid=x // POST api/<controller>?moduleid=x
[HttpPost] [HttpPost]

View File

@ -1,7 +1,9 @@
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Repository; using Oqtane.Repository;
using Oqtane.Shared;
namespace Oqtane.Controllers namespace Oqtane.Controllers
{ {
@ -17,6 +19,7 @@ namespace Oqtane.Controllers
// GET: api/<controller> // GET: api/<controller>
[HttpGet] [HttpGet]
[Authorize(Roles = Constants.HostRole)]
public IEnumerable<SiteTemplate> Get() public IEnumerable<SiteTemplate> Get()
{ {
return _siteTemplates.GetSiteTemplates(); return _siteTemplates.GetSiteTemplates();

View File

@ -0,0 +1,26 @@
using System;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Oqtane.Infrastructure;
namespace Oqtane.Extensions
{
public static class ApplicationBuilderExtensions
{
public static IApplicationBuilder ConfigureOqtaneAssemblies(this IApplicationBuilder app, IWebHostEnvironment env)
{
var startUps = AppDomain.CurrentDomain
.GetOqtaneAssemblies()
.SelectMany(x => x.GetInstances<IServerStartup>());
foreach (var startup in startUps)
{
startup.Configure(app, env);
}
return app;
}
}
}

View File

@ -3,6 +3,7 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Oqtane.Infrastructure;
// ReSharper disable once CheckNamespace // ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection namespace Microsoft.Extensions.DependencyInjection
@ -30,6 +31,22 @@ namespace Microsoft.Extensions.DependencyInjection
} }
} }
} }
return mvcBuilder;
}
public static IMvcBuilder ConfigureOqtaneMvc(this IMvcBuilder mvcBuilder)
{
var startUps = AppDomain.CurrentDomain
.GetOqtaneAssemblies()
.SelectMany(x => x.GetInstances<IServerStartup>());
foreach (var startup in startUps)
{
startup.ConfigureMvc(mvcBuilder);
}
return mvcBuilder; return mvcBuilder;
} }
} }

View File

@ -8,21 +8,23 @@ using Microsoft.Extensions.Hosting;
using Oqtane.Extensions; using Oqtane.Extensions;
using Oqtane.Infrastructure; using Oqtane.Infrastructure;
using Oqtane.Modules; using Oqtane.Modules;
using Oqtane.Services;
using Oqtane.Shared; using Oqtane.Shared;
using Oqtane.UI;
// ReSharper disable once CheckNamespace // ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection namespace Microsoft.Extensions.DependencyInjection
{ {
public static class OqtaneServiceCollectionExtensions public static class OqtaneServiceCollectionExtensions
{ {
public static IServiceCollection AddOqtaneParts(this IServiceCollection services) public static IServiceCollection AddOqtaneParts(this IServiceCollection services, Runtime runtime)
{ {
LoadAssemblies(); LoadAssemblies();
services.AddOqtaneServices(); services.AddOqtaneServices(runtime);
return services; return services;
} }
private static IServiceCollection AddOqtaneServices(this IServiceCollection services) private static IServiceCollection AddOqtaneServices(this IServiceCollection services, Runtime runtime)
{ {
if (services is null) if (services is null)
{ {
@ -53,11 +55,24 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddSingleton(hostedServiceType, serviceType); services.AddSingleton(hostedServiceType, serviceType);
} }
} }
var startUps = assembly.GetInstances<IServerStartup>();
foreach (var startup in startUps)
{
startup.ConfigureServices(services);
} }
if (runtime == Runtime.Server)
{
assembly.GetInstances<IClientStartup>()
.ToList()
.ForEach(x => x.ConfigureServices(services));
}
}
return services; return services;
} }
private static void LoadAssemblies() private static void LoadAssemblies()
{ {
var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location); var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);

View File

@ -0,0 +1,17 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
namespace Oqtane.Infrastructure
{
public interface IServerStartup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
void ConfigureServices(IServiceCollection services);
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
void Configure(IApplicationBuilder app, IWebHostEnvironment env);
void ConfigureMvc(IMvcBuilder mvcBuilder);
}
}

View File

@ -14,11 +14,13 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Oqtane.Extensions;
using Oqtane.Infrastructure; using Oqtane.Infrastructure;
using Oqtane.Repository; using Oqtane.Repository;
using Oqtane.Security; using Oqtane.Security;
using Oqtane.Services; using Oqtane.Services;
using Oqtane.Shared; using Oqtane.Shared;
using Oqtane.UI;
namespace Oqtane namespace Oqtane
{ {
@ -26,6 +28,7 @@ namespace Oqtane
{ {
public IConfigurationRoot Configuration { get; } public IConfigurationRoot Configuration { get; }
private string _webRoot; private string _webRoot;
private Runtime _runtime;
public Startup(IWebHostEnvironment env) public Startup(IWebHostEnvironment env)
{ {
@ -33,6 +36,9 @@ namespace Oqtane
.SetBasePath(env.ContentRootPath) .SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true); .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
Configuration = builder.Build(); Configuration = builder.Build();
_runtime = (Configuration.GetSection("Runtime").Value == "WebAssembly") ? Runtime.WebAssembly : Runtime.Server;
_webRoot = env.WebRootPath; _webRoot = env.WebRootPath;
AppDomain.CurrentDomain.SetData("DataDirectory", Path.Combine(env.ContentRootPath, "Data")); AppDomain.CurrentDomain.SetData("DataDirectory", Path.Combine(env.ContentRootPath, "Data"));
} }
@ -187,14 +193,13 @@ namespace Oqtane
services.AddTransient<ISqlRepository, SqlRepository>(); services.AddTransient<ISqlRepository, SqlRepository>();
services.AddTransient<IUpgradeManager, UpgradeManager>(); services.AddTransient<IUpgradeManager, UpgradeManager>();
// load the external assemblies into the app domain // load the external assemblies into the app domain, install services
services.AddOqtaneParts(); services.AddOqtaneParts(_runtime);
services.AddMvc() services.AddMvc()
.AddNewtonsoftJson()
.AddOqtaneApplicationParts() // register any Controllers from custom modules .AddOqtaneApplicationParts() // register any Controllers from custom modules
.AddNewtonsoftJson(); .ConfigureOqtaneMvc(); // any additional configuration from IStart classes.
services.AddSwaggerGen(c => services.AddSwaggerGen(c =>
{ {
@ -217,14 +222,12 @@ namespace Oqtane
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts(); app.UseHsts();
} }
app.UseHttpsRedirection(); app.UseHttpsRedirection();
app.UseStaticFiles(); app.UseStaticFiles();
app.UseBlazorFrameworkFiles(); app.UseBlazorFrameworkFiles();
app.UseRouting(); app.UseRouting();
app.UseAuthentication(); app.UseAuthentication();
app.UseAuthorization(); app.UseAuthorization();
app.UseSwagger(); app.UseSwagger();
app.UseSwaggerUI(c => app.UseSwaggerUI(c =>
{ {
@ -237,6 +240,7 @@ namespace Oqtane
endpoints.MapControllers(); endpoints.MapControllers();
endpoints.MapFallbackToPage("/_Host"); endpoints.MapFallbackToPage("/_Host");
}); });
app.ConfigureOqtaneAssemblies(env);
} }
} }
} }

View File

@ -101,6 +101,12 @@
flex-direction: row; flex-direction: row;
} }
.app-logo {
display: block;
margin-left: auto;
margin-right: auto;
}
.breadcrumbs { .breadcrumbs {
position: fixed; position: fixed;
left: 275px; left: 275px;
@ -164,6 +170,12 @@
} }
@media (max-width: 767px) { @media (max-width: 767px) {
.app-logo {
height: 80px;
display: flex;
align-items: center;
}
.breadcrumbs { .breadcrumbs {
position: fixed; position: fixed;
top: 150px; top: 150px;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -81,15 +81,27 @@ window.interop = {
if (link.href !== url) { if (link.href !== url) {
link.setAttribute('href', url); link.setAttribute('href', url);
} }
if (type !== "" && link.type !== type) { if (type !== "") {
if (link.type !== type) {
link.setAttribute('type', type); link.setAttribute('type', type);
} }
if (integrity !== "" && link.integrity !== integrity) { } else {
link.removeAttribute('type');
}
if (integrity !== "") {
if (link.integrity !== integrity) {
link.setAttribute('integrity', integrity); link.setAttribute('integrity', integrity);
} }
if (crossorigin !== "" && link.crossOrigin !== crossorigin) { } else {
link.removeAttribute('integrity');
}
if (crossorigin !== "") {
if (link.crossOrigin !== crossorigin) {
link.setAttribute('crossorigin', crossorigin); link.setAttribute('crossorigin', crossorigin);
} }
} else {
link.removeAttribute('crossorigin');
}
} }
}, },
includeScript: function (id, src, content, location, integrity, crossorigin) { includeScript: function (id, src, content, location, integrity, crossorigin) {
@ -126,12 +138,20 @@ window.interop = {
if (script.src !== src) { if (script.src !== src) {
script.src = src; script.src = src;
} }
if (integrity !== "" && script.integrity !== integrity) { if (integrity !== "") {
if (script.integrity !== integrity) {
script.setAttribute('integrity', integrity); script.setAttribute('integrity', integrity);
} }
if (crossorigin !== "" && script.crossorigin !== crossorigin) { } else {
script.removeAttribute('integrity');
}
if (crossorigin !== "") {
if (script.crossOrigin !== crossorigin) {
script.setAttribute('crossorigin', crossorigin); script.setAttribute('crossorigin', crossorigin);
} }
} else {
script.removeAttribute('crossorigin');
}
} }
else { else {
if (script.innerHTML !== content) { if (script.innerHTML !== content) {

View File

@ -1,7 +1,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Oqtane.Modules;
using Oqtane.Services;
using Oqtane.Shared; using Oqtane.Shared;
using Oqtane.Themes;
// ReSharper disable once CheckNamespace // ReSharper disable once CheckNamespace
namespace System.Reflection namespace System.Reflection
@ -31,7 +34,29 @@ namespace System.Reflection
} }
return assembly.GetTypes() return assembly.GetTypes()
.Where(t => t.GetInterfaces().Contains(interfaceType)); //.Where(t => t.GetInterfaces().Contains(interfaceType));
.Where(x => interfaceType.IsAssignableFrom(x) && !x.IsInterface && !x.IsAbstract);
}
public static IEnumerable<Type> GetTypes<T>(this Assembly assembly)
{
return assembly.GetTypes(typeof(T));
}
public static IEnumerable<T> GetInstances<T>(this Assembly assembly) where T : class
{
if (assembly is null)
{
throw new ArgumentNullException(nameof(assembly));
}
var type = typeof(T);
var list = assembly.GetTypes()
.Where(x => type.IsAssignableFrom(x) && !x.IsInterface && !x.IsAbstract && !x.IsGenericType);
foreach (var type1 in list)
{
if (Activator.CreateInstance(type1) is T instance) yield return instance;
}
} }
public static bool IsOqtaneAssembly(this Assembly assembly) public static bool IsOqtaneAssembly(this Assembly assembly)
@ -48,5 +73,10 @@ namespace System.Reflection
{ {
return appDomain.GetAssemblies().Where(a => a.IsOqtaneAssembly()); return appDomain.GetAssemblies().Where(a => a.IsOqtaneAssembly());
} }
public static IEnumerable<Assembly> GetOqtaneClientAssemblies(this AppDomain appDomain)
{
return appDomain.GetOqtaneAssemblies()
.Where(a => a.GetTypes<IModuleControl>().Any() || a.GetTypes<IThemeControl>().Any() || a.GetTypes<IClientStartup>().Any());
}
} }
} }

View File

@ -0,0 +1,11 @@

using Microsoft.Extensions.DependencyInjection;
namespace Oqtane.Services
{
public interface IClientStartup
{
// This method gets called by the runtime. Use this method to add services to the container.
void ConfigureServices(IServiceCollection services);
}
}

View File

@ -7,6 +7,6 @@ namespace Oqtane.Modules
SecurityAccessLevel SecurityAccessLevel { get; } // defines the security access level for this control - defaults to View SecurityAccessLevel SecurityAccessLevel { get; } // defines the security access level for this control - defaults to View
string Title { get; } // title to display for this control - defaults to module title string Title { get; } // title to display for this control - defaults to module title
string Actions { get; } // allows for routing by configuration rather than by convention ( comma delimited ) - defaults to using component file name string Actions { get; } // allows for routing by configuration rather than by convention ( comma delimited ) - defaults to using component file name
bool UseAdminContainer { get; } // container for embedding module control - defaults to true bool UseAdminContainer { get; } // container for embedding module control - defaults to true. false will suppress the default modal UI popup behavior and render the component in the page.
} }
} }

View File

@ -18,6 +18,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.2" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" /> <PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" />
<PackageReference Include="System.Text.Json" Version="4.7.1" /> <PackageReference Include="System.Text.Json" Version="4.7.1" />
</ItemGroup> </ItemGroup>

View File

@ -43,5 +43,6 @@
public const string ImageFiles = "jpg,jpeg,jpe,gif,bmp,png"; public const string ImageFiles = "jpg,jpeg,jpe,gif,bmp,png";
public const string UploadableFiles = "jpg,jpeg,jpe,gif,bmp,png,mov,wmv,avi,mp4,mp3,doc,docx,xls,xlsx,ppt,pptx,pdf,txt,zip,nupkg"; public const string UploadableFiles = "jpg,jpeg,jpe,gif,bmp,png,mov,wmv,avi,mp4,mp3,doc,docx,xls,xlsx,ppt,pptx,pdf,txt,zip,nupkg";
public const string ReservedDevices = "CON,NUL,PRN,COM1,COM2,COM3,COM4,COM5,COM6,COM7,COM8,COM9,LPT1,LPT2,LPT3,LPT4,LPT5,LPT6,LPT7,LPT8,LPT9";
} }
} }