Merge pull request #3 from oqtane/master

update
This commit is contained in:
Cody 2020-05-21 21:07:22 -07:00 committed by GitHub
commit 23764d1ae4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
181 changed files with 13529 additions and 12385 deletions

View File

@ -1,4 +1,5 @@
@namespace Oqtane.Modules.Admin.Files
@using System.IO
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IFileService FileService
@ -12,7 +13,7 @@
<Label For="upload" HelpText="Upload the file you want">Upload: </Label>
</td>
<td>
<FileManager UploadMultiple="true" ShowFiles="false" FolderId="@_folderId.ToString()" />
<FileManager UploadMultiple="true" ShowFiles="false" FolderId="@_folderId" />
</td>
</tr>
</table>
@ -70,18 +71,32 @@
private async Task Download()
{
if (url == string.Empty || _folderId == -1)
{
AddModuleMessage("You Must Enter A Url And Select A Folder", MessageType.Warning);
return;
}
var filename = url.Substring(url.LastIndexOf("/", StringComparison.Ordinal) + 1);
if (!Constants.UploadableFiles.Split(',')
.Contains(Path.GetExtension(filename).ToLower().Replace(".", "")))
{
AddModuleMessage("File Could Not Be Downloaded From Url Due To Its File Extension", MessageType.Warning);
return ;
}
if (!filename.IsPathOrFileValid())
{
AddModuleMessage("You Must Enter A Url With A Valid File Name", MessageType.Warning);
return;
}
try
{
if (url != string.Empty && _folderId != -1)
{
await FileService.UploadFileAsync(url, _folderId);
await logger.LogInformation("File Downloaded Successfully From Url {Url}", url);
AddModuleMessage("File Downloaded Successfully From Url", MessageType.Success);
}
else
{
AddModuleMessage("You Must Enter A Url And Select A Folder", MessageType.Warning);
}
await FileService.UploadFileAsync(url, _folderId);
await logger.LogInformation("File Downloaded Successfully From Url {Url}", url);
AddModuleMessage("File Downloaded Successfully From Url", MessageType.Success);
}
catch (Exception ex)
{

View File

@ -25,7 +25,7 @@
</tr>
<tr>
<td>
<Label for="name" HelpText="Enter the file name">Name: </Label>
<Label for="name" HelpText="Enter the folder name">Name: </Label>
</td>
<td>
<input id="name" class="form-control" @bind="@_name" />
@ -112,51 +112,63 @@
private async Task SaveFolder()
{
if (_name == string.Empty || _parentId == -1)
{
AddModuleMessage("Folders Must Have A Parent And A Name", MessageType.Warning);
return;
}
if (!_name.IsPathOrFileValid())
{
AddModuleMessage("Folder Name Not Valid.", MessageType.Warning);
return;
}
try
{
if (_name != string.Empty && _parentId != -1)
Folder folder;
if (_folderId != -1)
{
Folder folder;
if (_folderId != -1)
{
folder = await FolderService.GetFolderAsync(_folderId);
}
else
{
folder = new Folder();
}
folder = await FolderService.GetFolderAsync(_folderId);
}
else
{
folder = new Folder();
}
folder.SiteId = PageState.Site.SiteId;
folder.SiteId = PageState.Site.SiteId;
if (_parentId == -1)
{
folder.ParentId = null;
}
else
{
folder.ParentId = _parentId;
}
if (_parentId == -1)
{
folder.ParentId = null;
}
else
{
folder.ParentId = _parentId;
}
folder.Name = _name;
folder.IsSystem = _isSystem;
folder.Permissions = _permissionGrid.GetPermissions();
folder.Name = _name;
folder.IsSystem = _isSystem;
folder.Permissions = _permissionGrid.GetPermissions();
if (_folderId != -1)
{
folder = await FolderService.UpdateFolderAsync(folder);
}
else
{
folder = await FolderService.AddFolderAsync(folder);
}
if (_folderId != -1)
{
folder = await FolderService.UpdateFolderAsync(folder);
}
else
{
folder = await FolderService.AddFolderAsync(folder);
}
if (folder != null)
{
await FolderService.UpdateFolderOrderAsync(folder.SiteId, folder.FolderId, folder.ParentId);
await logger.LogInformation("Folder Saved {Folder}", folder);
NavigationManager.NavigateTo(NavigateUrl());
}
else
{
AddModuleMessage("Folders Must Have A Parent And A Name", MessageType.Warning);
AddModuleMessage("An Error Was Encountered Saving The Folder", MessageType.Error);
}
}
catch (Exception ex)

View File

@ -3,53 +3,66 @@
@inject NavigationManager NavigationManager
@inject IModuleDefinitionService ModuleDefinitionService
@inject IModuleService ModuleService
@inject ISystemService SystemService
<table class="table table-borderless">
<table class="table table-borderless">
<tr>
<td>
<Label For="owner" HelpText="Enter the name of the organization who is developing this module. It should not contain spaces or punctuation.">Owner Name: </Label>
</td>
<td>
<input id="owner" class="form-control" @bind="@_owner" />
</td>
</tr>
<tr>
<td>
<Label For="module" HelpText="Enter a name for this module. It should be in singular form (ie. Car) and not contain spaces or punctuation.">Module Name: </Label>
</td>
<td>
<input id="module" class="form-control" @bind="@_module" />
</td>
</tr>
<tr>
<td>
<Label For="description" HelpText="Enter s short description for the module">Description: </Label>
</td>
<td>
<textarea id="description" class="form-control" @bind="@_description" rows="3"></textarea>
</td>
</tr>
<tr>
<td>
<Label For="template" HelpText="Select a module template. Internal modules are created inside of the Oqtane solution. External modules are created outside of the Oqtane solution.">Template: </Label>
</td>
<td>
<select id="template" class="form-control" @onchange="(e => TemplateChanged(e))">
<option value="-">&lt;Select Template&gt;</option>
<option value="internal">Internal</option>
<option value="external">External</option>
</select>
</td>
</tr>
@if (!string.IsNullOrEmpty(_location))
{
<tr>
<td>
<Label For="owner" HelpText="Enter the name of the organization who is developing this module. It should not contain spaces or punctuation.">Owner Name: </Label>
<Label For="location" HelpText="Location where the module will be created">Location: </Label>
</td>
<td>
<input id="owner" class="form-control" @bind="@_owner" />
<input id="module" class="form-control" @bind="@_location" readonly />
</td>
</tr>
<tr>
<td>
<Label For="module" HelpText="Enter a name for this module. It should be in singular form (ie. Car) and not contain spaces or punctuation.">Module Name: </Label>
</td>
<td>
<input id="module" class="form-control" @bind="@_module" />
</td>
</tr>
<tr>
<td>
<Label For="description" HelpText="Enter s short description for the module">Description: </Label>
</td>
<td>
<textarea id="description" class="form-control" @bind="@_description" rows="3"></textarea>
</td>
</tr>
<tr>
<td>
<Label For="template" HelpText="Select a module template. Internal modules are created inside of the Oqtane solution. External modules are created outside of the Oqtane solution.">Template: </Label>
</td>
<td>
<select id="template" class="form-control" @bind="@_template">
<option value="">&lt;Select Template&gt;</option>
<option value="internal">Internal</option>
<option value="external">External</option>
</select>
</td>
</tr>
</table>
}
</table>
<button type="button" class="btn btn-success" @onclick="CreateModule">Create Module</button>
@code {
@code {
private string _owner = string.Empty;
private string _module = string.Empty;
private string _description = string.Empty;
private string _template = string.Empty;
private string _template = "-";
private string _location = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
@ -62,9 +75,9 @@
{
try
{
if (!string.IsNullOrEmpty(_owner) && !string.IsNullOrEmpty(_module) && !string.IsNullOrEmpty(_template))
if (!string.IsNullOrEmpty(_owner) && !string.IsNullOrEmpty(_module) && _template != "-")
{
var moduleDefinition = new ModuleDefinition { Owner = _owner.Replace(" ",""), Name = _module.Replace(" ", ""), Description = _description, Template = _template };
var moduleDefinition = new ModuleDefinition { Owner = _owner.Replace(" ", ""), Name = _module.Replace(" ", ""), Description = _description, Template = _template };
await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition, ModuleState.ModuleId);
}
else
@ -77,4 +90,35 @@
await logger.LogError(ex, "Error Creating Module");
}
}
private async void TemplateChanged(ChangeEventArgs e)
{
try
{
_location = string.Empty;
_template = (string)e.Value;
if (_template != "-")
{
Dictionary<string, string> systeminfo = await SystemService.GetSystemInfoAsync();
if (systeminfo != null)
{
string[] path = systeminfo["serverpath"].Split('\\');
if (_template == "internal")
{
_location = string.Join("\\", path, 0, path.Length - 1) + "\\Oqtane.Client\\Modules\\" + _owner + "." + _module + "s";
}
else
{
_location = string.Join("\\", path, 0, path.Length - 2) + "\\" + _owner + "." + _module + "s";
}
}
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Getting System Info {Error}", ex.Message);
AddModuleMessage("Error Getting System Info", MessageType.Error);
}
}
}

View File

@ -1,6 +0,0 @@
XCOPY "..\Client\bin\Debug\netstandard2.1\[Owner].[Module]s.Module.Client.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\netcoreapp3.1\" /Y
XCOPY "..\Client\bin\Debug\netstandard2.1\[Owner].[Module]s.Module.Client.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\netcoreapp3.1\" /Y
XCOPY "..\Server\bin\Debug\netcoreapp3.1\[Owner].[Module]s.Module.Server.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\netcoreapp3.1\" /Y
XCOPY "..\Server\bin\Debug\netcoreapp3.1\[Owner].[Module]s.Module.Server.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\netcoreapp3.1\" /Y
XCOPY "..\Shared\bin\Debug\netstandard2.1\[Owner].[Module]s.Module.Shared.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\netcoreapp3.1\" /Y
XCOPY "..\Shared\bin\Debug\netstandard2.1\[Owner].[Module]s.Module.Shared.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\netcoreapp3.1\" /Y

View File

@ -35,7 +35,7 @@
<Label HelpText="Upload one or more module packages. Once they are uploaded click Install to complete the installation.">Module: </Label>
</td>
<td>
<FileManager Filter="nupkg" ShowFiles="false" Folder="Modules" UploadMultiple="True" />
<FileManager Filter="nupkg" ShowFiles="false" Folder="Modules" UploadMultiple="true" />
</td>
</tr>
</table>

View File

@ -24,7 +24,7 @@
</td>
<td>
<select id="container" class="form-control" @bind="@_containerType">
<option value="">&lt;Select Container&gt;</option>
<option value="-">&lt;Inherit From Page Or Site&gt;</option>
@foreach (KeyValuePair<string, string> container in _containers)
{
<option value="@container.Key">@container.Value</option>
@ -32,6 +32,17 @@
</select>
</td>
</tr>
<tr>
<td>
<Label For="allpages" HelpText="Indicate if this module should be displayed on all pages">Display On All Pages? </Label>
</td>
<td>
<select id="allpages" class="form-control" @bind="@_allPages">
<option value="True">Yes</option>
<option value="False">No</option>
</select>
</td>
</tr>
<tr>
<td>
<Label For="page" HelpText="The page that the module is on">Page: </Label>
@ -77,6 +88,7 @@
private Dictionary<string, string> _containers;
private string _title;
private string _containerType;
private string _allPages = "false";
private string _permissionNames = "";
private string _permissions;
private string _pageId;
@ -95,6 +107,15 @@
_title = ModuleState.Title;
_containers = ThemeService.GetContainerTypes(await ThemeService.GetThemesAsync());
_containerType = ModuleState.ContainerType;
if (!string.IsNullOrEmpty(PageState.Page.DefaultContainerType) && _containerType == PageState.Page.DefaultContainerType)
{
_containerType = "-";
}
if (_containerType == PageState.Site.DefaultContainerType)
{
_containerType = "-";
}
_allPages = ModuleState.AllPages.ToString();
_permissions = ModuleState.Permissions;
_permissionNames = ModuleState.ModuleDefinition.PermissionNames;
_pageId = ModuleState.PageId.ToString();
@ -102,8 +123,8 @@
_settingsModuleType = Type.GetType(ModuleState.ModuleType);
if (_settingsModuleType != null)
{
var moduleobject = Activator.CreateInstance(_settingsModuleType);
_settingstitle = (string)_settingsModuleType.GetProperty("Title").GetValue(moduleobject, null);
var moduleobject = Activator.CreateInstance(_settingsModuleType) as IModuleControl;
_settingstitle = moduleobject.Title;
if (string.IsNullOrEmpty(_settingstitle))
{
_settingstitle = "Other Settings";
@ -120,18 +141,26 @@
private async Task SaveModule()
{
var module = ModuleState;
module.Permissions = _permissionGrid.GetPermissions();
await ModuleService.UpdateModuleAsync(module);
var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
pagemodule.PageId = int.Parse(_pageId);
pagemodule.Title = _title;
pagemodule.ContainerType = _containerType;
pagemodule.ContainerType = (_containerType != "-") ? _containerType : string.Empty;
if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Page.DefaultContainerType)
{
pagemodule.ContainerType = string.Empty;
}
if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Site.DefaultContainerType)
{
pagemodule.ContainerType = string.Empty;
}
await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
var module = ModuleState;
module.AllPages = bool.Parse(_allPages);
module.Permissions = _permissionGrid.GetPermissions();
await ModuleService.UpdateModuleAsync(module);
if (_settingsModuleType != null)
{
var moduleType = Type.GetType(ModuleState.ModuleType);

View File

@ -101,7 +101,7 @@
</td>
<td>
<select id="Theme" class="form-control" @onchange="(e => ThemeChanged(e))">
<option value="-">&lt;Select Theme&gt;</option>
<option value="-">&lt;Inherit From Site&gt;</option>
@foreach (KeyValuePair<string, string> item in _themes)
{
if (item.Key == _themetype)
@ -122,7 +122,7 @@
</td>
<td>
<select id="Layout" class="form-control" @bind="@_layouttype">
<option value="-">&lt;Select Layout&gt;</option>
<option value="-">&lt;Inherit From Site&gt;</option>
@foreach (KeyValuePair<string, string> panelayout in _panelayouts)
{
if (panelayout.Key == _layouttype)
@ -137,6 +137,20 @@
</select>
</td>
</tr>
<tr>
<td>
<Label For="defaultContainer" HelpText="Select the default container for the page">Default Container: </Label>
</td>
<td>
<select id="defaultContainer" class="form-control" @bind="@_containertype">
<option value="-">&lt;Inherit From Site&gt;</option>
@foreach (KeyValuePair<string, string> container in _containers)
{
<option value="@container.Key">@container.Value</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="Icon" HelpText="Optionally provide an icon for this page which will be displayed in the site navigation">Icon: </Label>
@ -187,6 +201,7 @@
@code {
private Dictionary<string, string> _themes;
private Dictionary<string, string> _panelayouts;
private Dictionary<string, string> _containers = new Dictionary<string, string>();
private List<Theme> _themeList;
private List<Page> _pageList;
private string _name;
@ -202,6 +217,7 @@
private string _mode = "view";
private string _themetype = "-";
private string _layouttype = "-";
private string _containertype = "-";
private string _icon = string.Empty;
private string _permissions = string.Empty;
private PermissionGrid _permissionGrid;
@ -216,11 +232,9 @@
_pageList = PageState.Pages;
_children = PageState.Pages.Where(item => item.ParentId == null).ToList();
_themetype = PageState.Site.DefaultThemeType;
_layouttype = PageState.Site.DefaultLayoutType;
_themes = ThemeService.GetThemeTypes(_themeList);
_panelayouts = ThemeService.GetPaneLayoutTypes(_themeList, _themetype);
_containers = ThemeService.GetContainerTypes(_themeList);
_permissions = string.Empty;
}
@ -351,16 +365,20 @@
page.Url = _url;
page.EditMode = (_mode == "edit" ? true : false);
page.ThemeType = (_themetype != "-") ? _themetype : string.Empty;
page.LayoutType = (_layouttype != "-") ? _layouttype : string.Empty;
if (page.ThemeType == PageState.Site.DefaultThemeType)
if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType)
{
page.ThemeType = string.Empty;
}
if (page.LayoutType == PageState.Site.DefaultLayoutType)
page.LayoutType = (_layouttype != "-") ? _layouttype : string.Empty;
if (!string.IsNullOrEmpty(page.LayoutType) && page.LayoutType == PageState.Site.DefaultLayoutType)
{
page.LayoutType = string.Empty;
}
page.DefaultContainerType = (_containertype != "-") ? _containertype : string.Empty;
if (!string.IsNullOrEmpty(page.DefaultContainerType) && page.DefaultContainerType == PageState.Site.DefaultContainerType)
{
page.DefaultContainerType = string.Empty;
}
page.Icon = (_icon == null ? string.Empty : _icon);
page.Permissions = _permissionGrid.GetPermissions();
page.IsPersonalizable = (_ispersonalizable == null ? false : Boolean.Parse(_ispersonalizable));

View File

@ -112,7 +112,7 @@
</td>
<td>
<select id="Theme" class="form-control" @onchange="(e => ThemeChanged(e))">
<option value="-">&lt;Select Theme&gt;</option>
<option value="-">&lt;Inherit From Site&gt;</option>
@foreach (KeyValuePair<string, string> item in _themes)
{
if (item.Key == _themetype)
@ -133,7 +133,7 @@
</td>
<td>
<select id="Layout" class="form-control" @bind="@_layouttype">
<option value="-">&lt;Select Layout&gt;</option>
<option value="-">&lt;Inherit From Site&gt;</option>
@foreach (KeyValuePair<string, string> panelayout in _panelayouts)
{
if (panelayout.Key == _layouttype)
@ -148,6 +148,20 @@
</select>
</td>
</tr>
<tr>
<td>
<Label For="defaultContainer" HelpText="Select the default container for the page">Default Container: </Label>
</td>
<td>
<select id="defaultContainer" class="form-control" @bind="@_containertype">
<option value="-">&lt;Inherit From Site&gt;</option>
@foreach (KeyValuePair<string, string> container in _containers)
{
<option value="@container.Key">@container.Value</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="Icon" HelpText="Optionally provide an icon for this page which will be displayed in the site navigation">Icon: </Label>
@ -200,6 +214,7 @@
@code {
private Dictionary<string, string> _themes;
private Dictionary<string, string> _panelayouts;
private Dictionary<string, string> _containers = new Dictionary<string, string>();
private List<Theme> _themeList;
private List<Page> _pageList;
private int _pageId;
@ -217,6 +232,7 @@
private string _mode;
private string _themetype = "-";
private string _layouttype = "-";
private string _containertype = "-";
private string _icon;
private string _permissions;
private string _createdby;
@ -241,6 +257,7 @@
_children = PageState.Pages.Where(item => item.ParentId == null).ToList();
_themes = ThemeService.GetThemeTypes(_themeList);
_containers = ThemeService.GetContainerTypes(_themeList);
_pageId = Int32.Parse(PageState.QueryString["id"]);
var page = PageState.Pages.FirstOrDefault(item => item.PageId == _pageId);
@ -270,16 +287,21 @@
_ispersonalizable = page.IsPersonalizable.ToString();
_mode = (page.EditMode) ? "edit" : "view";
_themetype = page.ThemeType;
_panelayouts = ThemeService.GetPaneLayoutTypes(_themeList, _themetype);
_layouttype = page.LayoutType;
if (_themetype == PageState.Site.DefaultThemeType)
{
_themetype = "-";
}
_panelayouts = ThemeService.GetPaneLayoutTypes(_themeList, _themetype);
_layouttype = page.LayoutType;
if (_layouttype == PageState.Site.DefaultLayoutType)
{
_layouttype = "-";
}
_containertype = page.DefaultContainerType;
if (string.IsNullOrEmpty(_containertype))
{
_containertype = "-";
}
_icon = page.Icon;
_permissions = page.Permissions;
_createdby = page.CreatedBy;
@ -426,15 +448,20 @@
page.Url = _url;
page.EditMode = (_mode == "edit");
page.ThemeType = (_themetype != "-") ? _themetype : string.Empty;
page.LayoutType = (_layouttype != "-") ? _layouttype : string.Empty;
if (page.ThemeType == PageState.Site.DefaultThemeType)
if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType)
{
page.ThemeType = string.Empty;
}
if (page.LayoutType == PageState.Site.DefaultLayoutType)
page.LayoutType = (_layouttype != "-") ? _layouttype : string.Empty;
if (!string.IsNullOrEmpty(page.LayoutType) && page.LayoutType == PageState.Site.DefaultLayoutType)
{
page.LayoutType = string.Empty;
}
page.DefaultContainerType = (_containertype != "-") ? _containertype : string.Empty;
if (!string.IsNullOrEmpty(page.DefaultContainerType) && page.DefaultContainerType == PageState.Site.DefaultContainerType)
{
page.DefaultContainerType = string.Empty;
}
page.Icon = _icon ?? string.Empty;
page.Permissions = _permissionGrid.GetPermissions();
page.IsPersonalizable = (_ispersonalizable != null && Boolean.Parse(_ispersonalizable));

View File

@ -39,7 +39,7 @@
<Label For="logo" HelpText="Upload a logo for the site">Logo: </Label>
</td>
<td>
<FileManager FileId="@_logofileid.ToString()" Filter="@Constants.ImageFiles" @ref="_logofilemanager" />
<FileManager FileId="@_logofileid" Filter="@Constants.ImageFiles" @ref="_logofilemanager" />
</td>
</tr>
<tr>
@ -47,7 +47,7 @@
<Label For="favicon" HelpText="Select Your default icon">Favicon: </Label>
</td>
<td>
<FileManager FileId="@_faviconfileid.ToString()" Filter="ico" @ref="_faviconfilemanager" />
<FileManager FileId="@_faviconfileid" Filter="ico" @ref="_faviconfilemanager" />
</td>
</tr>
<tr>
@ -185,7 +185,7 @@
<Label For="appIcon" HelpText="Include an application icon for your PWA. It should be a PNG which is 192 X 192 pixels in dimension.">App Icon: </Label>
</td>
<td>
<FileManager FileId="@_pwaappiconfileid.ToString()" Filter="png" @ref="_pwaappiconfilemanager" />
<FileManager FileId="@_pwaappiconfileid" Filter="png" @ref="_pwaappiconfilemanager" />
</td>
</tr>
<tr>
@ -193,7 +193,7 @@
<Label For="splashIcon" HelpText="Include a splash icon for your PWA. It should be a PNG which is 512 X 512 pixels in dimension.">Splash Icon: </Label>
</td>
<td>
<FileManager FileId="@_pwasplashiconfileid.ToString()" Filter="png" @ref="_pwasplashiconfilemanager" />
<FileManager FileId="@_pwasplashiconfileid" Filter="png" @ref="_pwasplashiconfilemanager" />
</td>
</tr>
</table>

View File

@ -8,7 +8,7 @@
<Label For="version" HelpText="Framework Version">Framework Version: </Label>
</td>
<td>
<input id="version" class="form-control" @bind="@_version" disabled />
<input id="version" class="form-control" @bind="@_version" readonly />
</td>
</tr>
<tr>
@ -16,7 +16,7 @@
<Label For="runtime" HelpText="Blazor Runtime (Server or WebAssembly)">Blazor Runtime: </Label>
</td>
<td>
<input id="runtime" class="form-control" @bind="@_runtime" disabled />
<input id="runtime" class="form-control" @bind="@_runtime" readonly />
</td>
</tr>
<tr>
@ -24,7 +24,7 @@
<Label For="clrversion" HelpText="Common Language Runtime Version">CLR Version: </Label>
</td>
<td>
<input id="clrversion" class="form-control" @bind="@_clrversion" disabled />
<input id="clrversion" class="form-control" @bind="@_clrversion" readonly />
</td>
</tr>
<tr>
@ -32,7 +32,7 @@
<Label For="osversion" HelpText="Operating System Version">OS Version: </Label>
</td>
<td>
<input id="osversion" class="form-control" @bind="@_osversion" disabled />
<input id="osversion" class="form-control" @bind="@_osversion" readonly />
</td>
</tr>
<tr>
@ -40,7 +40,7 @@
<Label For="serverpath" HelpText="Server Path">Server Path: </Label>
</td>
<td>
<input id="serverpath" class="form-control" @bind="@_serverpath" disabled />
<input id="serverpath" class="form-control" @bind="@_serverpath" readonly />
</td>
</tr>
<tr>
@ -48,7 +48,7 @@
<Label For="servertime" HelpText="Server Time">Server Time: </Label>
</td>
<td>
<input id="servertime" class="form-control" @bind="@_servertime" disabled />
<input id="servertime" class="form-control" @bind="@_servertime" readonly />
</td>
</tr>
</table>

View File

@ -35,7 +35,7 @@
<Label HelpText="Upload one or more theme packages. Once they are uploaded click Install to complete the installation.">Theme: </Label>
</td>
<td>
<FileManager Filter="nupkg" ShowFiles="false" Folder="Themes" UploadMultiple="True" />
<FileManager Filter="nupkg" ShowFiles="false" Folder="Themes" UploadMultiple="@true" />
</td>
</tr>
</table>

View File

@ -11,29 +11,27 @@
<TabPanel Name="Download">
@if (_upgradeavailable)
{
<ModuleMessage Type="MessageType.Info" Message="Download a new version of the framework. Once you are ready click Install to complete the installation."></ModuleMessage>
@("Framework") @_package.Version <button type="button" class="btn btn-success" @onclick=@(async () => await Download(Constants.PackageId, Constants.Version))>Download</button>
<ModuleMessage Type="MessageType.Info" Message="Select The Upgrade Button To Install a New Framework Version"></ModuleMessage>
@("Framework") @_package.Version <button type="button" class="btn btn-success" @onclick=@(async () => await Download(Constants.PackageId, Constants.Version))>Upgrade</button>
}
else
{
<ModuleMessage Type="MessageType.Info" Message="Framework Is Already Up To Date"></ModuleMessage>
}
</TabPanel>
@if (_upgradeavailable)
{
<TabPanel Name="Upload">
<table class="table table-borderless">
<tr>
<td>
<Label HelpText="Upload a new framework package. Once it is uploaded click Install to complete the installation.">Framework: </Label>
<Label HelpText="Upload a framework package and select Install to complete the installation">Framework: </Label>
</td>
<td>
<FileManager Filter="nupkg" ShowFiles="false" Folder="Framework" />
<FileManager Filter="nupkg" Folder="Framework" />
</td>
</tr>
</table>
<button type="button" class="btn btn-success" @onclick="Upgrade">Install</button>
</TabPanel>
}
</TabStrip>
}

View File

@ -64,7 +64,7 @@ else
<label for="Name" class="control-label">Photo: </label>
</td>
<td>
<FileManager FileId="@photofileid.ToString()" @ref="filemanager" />
<FileManager FileId="@photofileid" @ref="filemanager" />
</td>
</tr>
</table>

View File

@ -63,7 +63,7 @@ else
<label class="control-label">Photo: </label>
</td>
<td>
<FileManager FileId="@photofileid.ToString()" @ref="filemanager" />
<FileManager FileId="@photofileid" @ref="filemanager" />
</td>
</tr>
<tr>

View File

@ -1,6 +1,6 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleBase
@attribute [OqtaneIgnore]
@if (_visible)
{
<div class="app-admin-modal">
@ -62,9 +62,9 @@
[Parameter]
public string Class { get; set; } // optional
[Parameter]
public bool Disabled { get; set; } // optional
[Parameter]
public bool Disabled { get; set; } // optional
[Parameter]
public string EditMode { get; set; } // optional - specifies if a user must be in edit mode to see the action - default is true
@ -109,8 +109,8 @@
Type moduleType = Type.GetType(typename);
if (moduleType != null)
{
var moduleobject = Activator.CreateInstance(moduleType);
security = (SecurityAccessLevel)moduleType.GetProperty("SecurityAccessLevel").GetValue(moduleobject, null);
var moduleobject = Activator.CreateInstance(moduleType) as IModuleControl;
security = moduleobject.SecurityAccessLevel;
}
else
{

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleBase
@inherits ModuleBase
@attribute [OqtaneIgnore]
@inject IUserService UserService
@if (_authorized)
@ -100,8 +101,8 @@
var moduleType = Type.GetType(typename);
if (moduleType != null)
{
var moduleobject = Activator.CreateInstance(moduleType);
security = (SecurityAccessLevel)moduleType.GetProperty("SecurityAccessLevel").GetValue(moduleobject, null);
var moduleobject = Activator.CreateInstance(moduleType) as IModuleControl;
security = moduleobject.SecurityAccessLevel;
}
else
{

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleBase
@inherits ModuleBase
@attribute [OqtaneIgnore]
@if (_text != string.Empty)
{

View File

@ -1,5 +1,7 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleBase
@attribute [OqtaneIgnore]
@inject IFolderService FolderService
@inject IFileService FileService
@inject IJSRuntime JsRuntime
@ -9,33 +11,36 @@
<div id="@Id" class="container-fluid px-0">
<div class="row">
<div class="col">
<div>
<select class="form-control" @onchange="(e => FolderChanged(e))">
@if (string.IsNullOrEmpty(Folder))
{
<option value="-1">&lt;Select Folder&gt;</option>
}
@foreach (Folder folder in _folders)
{
if (folder.FolderId == _folderid)
@if (ShowFolders || FolderId <= 0)
{
<div>
<select class="form-control" @onchange="(e => FolderChanged(e))">
@if (string.IsNullOrEmpty(Folder))
{
<option value="@(folder.FolderId)" selected>@(new string('-', folder.Level * 2))@(folder.Name)</option>
<option value="-1">&lt;Select Folder&gt;</option>
}
else
@foreach (Folder folder in _folders)
{
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
if (folder.FolderId == FolderId)
{
<option value="@(folder.FolderId)" selected>@(new string('-', folder.Level * 2))@(folder.Name)</option>
}
else
{
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
}
}
}
</select>
</div>
@if (_showfiles)
</select>
</div>
}
@if (ShowFiles)
{
<div>
<select class="form-control" @onchange="(e => FileChanged(e))">
<option value="-1">&lt;Select File&gt;</option>
@foreach (File file in _files)
{
if (file.FileId == _fileid)
if (file.FileId == FileId)
{
<option value="@(file.FileId)" selected>@(file.Name)</option>
}
@ -47,33 +52,33 @@
</select>
</div>
}
@if (_haseditpermission)
@if (ShowUpload && _haseditpermission)
{
<div>
@if (_uploadmultiple)
@if (UploadMultiple)
{
<input type="file" id="@_fileinputid" name="file" accept="@_filter" multiple />
<input type="file" id="@_fileinputid" name="file" accept="@_filter" multiple/>
}
else
{
<input type="file" id="@_fileinputid" name="file" accept="@_filter" />
<input type="file" id="@_fileinputid" name="file" accept="@_filter"/>
}
<span id="@_progressinfoid"></span><progress id="@_progressbarid" style="width: 150px; visibility: hidden;"></progress>
<span class="float-right">
<button type="button" class="btn btn-success" @onclick="UploadFile">Upload</button>
@if (_showfiles && GetFileId() != -1)
{
<button type="button" class="btn btn-danger" @onclick="DeleteFile">Delete</button>
}
<button type="button" class="btn btn-success" @onclick="UploadFile">Upload</button>
@if (_showfiles && GetFileId() != -1)
{
<button type="button" class="btn btn-danger" @onclick="DeleteFile">Delete</button>
}
</span>
</div>
@((MarkupString)_message)
}
@((MarkupString) _message)
</div>
@if (_image != string.Empty)
{
<div class="col-auto">
@((MarkupString)_image)
@((MarkupString) _image)
</div>
}
</div>
@ -83,15 +88,12 @@
@code {
private string _id;
private List<Folder> _folders;
private int _folderid = -1;
private List<File> _files = new List<File>();
private int _fileid = -1;
private bool _showfiles = true;
private string _fileinputid = string.Empty;
private string _progressinfoid = string.Empty;
private string _progressbarid = string.Empty;
private string _filter = "*";
private bool _uploadmultiple = false;
private bool _haseditpermission = false;
private string _message = string.Empty;
private string _image = string.Empty;
@ -104,19 +106,25 @@
public string Folder { get; set; } // optional - for setting a specific folder by default
[Parameter]
public string FolderId { get; set; } // optional - for setting a specific folderid by default
public int FolderId { get; set; } = -1; // optional - for setting a specific folderid by default
[Parameter]
public string ShowFiles { get; set; } // optional - for indicating whether a list of files should be displayed - default is true
public bool ShowFiles { get; set; } = true; // optional - for indicating whether a list of files should be displayed - default is true
[Parameter]
public string FileId { get; set; } // optional - for setting a specific file by default
public bool ShowUpload { get; set; } = true; // optional - for indicating whether a Upload controls should be displayed - default is true
[Parameter]
public bool ShowFolders { get; set; } = true; // optional - for indicating whether a list of folders should be displayed - default is true
[Parameter]
public int FileId { get; set; } = -1; // optional - for setting a specific file by default
[Parameter]
public string Filter { get; set; } // optional - comma delimited list of file types that can be selected or uploaded ie. "jpg,gif"
[Parameter]
public string UploadMultiple { get; set; } // optional - enable multiple file uploads - default false
public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false
protected override async Task OnInitializedAsync()
{
@ -128,56 +136,39 @@
if (!string.IsNullOrEmpty(Folder))
{
_folders = new List<Folder> {new Folder {FolderId = -1, Name = Folder}};
_folderid = -1;
FolderId = -1;
}
else
{
_folders = await FolderService.GetFoldersAsync(ModuleState.SiteId);
if (!string.IsNullOrEmpty(FolderId))
{
_folderid = int.Parse(FolderId);
}
}
if (!string.IsNullOrEmpty(FileId))
if (FileId != -1)
{
_fileid = int.Parse(FileId);
if (_fileid != -1)
File file = await FileService.GetFileAsync(FileId);
if (file != null)
{
File file = await FileService.GetFileAsync(int.Parse(FileId));
if (file != null)
{
_folderid = file.FolderId;
}
else
{
_fileid = -1; // file does not exist
}
FolderId = file.FolderId;
}
else
{
FileId = -1; // file does not exist
}
await SetImage();
}
if (!string.IsNullOrEmpty(ShowFiles))
{
_showfiles = bool.Parse(ShowFiles);
}
await SetImage();
if (!string.IsNullOrEmpty(Filter))
{
_filter = "." + Filter.Replace(",",",.");
_filter = "." + Filter.Replace(",", ",.");
}
await GetFiles();
// create unique id for component
// create unique id for component
_guid = Guid.NewGuid().ToString("N");
_fileinputid = _guid + "FileInput";
_progressinfoid = _guid + "ProgressInfo";
_progressbarid = _guid + "ProgressBar";
if (!string.IsNullOrEmpty(UploadMultiple))
{
_uploadmultiple = bool.Parse(UploadMultiple);
}
}
private async Task GetFiles()
@ -190,11 +181,11 @@
}
else
{
Folder folder = _folders.FirstOrDefault(item => item.FolderId == _folderid);
Folder folder = _folders.FirstOrDefault(item => item.FolderId == FolderId);
if (folder != null)
{
_haseditpermission = UserSecurity.IsAuthorized(PageState.User,PermissionNames.Edit, folder.Permissions);
_files = await FileService.GetFilesAsync(_folderid);
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, folder.Permissions);
_files = await FileService.GetFilesAsync(FolderId);
}
else
{
@ -221,9 +212,9 @@
_message = string.Empty;
try
{
_folderid = int.Parse((string)e.Value);
FolderId = int.Parse((string) e.Value);
await GetFiles();
_fileid = -1;
FileId = -1;
_image = string.Empty;
StateHasChanged();
}
@ -237,7 +228,7 @@
private async Task FileChanged(ChangeEventArgs e)
{
_message = string.Empty;
_fileid = int.Parse((string)e.Value);
FileId = int.Parse((string) e.Value);
await SetImage();
StateHasChanged();
@ -246,21 +237,21 @@
private async Task SetImage()
{
_image = string.Empty;
if (_fileid != -1)
if (FileId != -1)
{
File file = await FileService.GetFileAsync(_fileid);
File file = await FileService.GetFileAsync(FileId);
if (file != null && file.ImageHeight != 0 && file.ImageWidth != 0)
{
var maxwidth = 200;
var maxheight = 200;
var ratioX = (double)maxwidth / (double)file.ImageWidth;
var ratioY = (double)maxheight / (double)file.ImageHeight;
var ratioX = (double) maxwidth / (double) file.ImageWidth;
var ratioY = (double) maxheight / (double) file.ImageHeight;
var ratio = ratioX < ratioY ? ratioX : ratioY;
_image = "<img src=\"" + ContentUrl(_fileid) + "\" alt=\"" + file.Name +
"\" width=\"" + Convert.ToInt32(file.ImageWidth * ratio).ToString() +
"\" height=\"" + Convert.ToInt32(file.ImageHeight * ratio).ToString() + "\" />";
_image = "<img src=\"" + ContentUrl(FileId) + "\" alt=\"" + file.Name +
"\" width=\"" + Convert.ToInt32(file.ImageWidth * ratio).ToString() +
"\" height=\"" + Convert.ToInt32(file.ImageHeight * ratio).ToString() + "\" />";
}
}
}
@ -280,7 +271,7 @@
}
else
{
result = await FileService.UploadFilesAsync(_folderid, upload, _guid);
result = await FileService.UploadFilesAsync(FolderId, upload, _guid);
}
if (result == string.Empty)
@ -294,7 +285,7 @@
var file = _files.Where(item => item.Name == upload[0]).FirstOrDefault();
if (file != null)
{
_fileid = file.FileId;
FileId = file.FileId;
await SetImage();
}
}
@ -324,21 +315,21 @@
try
{
await FileService.DeleteFileAsync(_fileid);
await logger.LogInformation("File Deleted {File}", _fileid);
await FileService.DeleteFileAsync(FileId);
await logger.LogInformation("File Deleted {File}", FileId);
_message = "<br /><div class=\"alert alert-success\" role=\"alert\">File Deleted</div>";
await GetFiles();
_fileid = -1;
FileId = -1;
await SetImage();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting File {File} {Error}", _fileid, ex.Message);
await logger.LogError(ex, "Error Deleting File {File} {Error}", FileId, ex.Message);
_message = "<br /><div class=\"alert alert-danger\" role=\"alert\">Error Deleting File</div>";
}
}
public int GetFileId() => _fileid;
public int GetFileId() => FileId;
}

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleBase
@inherits ModuleBase
@attribute [OqtaneIgnore]
@if (!string.IsNullOrEmpty(HelpText))
{
@ -41,4 +42,4 @@ else
_openLabel += ">";
}
}
}

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleBase
@inherits ModuleBase
@attribute [OqtaneIgnore]
@if (!string.IsNullOrEmpty(_message))
{

View File

@ -1,6 +1,8 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleBase
@typeparam TableItem
@attribute [OqtaneIgnore]
@typeparam TableItem
<p>
@if(Format == "Table")
@ -209,4 +211,4 @@
UpdateList(_page);
}
}
}

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleBase
@inherits ModuleBase
@attribute [OqtaneIgnore]
@inject IRoleService RoleService
@inject IUserService UserService

View File

@ -1,28 +1,82 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleBase
@attribute [OqtaneIgnore]
@inject IJSRuntime JsRuntime
@if (_filemanagervisible)
{
<FileManager @ref="_fileManager" Filter="@Constants.ImageFiles" />
@((MarkupString)_message)
<br />
}
<div class="row justify-content-center">
<button type="button" class="btn btn-success" @onclick="InsertImage">Insert Image</button>
@if (_filemanagervisible)
{
@((MarkupString)"&nbsp;&nbsp;")
<button type="button" class="btn btn-secondary" @onclick="CloseFileManager">Close</button>
}
</div>
<div class="row">
<div class ="col">
<div @ref="@_toolBar">
@ToolbarContent
</div>
<div @ref="@_editorElement">
</div>
<div class="row" style="margin-bottom: 50px;">
<div class="col">
<TabStrip>
<TabPanel Name="Rich" Heading="Rich Text Editor">
@if (_filemanagervisible)
{
<FileManager @ref="_fileManager" Filter="@Constants.ImageFiles" />
@((MarkupString)_message)
<br />
}
<div class="row justify-content-center" style="margin-bottom: 20px;">
<button type="button" class="btn btn-secondary" @onclick="RefreshRichText">Synchronize Content</button>&nbsp;&nbsp;
<button type="button" class="btn btn-primary" @onclick="InsertImage">Insert Image</button>
@if (_filemanagervisible)
{
@((MarkupString)"&nbsp;&nbsp;")
<button type="button" class="btn btn-secondary" @onclick="CloseFileManager">Close</button>
}
</div>
<div class="row">
<div class="col">
<div @ref="@_toolBar">
@if (ToolbarContent != null)
{
@ToolbarContent
}
else
{
<select class="ql-header">
<option selected=""></option>
<option value="1"></option>
<option value="2"></option>
<option value="3"></option>
<option value="4"></option>
<option value="5"></option>
</select>
<span class="ql-formats">
<button class="ql-bold"></button>
<button class="ql-italic"></button>
<button class="ql-underline"></button>
<button class="ql-strike"></button>
</span>
<span class="ql-formats">
<select class="ql-color"></select>
<select class="ql-background"></select>
</span>
<span class="ql-formats">
<button class="ql-list" value="ordered"></button>
<button class="ql-list" value="bullet"></button>
</span>
<span class="ql-formats">
<button class="ql-link"></button>
</span>
}
</div>
<div @ref="@_editorElement">
</div>
</div>
</div>
</TabPanel>
<TabPanel Name="Raw" Heading="Raw HTML Editor">
<div class="row justify-content-center" style="margin-bottom: 20px;">
<button type="button" class="btn btn-secondary" @onclick="RefreshRawHtml">Synchronize Content</button>
</div>
@if (ReadOnly)
{
<textarea class="form-control" placeholder="@Placeholder" @bind="@_content" rows="10" readonly></textarea>
}
else
{
<textarea class="form-control" placeholder="@Placeholder" @bind="@_content" rows="10"></textarea>
}
</TabPanel>
</TabStrip>
</div>
</div>
@ -31,10 +85,12 @@
private ElementReference _toolBar;
private bool _filemanagervisible = false;
private FileManager _fileManager;
private string _content = string.Empty;
private string _original = string.Empty;
private string _message = string.Empty;
[Parameter]
public RenderFragment ToolbarContent { get; set; }
public string Content { get; set; }
[Parameter]
public bool ReadOnly { get; set; } = false;
@ -42,60 +98,78 @@
[Parameter]
public string Placeholder { get; set; } = "Enter Your Content...";
// parameters only applicable to rich text editor
[Parameter]
public RenderFragment ToolbarContent { get; set; }
[Parameter]
public string Theme { get; set; } = "snow";
[Parameter]
public string DebugLevel { get; set; } = "info";
protected override void OnInitialized()
{
_content = Content; // raw HTML
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await RichTextEditorInterop.CreateEditor(
JsRuntime,
var interop = new RichTextEditorInterop(JsRuntime);
await interop.CreateEditor(
_editorElement,
_toolBar,
ReadOnly,
Placeholder,
Theme,
DebugLevel);
await interop.LoadEditorContent(_editorElement, Content);
// preserve a copy of the rich text content ( Quill sanitizes content so we need to retrieve it from the editor )
_original = await interop.GetHtml(_editorElement);
}
}
public async Task<string> GetText()
public void CloseFileManager()
{
return await RichTextEditorInterop.GetText(
JsRuntime,
_editorElement);
_filemanagervisible = false;
_message = string.Empty;
StateHasChanged();
}
public async Task RefreshRichText()
{
var interop = new RichTextEditorInterop(JsRuntime);
await interop.LoadEditorContent(_editorElement, _content);
}
public async Task RefreshRawHtml()
{
var interop = new RichTextEditorInterop(JsRuntime);
_content = await interop.GetHtml(_editorElement);
StateHasChanged();
}
public async Task<string> GetHtml()
{
return await RichTextEditorInterop.GetHtml(
JsRuntime,
_editorElement);
}
// get rich text content
var interop = new RichTextEditorInterop(JsRuntime);
string content = await interop.GetHtml(_editorElement);
public async Task<string> GetContent()
{
return await RichTextEditorInterop.GetContent(
JsRuntime,
_editorElement);
}
public async Task LoadContent(string content)
{
await RichTextEditorInterop.LoadEditorContent(
JsRuntime,
_editorElement, content);
}
public async Task EnableEditor(bool mode)
{
await RichTextEditorInterop.EnableEditor(
JsRuntime,
_editorElement, mode);
if (_original != content)
{
// rich text content has changed - return it
return content;
}
else
{
// return raw html content
return _content;
}
}
public async Task InsertImage()
@ -105,9 +179,8 @@
var fileid = _fileManager.GetFileId();
if (fileid != -1)
{
await RichTextEditorInterop.InsertImage(
JsRuntime,
_editorElement, ContentUrl(fileid));
var interop = new RichTextEditorInterop(JsRuntime);
await interop.InsertImage(_editorElement, ContentUrl(fileid));
_filemanagervisible = false;
_message = string.Empty;
}
@ -121,16 +194,25 @@
_filemanagervisible = true;
_message = string.Empty;
}
StateHasChanged();
}
public void CloseFileManager()
// other rich text editor methods which can be used by developers
public async Task<string> GetText()
{
_filemanagervisible = false;
_message = string.Empty;
StateHasChanged();
var interop = new RichTextEditorInterop(JsRuntime);
return await interop.GetText(_editorElement);
}
public async Task<string> GetContent()
{
var interop = new RichTextEditorInterop(JsRuntime);
return await interop.GetContent(_editorElement);
}
public async Task EnableEditor(bool mode)
{
var interop = new RichTextEditorInterop(JsRuntime);
await interop.EnableEditor(_editorElement, mode);
}
}

View File

@ -0,0 +1,124 @@
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using System.Threading.Tasks;
namespace Oqtane.Modules.Controls
{
public class RichTextEditorInterop
{
private readonly IJSRuntime _jsRuntime;
public RichTextEditorInterop(IJSRuntime jsRuntime)
{
_jsRuntime = jsRuntime;
}
public Task CreateEditor(
ElementReference quillElement,
ElementReference toolbar,
bool readOnly,
string placeholder,
string theme,
string debugLevel)
{
try
{
_jsRuntime.InvokeAsync<object>(
"interop.createQuill",
quillElement, toolbar, readOnly,
placeholder, theme, debugLevel);
return Task.CompletedTask;
}
catch
{
return Task.CompletedTask;
}
}
public ValueTask<string> GetText(ElementReference quillElement)
{
try
{
return _jsRuntime.InvokeAsync<string>(
"interop.getQuillText",
quillElement);
}
catch
{
return new ValueTask<string>(Task.FromResult(string.Empty));
}
}
public ValueTask<string> GetHtml(ElementReference quillElement)
{
try
{
return _jsRuntime.InvokeAsync<string>(
"interop.getQuillHTML",
quillElement);
}
catch
{
return new ValueTask<string>(Task.FromResult(string.Empty));
}
}
public ValueTask<string> GetContent(ElementReference quillElement)
{
try
{
return _jsRuntime.InvokeAsync<string>(
"interop.getQuillContent",
quillElement);
}
catch
{
return new ValueTask<string>(Task.FromResult(string.Empty));
}
}
public Task LoadEditorContent(ElementReference quillElement, string content)
{
try
{
_jsRuntime.InvokeAsync<object>(
"interop.loadQuillContent",
quillElement, content);
return Task.CompletedTask;
}
catch
{
return Task.CompletedTask;
}
}
public Task EnableEditor(ElementReference quillElement, bool mode)
{
try
{
_jsRuntime.InvokeAsync<object>(
"interop.enableQuillEditor", quillElement, mode);
return Task.CompletedTask;
}
catch
{
return Task.CompletedTask;
}
}
public Task InsertImage(ElementReference quillElement, string imageUrl)
{
try
{
_jsRuntime.InvokeAsync<object>(
"interop.insertQuillImage",
quillElement, imageUrl);
return Task.CompletedTask;
}
catch
{
return Task.CompletedTask;
}
}
}
}

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleBase
@inherits ModuleBase
@attribute [OqtaneIgnore]
<div class="d-flex">
<div>

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleBase
@inherits ModuleBase
@attribute [OqtaneIgnore]
@if (Name == Parent.ActiveTab)
{

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleBase
@inherits ModuleBase
@attribute [OqtaneIgnore]
<CascadingValue Value="this">
<div class="container-fluid">

View File

@ -3,115 +3,62 @@
@using Oqtane.Modules.Controls
@namespace Oqtane.Modules.HtmlText
@inherits ModuleBase
@inject IHtmlTextService HtmlTextService
@inject NavigationManager NavigationManager
@inject HttpClient http
@inject SiteState sitestate
<div class="row" style="margin-bottom: 50px;">
<div class="col @_visibleText">
<textarea class="form-control" @bind="@content" rows="10"></textarea>
</div>
<div class="col @_visibleRich">
<RichTextEditor @ref="@RichTextEditorHtml">
<ToolbarContent>
<select class="ql-header">
<option selected=""></option>
<option value="1"></option>
<option value="2"></option>
<option value="3"></option>
<option value="4"></option>
<option value="5"></option>
</select>
<span class="ql-formats">
<button class="ql-bold"></button>
<button class="ql-italic"></button>
<button class="ql-underline"></button>
<button class="ql-strike"></button>
</span>
<span class="ql-formats">
<select class="ql-color"></select>
<select class="ql-background"></select>
</span>
<span class="ql-formats">
<button class="ql-list" value="ordered"></button>
<button class="ql-list" value="bullet"></button>
</span>
<span class="ql-formats">
<button class="ql-link"></button>
</span>
</ToolbarContent>
</RichTextEditor>
</div>
</div>
<div class="row">
<div class="col">
@if (!RichTextEditorMode)
{
<button type="button" class="btn btn-secondary" @onclick="RichTextEditor">Rich Text Editor</button>
}
else
{
<button type="button" class="btn btn-secondary" @onclick="RawHtmlEditor">Raw HTML Editor</button>
}
<button type="button" class="btn btn-success" @onclick="SaveContent">Save</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">Cancel</NavLink>
</div>
</div>
<div class="row">
<div class="col">
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
</div>
</div>
@if (_content != null)
{
<RichTextEditor Content="@_content" @ref="@RichTextEditorHtml"></RichTextEditor>
<button type="button" class="btn btn-success" @onclick="SaveContent">Save</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">Cancel</NavLink>
@if (!string.IsNullOrEmpty(_content))
{
<br />
<br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
}
}
@code {
private string _visibleText = "d-none";
private string _visibleRich;
private bool _richTextEditorMode;
private RichTextEditor RichTextEditorHtml;
private string content;
private string createdby;
private DateTime createdon;
private string modifiedby;
private DateTime modifiedon;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Title => "Edit Html/Text";
public bool RichTextEditorMode
public override List<Resource> Resources => new List<Resource>()
{
get => _richTextEditorMode;
set
{
_richTextEditorMode = value;
if (_richTextEditorMode)
{
_visibleText = "d-none";
_visibleRich = string.Empty;
}
else
{
_visibleText = string.Empty;
_visibleRich = "d-none";
}
}
}
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" },
// the following resources should be declared in the RichTextEditor component however the framework currently only supports resource management for modules and themes
new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill1.3.6.bubble.css" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill1.3.6.snow.css" },
new Resource { ResourceType = ResourceType.Script, Url = "js/quill1.3.6.min.js" },
new Resource { ResourceType = ResourceType.Script, Url = "js/quill-blot-formatter.min.js" },
new Resource { ResourceType = ResourceType.Script, Url = "js/quill-interop.js" }
};
protected override async Task OnAfterRenderAsync(bool firstRender)
private RichTextEditor RichTextEditorHtml;
private string _content = null;
private string _createdby;
private DateTime _createdon;
private string _modifiedby;
private DateTime _modifiedon;
protected override async Task OnInitializedAsync()
{
try
{
if (firstRender)
var htmltext = await HtmlTextService.GetHtmlTextAsync(ModuleState.ModuleId);
if (htmltext != null)
{
if (content == null)
{
RichTextEditorMode = true;
await LoadText();
}
_content = htmltext.Content;
_content = _content.Replace(Constants.ContentUrl, "/" + PageState.Alias.AliasId.ToString() + Constants.ContentUrl);
_createdby = htmltext.CreatedBy;
_createdon = htmltext.CreatedOn;
_modifiedby = htmltext.ModifiedBy;
_modifiedon = htmltext.ModifiedOn;
}
else
{
_content = string.Empty;
}
}
catch (Exception ex)
@ -121,66 +68,27 @@
}
}
private async Task LoadText()
{
var htmltextservice = new HtmlTextService(http, sitestate);
var htmltext = await htmltextservice.GetHtmlTextAsync(ModuleState.ModuleId);
if (htmltext != null)
{
content = htmltext.Content;
createdby = htmltext.CreatedBy;
createdon = htmltext.CreatedOn;
modifiedby = htmltext.ModifiedBy;
modifiedon = htmltext.ModifiedOn;
if (RichTextEditorMode)
{
await RichTextEditorHtml.LoadContent(content);
StateHasChanged();
}
}
}
private async Task RichTextEditor()
{
RichTextEditorMode = true;
await RichTextEditorHtml.LoadContent(content);
StateHasChanged();
}
private async Task RawHtmlEditor()
{
content = await this.RichTextEditorHtml.GetHtml();
RichTextEditorMode = false;
StateHasChanged();
}
private async Task SaveContent()
{
if (RichTextEditorMode)
{
content = await RichTextEditorHtml.GetHtml();
}
content = content.Replace(((PageState.Alias.Path == string.Empty) ? "/~" : PageState.Alias.Path) + Constants.ContentUrl, Constants.ContentUrl);
string content = await RichTextEditorHtml.GetHtml();
content = content.Replace("/" + PageState.Alias.AliasId.ToString() + Constants.ContentUrl, Constants.ContentUrl);
try
{
var htmltextservice = new HtmlTextService(http, sitestate);
var htmltext = await htmltextservice.GetHtmlTextAsync(ModuleState.ModuleId);
var htmltext = await HtmlTextService.GetHtmlTextAsync(ModuleState.ModuleId);
if (htmltext != null)
{
htmltext.Content = content;
await htmltextservice.UpdateHtmlTextAsync(htmltext);
await HtmlTextService.UpdateHtmlTextAsync(htmltext);
}
else
{
htmltext = new HtmlTextInfo();
htmltext.ModuleId = ModuleState.ModuleId;
htmltext.Content = content;
await htmltextservice.AddHtmlTextAsync(htmltext);
await HtmlTextService.AddHtmlTextAsync(htmltext);
}
await logger.LogInformation("Html/Text Content Saved {HtmlText}", htmltext);
NavigationManager.NavigateTo(NavigateUrl());
}
@ -190,5 +98,4 @@
AddModuleMessage("Error Saving Content", MessageType.Error);
}
}
}

View File

@ -1,36 +1,32 @@
@using Oqtane.Modules.HtmlText.Services
@using Oqtane.Modules.HtmlText.Models
@namespace Oqtane.Modules.HtmlText
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject HttpClient http
@inject SiteState sitestate
@inject IHtmlTextService HtmlTextService
@((MarkupString)content)
@if (PageState.EditMode)
{
<br />
}
<ActionLink Action="Edit" />
@if (PageState.EditMode)
{
<br /><br />
<br /><ActionLink Action="Edit" /><br /><br />
}
@code {
public override List<Resource> Resources => new List<Resource>()
{
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }
};
private string content = "";
protected override async Task OnParametersSetAsync()
{
try
{
var htmltextservice = new HtmlTextService(http, sitestate);
var htmltext = await htmltextservice.GetHtmlTextAsync(ModuleState.ModuleId);
var htmltext = await HtmlTextService.GetHtmlTextAsync(ModuleState.ModuleId);
if (htmltext != null)
{
content = htmltext.Content;
content = content.Replace(Constants.ContentUrl, ((PageState.Alias.Path == "") ? "/~" : PageState.Alias.Path) + Constants.ContentUrl);
content = content.Replace(Constants.ContentUrl, "/" + PageState.Alias.AliasId.ToString() + Constants.ContentUrl);
}
}
catch (Exception ex)

View File

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

View File

@ -6,10 +6,11 @@ using Oqtane.Services;
using System;
using Oqtane.Enums;
using Oqtane.UI;
using System.Collections.Generic;
namespace Oqtane.Modules
{
public class ModuleBase : ComponentBase, IModuleControl
public abstract class ModuleBase : ComponentBase, IModuleControl
{
private Logger _logger;
@ -37,6 +38,9 @@ namespace Oqtane.Modules
public virtual bool UseAdminContainer { get { return true; } }
public virtual List<Resource> Resources { get; set; }
// path method
public string ModulePath()

View File

@ -6,7 +6,7 @@
<LangVersion>7.3</LangVersion>
<RazorLangVersion>3.0</RazorLangVersion>
<Configurations>Debug;Release</Configurations>
<Version>0.9.0</Version>
<Version>1.0.0</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -27,10 +27,11 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="3.2.0-rc1.20223.4" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Build" Version="3.2.0-rc1.20223.4" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="3.1.2" />
<PackageReference Include="System.Net.Http.Json" Version="3.2.0-rc1.20217.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="3.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Build" Version="3.2.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="3.2.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="3.1.4" />
<PackageReference Include="System.Net.Http.Json" Version="3.2.0" />
</ItemGroup>
<ItemGroup>

View File

@ -4,12 +4,16 @@ using System.Threading.Tasks;
using Oqtane.Services;
using System.Reflection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using Oqtane.Modules;
using Oqtane.Shared;
using Oqtane.Providers;
using Microsoft.AspNetCore.Components.Authorization;
using System.IO.Compression;
using System.IO;
namespace Oqtane.Client
{
@ -19,10 +23,9 @@ namespace Oqtane.Client
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
HttpClient httpClient = new HttpClient {BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)};
builder.Services.AddSingleton(
new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }
);
builder.Services.AddSingleton(httpClient);
builder.Services.AddOptions();
// register auth services
@ -57,14 +60,16 @@ namespace Oqtane.Client
builder.Services.AddScoped<ISqlService, SqlService>();
builder.Services.AddScoped<ISystemService, SystemService>();
await LoadClientAssemblies(httpClient);
// dynamically register module contexts and repository services
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies)
{
Type[] implementationtypes = assembly.GetTypes()
.Where(item => item.GetInterfaces().Contains(typeof(IService)))
.ToArray();
foreach (Type implementationtype in implementationtypes)
var implementationTypes = assembly.GetTypes()
.Where(item => item.GetInterfaces().Contains(typeof(IService)));
foreach (Type implementationtype in implementationTypes)
{
Type servicetype = Type.GetType(implementationtype.AssemblyQualifiedName.Replace(implementationtype.Name, "I" + implementationtype.Name));
if (servicetype != null)
@ -76,9 +81,62 @@ namespace Oqtane.Client
builder.Services.AddScoped(implementationtype, implementationtype); // no interface defined for service
}
}
assembly.GetInstances<IClientStartup>()
.ToList()
.ForEach(x => x.ConfigureServices(builder.Services));
}
await builder.Build().RunAsync();
}
private static async Task LoadClientAssemblies(HttpClient http)
{
// get list of loaded assemblies on the client
var assemblies = AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetName().Name).ToList();
// get assemblies from server and load into client app domain
var zip = await http.GetByteArrayAsync($"/~/api/Installation/load");
// asemblies and debug symbols are packaged in a zip file
using (ZipArchive archive = new ZipArchive(new MemoryStream(zip)))
{
Dictionary<string, byte[]> dlls = new Dictionary<string, byte[]>();
Dictionary<string, byte[]> pdbs = new Dictionary<string, byte[]>();
foreach (ZipArchiveEntry entry in archive.Entries)
{
if (!assemblies.Contains(Path.GetFileNameWithoutExtension(entry.Name)))
{
using (var memoryStream = new MemoryStream())
{
entry.Open().CopyTo(memoryStream);
byte[] file = memoryStream.ToArray();
switch (Path.GetExtension(entry.Name))
{
case ".dll":
dlls.Add(entry.Name, file);
break;
case ".pdb":
pdbs.Add(entry.Name, file);
break;
}
}
}
}
foreach (var item in dlls)
{
if (pdbs.ContainsKey(item.Key))
{
Assembly.Load(item.Value, pdbs[item.Key]);
}
else
{
Assembly.Load(item.Value);
}
}
}
}
}
}

View File

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

View File

@ -12,7 +12,6 @@ namespace Oqtane.Services
Task UpdateModuleDefinitionAsync(ModuleDefinition moduleDefinition);
Task InstallModuleDefinitionsAsync();
Task DeleteModuleDefinitionAsync(int moduleDefinitionId, int siteId);
Task LoadModuleDefinitionsAsync(int siteId, Runtime runtime);
Task CreateModuleDefinitionAsync(ModuleDefinition moduleDefinition, int moduleId);
}
}

View File

@ -3,14 +3,20 @@ using System.Threading.Tasks;
using System.Net.Http;
using System.Linq;
using System.Collections.Generic;
using Oqtane.Shared;
namespace Oqtane.Services
{
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()
{

View File

@ -3,15 +3,21 @@ using System.Threading.Tasks;
using System.Net.Http;
using System.Linq;
using System.Collections.Generic;
using Oqtane.Shared;
namespace Oqtane.Services
{
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()
{
List<Job> jobs = await GetJsonAsync<List<Job>>(Apiurl);

View File

@ -49,46 +49,9 @@ namespace Oqtane.Services
await DeleteAsync($"{Apiurl}/{moduleDefinitionId}?siteid={siteId}");
}
public async Task LoadModuleDefinitionsAsync(int siteId, Runtime runtime)
{
// get list of modules from the server
List<ModuleDefinition> moduledefinitions = await GetModuleDefinitionsAsync(siteId);
// download assemblies to browser when running client-side Blazor
if (runtime == Runtime.WebAssembly)
{
// get list of loaded assemblies on the client ( in the client-side hosting module the browser client has its own app domain )
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (ModuleDefinition moduledefinition in moduledefinitions)
{
// if a module has dependencies, check if they are loaded
if (moduledefinition.Dependencies != "")
{
foreach (string dependency in moduledefinition.Dependencies.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
{
string assemblyname = dependency.Replace(".dll", "");
if (assemblies.Where(item => item.FullName.StartsWith(assemblyname + ",")).FirstOrDefault() == null)
{
// download assembly from server and load
var bytes = await _http.GetByteArrayAsync($"{Apiurl}/load/{assemblyname}.dll");
Assembly.Load(bytes);
}
}
}
// check if the module assembly is loaded
if (assemblies.Where(item => item.FullName.StartsWith(moduledefinition.AssemblyName + ",")).FirstOrDefault() == null)
{
// download assembly from server and load
var bytes = await _http.GetByteArrayAsync($"{Apiurl}/load/{moduledefinition.AssemblyName}.dll");
Assembly.Load(bytes);
}
}
}
}
public async Task CreateModuleDefinitionAsync(ModuleDefinition moduleDefinition, int moduleId)
{
await PostJsonAsync($"{Apiurl}?moduleid={moduleId.ToString()}", moduleDefinition);
await PostJsonAsync($"{Apiurl}?moduleid={moduleId}", moduleDefinition);
}
}
}

View File

@ -82,7 +82,6 @@ namespace Oqtane.Services
var result = await response.Content.ReadFromJsonAsync<TResult>();
return result;
}
return default;
}
@ -121,6 +120,8 @@ namespace Oqtane.Services
if (response.StatusCode != HttpStatusCode.NoContent && response.StatusCode != HttpStatusCode.NotFound)
{
//TODO: Log errors here
Console.WriteLine($"Request: {response.RequestMessage.RequestUri}");
Console.WriteLine($"Response status: {response.StatusCode} {response.ReasonPhrase}");
}
@ -134,13 +135,13 @@ namespace Oqtane.Services
//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)
{
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)
{
string apiurl = "/";

View File

@ -1,4 +1,5 @@
using Oqtane.Models;
using System;
using Oqtane.Models;
using System.Threading.Tasks;
using System.Net.Http;
using System.Linq;
@ -103,10 +104,10 @@ namespace Oqtane.Services
public async Task UpdateSettingsAsync(Dictionary<string, string> settings, string entityName, int entityId)
{
var settingsList = await GetJsonAsync<List<Setting>>($"{Apiurl}?entityname={entityName}&entityid={entityId}");
foreach (KeyValuePair<string, string> kvp in settings)
{
Setting setting = settingsList.FirstOrDefault(item => item.SettingName == kvp.Key);
Setting setting = settingsList.FirstOrDefault(item => item.SettingName.Equals(kvp.Key,StringComparison.OrdinalIgnoreCase));
if (setting == null)
{
setting = new Setting();

View File

@ -1,4 +1,5 @@
using Oqtane.Models;
using Oqtane.Shared;
using System.Net.Http;
using System.Threading.Tasks;
@ -6,9 +7,14 @@ namespace Oqtane.Services
{
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)
{

View File

@ -3,14 +3,20 @@ using System.Net.Http;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
using Oqtane.Shared;
namespace Oqtane.Services
{
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()
{

View File

@ -23,33 +23,6 @@ namespace Oqtane.Services
public async Task<List<Theme>> GetThemesAsync()
{
List<Theme> themes = await GetJsonAsync<List<Theme>>(Apiurl);
// get list of loaded assemblies
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Theme theme in themes)
{
if (theme.Dependencies != "")
{
foreach (string dependency in theme.Dependencies.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
{
string assemblyname = dependency.Replace(".dll", "");
if (assemblies.Where(item => item.FullName.StartsWith(assemblyname + ",")).FirstOrDefault() == null)
{
// download assembly from server and load
var bytes = await _http.GetByteArrayAsync($"{Apiurl}/load/{assemblyname}.dll");
Assembly.Load(bytes);
}
}
}
if (assemblies.Where(item => item.FullName.StartsWith(theme.AssemblyName + ",")).FirstOrDefault() == null)
{
// download assembly from server and load
var bytes = await _http.GetByteArrayAsync($"{Apiurl}/load/{theme.AssemblyName}.dll");
Assembly.Load(bytes);
}
}
return themes.OrderBy(item => item.Name).ToList();
}

View File

@ -2,7 +2,9 @@
@inherits ContainerBase
<div class="container">
<div class="row px-4">
<ModuleActions /><h2><ModuleTitle /></h2>
<div class="d-flex flex-nowrap">
<ModuleActions /><h2><ModuleTitle /></h2>
</div>
<hr class="app-rule" />
</div>
<div class="row px-4">

View File

@ -7,15 +7,7 @@
<div class="sidebar">
<nav class="navbar">
<Logo />
<button class="navbar-toggler" aria-expanded="false" aria-controls="navbarSupportedContent"
aria-label="Toggle navigation" type="button" data-toggle="collapse"
data-target="#navbarSupportedContent">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<Menu Orientation="Vertical" />
</div>
<Logo /><Menu Orientation="Vertical" />
</nav>
</div>
@ -36,9 +28,8 @@
@code {
public override string Panes => "Content";
protected override async Task OnParametersSetAsync()
public override List<Resource> Resources => new List<Resource>()
{
await IncludeCSS("Theme.css");
}
new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "Theme.css" }
};
}

View File

@ -17,7 +17,6 @@ namespace Oqtane.Themes
[CascadingParameter]
protected Module ModuleState { get; set; }
public virtual string Name { get; set; }
public string ThemePath()
{

View File

@ -1,26 +1,33 @@
@namespace Oqtane.Themes.Controls
@inherits ThemeControlBase
@inherits ThemeControlBase
@attribute [OqtaneIgnore]
@if (BreadCrumbPages.Any())
{
<ol class="breadcrumb">
@foreach (var p in BreadCrumbPages)
{
<li class="breadcrumb-item @ActiveClass(p)">
<a href="@NavigateUrl(p.Path)">@p.Name</a>
</li>
}
</ol>
<span class="app-breadcrumbs">
<ol class="breadcrumb">
@foreach (var p in BreadCrumbPages)
{
if (p.PageId == PageState.Page.PageId)
{
<li class="breadcrumb-item active">
<a href="@NavigateUrl(p.Path)">@p.Name</a>
</li>
}
else
{
<li class="breadcrumb-item">
<a href="@NavigateUrl(p.Path)">@p.Name</a>
</li>
}
}
</ol>
</span>
}
@code {
protected IEnumerable<Page> BreadCrumbPages => GetBreadCrumbPages().Reverse().ToList();
protected string ActiveClass(Page page)
{
return (page.PageId == PageState.Page.PageId) ? " active" : string.Empty;
}
private IEnumerable<Page> GetBreadCrumbPages()
{

View File

@ -1,6 +1,6 @@
@namespace Oqtane.Themes.Controls
@using Oqtane.Enums
@inherits ThemeControlBase
@inherits ThemeControlBase
@attribute [OqtaneIgnore]
@inject NavigationManager NavigationManager
@inject IUserService UserService
@inject IModuleDefinitionService ModuleDefinitionService
@ -16,7 +16,7 @@
<div class="@CardClass">
<div class="@HeaderClass">
Control Panel
<span class="font-weight-bold">Control Panel</span>
<button type="button" class="close" @onclick="HideControlPanel" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
@ -149,7 +149,7 @@
<label for="Pane" class="control-label">Pane: </label>
<select class="form-control" @bind="@_pane">
<option value="">&lt;Select Pane&gt;</option>
@foreach (string pane in PageState.Page.Panes.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
@foreach (string pane in PageState.Page.Panes)
{
<option value="@pane">@pane Pane</option>
}
@ -245,17 +245,17 @@
{
if (string.IsNullOrEmpty(ButtonClass))
{
ButtonClass = "btn-outline-primary";
ButtonClass = "btn-outline-secondary";
}
if (string.IsNullOrEmpty(CardClass))
{
CardClass = "card bg-secondary mb-3";
CardClass = "card border-secondary mb-3";
}
if (string.IsNullOrEmpty(HeaderClass))
{
HeaderClass = "card-header text-white";
HeaderClass = "card-header";
}
if (string.IsNullOrEmpty(BodyClass))
@ -275,7 +275,7 @@
}
}
var panes = PageState.Page.Panes.Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries);
var panes = PageState.Page.Panes;
_pane = panes.Count() == 1 ? panes.SingleOrDefault() : "";
var themes = await ThemeService.GetThemesAsync();
_containers = ThemeService.GetContainerTypes(themes);
@ -361,6 +361,7 @@
module.SiteId = PageState.Site.SiteId;
module.PageId = PageState.Page.PageId;
module.ModuleDefinitionName = _moduleDefinitionName;
module.AllPages = false;
module.Permissions = PageState.Page.Permissions;
module = await ModuleService.AddModuleAsync(module);
_moduleId = module.ModuleId.ToString();

View File

@ -1,14 +1,17 @@
@namespace Oqtane.Themes.Controls
@inherits LoginBase
@inherits LoginBase
@attribute [OqtaneIgnore]
<AuthorizeView>
<Authorizing>
<text>...</text>
</Authorizing>
<Authorized>
<button type="button" class="btn btn-primary" @onclick="LogoutUser">Logout</button>
</Authorized>
<NotAuthorized>
<button type="button" class="btn btn-primary" @onclick="LoginUser">Login</button>
</NotAuthorized>
</AuthorizeView>
<span class="app-login">
<AuthorizeView>
<Authorizing>
<text>...</text>
</Authorizing>
<Authorized>
<button type="button" class="btn btn-primary" @onclick="LogoutUser">Logout</button>
</Authorized>
<NotAuthorized>
<button type="button" class="btn btn-primary" @onclick="LoginUser">Login</button>
</NotAuthorized>
</AuthorizeView>
</span>

View File

@ -1,21 +1,13 @@
@namespace Oqtane.Themes.Controls
@inherits ThemeControlBase
@inject NavigationManager NavigationManager
@inherits ThemeControlBase
@attribute [OqtaneIgnore]
@if (PageState.Site.LogoFileId != null)
{
<a href="@Href">
<img class="img-fluid" src="@ContentUrl(PageState.Site.LogoFileId.Value)" alt="@PageState.Site.Name"/>
</a>
<span class="app-logo">
<a href="@PageState.Alias.Path">
<img class="img-fluid" src="@ContentUrl(PageState.Site.LogoFileId.Value)" alt="@PageState.Site.Name" />
</a>
</span>
}
@code {
string Href
{
get
{
var uri = new Uri(NavigationManager.Uri);
return $"{uri.Scheme}://{uri.Authority}";
}
}
}

View File

@ -1,12 +1,14 @@
@namespace Oqtane.Themes.Controls
@inherits MenuBase
@attribute [OqtaneIgnore]
@if (MenuPages.Any())
{
<div class="app-menu">
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#Menu" aria-controls="Menu" aria-expanded="false" aria-label="Toggle navigation">
<span class="app-menu-toggler">
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#Menu" aria-controls="Menu" aria-expanded="false" aria-label="Toggle Navigation">
<span class="navbar-toggler-icon"></span>
</button>
</span>
<div class="app-menu">
<div class="collapse navbar-collapse" id="Menu">
<ul class="navbar-nav mr-auto">
@foreach (var p in MenuPages)

View File

@ -1,27 +1,34 @@
@namespace Oqtane.Themes.Controls
@inherits MenuBase
@attribute [OqtaneIgnore]
@if (MenuPages.Any())
{
<span class="app-menu-toggler">
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#Menu" aria-controls="Menu" aria-expanded="false" aria-label="Toggle Navigation">
<span class="navbar-toggler-icon"></span>
</button>
</span>
<div class="app-menu">
<ul class="nav flex-column">
@foreach (var p in MenuPages)
{
<li class="nav-item px-3">
<a href="@GetUrl(p)" class="nav-link" style="padding-left:@((p.Level + 1) * 15)px !important;" target="@GetTarget(p)">
<div class="collapse navbar-collapse" id="Menu">
<ul class="nav flex-column">
@foreach (var p in MenuPages)
{
<li class="nav-item px-3">
<a href="@GetUrl(p)" class="nav-link" style="padding-left:@((p.Level + 1) * 15)px !important;" target="@GetTarget(p)">
@if (p.HasChildren)
{
<i class="oi oi-chevron-right"></i>
}
@if (p.Icon != string.Empty)
{
<span class="oi oi-@p.Icon" aria-hidden="true"></span>
}
@p.Name
</a>
</li>
}
</ul>
@if (p.HasChildren)
{
<i class="oi oi-chevron-right"></i>
}
@if (p.Icon != string.Empty)
{
<span class="oi oi-@p.Icon" aria-hidden="true"></span>
}
@p.Name
</a>
</li>
}
</ul>
</div>
</div>
}

View File

@ -1,20 +1,23 @@
@namespace Oqtane.Themes.Controls
@inherits ModuleActionsBase
@inherits ModuleActionsBase
@attribute [OqtaneIgnore]
@if (PageState.EditMode && !PageState.Page.EditMode && UserSecurity.IsAuthorized(PageState.User,PermissionNames.Edit, ModuleState.Permissions))
{
<a class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"></a>
<div class="dropdown-menu" x-placement="bottom-start" style="position: absolute; will-change: transform; top: 0px; left: 0px; transform: translate3d(0px, 37px, 0px);">
@foreach (var action in Actions)
{
if (string.IsNullOrEmpty(action.Name))
{
<div class="dropdown-divider"></div>
}
else
{
<a class="dropdown-item" @onclick="(async () => await ModuleAction(action))">@action.Name</a>
}
}
<div class="app-moduleactions">
<a class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"></a>
<div class="dropdown-menu" x-placement="bottom-start" style="position: absolute; will-change: transform; top: 0px; left: 0px; transform: translate3d(0px, 37px, 0px);">
@foreach (var action in Actions)
{
if (string.IsNullOrEmpty(action.Name))
{
<div class="dropdown-divider"></div>
}
else
{
<a class="dropdown-item" @onclick="(async () => await ModuleAction(action))">@action.Name</a>
}
}
</div>
</div>
}
}

View File

@ -60,7 +60,7 @@ namespace Oqtane.Themes.Controls
actionList.Add(new ActionViewModel {Name = "Move To Bottom", Action = async (s, m) => await MoveBottom(s, m)});
}
foreach (string pane in PageState.Page.Panes.Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries))
foreach (string pane in PageState.Page.Panes)
{
if (pane != ModuleState.Pane)
{

View File

@ -1,7 +1,10 @@
@namespace Oqtane.Themes.Controls
@inherits ContainerBase
@inherits ContainerBase
@attribute [OqtaneIgnore]
@((MarkupString)title)
<span class="app-moduletitle">
@((MarkupString)title)
</span>
@code {
private string title = "";

View File

@ -1,22 +1,24 @@
@namespace Oqtane.Themes.Controls
@inherits ThemeControlBase
@inherits ThemeControlBase
@attribute [OqtaneIgnore]
@inject NavigationManager NavigationManager
<AuthorizeView>
<Authorizing>
<text>...</text>
</Authorizing>
<Authorized>
<button type="button" class="btn btn-primary" @onclick="UpdateProfile">@context.User.Identity.Name</button>
</Authorized>
<NotAuthorized>
@if (PageState.Site.AllowRegistration)
{
<button type="button" class="btn btn-primary" @onclick="RegisterUser">Register</button>
}
</NotAuthorized>
</AuthorizeView>
<span class="app-profile">
<AuthorizeView>
<Authorizing>
<text>...</text>
</Authorizing>
<Authorized>
<button type="button" class="btn btn-primary" @onclick="UpdateProfile">@context.User.Identity.Name</button>
</Authorized>
<NotAuthorized>
@if (PageState.Site.AllowRegistration)
{
<button type="button" class="btn btn-primary" @onclick="RegisterUser">Register</button>
}
</NotAuthorized>
</AuthorizeView>
</span>
@code {

View File

@ -2,6 +2,5 @@
{
public interface IContainerControl
{
string Name { get; }
}
}

View File

@ -2,7 +2,9 @@
@inherits ContainerBase
<div class="container">
<div class="row px-4">
<ModuleActions /><h2><ModuleTitle /></h2>
<div class="d-flex flex-nowrap">
<ModuleActions /><h2><ModuleTitle /></h2>
</div>
<hr class="app-rule" />
</div>
<div class="row px-4">

View File

@ -3,9 +3,12 @@
<main role="main">
<nav class="navbar navbar-expand-md navbar-dark bg-primary fixed-top">
<Logo /><Menu Orientation="Horizontal" /><div class="ml-md-auto"><UserProfile /> <Login /> <ControlPanel ButtonClass="btn-outline-secondary" CardClass="bg-light" /></div>
<Logo /><Menu Orientation="Horizontal" />
<div class="controls ml-md-auto">
<div class="controls-group"><UserProfile /> <Login /> <ControlPanel /></div>
</div>
</nav>
<div class="container">
<div class="content container">
<PaneLayout />
<div class="row px-4">
<Pane Name="Admin" />
@ -16,8 +19,12 @@
@code {
public override string Panes => string.Empty;
protected override async Task OnParametersSetAsync()
public override List<Resource> Resources => new List<Resource>()
{
await IncludeCSS("Theme.css");
}
new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "BootswatchCyborg.css" },
// remote stylesheets can be linked using the format below, however we want the default theme to display properly in local development scenarios where an Internet connection is not available
//new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://stackpath.bootstrapcdn.com/bootswatch/4.4.1/cyborg/bootstrap.min.css", Integrity = "sha384-l7xaoY0cJM4h9xh1RfazbgJVUZvdtyLWPueWNtLAphf/UbBgOVzqbOTogxPwYLHM", CrossOrigin = "anonymous" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "Theme.css" }
};
}

View File

@ -0,0 +1,9 @@
@namespace Oqtane.Themes.OqtaneTheme
@inherits ContainerBase
<div class="container">
@if (PageState.EditMode)
{
<ModuleActions />
}
<ModuleInstance />
</div>

View File

@ -1,7 +1,9 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using Oqtane.Models;
using Oqtane.Shared;
using Oqtane.UI;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Oqtane.Themes
@ -11,24 +13,21 @@ namespace Oqtane.Themes
[Inject]
protected IJSRuntime JSRuntime { get; set; }
// optional interface properties
[CascadingParameter]
protected PageState PageState { get; set; }
public virtual string Panes { get; set; }
public virtual List<Resource> Resources { get; set; }
// path method
public string ThemePath()
{
return "Themes/" + GetType().Namespace + "/";
}
public async Task IncludeCSS(string Url)
{
if (!Url.StartsWith("http"))
{
Url = ThemePath() + Url;
}
var interop = new Interop(JSRuntime);
await interop.IncludeCSS("Theme", Url);
}
// url methods
public string NavigateUrl()
{

View File

@ -7,7 +7,8 @@
<div class="container">
<div class="row">
<div class="mx-auto text-center">
<img src="oqtane.png" />
<img src="oqtane-black.png" />
<div style="font-weight: bold">Version: @Constants.Version</div>
</div>
</div>
<hr class="app-rule" />

View File

@ -1,6 +1,4 @@
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using System;
using Microsoft.JSInterop;
using System.Threading.Tasks;
namespace Oqtane.UI
@ -18,7 +16,7 @@ namespace Oqtane.UI
{
try
{
_jsRuntime.InvokeAsync<string>(
_jsRuntime.InvokeAsync<object>(
"interop.setCookie",
name, value, days);
return Task.CompletedTask;
@ -47,7 +45,7 @@ namespace Oqtane.UI
{
try
{
_jsRuntime.InvokeAsync<string>(
_jsRuntime.InvokeAsync<object>(
"interop.updateTitle",
title);
return Task.CompletedTask;
@ -62,7 +60,7 @@ namespace Oqtane.UI
{
try
{
_jsRuntime.InvokeAsync<string>(
_jsRuntime.InvokeAsync<object>(
"interop.includeMeta",
id, attribute, name, content);
return Task.CompletedTask;
@ -77,7 +75,7 @@ namespace Oqtane.UI
{
try
{
_jsRuntime.InvokeAsync<string>(
_jsRuntime.InvokeAsync<object>(
"interop.includeLink",
id, rel, url, type, integrity, crossorigin);
return Task.CompletedTask;
@ -92,7 +90,7 @@ namespace Oqtane.UI
{
try
{
_jsRuntime.InvokeAsync<string>(
_jsRuntime.InvokeAsync<object>(
"interop.includeScript",
id, src, content, location, integrity, crossorigin);
return Task.CompletedTask;
@ -107,7 +105,7 @@ namespace Oqtane.UI
{
try
{
_jsRuntime.InvokeAsync<string>(
_jsRuntime.InvokeAsync<object>(
"interop.includeLink",
id, "stylesheet", url, "text/css");
return Task.CompletedTask;
@ -118,6 +116,22 @@ namespace Oqtane.UI
}
}
public Task RemoveElementsById(string prefix, string first, string last)
{
try
{
_jsRuntime.InvokeAsync<object>(
"interop.removeElementsById",
prefix, first, last);
return Task.CompletedTask;
}
catch
{
return Task.CompletedTask;
}
}
public ValueTask<string> GetElementByName(string name)
{
try
@ -136,7 +150,7 @@ namespace Oqtane.UI
{
try
{
_jsRuntime.InvokeAsync<string>(
_jsRuntime.InvokeAsync<object>(
"interop.submitForm",
path, fields);
return Task.CompletedTask;
@ -165,7 +179,7 @@ namespace Oqtane.UI
{
try
{
_jsRuntime.InvokeAsync<string>(
_jsRuntime.InvokeAsync<object>(
"interop.uploadFiles",
posturl, folder, id);
return Task.CompletedTask;

View File

@ -32,7 +32,7 @@
}
else
{
_paneadminborder = "";
_paneadminborder = "container";
_panetitle = "";
}

View File

@ -1,80 +0,0 @@
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);
}
}
}

View File

@ -1,4 +1,6 @@
@namespace Oqtane.UI
@using System.Diagnostics.CodeAnalysis
@using System.Runtime.InteropServices
@namespace Oqtane.UI
@inject AuthenticationStateProvider AuthenticationStateProvider
@inject SiteState SiteState
@inject NavigationManager NavigationManager
@ -9,11 +11,7 @@
@inject IPageService PageService
@inject IUserService UserService
@inject IModuleService ModuleService
@inject IModuleDefinitionService ModuleDefinitionService
@inject ILogService LogService
@using System.Diagnostics.CodeAnalysis
@using Oqtane.Enums
@using System.Runtime.InteropServices
@implements IHandleAfterRender
@DynamicComponent
@ -89,7 +87,7 @@
// get path
var path = uri.LocalPath.Substring(1);
// parse querystring
// parse querystring
var querystring = ParseQueryString(uri.Query);
// the reload parameter is used during user login/logout
@ -158,7 +156,6 @@
if (PageState == null || reload >= Reload.Site)
{
await ModuleDefinitionService.LoadModuleDefinitionsAsync(site.SiteId, runtime);
pages = await PageService.GetPagesAsync(site.SiteId);
}
else
@ -182,7 +179,7 @@
// extract admin route elements from path
var segments = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
int result;
// check if path has moduleid and control specification ie. page/moduleid/control/
// check if path has moduleid and action specification ie. pagename/moduleid/action/
if (segments.Length >= 2 && int.TryParse(segments[segments.Length - 2], out result))
{
action = segments[segments.Length - 1];
@ -191,7 +188,7 @@
}
else
{
// check if path has only moduleid specification ie. page/moduleid/
// check if path has moduleid specification ie. pagename/moduleid/
if (segments.Length >= 1 && int.TryParse(segments[segments.Length - 1], out result))
{
moduleid = result;
@ -237,10 +234,25 @@
}
// check if user is authorized to view page
if (UserSecurity.IsAuthorized(user,PermissionNames.View, page.Permissions))
if (UserSecurity.IsAuthorized(user, PermissionNames.View, page.Permissions))
{
page = await ProcessPage(page, site, user);
if (PageState != null && (PageState.ModuleId != moduleid || PageState.Action != action))
{
reload = Reload.Page;
}
if (PageState == null || reload >= Reload.Page)
{
modules = await ModuleService.GetModulesAsync(site.SiteId);
(page, modules) = ProcessModules(page, modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType);
}
else
{
modules = PageState.Modules;
}
_pagestate = new PageState
{
Alias = alias,
@ -248,31 +260,16 @@
Pages = pages,
Page = page,
User = user,
Modules = modules,
Uri = new Uri(_absoluteUri, UriKind.Absolute),
QueryString = querystring,
ModuleId = moduleid,
Action = action,
EditMode = editmode,
LastSyncDate = lastsyncdate,
Runtime = runtime
};
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);
}
}
@ -358,19 +355,30 @@
page.ThemeType = site.DefaultThemeType;
page.LayoutType = site.DefaultLayoutType;
}
Type type;
page.Panes = new List<string>();
page.Resources = new List<Resource>();
string panes = "";
Type themetype = Type.GetType(page.ThemeType);
var themeobject = Activator.CreateInstance(themetype) as IThemeControl;
if (themeobject != null)
{
panes = themeobject.Panes;
page.Resources = ManagePageResources(page.Resources, themeobject.Resources);
}
if (!string.IsNullOrEmpty(page.LayoutType))
{
type = Type.GetType(page.LayoutType);
}
else
{
type = Type.GetType(page.ThemeType);
Type layouttype = Type.GetType(page.LayoutType);
var layoutobject = Activator.CreateInstance(layouttype) as ILayoutControl;
if (layoutobject != null)
{
panes = layoutobject.Panes;
}
}
var property = type.GetProperty("Panes");
page.Panes = (string)property.GetValue(Activator.CreateInstance(type), null);
page.Panes = panes.Replace(";", ",").Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();
}
catch
{
@ -380,12 +388,12 @@
return page;
}
private List<Module> ProcessModules(List<Module> modules, int pageid, int moduleid, string control, string panes, string defaultcontainertype)
private (Page Page, List<Module> Modules) ProcessModules(Page page, List<Module> modules, int moduleid, string action, string defaultcontainertype)
{
var paneindex = new Dictionary<string, int>();
foreach (Module module in modules)
{
if (module.PageId == pageid || module.ModuleId == moduleid)
if (module.PageId == page.PageId || module.ModuleId == moduleid)
{
var typename = string.Empty;
if (module.ModuleDefinition != null)
@ -397,63 +405,72 @@
typename = Constants.ErrorModule;
}
if (module.ModuleId == moduleid && control != "")
if (module.ModuleId == moduleid && action != "")
{
// 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 + "="))
if (route.StartsWith(action + "="))
{
typename = route.Replace(control + "=", "");
typename = route.Replace(action + "=", "");
}
}
}
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);
}
}
module.ModuleType = typename.Replace(Constants.ActionToken, action);
}
else
{
module.ModuleType = typename.Replace(Constants.ActionToken, Constants.DefaultAction);
}
// get additional metadata from IModuleControl interface
typename = module.ModuleType;
if (Constants.DefaultModuleActions.Contains(action))
{
// core framework module action components
typename = Constants.DefaultModuleActionsTemplate.Replace(Constants.ActionToken, action);
}
Type moduletype = Type.GetType(typename);
// ensure component implements IModuleControl
if (moduletype != null && !moduletype.GetInterfaces().Contains(typeof(IModuleControl)))
{
module.ModuleType = "";
}
if (moduletype != null && module.ModuleType != "")
{
var moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources);
// additional metadata needed for admin components
if (module.ModuleId == moduleid && action != "")
{
module.SecurityAccessLevel = moduleobject.SecurityAccessLevel;
module.ControlTitle = moduleobject.Title;
module.Actions = moduleobject.Actions;
module.UseAdminContainer = moduleobject.UseAdminContainer;
}
}
// 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()))
if (page.Panes == null || page.Panes.FindIndex(item => item.Equals(module.Pane, StringComparison.OrdinalIgnoreCase)) == -1)
{
module.Pane = Constants.AdminPane;
}
// calculate module position within pane
if (paneindex.ContainsKey(module.Pane))
if (paneindex.ContainsKey(module.Pane.ToLower()))
{
paneindex[module.Pane] += 1;
paneindex[module.Pane.ToLower()] += 1;
}
else
{
paneindex.Add(module.Pane, 0);
paneindex.Add(module.Pane.ToLower(), 0);
}
module.PaneModuleIndex = paneindex[module.Pane];
module.PaneModuleIndex = paneindex[module.Pane.ToLower()];
if (string.IsNullOrEmpty(module.ContainerType))
{
@ -462,16 +479,32 @@
}
}
foreach (Module module in modules.Where(item => item.PageId == pageid))
foreach (Module module in modules.Where(item => item.PageId == page.PageId))
{
module.PaneModuleCount = paneindex[module.Pane] + 1;
module.PaneModuleCount = paneindex[module.Pane.ToLower()] + 1;
}
return modules;
return (page, modules);
}
private List<Resource> ManagePageResources(List<Resource> pageresources, List<Resource> resources)
{
if (resources != null)
{
foreach (var resource in resources)
{
// ensure resource does not exist already
if (pageresources.Find(item => item.Url == resource.Url) == null)
{
pageresources.Add(resource);
}
}
}
return pageresources;
}
private Runtime GetRuntime()
=> RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER"))
? Runtime.WebAssembly
: Runtime.Server;
=> RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER"))
? Runtime.WebAssembly
: Runtime.Server;
}

View File

@ -12,6 +12,8 @@
protected override async Task OnParametersSetAsync()
{
var interop = new Interop(JsRuntime);
// set page title
if (!string.IsNullOrEmpty(PageState.Page.Title))
{
await interop.UpdateTitle(PageState.Page.Title);
@ -20,10 +22,34 @@
{
await interop.UpdateTitle(PageState.Site.Name + " - " + PageState.Page.Name);
}
// update page resources
int stylesheet = 0;
int script = 0;
foreach (Resource resource in PageState.Page.Resources)
{
switch (resource.ResourceType)
{
case ResourceType.Stylesheet:
stylesheet += 1;
await interop.IncludeLink("app-stylesheet" + stylesheet.ToString("00"), "stylesheet", resource.Url, "text/css", resource.Integrity ?? "", resource.CrossOrigin ?? "");
break;
case ResourceType.Script:
script += 1;
await interop.IncludeScript("app-script" + script.ToString("00"), resource.Url, "", "body", resource.Integrity ?? "", resource.CrossOrigin ?? "");
break;
}
}
// remove any page resources references which are no longer required for this page
await interop.RemoveElementsById("app-stylesheet", "app-stylesheet" + (stylesheet + 1).ToString("00"), "");
await interop.RemoveElementsById("app-script", "app-script" + (script + 1).ToString("00"), "");
// add favicon
if (PageState.Site.FaviconFileId != null)
{
await interop.IncludeLink("fav-icon", "shortcut icon", Utilities.ContentUrl(PageState.Alias, PageState.Site.FaviconFileId.Value), "image/x-icon", "", "");
}
// add PWA support
if (PageState.Site.PwaIsEnabled)
{
await InitializePwa(interop);

View File

@ -17,4 +17,5 @@
@using Oqtane.Shared
@using Oqtane.Themes
@using Oqtane.Themes.Controls
@using Oqtane.UI
@using Oqtane.UI
@using Oqtane.Enums

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Framework</id>
<version>0.9.0</version>
<version>1.0.0</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -18,8 +18,11 @@
</metadata>
<files>
<file src="..\Oqtane.Client\bin\Release\netstandard2.1\Oqtane.Client.dll" target="lib" />
<file src="..\Oqtane.Client\bin\Release\netstandard2.1\Oqtane.Client.pdb" target="lib" />
<file src="..\Oqtane.Server\bin\Release\netcoreapp3.1\Oqtane.Server.dll" target="lib" />
<file src="..\Oqtane.Server\bin\Release\netcoreapp3.1\Oqtane.Server.pdb" target="lib" />
<file src="..\Oqtane.Shared\bin\Release\netstandard2.1\Oqtane.Shared.dll" target="lib" />
<file src="..\Oqtane.Server\bin\Release\netcoreapp3.1\Oqtane.Upgrade.dll" target="lib" />
<file src="..\Oqtane.Shared\bin\Release\netstandard2.1\Oqtane.Shared.pdb" target="lib" />
<file src="..\Oqtane.Server\wwwroot\**\*.*" target="wwwroot" />
</files>
</package>

View File

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

@ -16,6 +16,7 @@ using System.Net;
using Oqtane.Enums;
using Oqtane.Infrastructure;
using Oqtane.Repository;
using Microsoft.AspNetCore.Routing.Constraints;
// ReSharper disable StringIndexOfIsCultureSpecific.1
@ -64,7 +65,7 @@ namespace Oqtane.Controllers
{
foreach (string file in Directory.GetFiles(folder))
{
files.Add(new Models.File {Name = Path.GetFileName(file), Extension = Path.GetExtension(file)?.Replace(".", "")});
files.Add(new Models.File { Name = Path.GetFileName(file), Extension = Path.GetExtension(file)?.Replace(".", "") });
}
}
}
@ -188,41 +189,54 @@ namespace Oqtane.Controllers
{
Models.File file = null;
Folder folder = _folders.GetFolder(int.Parse(folderid));
if (folder != null && _userPermissions.IsAuthorized(User, PermissionNames.Edit, folder.Permissions))
{
string folderPath = GetFolderPath(folder);
CreateDirectory(folderPath);
string filename = url.Substring(url.LastIndexOf("/", StringComparison.Ordinal) + 1);
// check for allowable file extensions
if (Constants.UploadableFiles.Contains(Path.GetExtension(filename).Replace(".", "")))
{
try
{
var client = new WebClient();
string targetPath = Path.Combine(folderPath, filename);
// remove file if it already exists
if (System.IO.File.Exists(targetPath))
{
System.IO.File.Delete(targetPath);
}
client.DownloadFile(url, targetPath);
_files.AddFile(CreateFile(filename, folder.FolderId, targetPath));
}
catch
{
_logger.Log(LogLevel.Error, this, LogFunction.Create, "File Could Not Be Downloaded From Url {Url}", url);
}
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Create, "File Could Not Be Downloaded From Url Due To Its File Extension {Url}", url);
}
}
else
if (folder == null || !_userPermissions.IsAuthorized(User, PermissionNames.Edit, folder.Permissions))
{
_logger.Log(LogLevel.Error, this, LogFunction.Create, "User Not Authorized To Download File {Url} {FolderId}", url, folderid);
_logger.Log(LogLevel.Error, this, LogFunction.Create,
"User Not Authorized To Download File {Url} {FolderId}", url, folderid);
HttpContext.Response.StatusCode = 401;
return file;
}
string folderPath = GetFolderPath(folder);
CreateDirectory(folderPath);
string filename = url.Substring(url.LastIndexOf("/", StringComparison.Ordinal) + 1);
// check for allowable file extensions
if (!Constants.UploadableFiles.Split(',')
.Contains(Path.GetExtension(filename).ToLower().Replace(".", "")))
{
_logger.Log(LogLevel.Error, this, LogFunction.Create,
"File Could Not Be Downloaded From Url Due To Its File Extension {Url}", url);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Conflict;
return file;
}
if (!filename.IsPathOrFileValid())
{
_logger.Log(LogLevel.Error, this, LogFunction.Create,
$"File Could Not Be Downloaded From Url Due To Its File Name Not Allowed {url}");
HttpContext.Response.StatusCode = (int)HttpStatusCode.Conflict;
return file;
}
try
{
var client = new WebClient();
string targetPath = Path.Combine(folderPath, filename);
// remove file if it already exists
if (System.IO.File.Exists(targetPath))
{
System.IO.File.Delete(targetPath);
}
client.DownloadFile(url, targetPath);
file = _files.AddFile(CreateFile(filename, folder.FolderId, targetPath));
}
catch
{
_logger.Log(LogLevel.Error, this, LogFunction.Create,
"File Could Not Be Downloaded From Url {Url}", url);
}
return file;
@ -232,46 +246,56 @@ namespace Oqtane.Controllers
[HttpPost("upload")]
public async Task UploadFile(string folder, IFormFile file)
{
if (file.Length > 0)
if (file.Length <= 0)
{
string folderPath = "";
return;
}
if (int.TryParse(folder, out int folderId))
if (!file.FileName.IsPathOrFileValid())
{
HttpContext.Response.StatusCode = (int)HttpStatusCode.Conflict;
return;
}
string folderPath = "";
if (int.TryParse(folder, out int folderId))
{
Folder virtualFolder = _folders.GetFolder(folderId);
if (virtualFolder != null &&
_userPermissions.IsAuthorized(User, PermissionNames.Edit, virtualFolder.Permissions))
{
Folder virtualFolder = _folders.GetFolder(folderId);
if (virtualFolder != null && _userPermissions.IsAuthorized(User, PermissionNames.Edit, virtualFolder.Permissions))
{
folderPath = GetFolderPath(virtualFolder);
}
folderPath = GetFolderPath(virtualFolder);
}
else
}
else
{
if (User.IsInRole(Constants.HostRole))
{
if (User.IsInRole(Constants.HostRole))
{
folderPath = GetFolderPath(folder);
}
folderPath = GetFolderPath(folder);
}
}
if (folderPath != "")
{
CreateDirectory(folderPath);
using (var stream = new FileStream(Path.Combine(folderPath, file.FileName), FileMode.Create))
{
await file.CopyToAsync(stream);
}
if (folderPath != "")
string upload = await MergeFile(folderPath, file.FileName);
if (upload != "" && folderId != -1)
{
CreateDirectory(folderPath);
using (var stream = new FileStream(Path.Combine(folderPath, file.FileName), FileMode.Create))
{
await file.CopyToAsync(stream);
}
string upload = await MergeFile(folderPath, file.FileName);
if (upload != "" && folderId != -1)
{
_files.AddFile(CreateFile(upload, folderId, Path.Combine(folderPath, upload)));
}
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Create, "User Not Authorized To Upload File {Folder} {File}", folder, file);
HttpContext.Response.StatusCode = 401;
_files.AddFile(CreateFile(upload, folderId, Path.Combine(folderPath, upload)));
}
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Create,
"User Not Authorized To Upload File {Folder} {File}", folder, file);
HttpContext.Response.StatusCode = 401;
}
}
private async Task<string> MergeFile(string folder, string filename)
@ -282,7 +306,8 @@ namespace Oqtane.Controllers
string token = ".part_";
string parts = Path.GetExtension(filename)?.Replace(token, ""); // returns "x_y"
int totalparts = int.Parse(parts?.Substring(parts.IndexOf("_") + 1));
filename = filename?.Substring(0, filename.IndexOf(token)); // base filename
filename = Path.GetFileNameWithoutExtension(filename); // base filename
string[] fileParts = Directory.GetFiles(folder, filename + token + "*"); // list of all file parts
// if all of the file parts exist ( note that file parts can arrive out of order )
@ -317,7 +342,7 @@ namespace Oqtane.Controllers
}
// 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"));
}
@ -339,13 +364,15 @@ namespace Oqtane.Controllers
}
// clean up file parts which are more than 2 hours old ( which can happen if a prior file upload failed )
fileParts = Directory.GetFiles(folder, "*" + token + "*");
foreach (string filepart in fileParts)
var cleanupFiles = Directory.EnumerateFiles(folder, "*" + token + "*")
.Where(f => Path.GetExtension(f).StartsWith(token));
foreach (var file in cleanupFiles)
{
DateTime createddate = System.IO.File.GetCreationTime(filepart).ToUniversalTime();
if (createddate < DateTime.UtcNow.AddHours(-2))
var createdDate = System.IO.File.GetCreationTime(file).ToUniversalTime();
if (createdDate < DateTime.UtcNow.AddHours(-2))
{
System.IO.File.Delete(filepart);
System.IO.File.Delete(file);
}
}
@ -396,12 +423,13 @@ namespace Oqtane.Controllers
[HttpGet("download/{id}")]
public IActionResult Download(int id)
{
string errorpath = Path.Combine(GetFolderPath("images"), "error.png");
Models.File file = _files.GetFile(id);
if (file != null)
{
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))
{
byte[] filebytes = System.IO.File.ReadAllBytes(filepath);
@ -411,22 +439,29 @@ namespace Oqtane.Controllers
{
_logger.Log(LogLevel.Error, this, LogFunction.Read, "File Does Not Exist {FileId} {FilePath}", id, filepath);
HttpContext.Response.StatusCode = 404;
return null;
if (System.IO.File.Exists(errorpath))
{
byte[] filebytes = System.IO.File.ReadAllBytes(errorpath);
return File(filebytes, "application/octet-stream", file.Name);
}
}
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Access File {FileId}", id);
HttpContext.Response.StatusCode = 401;
return null;
byte[] filebytes = System.IO.File.ReadAllBytes(errorpath);
return File(filebytes, "application/octet-stream", file.Name);
}
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Read, "File Not Found {FileId}", id);
HttpContext.Response.StatusCode = 404;
return null;
byte[] filebytes = System.IO.File.ReadAllBytes(errorpath);
return File(filebytes, "application/octet-stream", "error.png");
}
return null;
}
private string GetFolderPath(Folder folder)
@ -448,7 +483,7 @@ namespace Oqtane.Controllers
string[] folders = folderpath.Split(separators, StringSplitOptions.RemoveEmptyEntries);
foreach (string folder in folders)
{
path = Utilities.PathCombine(path, folder,"\\");
path = Utilities.PathCombine(path, folder, "\\");
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
@ -465,11 +500,11 @@ namespace Oqtane.Controllers
FileInfo fileinfo = new FileInfo(filepath);
file.Extension = fileinfo.Extension.ToLower().Replace(".", "");
file.Size = (int) fileinfo.Length;
file.Size = (int)fileinfo.Length;
file.ImageHeight = 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);
using (var image = Image.FromStream(stream))

View File

@ -10,7 +10,6 @@ using Oqtane.Extensions;
using Oqtane.Infrastructure;
using Oqtane.Repository;
using Oqtane.Security;
using System.IO;
namespace Oqtane.Controllers
{
@ -33,7 +32,7 @@ namespace Oqtane.Controllers
public IEnumerable<Folder> Get(string siteid)
{
List<Folder> folders = new List<Folder>();
foreach(Folder folder in _folders.GetFolders(int.Parse(siteid)))
foreach (Folder folder in _folders.GetFolders(int.Parse(siteid)))
{
if (_userPermissions.IsAuthorized(User, PermissionNames.Browse, folder.Permissions))
{
@ -85,7 +84,7 @@ namespace Oqtane.Controllers
return null;
}
}
// POST api/<controller>
[HttpPost]
[Authorize(Roles = Constants.RegisteredRole)]
@ -104,15 +103,25 @@ namespace Oqtane.Controllers
new Permission(PermissionNames.Edit, Constants.AdminRole, true),
}.EncodePermissions();
}
if (_userPermissions.IsAuthorized(User,PermissionNames.Edit, permissions))
if (_userPermissions.IsAuthorized(User, PermissionNames.Edit, permissions))
{
if (string.IsNullOrEmpty(folder.Path) && folder.ParentId != null)
if (folder.IsPathValid())
{
Folder parent = _folders.GetFolder(folder.ParentId.Value);
folder.Path = Utilities.PathCombine(parent.Path, folder.Name,"\\");
if (string.IsNullOrEmpty(folder.Path) && folder.ParentId != null)
{
Folder parent = _folders.GetFolder(folder.ParentId.Value);
folder.Path = Utilities.PathCombine(parent.Path, folder.Name);
}
folder.Path = Utilities.PathCombine(folder.Path, "\\");
folder = _folders.AddFolder(folder);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Folder Added {Folder}", folder);
}
else
{
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Folder Name Not Valid {Folder}", folder);
HttpContext.Response.StatusCode = 401;
folder = null;
}
folder = _folders.AddFolder(folder);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Folder Added {Folder}", folder);
}
else
{
@ -131,13 +140,23 @@ namespace Oqtane.Controllers
{
if (ModelState.IsValid && _userPermissions.IsAuthorized(User, EntityNames.Folder, folder.FolderId, PermissionNames.Edit))
{
if (string.IsNullOrEmpty(folder.Path) && folder.ParentId != null)
if (folder.IsPathValid())
{
Folder parent = _folders.GetFolder(folder.ParentId.Value);
folder.Path = Utilities.PathCombine(parent.Path, folder.Name,"\\");
if (string.IsNullOrEmpty(folder.Path) && folder.ParentId != null)
{
Folder parent = _folders.GetFolder(folder.ParentId.Value);
folder.Path = Utilities.PathCombine(parent.Path, folder.Name);
}
folder.Path = Utilities.PathCombine(folder.Path, "\\");
folder = _folders.UpdateFolder(folder);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Folder Updated {Folder}", folder);
}
else
{
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Folder Name Not Valid {Folder}", folder);
HttpContext.Response.StatusCode = 401;
folder = null;
}
folder = _folders.UpdateFolder(folder);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Folder Updated {Folder}", folder);
}
else
{

View File

@ -4,6 +4,11 @@ using Microsoft.Extensions.Configuration;
using Oqtane.Models;
using Oqtane.Shared;
using Oqtane.Infrastructure;
using System;
using System.IO;
using System.Reflection;
using System.Linq;
using System.IO.Compression;
namespace Oqtane.Controllers
{
@ -55,5 +60,56 @@ namespace Oqtane.Controllers
_installationManager.UpgradeFramework();
return installation;
}
// GET api/<controller>/load
[HttpGet("load")]
public IActionResult Load()
{
if (_config.GetSection("Runtime").Value == "WebAssembly")
{
// get list of assemblies which should be downloaded to browser
var assemblies = AppDomain.CurrentDomain.GetOqtaneClientAssemblies();
var list = assemblies.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));
// create zip file containing assemblies and debug symbols
string binfolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
byte[] zipfile;
using (var memoryStream = new MemoryStream())
{
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
ZipArchiveEntry entry;
foreach (string file in list)
{
entry = archive.CreateEntry(file + ".dll");
using (var filestream = new FileStream(Path.Combine(binfolder, file + ".dll"), FileMode.Open, FileAccess.Read))
using (var entrystream = entry.Open())
{
filestream.CopyTo(entrystream);
}
if (System.IO.File.Exists(Path.Combine(binfolder, file + ".pdb")))
{
entry = archive.CreateEntry(file + ".pdb");
using (var filestream = new FileStream(Path.Combine(binfolder, file + ".pdb"), FileMode.Open, FileAccess.Read))
using (var entrystream = entry.Open())
{
filestream.CopyTo(entrystream);
}
}
}
}
zipfile = memoryStream.ToArray();
}
return File(zipfile, "application/octet-stream", "oqtane.zip");
}
else
{
HttpContext.Response.StatusCode = 401;
return null;
}
}
}
}

View File

@ -16,14 +16,16 @@ namespace Oqtane.Controllers
{
private readonly IModuleRepository _modules;
private readonly IPageModuleRepository _pageModules;
private readonly IPageRepository _pages;
private readonly IModuleDefinitionRepository _moduleDefinitions;
private readonly IUserPermissions _userPermissions;
private readonly ILogManager _logger;
public ModuleController(IModuleRepository modules, IPageModuleRepository pageModules, IModuleDefinitionRepository moduleDefinitions, IUserPermissions userPermissions, ILogManager logger)
public ModuleController(IModuleRepository modules, IPageModuleRepository pageModules, IPageRepository pages, IModuleDefinitionRepository moduleDefinitions, IUserPermissions userPermissions, ILogManager logger)
{
_modules = modules;
_pageModules = pageModules;
_pages = pages;
_moduleDefinitions = moduleDefinitions;
_userPermissions = userPermissions;
_logger = logger;
@ -42,6 +44,7 @@ namespace Oqtane.Controllers
Module module = new Module();
module.SiteId = pagemodule.Module.SiteId;
module.ModuleDefinitionName = pagemodule.Module.ModuleDefinitionName;
module.AllPages = pagemodule.Module.AllPages;
module.Permissions = pagemodule.Module.Permissions;
module.CreatedBy = pagemodule.Module.CreatedBy;
module.CreatedOn = pagemodule.Module.CreatedOn;
@ -111,7 +114,20 @@ namespace Oqtane.Controllers
if (ModelState.IsValid && _userPermissions.IsAuthorized(User, EntityNames.Module, module.ModuleId, PermissionNames.Edit))
{
module = _modules.UpdateModule(module);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Module Updated {Module}", module);
if (module.AllPages)
{
var pageModule = _pageModules.GetPageModules(module.SiteId).FirstOrDefault(item => item.ModuleId == module.ModuleId);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Module Updated {Module}", module);
var pages = _pages.GetPages(module.SiteId).ToList();
foreach (Page page in pages)
{
if (page.PageId != pageModule.PageId && !page.EditMode)
{
_pageModules.AddPageModule(new PageModule { PageId = page.PageId, ModuleId = pageModule.ModuleId, Title = pageModule.Title, Pane = pageModule.Pane, Order = pageModule.Order, ContainerType = pageModule.ContainerType });
}
}
}
}
else
{

View File

@ -13,7 +13,8 @@ using Oqtane.Repository;
using Oqtane.Security;
using System;
using Microsoft.Extensions.DependencyInjection;
// ReSharper disable StringIndexOfIsCultureSpecific.1
using Microsoft.Extensions.Configuration;
using System.Xml.Linq;
namespace Oqtane.Controllers
{
@ -23,20 +24,24 @@ namespace Oqtane.Controllers
private readonly IModuleDefinitionRepository _moduleDefinitions;
private readonly IModuleRepository _modules;
private readonly ITenantRepository _tenants;
private readonly ISqlRepository _sql;
private readonly IUserPermissions _userPermissions;
private readonly IInstallationManager _installationManager;
private readonly IWebHostEnvironment _environment;
private readonly IConfigurationRoot _config;
private readonly IServiceProvider _serviceProvider;
private readonly ILogManager _logger;
public ModuleDefinitionController(IModuleDefinitionRepository moduleDefinitions, IModuleRepository modules,ITenantRepository tenants, IUserPermissions userPermissions, IInstallationManager installationManager, IWebHostEnvironment environment, IServiceProvider serviceProvider, ILogManager logger)
public ModuleDefinitionController(IModuleDefinitionRepository moduleDefinitions, IModuleRepository modules,ITenantRepository tenants, ISqlRepository sql, IUserPermissions userPermissions, IInstallationManager installationManager, IWebHostEnvironment environment, IConfigurationRoot config, IServiceProvider serviceProvider, ILogManager logger)
{
_moduleDefinitions = moduleDefinitions;
_modules = modules;
_tenants = tenants;
_sql = sql;
_userPermissions = userPermissions;
_installationManager = installationManager;
_environment = environment;
_config = config;
_serviceProvider = serviceProvider;
_logger = logger;
}
@ -99,75 +104,60 @@ namespace Oqtane.Controllers
public void Delete(int id, int siteid)
{
ModuleDefinition moduledefinition = _moduleDefinitions.GetModuleDefinition(id, siteid);
if (moduledefinition != null)
if (moduledefinition != null )
{
if (!string.IsNullOrEmpty(moduledefinition.ServerManagerType))
if (!string.IsNullOrEmpty(moduledefinition.ServerManagerType) && Utilities.GetAssemblyName(moduledefinition.ServerManagerType) != "Oqtane.Server")
{
Type moduletype = Type.GetType(moduledefinition.ServerManagerType);
if (moduletype != null && moduletype.GetInterface("IInstallable") != null)
foreach (Tenant tenant in _tenants.GetTenants())
{
foreach (Tenant tenant in _tenants.GetTenants())
try
{
try
if (moduletype.GetInterface("IInstallable") != null)
{
var moduleobject = ActivatorUtilities.CreateInstance(_serviceProvider, moduletype);
((IInstallable)moduleobject).Uninstall(tenant);
}
catch
else
{
// an error occurred executing the uninstall
_sql.ExecuteScript(tenant, moduletype.Assembly, Utilities.GetTypeName(moduledefinition.ModuleDefinitionName) + ".Uninstall.sql");
}
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "{ModuleDefinitionName} Uninstalled For Tenant {Tenant}", moduledefinition.ModuleDefinitionName, tenant.Name);
}
catch (Exception ex)
{
_logger.Log(LogLevel.Error, this, LogFunction.Delete, "Error Uninstalling {ModuleDefinitionName} For Tenant {Tenant} {Error}", moduledefinition.ModuleDefinitionName, tenant.Name, ex.Message);
}
}
}
// format root assembly name
string assemblyname = Utilities.GetAssemblyName(moduledefinition.ModuleDefinitionName);
if (assemblyname != "Oqtane.Client")
{
assemblyname = assemblyname.Replace(".Client", "");
// clean up module static resource folder
string folder = Path.Combine(_environment.WebRootPath, Path.Combine("Modules",assemblyname));
string folder = Path.Combine(_environment.WebRootPath, Path.Combine("Modules", Utilities.GetTypeName(moduledefinition.ModuleDefinitionName)));
if (Directory.Exists(folder))
{
Directory.Delete(folder, true);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Static Resources Removed For {AssemblynName}", assemblyname);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Static Resources Removed For {ModuleDefinitionName}", moduledefinition.ModuleDefinitionName);
}
// remove module assembly from /bin
// get root assembly name ( note that this only works if modules follow a specific naming convention for their assemblies )
string assemblyname = Utilities.GetAssemblyName(moduledefinition.ModuleDefinitionName).ToLower();
assemblyname = assemblyname.Replace(".client", "").Replace(".oqtane", "");
// remove module assemblies from /bin
string binfolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
foreach (string file in Directory.EnumerateFiles(binfolder, assemblyname + "*.*"))
{
System.IO.File.Delete(file);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Assembly Removed {Filename}", file);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Assembly {Filename} Removed For {ModuleDefinitionName}", file, moduledefinition.ModuleDefinitionName);
}
// remove module definition
_moduleDefinitions.DeleteModuleDefinition(id, siteid);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Definition {ModuleDefinitionName} Deleted", moduledefinition.Name);
// restart application
_installationManager.RestartApplication();
}
// remove module definition
_moduleDefinitions.DeleteModuleDefinition(id, siteid);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Definition Deleted {ModuleDefinitionName}", moduledefinition.Name);
// restart application
_installationManager.RestartApplication();
}
}
// GET api/<controller>/load/assembyname
[HttpGet("load/{assemblyname}")]
public IActionResult Load(string assemblyname)
{
if (Path.GetExtension(assemblyname).ToLower() == ".dll")
{
string binfolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
byte[] file = System.IO.File.ReadAllBytes(Path.Combine(binfolder, assemblyname));
return File(file, "application/octet-stream", assemblyname);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Download Assembly {Assembly}", assemblyname);
HttpContext.Response.StatusCode = 401;
return null;
}
}
@ -180,19 +170,19 @@ namespace Oqtane.Controllers
{
string rootPath;
DirectoryInfo rootFolder = Directory.GetParent(_environment.ContentRootPath);
string templatePath = Utilities.PathCombine(rootFolder.FullName, "Oqtane.Client", "Modules", "Admin", "ModuleCreator", "Templates",moduleDefinition.Template,"\\");
string templatePath = Utilities.PathCombine(_environment.WebRootPath, "Modules", "Templates", moduleDefinition.Template,"\\");
if (moduleDefinition.Template == "internal")
{
rootPath = Utilities.PathCombine(rootFolder.FullName,"\\");
moduleDefinition.ModuleDefinitionName = moduleDefinition.Owner + "." + moduleDefinition.Name + "s.Modules, Oqtane.Client";
moduleDefinition.ModuleDefinitionName = moduleDefinition.Owner + "." + moduleDefinition.Name + "s, Oqtane.Client";
moduleDefinition.ServerManagerType = moduleDefinition.Owner + "." + moduleDefinition.Name + "s.Manager." + moduleDefinition.Name + "Manager, Oqtane.Server";
}
else
{
rootPath = Utilities.PathCombine(rootFolder.Parent.FullName , moduleDefinition.Owner + "." + moduleDefinition.Name + "s.Module","\\");
moduleDefinition.ModuleDefinitionName = moduleDefinition.Owner + "." + moduleDefinition.Name + "s.Modules, " + moduleDefinition.Owner + "." + moduleDefinition.Name + "s.Module.Client";
moduleDefinition.ServerManagerType = moduleDefinition.Owner + "." + moduleDefinition.Name + "s.Manager." + moduleDefinition.Name + "Manager, " + moduleDefinition.Owner + "." + moduleDefinition.Name + "s.Module.Server";
rootPath = Utilities.PathCombine(rootFolder.Parent.FullName , moduleDefinition.Owner + "." + moduleDefinition.Name + "s","\\");
moduleDefinition.ModuleDefinitionName = moduleDefinition.Owner + "." + moduleDefinition.Name + "s, " + moduleDefinition.Owner + "." + moduleDefinition.Name + "s.Client.Oqtane";
moduleDefinition.ServerManagerType = moduleDefinition.Owner + "." + moduleDefinition.Name + "s.Manager." + moduleDefinition.Name + "Manager, " + moduleDefinition.Owner + "." + moduleDefinition.Name + "s.Server.Oqtane";
}
ProcessTemplatesRecursively(new DirectoryInfo(templatePath), rootPath, rootFolder.Name, templatePath, moduleDefinition);
@ -204,7 +194,11 @@ namespace Oqtane.Controllers
if (moduleDefinition.Template == "internal")
{
// need logic to add embedded scripts to Oqtane.Server.csproj - also you need to remove them on uninstall
// add embedded resources to project
List<string> resources = new List<string>();
resources.Add(Utilities.PathCombine("Modules", moduleDefinition.Owner + "." + moduleDefinition.Name + "s", "Scripts", moduleDefinition.Owner + "." + moduleDefinition.Name + "s.1.0.0.sql"));
resources.Add(Utilities.PathCombine("Modules", moduleDefinition.Owner + "." + moduleDefinition.Name + "s", "Scripts", moduleDefinition.Owner + "." + moduleDefinition.Name + "s.Uninstall.sql"));
EmbedResourceFiles(Utilities.PathCombine(rootPath, "Oqtane.Server", "Oqtane.Server.csproj"), resources);
}
_installationManager.RestartApplication();
@ -253,5 +247,19 @@ namespace Oqtane.Controllers
}
}
}
private void EmbedResourceFiles(string projectfile, List<string> resources)
{
XDocument project = XDocument.Load(projectfile);
var itemGroup = project.Descendants("ItemGroup").Descendants("EmbeddedResource").FirstOrDefault().Parent;
if (itemGroup != null)
{
foreach (var resource in resources)
{
itemGroup.Add(new XElement("EmbeddedResource", new XAttribute("Include", resource)));
}
}
project.Save(projectfile);
}
}
}

View File

@ -124,6 +124,16 @@ namespace Oqtane.Controllers
page = _pages.AddPage(page);
_syncManager.AddSyncEvent(_tenants.GetTenant().TenantId, EntityNames.Site, page.SiteId);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Page Added {Page}", page);
if (!page.EditMode)
{
var modules = _modules.GetModules(page.SiteId).Where(item => item.AllPages).ToList();
foreach (Module module in modules)
{
var pageModule = _pageModules.GetPageModules(page.SiteId).FirstOrDefault(item => item.ModuleId == module.ModuleId);
_pageModules.AddPageModule(new PageModule { PageId = page.PageId, ModuleId = pageModule.ModuleId, Title = pageModule.Title, Pane = pageModule.Pane, Order = pageModule.Order, ContainerType = pageModule.ContainerType });
}
}
}
else
{
@ -156,6 +166,7 @@ namespace Oqtane.Controllers
page.EditMode = false;
page.ThemeType = parent.ThemeType;
page.LayoutType = parent.LayoutType;
page.DefaultContainerType = parent.DefaultContainerType;
page.Icon = parent.Icon;
page.Permissions = new List<Permission> {
new Permission(PermissionNames.View, userid, true),
@ -174,6 +185,7 @@ namespace Oqtane.Controllers
module.SiteId = page.SiteId;
module.PageId = page.PageId;
module.ModuleDefinitionName = pm.Module.ModuleDefinitionName;
module.AllPages = false;
module.Permissions = new List<Permission> {
new Permission(PermissionNames.View, userid, true),
new Permission(PermissionNames.Edit, userid, true)

View File

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

View File

@ -54,22 +54,20 @@ namespace Oqtane.Controllers
{
List<Theme> themes = _themes.GetThemes().ToList();
Theme theme = themes.Where(item => item.ThemeName == themename).FirstOrDefault();
if (theme != null)
if (theme != null && Utilities.GetAssemblyName(theme.ThemeName) != "Oqtane.Client")
{
themename = theme.ThemeName.Substring(0, theme.ThemeName.IndexOf(","));
string folder = Path.Combine(_environment.WebRootPath, "Themes" , themename);
// clean up theme static resource folder
string folder = Path.Combine(_environment.WebRootPath, "Themes" , Utilities.GetTypeName(theme.ThemeName));
if (Directory.Exists(folder))
{
Directory.Delete(folder, true);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Theme Static Resources Removed For {ThemeName}", theme.ThemeName);
}
// remove theme assembly from /bin
string binfolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
foreach (string file in Directory.EnumerateFiles(binfolder, themename + "*.dll"))
{
System.IO.File.Delete(file);
}
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Theme Deleted {ThemeName}", themename);
System.IO.File.Delete(Path.Combine(binfolder, Utilities.GetAssemblyName(theme.ThemeName) + ".dll"));
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Theme Assembly {Filename} Removed For {ThemeName}", Utilities.GetAssemblyName(theme.ThemeName) + ".dll", themename);
_installationManager.RestartApplication();
}

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

@ -1,35 +0,0 @@
using System.Collections.Generic;
using System.Linq;
// ReSharper disable once CheckNamespace
namespace System.Reflection
{
public static class AssemblyExtensions
{
public static IEnumerable<Type> GetInterfaces<TInterfaceType>(this Assembly assembly)
{
if (assembly is null)
{
throw new ArgumentNullException(nameof(assembly));
}
return assembly.GetTypes(typeof(TInterfaceType));
}
public static IEnumerable<Type> GetTypes(this Assembly assembly, Type interfaceType)
{
if (assembly is null)
{
throw new ArgumentNullException(nameof(assembly));
}
if (interfaceType is null)
{
throw new ArgumentNullException(nameof(interfaceType));
}
return assembly.GetTypes()
.Where(t => t.GetInterfaces().Contains(interfaceType));
}
}
}

View File

@ -1,7 +1,9 @@
using System;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Oqtane.Infrastructure;
// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection
@ -16,10 +18,11 @@ namespace Microsoft.Extensions.DependencyInjection
}
// load MVC application parts from module assemblies
foreach (var assembly in OqtaneServiceCollectionExtensions.GetOqtaneModuleAssemblies())
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
foreach (var assembly in assemblies)
{
// check if assembly contains MVC Controllers
if (assembly.GetTypes().Where(t => t.IsSubclassOf(typeof(Controller))).ToArray().Length > 0)
if (assembly.GetTypes().Any(t => t.IsSubclassOf(typeof(Controller))))
{
var partFactory = ApplicationPartFactory.GetApplicationPartFactory(assembly);
foreach (var part in partFactory.GetApplicationParts(assembly))
@ -28,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;
}
}

View File

@ -5,67 +5,37 @@ using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Microsoft.Extensions.Hosting;
using Oqtane.Extensions;
using Oqtane.Infrastructure;
using Oqtane.Modules;
using Oqtane.Services;
using Oqtane.Shared;
using Oqtane.UI;
// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection
{
public static class OqtaneServiceCollectionExtensions
{
private static readonly IList<Assembly> OqtaneModuleAssemblies = new List<Assembly>();
private static Assembly[] Assemblies => AppDomain.CurrentDomain.GetAssemblies();
internal static IEnumerable<Assembly> GetOqtaneModuleAssemblies() => OqtaneModuleAssemblies;
public static IServiceCollection AddOqtaneModules(this IServiceCollection services)
public static IServiceCollection AddOqtaneParts(this IServiceCollection services, Runtime runtime)
{
if (services is null)
{
throw new ArgumentNullException(nameof(services));
}
LoadAssemblies("Module");
LoadAssemblies();
services.AddOqtaneServices(runtime);
return services;
}
public static IServiceCollection AddOqtaneThemes(this IServiceCollection services)
private static IServiceCollection AddOqtaneServices(this IServiceCollection services, Runtime runtime)
{
if (services is null)
{
throw new ArgumentNullException(nameof(services));
}
LoadAssemblies("Theme");
return services;
}
public static IServiceCollection AddOqtaneSiteTemplates(this IServiceCollection services)
{
if (services is null)
{
throw new ArgumentNullException(nameof(services));
}
LoadAssemblies("SiteTemplate");
return services;
}
public static IServiceCollection AddOqtaneServices(this IServiceCollection services)
{
if (services is null)
{
throw new ArgumentNullException(nameof(services));
}
// dynamically register module services, contexts, and repository classes
var assemblies = Assemblies.Where(item => item.FullName != null && (item.FullName.StartsWith("Oqtane.") || item.FullName.Contains(".Module."))).ToArray();
var hostedServiceType = typeof(IHostedService);
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
foreach (var assembly in assemblies)
{
// dynamically register module services, contexts, and repository classes
var implementationTypes = assembly.GetInterfaces<IService>();
foreach (var implementationType in implementationTypes)
{
@ -75,22 +45,8 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddScoped(serviceType ?? implementationType, implementationType);
}
}
}
return services;
}
public static IServiceCollection AddOqtaneHostedServices(this IServiceCollection services)
{
if (services is null)
{
throw new ArgumentNullException(nameof(services));
}
// dynamically register hosted services
var hostedServiceType = typeof(IHostedService);
foreach (var assembly in Assemblies)
{
// dynamically register hosted services
var serviceTypes = assembly.GetTypes(hostedServiceType);
foreach (var serviceType in serviceTypes)
{
@ -99,39 +55,68 @@ namespace Microsoft.Extensions.DependencyInjection
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;
}
private static void LoadAssemblies(string pattern)
private static void LoadAssemblies()
{
var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
if (assemblyPath == null) return;
var assembliesFolder = new DirectoryInfo(assemblyPath);
// iterate through Oqtane assemblies in /bin ( filter is narrow to optimize loading process )
foreach (var dll in assembliesFolder.EnumerateFiles($"*.{pattern}.*.dll"))
foreach (var dll in assembliesFolder.EnumerateFiles($"*.dll", SearchOption.TopDirectoryOnly).Where(f => f.IsOqtaneAssembly()))
{
// check if assembly is already loaded
var assembly = Assemblies.FirstOrDefault(a =>!a.IsDynamic && a.Location == dll.FullName);
if (assembly == null)
AssemblyName assemblyName;
try
{
// load assembly ( and symbols ) from stream to prevent locking files ( as long as dependencies are in /bin they will load as well )
string pdb = dll.FullName.Replace(".dll", ".pdb");
if (File.Exists(pdb))
assemblyName = AssemblyName.GetAssemblyName(dll.FullName);
}
catch
{
Console.WriteLine($"Not Assembly : {dll.Name}");
continue;
}
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
if (!assemblies.Any(a => AssemblyName.ReferenceMatchesDefinition(assemblyName, a.GetName())))
{
try
{
assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(File.ReadAllBytes(dll.FullName)), new MemoryStream(File.ReadAllBytes(pdb)));
var pdb = Path.ChangeExtension(dll.FullName, ".pdb");
Assembly assembly = null;
// load assembly ( and symbols ) from stream to prevent locking files ( as long as dependencies are in /bin they will load as well )
if (File.Exists(pdb))
{
assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(File.ReadAllBytes(dll.FullName)), new MemoryStream(File.ReadAllBytes(pdb)));
}
else
{
assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(File.ReadAllBytes(dll.FullName)));
}
Console.WriteLine($"Loaded : {assemblyName}");
}
else
catch (Exception e)
{
assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(File.ReadAllBytes(dll.FullName)));
}
if (pattern == "Module")
{
// build a list of module assemblies
OqtaneModuleAssemblies.Add(assembly);
Console.WriteLine($"Failed : {assemblyName}\n{e}");
}
}
}

View File

@ -0,0 +1,17 @@
using System.Collections.Generic;
using System.Linq;
namespace Oqtane.Extensions
{
public static class StringExtensions
{
public static bool StartWithAnyOf(this string s, IEnumerable<string> list)
{
if (s == null)
{
return false;
}
return list.Any(f => s.StartsWith(f));
}
}
}

View File

@ -332,10 +332,13 @@ namespace Oqtane.Infrastructure
using (var scope = _serviceScopeFactory.CreateScope())
{
var moduledefinitions = scope.ServiceProvider.GetRequiredService<IModuleDefinitionRepository>();
var sql = scope.ServiceProvider.GetRequiredService<ISqlRepository>();
foreach (var moduledefinition in moduledefinitions.GetModuleDefinitions())
{
if (!string.IsNullOrEmpty(moduledefinition.ServerManagerType) && !string.IsNullOrEmpty(moduledefinition.ReleaseVersions))
if (!string.IsNullOrEmpty(moduledefinition.ReleaseVersions) && !string.IsNullOrEmpty(moduledefinition.ServerManagerType))
{
Type moduletype = Type.GetType(moduledefinition.ServerManagerType);
string[] versions = moduledefinition.ReleaseVersions.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
using (var db = new InstallationContext(NormalizeConnectionString(_config.GetConnectionString(SettingKeys.ConnectionStringKey))))
{
@ -351,19 +354,22 @@ namespace Oqtane.Infrastructure
if (index == -1) index = 0;
for (int i = index; i < versions.Length; i++)
{
Type moduletype = Type.GetType(moduledefinition.ServerManagerType);
if (moduletype != null && moduletype.GetInterface("IInstallable") != null)
try
{
try
if (moduletype.GetInterface("IInstallable") != null)
{
var moduleobject = ActivatorUtilities.CreateInstance(scope.ServiceProvider, moduletype);
((IInstallable)moduleobject).Install(tenant, versions[i]);
((IInstallable)moduleobject).Install(tenant, versions[i]);
}
catch (Exception ex)
else
{
result.Message = "An Error Occurred Installing " + moduledefinition.Name + " - " + ex.Message.ToString();
sql.ExecuteScript(tenant, moduletype.Assembly, Utilities.GetTypeName(moduledefinition.ModuleDefinitionName) + "." + versions[i] + ".sql");
}
}
catch (Exception ex)
{
result.Message = "An Error Occurred Installing " + moduledefinition.Name + " Version " + versions[i] + " - " + ex.Message.ToString();
}
}
}
}

View File

@ -28,7 +28,7 @@ namespace Oqtane.Infrastructure
{
var webRootPath = _environment.WebRootPath;
var install = UnpackPackages(folders, webRootPath);
var install = InstallPackages(folders, webRootPath);
if (install && restart)
{
@ -36,7 +36,7 @@ namespace Oqtane.Infrastructure
}
}
public static bool UnpackPackages(string folders, string webRootPath)
public static bool InstallPackages(string folders, string webRootPath)
{
bool install = false;
string binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
@ -44,20 +44,14 @@ namespace Oqtane.Infrastructure
foreach (string folder in folders.Split(','))
{
string sourceFolder = Path.Combine(webRootPath, folder);
// create folder if it does not exist
if (!Directory.Exists(sourceFolder))
{
Directory.CreateDirectory(sourceFolder);
}
// iterate through packages
// iterate through Nuget packages in source folder
foreach (string packagename in Directory.GetFiles(sourceFolder, "*.nupkg"))
{
string name = Path.GetFileNameWithoutExtension(packagename);
string[] segments = name?.Split('.');
if (segments != null) name = string.Join('.', segments, 0, segments.Length - 3);
// iterate through files
using (ZipArchive archive = ZipFile.OpenRead(packagename))
{
@ -86,31 +80,33 @@ namespace Oqtane.Infrastructure
// if compatible with framework version
if (frameworkversion == "" || Version.Parse(Constants.Version).CompareTo(Version.Parse(frameworkversion)) >= 0)
{
// module and theme packages must be in form of name.1.0.0.nupkg
string name = Path.GetFileNameWithoutExtension(packagename);
string[] segments = name?.Split('.');
if (segments != null) name = string.Join('.', segments, 0, segments.Length - 3);
// deploy to appropriate locations
foreach (ZipArchiveEntry entry in archive.Entries)
{
string foldername = Path.GetDirectoryName(entry.FullName).Split('\\')[0];
string filename = Path.GetFileName(entry.FullName);
switch (Path.GetExtension(filename).ToLower())
{
case ".pdb":
case ".dll":
if (binFolder != null) entry.ExtractToFile(Path.Combine(binFolder, filename), true);
break;
case ".png":
case ".jpg":
case ".jpeg":
case ".gif":
case ".svg":
case ".js":
case ".css":
string entryPath = Utilities.PathCombine(entry.FullName.Replace("wwwroot", name).Split('/'));
filename = Path.Combine(sourceFolder, entryPath);
if (!Directory.Exists(Path.GetDirectoryName(filename)))
{
Directory.CreateDirectory(Path.GetDirectoryName(filename));
}
entry.ExtractToFile(filename, true);
switch (foldername)
{
case "lib":
filename = Path.Combine(binFolder, filename);
ExtractFile(entry, filename);
break;
case "wwwroot":
filename = Path.Combine(sourceFolder, Utilities.PathCombine(entry.FullName.Replace("wwwroot", name).Split('/')));
ExtractFile(entry, filename);
break;
case "content":
if (Path.GetDirectoryName(entry.FullName) != "content") // assets must be in subfolders
{
filename = Path.Combine(webRootPath, Utilities.PathCombine(entry.FullName.Replace("content", "").Split('/')));
ExtractFile(entry, filename);
}
break;
}
}
@ -126,6 +122,15 @@ namespace Oqtane.Infrastructure
return install;
}
private static void ExtractFile(ZipArchiveEntry entry, string filename)
{
if (!Directory.Exists(Path.GetDirectoryName(filename)))
{
Directory.CreateDirectory(Path.GetDirectoryName(filename));
}
entry.ExtractToFile(filename, true);
}
public void UpgradeFramework()
{
string folder = Path.Combine(_environment.WebRootPath, "Framework");
@ -180,17 +185,17 @@ namespace Oqtane.Infrastructure
private void FinishUpgrade()
{
string folder = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
// check if upgrade application exists
string folder = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
if (folder == null || !File.Exists(Path.Combine(folder, "Oqtane.Upgrade.exe"))) return;
// run upgrade application
var process = new Process
{
StartInfo =
{
FileName = Path.Combine(folder, "Oqtane.Upgrade.exe"),
Arguments = "",
Arguments = "\"" + _environment.ContentRootPath + "\" \"" + _environment.WebRootPath + "\"",
ErrorDialog = false,
UseShellExecute = false,
CreateNoWindow = true,

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

@ -55,9 +55,9 @@ namespace Oqtane.SiteTemplates
new Permission(PermissionNames.View, Constants.AdminRole, true),
new Permission(PermissionNames.Edit, Constants.AdminRole, true)
}.EncodePermissions(),
Content = "<p><a href=\"https://www.oqtane.org\" target=\"_new\">Oqtane</a> is an open source <b>modular application framework</b> built from the ground up using modern .NET Core technology. It leverages the revolutionary new Blazor component model to create a <b>fully dynamic</b> web development experience which can be executed on a client or server. Whether you are looking for a platform to <b>accelerate your web development</b> efforts, or simply interested in exploring the anatomy of a large-scale Blazor application, Oqtane provides a solid foundation based on proven enterprise architectural principles.</p>" +
"<p align=\"center\"><a href=\"https://www.oqtane.org\" target=\"_new\"><img class=\"img-fluid\" src=\"oqtane.png\"></a></p><p align=\"center\"><a class=\"btn btn-primary\" href=\"https://www.oqtane.org/Community\" target=\"_new\">Join Our Community</a>&nbsp;&nbsp;<a class=\"btn btn-primary\" href=\"https://github.com/oqtane/oqtane.framework\" target=\"_new\">Clone Our Repo</a></p>" +
"<p><a href=\"https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor\" target=\"_new\">Blazor</a> is a single-page app framework that lets you build interactive web applications using C# instead of JavaScript. Client-side Blazor relies on WebAssembly, an open web standard that does not require plugins or code transpilation in order to run natively in a web browser. Server-side Blazor uses SignalR to host your application on a web server and provide a responsive and robust debugging experience. Blazor applications works in all modern web browsers, including mobile browsers.</p>" +
Content = "<p><a href=\"https://www.oqtane.org\" target=\"_new\">Oqtane</a> is an open source <b>modular application framework</b> that provides advanced functionality for developing web and mobile applications on ASP.NET Core. It leverages the revolutionary new Blazor component model to compose a <b>fully dynamic</b> web development experience which can be hosted either client-side or server-side. Whether you are looking for a platform to <b>accelerate your web development</b> efforts, or simply interested in exploring the anatomy of a large-scale Blazor application, Oqtane provides a solid foundation based on proven enterprise architectural principles.</p>" +
"<p align=\"center\"><a href=\"https://www.oqtane.org\" target=\"_new\"><img class=\"img-fluid\" src=\"oqtane-white.png\"></a></p><p align=\"center\"><a class=\"btn btn-primary\" href=\"https://www.oqtane.org/Community\" target=\"_new\">Join Our Community</a>&nbsp;&nbsp;<a class=\"btn btn-primary\" href=\"https://github.com/oqtane/oqtane.framework\" target=\"_new\">Clone Our Repo</a></p>" +
"<p><a href=\"https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor\" target=\"_new\">Blazor</a> is an open source and cross-platform web UI framework for building single-page apps using .NET and C# instead of JavaScript. Blazor WebAssembly relies on Wasm, an open web standard that does not require plugins or code transpilation in order to run natively in a web browser. Blazor Server uses SignalR to host your application on a web server and provide a responsive and robust development experience. Blazor applications work in all modern web browsers, including mobile browsers.</p>" +
"<p>Blazor is a feature of <a href=\"https://dotnet.microsoft.com/apps/aspnet\" target=\"_new\">ASP.NET Core 3</a>, the popular cross platform web development framework from Microsoft that extends the <a href=\"https://dotnet.microsoft.com/learn/dotnet/what-is-dotnet\" target=\"_new\" >.NET developer platform</a> with tools and libraries for building web apps.</p>"
},
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "MIT License", Pane = "Content",
@ -132,16 +132,16 @@ namespace Oqtane.SiteTemplates
}
});
if (System.IO.File.Exists(Path.Combine(_environment.WebRootPath, "images", "logo.png")))
if (System.IO.File.Exists(Path.Combine(_environment.WebRootPath, "images", "logo-white.png")))
{
string folderpath = Utilities.PathCombine(_environment.ContentRootPath, "Content", "Tenants", site.TenantId.ToString(), "Sites", site.SiteId.ToString(),"\\");
System.IO.Directory.CreateDirectory(folderpath);
if (!System.IO.File.Exists(Path.Combine(folderpath, "logo.png")))
if (!System.IO.File.Exists(Path.Combine(folderpath, "logo-white.png")))
{
System.IO.File.Copy(Path.Combine(_environment.WebRootPath, "images", "logo.png"), Path.Combine(folderpath, "logo.png"));
System.IO.File.Copy(Path.Combine(_environment.WebRootPath, "images", "logo-white.png"), Path.Combine(folderpath, "logo-white.png"));
}
Folder folder = _folderRepository.GetFolder(site.SiteId, "");
Oqtane.Models.File file = _fileRepository.AddFile(new Oqtane.Models.File { FolderId = folder.FolderId, Name = "logo.png", Extension = "png", Size = 8192, ImageHeight = 80, ImageWidth = 250 });
Oqtane.Models.File file = _fileRepository.AddFile(new Oqtane.Models.File { FolderId = folder.FolderId, Name = "logo-white.png", Extension = "png", Size = 8192, ImageHeight = 80, ImageWidth = 250 });
site.LogoFileId = file.FileId;
_siteRepository.UpdateSite(site);
}

View File

@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<LangVersion>7.3</LangVersion>
<Configurations>Debug;Release</Configurations>
<Version>0.9.0</Version>
<Version>1.0.0</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -16,28 +16,31 @@
<PackageReleaseNotes>Not for production use.</PackageReleaseNotes>
<RootNamespace>Oqtane</RootNamespace>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Modules\HtmlText\Scripts\HtmlText.1.0.0.sql" />
<EmbeddedResource Include="Modules\HtmlText\Scripts\HtmlText.Uninstall.sql" />
<Compile Remove="wwwroot\Modules\Templates\**" />
<Content Remove="wwwroot\Modules\Templates\**" />
<EmbeddedResource Remove="wwwroot\Modules\Templates\**" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Scripts\Master.0.9.0.sql" />
<EmbeddedResource Include="Scripts\Tenant.0.9.0.sql" />
<EmbeddedResource Include="Scripts\Tenant.0.9.1.sql" />
<EmbeddedResource Include="Scripts\Tenant.0.9.2.sql" />
<EmbeddedResource Include="Modules\HtmlText\Scripts\HtmlText.1.0.0.sql" />
<EmbeddedResource Include="Modules\HtmlText\Scripts\HtmlText.Uninstall.sql" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="dbup" Version="4.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="3.2.0-rc1.20223.4" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="3.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.4" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.4" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.4.1" />
<PackageReference Include="System.Drawing.Common" Version="4.7.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Oqtane.Client\Oqtane.Client.csproj" />
<ProjectReference Include="..\Oqtane.Shared\Oqtane.Shared.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@ -14,8 +14,6 @@
<link id="fav-icon" rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
<!-- stub the PWA manifest but defer the assignment of href -->
<link id="pwa-manifest" rel="manifest" />
<link href="css/quill/quill1.3.6.bubble.css" rel="stylesheet" />
<link href="css/quill/quill1.3.6.snow.css" rel="stylesheet" />
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link href="css/app.css" rel="stylesheet" />
</head>
@ -25,10 +23,7 @@
<component type="typeof(Oqtane.App)" render-mode="Server" />
</app>
<script src="js/site.js"></script>
<script src="js/interop.js"></script>
<script src="js/quill1.3.6.min.js"></script>
<script src="js/quill-blot-formatter.min.js"></script>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>

View File

@ -75,6 +75,7 @@ namespace Oqtane.Repository
// get module assemblies
_moduleDefinitions = LoadModuleDefinitionsFromAssemblies();
}
List<ModuleDefinition> moduleDefinitions = _moduleDefinitions;
List<Permission> permissions = new List<Permission>();
@ -94,7 +95,7 @@ namespace Oqtane.Repository
if (moduledef == null)
{
// new module definition
moduledef = new ModuleDefinition { ModuleDefinitionName = moduledefinition.ModuleDefinitionName };
moduledef = new ModuleDefinition {ModuleDefinitionName = moduledefinition.ModuleDefinitionName};
_db.ModuleDefinition.Add(moduledef);
_db.SaveChanges();
if (siteId != -1)
@ -109,18 +110,22 @@ namespace Oqtane.Repository
{
moduledefinition.Name = moduledef.Name;
}
if (!string.IsNullOrEmpty(moduledef.Description))
{
moduledefinition.Description = moduledef.Description;
}
if (!string.IsNullOrEmpty(moduledef.Categories))
{
moduledefinition.Categories = moduledef.Categories;
}
if (!string.IsNullOrEmpty(moduledef.Version))
{
moduledefinition.Version = moduledef.Version;
}
if (siteId != -1)
{
if (permissions.Count == 0)
@ -139,9 +144,11 @@ namespace Oqtane.Repository
}
}
}
// remove module definition from list as it is already synced
moduledefs.Remove(moduledef);
}
moduledefinition.ModuleDefinitionId = moduledef.ModuleDefinitionId;
moduledefinition.SiteId = siteId;
moduledefinition.CreatedBy = moduledef.CreatedBy;
@ -157,6 +164,7 @@ namespace Oqtane.Repository
{
_permissions.DeletePermissions(siteId, EntityNames.ModuleDefinition, moduledefinition.ModuleDefinitionId);
}
_db.ModuleDefinition.Remove(moduledefinition); // delete
_db.SaveChanges();
}
@ -168,12 +176,12 @@ namespace Oqtane.Repository
{
List<ModuleDefinition> moduleDefinitions = new List<ModuleDefinition>();
// iterate through Oqtane module assemblies
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies()
.Where(item => item.FullName.StartsWith("Oqtane.") || item.FullName.Contains(".Module.")).ToArray();
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
foreach (Assembly assembly in assemblies)
{
moduleDefinitions = LoadModuleDefinitionsFromAssembly(moduleDefinitions, assembly);
}
return moduleDefinitions;
}
@ -183,84 +191,90 @@ namespace Oqtane.Repository
Type[] modulecontroltypes = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IModuleControl))).ToArray();
foreach (Type modulecontroltype in modulecontroltypes)
{
if (modulecontroltype.Name != "ModuleBase" && !modulecontroltype.Namespace.EndsWith(".Controls"))
{
string[] typename = modulecontroltype.AssemblyQualifiedName?.Split(',').Select(item => item.Trim()).ToArray();
string[] segments = typename[0].Split('.');
Array.Resize(ref segments, segments.Length - 1);
string moduleType = string.Join(".", segments);
string qualifiedModuleType = moduleType + ", " + typename[1];
// Check if type should be ignored
if (modulecontroltype.Name == "ModuleBase"
|| modulecontroltype.IsGenericType
|| modulecontroltype.IsAbstract
|| Attribute.IsDefined(modulecontroltype, typeof(OqtaneIgnoreAttribute))
) continue;
int index = moduledefinitions.FindIndex(item => item.ModuleDefinitionName == qualifiedModuleType);
if (index == -1)
string moduleNamespace = modulecontroltype.Namespace;
string qualifiedModuleType = moduleNamespace + ", " + modulecontroltype.Assembly.GetName().Name;
int index = moduledefinitions.FindIndex(item => item.ModuleDefinitionName == qualifiedModuleType);
if (index == -1)
{
// determine if this module implements IModule
Type moduletype = assembly
.GetTypes()
.Where(item => item.Namespace != null)
.Where(item => item.Namespace.StartsWith(moduleNamespace))
.FirstOrDefault(item => item.GetInterfaces().Contains(typeof(IModule)));
if (moduletype != null)
{
// determine if this module implements IModule
Type moduletype = assembly
.GetTypes()
.Where(item => item.Namespace != null)
.Where(item => item.Namespace.StartsWith(moduleType))
.FirstOrDefault(item => item.GetInterfaces().Contains(typeof(IModule)));
if (moduletype != null)
{
// get property values from IModule
var moduleobject = Activator.CreateInstance(moduletype);
moduledefinition = (ModuleDefinition)moduletype.GetProperty("ModuleDefinition").GetValue(moduleobject);
}
else
{
// set default property values
moduledefinition = new ModuleDefinition
{
Name = moduleType.Substring(moduleType.LastIndexOf(".") + 1),
Description = "Manage " + moduleType.Substring(moduleType.LastIndexOf(".") + 1),
Categories = ((qualifiedModuleType.StartsWith("Oqtane.Modules.Admin.")) ? "Admin" : "")
};
}
// set internal properties
moduledefinition.ModuleDefinitionName = qualifiedModuleType;
moduledefinition.Version = ""; // will be populated from database
moduledefinition.ControlTypeTemplate = moduleType + "." + Constants.ActionToken + ", " + typename[1];
moduledefinition.AssemblyName = assembly.GetName().Name;
if (string.IsNullOrEmpty(moduledefinition.Categories))
{
moduledefinition.Categories = "Common";
}
if (moduledefinition.Categories == "Admin")
{
moduledefinition.Permissions = new List<Permission>
{
new Permission(PermissionNames.Utilize, Constants.AdminRole, true)
}.EncodePermissions();
}
else
{
moduledefinition.Permissions = new List<Permission>
{
new Permission(PermissionNames.Utilize, Constants.AdminRole, true),
new Permission(PermissionNames.Utilize, Constants.RegisteredRole, true)
}.EncodePermissions();
}
moduledefinitions.Add(moduledefinition);
index = moduledefinitions.FindIndex(item => item.ModuleDefinitionName == qualifiedModuleType);
// get property values from IModule
var moduleobject = Activator.CreateInstance(moduletype) as IModule;
moduledefinition = moduleobject.ModuleDefinition;
}
moduledefinition = moduledefinitions[index];
// actions
var modulecontrolobject = Activator.CreateInstance(modulecontroltype);
string actions = (string)modulecontroltype.GetProperty("Actions")?.GetValue(modulecontrolobject);
if (!string.IsNullOrEmpty(actions))
else
{
foreach (string action in actions.Split(','))
// set default property values
moduledefinition = new ModuleDefinition
{
moduledefinition.ControlTypeRoutes += (action + "=" + modulecontroltype.FullName + ", " + typename[1] + ";");
}
Name = moduleNamespace.Substring(moduleNamespace.LastIndexOf(".") + 1),
Description = "Manage " + moduleNamespace.Substring(moduleNamespace.LastIndexOf(".") + 1),
Categories = ((qualifiedModuleType.StartsWith("Oqtane.Modules.Admin.")) ? "Admin" : "")
};
}
moduledefinitions[index] = moduledefinition;
// set internal properties
moduledefinition.ModuleDefinitionName = qualifiedModuleType;
moduledefinition.Version = ""; // will be populated from database
moduledefinition.ControlTypeTemplate = moduleNamespace + "." + Constants.ActionToken + ", " + modulecontroltype.Assembly.GetName().Name;
moduledefinition.AssemblyName = assembly.GetName().Name;
if (string.IsNullOrEmpty(moduledefinition.Categories))
{
moduledefinition.Categories = "Common";
}
if (moduledefinition.Categories == "Admin")
{
moduledefinition.Permissions = new List<Permission>
{
new Permission(PermissionNames.Utilize, Constants.AdminRole, true)
}.EncodePermissions();
}
else
{
moduledefinition.Permissions = new List<Permission>
{
new Permission(PermissionNames.Utilize, Constants.AdminRole, true),
new Permission(PermissionNames.Utilize, Constants.RegisteredRole, true)
}.EncodePermissions();
}
Console.WriteLine($"Registering module: {moduledefinition.ModuleDefinitionName}");
moduledefinitions.Add(moduledefinition);
index = moduledefinitions.FindIndex(item => item.ModuleDefinitionName == qualifiedModuleType);
}
moduledefinition = moduledefinitions[index];
// actions
var modulecontrolobject = Activator.CreateInstance(modulecontroltype) as IModuleControl;
string actions = modulecontrolobject.Actions;
if (!string.IsNullOrEmpty(actions))
{
foreach (string action in actions.Split(','))
{
moduledefinition.ControlTypeRoutes += (action + "=" + modulecontroltype.FullName + ", " + modulecontroltype.Assembly.GetName().Name + ";");
}
}
moduledefinitions[index] = moduledefinition;
}
return moduledefinitions;
}
}
}

View File

@ -757,6 +757,7 @@ namespace Oqtane.Repository
EditMode = pagetemplate.EditMode,
ThemeType = "",
LayoutType = "",
DefaultContainerType = "",
Icon = pagetemplate.Icon,
Permissions = pagetemplate.PagePermissions,
IsPersonalizable = pagetemplate.IsPersonalizable,
@ -775,6 +776,7 @@ namespace Oqtane.Repository
{
SiteId = site.SiteId,
ModuleDefinitionName = pagetemplatemodule.ModuleDefinitionName,
AllPages = false,
Permissions = pagetemplatemodule.ModulePermissions,
};
module = _moduleRepository.AddModule(module);

View File

@ -22,8 +22,8 @@ namespace Oqtane.Repository
List<SiteTemplate> siteTemplates = new List<SiteTemplate>();
// iterate through Oqtane site template assemblies
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies()
.Where(item => item.FullName.StartsWith("Oqtane.") || item.FullName.Contains(".SiteTemplate.")).ToArray();
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
foreach (Assembly assembly in assemblies)
{
siteTemplates = LoadSiteTemplatesFromAssembly(siteTemplates, assembly);

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Oqtane.Models;
using Oqtane.Shared;
using Oqtane.Themes;
namespace Oqtane.Repository
@ -31,8 +32,7 @@ namespace Oqtane.Repository
List<Theme> themes = new List<Theme>();
// iterate through Oqtane theme assemblies
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies()
.Where(item => item.FullName.StartsWith("Oqtane.") || item.FullName.Contains(".Theme.")).ToArray();
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
foreach (Assembly assembly in assemblies)
{
themes = LoadThemesFromAssembly(themes, assembly);
@ -47,76 +47,77 @@ namespace Oqtane.Repository
Type[] themeControlTypes = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IThemeControl))).ToArray();
foreach (Type themeControlType in themeControlTypes)
{
if (themeControlType.Name != "ThemeBase")
// Check if type should be ignored
if (themeControlType.Name == "ThemeBase"
|| themeControlType.IsGenericType
|| Attribute.IsDefined(themeControlType, typeof(OqtaneIgnoreAttribute))
) continue;
string themeNamespace = themeControlType.Namespace;
string qualifiedModuleType = themeNamespace + ", " + themeControlType.Assembly.GetName().Name;
int index = themes.FindIndex(item => item.ThemeName == themeNamespace);
if (index == -1)
{
string[] typename = themeControlType.AssemblyQualifiedName.Split(',').Select(item => item.Trim()).ToList().ToArray();
string[] segments = typename[0].Split('.');
Array.Resize(ref segments, segments.Length - 1);
string @namespace = string.Join(".", segments);
int index = themes.FindIndex(item => item.ThemeName == @namespace);
if (index == -1)
{
// determine if this theme implements ITheme
Type themetype = assembly.GetTypes()
.Where(item => item.Namespace != null)
.Where(item => item.Namespace.StartsWith(@namespace))
.Where(item => item.GetInterfaces().Contains(typeof(ITheme))).FirstOrDefault();
if (themetype != null)
{
var themeobject = Activator.CreateInstance(themetype);
theme = (Theme)themetype.GetProperty("Theme").GetValue(themeobject);
}
else
{
theme = new Theme
{
Name = themeControlType.Name,
Version = new Version(1, 0, 0).ToString()
};
}
// set internal properties
theme.ThemeName = @namespace;
theme.ThemeControls = "";
theme.PaneLayouts = "";
theme.ContainerControls = "";
theme.AssemblyName = assembly.FullName.Split(",")[0];
themes.Add(theme);
index = themes.FindIndex(item => item.ThemeName == @namespace);
}
theme = themes[index];
theme.ThemeControls += (themeControlType.FullName + ", " + typename[1] + ";");
// layouts
Type[] layouttypes = assembly.GetTypes()
// determine if this theme implements ITheme
Type themetype = assembly.GetTypes()
.Where(item => item.Namespace != null)
.Where(item => item.Namespace.StartsWith(@namespace))
.Where(item => item.GetInterfaces().Contains(typeof(ILayoutControl))).ToArray();
foreach (Type layouttype in layouttypes)
.Where(item => item.Namespace.StartsWith(themeNamespace))
.Where(item => item.GetInterfaces().Contains(typeof(ITheme))).FirstOrDefault();
if (themetype != null)
{
string panelayout = layouttype.FullName + ", " + typename[1] + ";";
if (!theme.PaneLayouts.Contains(panelayout))
{
theme.PaneLayouts += panelayout;
}
var themeobject = Activator.CreateInstance(themetype) as ITheme;
theme = themeobject.Theme;
}
// containers
Type[] containertypes = assembly.GetTypes()
.Where(item => item.Namespace != null)
.Where(item => item.Namespace.StartsWith(@namespace))
.Where(item => item.GetInterfaces().Contains(typeof(IContainerControl))).ToArray();
foreach (Type containertype in containertypes)
else
{
string container = containertype.FullName + ", " + typename[1] + ";";
if (!theme.ContainerControls.Contains(container))
theme = new Theme
{
theme.ContainerControls += container;
}
Name = themeControlType.Name,
Version = new Version(1, 0, 0).ToString()
};
}
themes[index] = theme;
// set internal properties
theme.ThemeName = themeNamespace;
theme.ThemeControls = "";
theme.PaneLayouts = "";
theme.ContainerControls = "";
theme.AssemblyName = assembly.FullName.Split(",")[0];
themes.Add(theme);
index = themes.FindIndex(item => item.ThemeName == themeNamespace);
}
theme = themes[index];
theme.ThemeControls += (themeControlType.FullName + ", " + themeControlType.Assembly.GetName().Name + ";");
// layouts
Type[] layouttypes = assembly.GetTypes()
.Where(item => item.Namespace != null)
.Where(item => item.Namespace.StartsWith(themeNamespace))
.Where(item => item.GetInterfaces().Contains(typeof(ILayoutControl))).ToArray();
foreach (Type layouttype in layouttypes)
{
string panelayout = layouttype.FullName + ", " + themeControlType.Assembly.GetName().Name + ";";
if (!theme.PaneLayouts.Contains(panelayout))
{
theme.PaneLayouts += panelayout;
}
}
// containers
Type[] containertypes = assembly.GetTypes()
.Where(item => item.Namespace != null)
.Where(item => item.Namespace.StartsWith(themeNamespace))
.Where(item => item.GetInterfaces().Contains(typeof(IContainerControl))).ToArray();
foreach (Type containertype in containertypes)
{
string container = containertype.FullName + ", " + themeControlType.Assembly.GetName().Name + ";";
if (!theme.ContainerControls.Contains(container))
{
theme.ContainerControls += container;
}
}
themes[index] = theme;
}
return themes;
}

View File

@ -0,0 +1,14 @@
/*
Version 0.9.1 migration script
*/
ALTER TABLE [dbo].[Module] ADD
[AllPages] [bit] NULL
GO
UPDATE [dbo].[Module]
SET [AllPages] = 0
GO

View File

@ -0,0 +1,18 @@
/*
Version 0.9.2 migration script
*/
ALTER TABLE [dbo].[Role]
ALTER COLUMN [Description] VARCHAR (256) NOT NULL
GO
ALTER TABLE [dbo].[Page] ADD
[DefaultContainerType] [nvarchar](200) NULL
GO
UPDATE [dbo].[Page]
SET [DefaultContainerType] = ''
GO

View File

@ -14,11 +14,13 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
using Oqtane.Extensions;
using Oqtane.Infrastructure;
using Oqtane.Repository;
using Oqtane.Security;
using Oqtane.Services;
using Oqtane.Shared;
using Oqtane.Shared;
using Oqtane.UI;
namespace Oqtane
{
@ -26,6 +28,7 @@ namespace Oqtane
{
public IConfigurationRoot Configuration { get; }
private string _webRoot;
private Runtime _runtime;
public Startup(IWebHostEnvironment env)
{
@ -33,6 +36,9 @@ namespace Oqtane
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
Configuration = builder.Build();
_runtime = (Configuration.GetSection("Runtime").Value == "WebAssembly") ? Runtime.WebAssembly : Runtime.Server;
_webRoot = env.WebRootPath;
AppDomain.CurrentDomain.SetData("DataDirectory", Path.Combine(env.ContentRootPath, "Data"));
}
@ -41,7 +47,7 @@ namespace Oqtane
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddNewtonsoftJson();
services.AddServerSideBlazor();
// setup HttpClient for server side in a client side compatible fashion ( with auth cookie )
@ -157,7 +163,7 @@ namespace Oqtane
services.AddSingleton<IDatabaseManager, DatabaseManager>();
// install any modules or themes ( this needs to occur BEFORE the assemblies are loaded into the app domain )
InstallationManager.UnpackPackages("Modules,Themes", _webRoot);
InstallationManager.InstallPackages("Modules,Themes", _webRoot);
// register transient scoped core services
services.AddTransient<IModuleDefinitionRepository, ModuleDefinitionRepository>();
@ -187,17 +193,13 @@ namespace Oqtane
services.AddTransient<ISqlRepository, SqlRepository>();
services.AddTransient<IUpgradeManager, UpgradeManager>();
// load the external assemblies into the app domain
services.AddOqtaneModules();
services.AddOqtaneThemes();
services.AddOqtaneSiteTemplates();
// load the external assemblies into the app domain, install services
services.AddOqtaneParts(_runtime);
services.AddMvc()
.AddNewtonsoftJson()
.AddOqtaneApplicationParts() // register any Controllers from custom modules
.AddNewtonsoftJson();
services.AddOqtaneServices();
services.AddOqtaneHostedServices();
.ConfigureOqtaneMvc(); // any additional configuration from IStart classes.
services.AddSwaggerGen(c =>
{
@ -220,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.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseBlazorFrameworkFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
@ -240,6 +240,7 @@ namespace Oqtane
endpoints.MapControllers();
endpoints.MapFallbackToPage("/_Host");
});
app.ConfigureOqtaneAssemblies(env);
}
}
}

View File

@ -0,0 +1 @@
/* HtmlText Module Custom Styles */

View File

@ -2,11 +2,10 @@
@using [Owner].[Module]s.Services
@using [Owner].[Module]s.Models
@namespace [Owner].[Module]s.Modules
@namespace [Owner].[Module]s
@inherits ModuleBase
@inject I[Module]Service [Module]Service
@inject NavigationManager NavigationManager
@inject HttpClient http
@inject SiteState sitestate
<table class="table table-borderless">
<tr>
@ -29,9 +28,14 @@
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Actions => "Add,Edit";
I[Module]Service [Module]Service;
public override List<Resource> Resources => new List<Resource>()
{
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }
};
int _id;
string _name;
string _createdby;
@ -43,7 +47,6 @@
{
try
{
[Module]Service = new [Module]Service(http, sitestate);
if (PageState.Action == "Edit")
{
_id = Int32.Parse(PageState.QueryString["id"]);

View File

@ -1,11 +1,10 @@
@using [Owner].[Module]s.Services
@using [Owner].[Module]s.Models
@namespace [Owner].[Module]s.Modules
@namespace [Owner].[Module]s
@inherits ModuleBase
@inject I[Module]Service [Module]Service
@inject NavigationManager NavigationManager
@inject HttpClient http
@inject SiteState sitestate
@if (_[Module]s == null)
{
@ -42,7 +41,7 @@ else
<hr />
[Module] Module Created Successfully. Use Edit Mode To Add A [Module]. You Can Access The Files At The Following Locations:<br /><br />
[RootPath]Client\<br />
- [Owner].[Module]s.Module.Client.csproj - client project<br />
- [Owner].[Module]s.Client.csproj - client project<br />
- _Imports.razor - global imports for module components<br />
- Edit.razor - component for adding or editing content<br />
- Index.razor - main component for your module **the content you are reading is in this file**<br />
@ -51,34 +50,38 @@ else
- Services\I[Module]Service.cs - interface for defining service API methods<br />
- Services\[Module]Service.cs - implements service API interface methods<br /><br />
[RootPath]Package\<br />
- [Owner].[Module]s.Module.nuspec - nuget manifest for packaging module<br />
- [Owner].[Module]s.Module.Package.csproj - packaging project<br />
- [Owner].[Module]s.nuspec - nuget manifest for packaging module<br />
- [Owner].[Module]s.Package.csproj - packaging project<br />
- debug.cmd - copies assemblies to Oqtane bin folder when in Debug mode<br />
- release.cmd - creates nuget package and deploys to Oqtane wwwroot/modules folder when in Release mode<br /><br />
[RootPath]Server\<br />
- [Owner].[Module]s.Module.Server.csproj - server project<br />
- [Owner].[Module]s.Server.csproj - server project<br />
- Controllers\[Module]Controller.cs - API methods implemented using a REST pattern<br />
- Manager\[Module]Manager.cs - implements optional module interfaces for features such as import/export of content<br />
- Repository\I[Module]Repository.cs - interface for defining repository methods<br />
- Repository\[Module]Respository.cs - implements repository interface methods for data access using EF Core<br />
- Repository\[Module]Context.cs - provides a DB Context for data access<br />
- Scripts\[Owner].[Module].1.0.0.sql - database schema definition script<br /><br />
- Scripts\[Owner].[Module].Uninstall.sql - database uninstall script<br /><br />
- Scripts\[Owner].[Module]s.1.0.0.sql - database schema definition script<br />
- Scripts\[Owner].[Module]s.Uninstall.sql - database uninstall script<br />
- wwwroot\Module.css - module style sheet<br /><br />
[RootPath]Shared\<br />
- [Owner].[Module]s.Module.Shared.csproj - shared project<br />
- [Owner].[Module]s.csproj - shared project<br />
- Models\[Module].cs - model definition<br /><br />
<!-- The content above is for informational purposes only and can be safely removed -->
@code {
I[Module]Service [Module]Service;
public override List<Resource> Resources => new List<Resource>()
{
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }
};
List<[Module]> _[Module]s;
protected override async Task OnInitializedAsync()
{
try
{
[Module]Service = new [Module]Service(http, sitestate);
_[Module]s = await [Module]Service.Get[Module]sAsync(ModuleState.ModuleId);
}
catch (Exception ex)

View File

@ -1,7 +1,7 @@
using Oqtane.Models;
using Oqtane.Modules;
namespace [Owner].[Module]s.Modules
namespace [Owner].[Module]s
{
public class ModuleInfo : IModule
{
@ -10,7 +10,6 @@ namespace [Owner].[Module]s.Modules
Name = "[Module]",
Description = "[Module]",
Version = "1.0.0",
Dependencies = "[Owner].[Module]s.Module.Shared",
ServerManagerType = "[ServerManagerType]",
ReleaseVersions = "1.0.0"
};

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