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 @namespace Oqtane.Modules.Admin.Files
@using System.IO
@inherits ModuleBase @inherits ModuleBase
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IFileService FileService @inject IFileService FileService
@ -12,7 +13,7 @@
<Label For="upload" HelpText="Upload the file you want">Upload: </Label> <Label For="upload" HelpText="Upload the file you want">Upload: </Label>
</td> </td>
<td> <td>
<FileManager UploadMultiple="true" ShowFiles="false" FolderId="@_folderId.ToString()" /> <FileManager UploadMultiple="true" ShowFiles="false" FolderId="@_folderId" />
</td> </td>
</tr> </tr>
</table> </table>
@ -70,19 +71,33 @@
private async Task Download() private async Task Download()
{ {
try if (url == string.Empty || _folderId == -1)
{ {
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
{ {
await FileService.UploadFileAsync(url, _folderId); await FileService.UploadFileAsync(url, _folderId);
await logger.LogInformation("File Downloaded Successfully From Url {Url}", url); await logger.LogInformation("File Downloaded Successfully From Url {Url}", url);
AddModuleMessage("File Downloaded Successfully From Url", MessageType.Success); AddModuleMessage("File Downloaded Successfully From Url", MessageType.Success);
} }
else
{
AddModuleMessage("You Must Enter A Url And Select A Folder", MessageType.Warning);
}
}
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Downloading File From Url {Url} {Error}", url, ex.Message); await logger.LogError(ex, "Error Downloading File From Url {Url} {Error}", url, ex.Message);

View File

@ -25,7 +25,7 @@
</tr> </tr>
<tr> <tr>
<td> <td>
<Label for="name" HelpText="Enter the file name">Name: </Label> <Label for="name" HelpText="Enter the folder name">Name: </Label>
</td> </td>
<td> <td>
<input id="name" class="form-control" @bind="@_name" /> <input id="name" class="form-control" @bind="@_name" />
@ -112,9 +112,19 @@
private async Task SaveFolder() private async Task SaveFolder()
{ {
try if (_name == string.Empty || _parentId == -1)
{ {
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
{ {
Folder folder; Folder folder;
if (_folderId != -1) if (_folderId != -1)
@ -150,13 +160,15 @@
folder = await FolderService.AddFolderAsync(folder); folder = await FolderService.AddFolderAsync(folder);
} }
if (folder != null)
{
await FolderService.UpdateFolderOrderAsync(folder.SiteId, folder.FolderId, folder.ParentId); await FolderService.UpdateFolderOrderAsync(folder.SiteId, folder.FolderId, folder.ParentId);
await logger.LogInformation("Folder Saved {Folder}", folder); await logger.LogInformation("Folder Saved {Folder}", folder);
NavigationManager.NavigateTo(NavigateUrl()); NavigationManager.NavigateTo(NavigateUrl());
} }
else else
{ {
AddModuleMessage("Folders Must Have A Parent And A Name", MessageType.Warning); AddModuleMessage("An Error Was Encountered Saving The Folder", MessageType.Error);
} }
} }
catch (Exception ex) catch (Exception ex)

View File

@ -3,6 +3,7 @@
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IModuleDefinitionService ModuleDefinitionService @inject IModuleDefinitionService ModuleDefinitionService
@inject IModuleService ModuleService @inject IModuleService ModuleService
@inject ISystemService SystemService
<table class="table table-borderless"> <table class="table table-borderless">
<tr> <tr>
@ -34,13 +35,24 @@
<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> <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>
<td> <td>
<select id="template" class="form-control" @bind="@_template"> <select id="template" class="form-control" @onchange="(e => TemplateChanged(e))">
<option value="">&lt;Select Template&gt;</option> <option value="-">&lt;Select Template&gt;</option>
<option value="internal">Internal</option> <option value="internal">Internal</option>
<option value="external">External</option> <option value="external">External</option>
</select> </select>
</td> </td>
</tr> </tr>
@if (!string.IsNullOrEmpty(_location))
{
<tr>
<td>
<Label For="location" HelpText="Location where the module will be created">Location: </Label>
</td>
<td>
<input id="module" class="form-control" @bind="@_location" readonly />
</td>
</tr>
}
</table> </table>
<button type="button" class="btn btn-success" @onclick="CreateModule">Create Module</button> <button type="button" class="btn btn-success" @onclick="CreateModule">Create Module</button>
@ -49,7 +61,8 @@
private string _owner = string.Empty; private string _owner = string.Empty;
private string _module = string.Empty; private string _module = string.Empty;
private string _description = 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; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
@ -62,7 +75,7 @@
{ {
try 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); await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition, ModuleState.ModuleId);
@ -77,4 +90,35 @@
await logger.LogError(ex, "Error Creating Module"); 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> <Label HelpText="Upload one or more module packages. Once they are uploaded click Install to complete the installation.">Module: </Label>
</td> </td>
<td> <td>
<FileManager Filter="nupkg" ShowFiles="false" Folder="Modules" UploadMultiple="True" /> <FileManager Filter="nupkg" ShowFiles="false" Folder="Modules" UploadMultiple="true" />
</td> </td>
</tr> </tr>
</table> </table>

View File

@ -24,7 +24,7 @@
</td> </td>
<td> <td>
<select id="container" class="form-control" @bind="@_containerType"> <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) @foreach (KeyValuePair<string, string> container in _containers)
{ {
<option value="@container.Key">@container.Value</option> <option value="@container.Key">@container.Value</option>
@ -32,6 +32,17 @@
</select> </select>
</td> </td>
</tr> </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> <tr>
<td> <td>
<Label For="page" HelpText="The page that the module is on">Page: </Label> <Label For="page" HelpText="The page that the module is on">Page: </Label>
@ -77,6 +88,7 @@
private Dictionary<string, string> _containers; private Dictionary<string, string> _containers;
private string _title; private string _title;
private string _containerType; private string _containerType;
private string _allPages = "false";
private string _permissionNames = ""; private string _permissionNames = "";
private string _permissions; private string _permissions;
private string _pageId; private string _pageId;
@ -95,6 +107,15 @@
_title = ModuleState.Title; _title = ModuleState.Title;
_containers = ThemeService.GetContainerTypes(await ThemeService.GetThemesAsync()); _containers = ThemeService.GetContainerTypes(await ThemeService.GetThemesAsync());
_containerType = ModuleState.ContainerType; _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; _permissions = ModuleState.Permissions;
_permissionNames = ModuleState.ModuleDefinition.PermissionNames; _permissionNames = ModuleState.ModuleDefinition.PermissionNames;
_pageId = ModuleState.PageId.ToString(); _pageId = ModuleState.PageId.ToString();
@ -102,8 +123,8 @@
_settingsModuleType = Type.GetType(ModuleState.ModuleType); _settingsModuleType = Type.GetType(ModuleState.ModuleType);
if (_settingsModuleType != null) if (_settingsModuleType != null)
{ {
var moduleobject = Activator.CreateInstance(_settingsModuleType); var moduleobject = Activator.CreateInstance(_settingsModuleType) as IModuleControl;
_settingstitle = (string)_settingsModuleType.GetProperty("Title").GetValue(moduleobject, null); _settingstitle = moduleobject.Title;
if (string.IsNullOrEmpty(_settingstitle)) if (string.IsNullOrEmpty(_settingstitle))
{ {
_settingstitle = "Other Settings"; _settingstitle = "Other Settings";
@ -120,18 +141,26 @@
private async Task SaveModule() private async Task SaveModule()
{ {
var module = ModuleState;
module.Permissions = _permissionGrid.GetPermissions();
await ModuleService.UpdateModuleAsync(module);
var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId); var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
pagemodule.PageId = int.Parse(_pageId); pagemodule.PageId = int.Parse(_pageId);
pagemodule.Title = _title; 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.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); 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) if (_settingsModuleType != null)
{ {
var moduleType = Type.GetType(ModuleState.ModuleType); var moduleType = Type.GetType(ModuleState.ModuleType);

View File

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

View File

@ -112,7 +112,7 @@
</td> </td>
<td> <td>
<select id="Theme" class="form-control" @onchange="(e => ThemeChanged(e))"> <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) @foreach (KeyValuePair<string, string> item in _themes)
{ {
if (item.Key == _themetype) if (item.Key == _themetype)
@ -133,7 +133,7 @@
</td> </td>
<td> <td>
<select id="Layout" class="form-control" @bind="@_layouttype"> <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) @foreach (KeyValuePair<string, string> panelayout in _panelayouts)
{ {
if (panelayout.Key == _layouttype) if (panelayout.Key == _layouttype)
@ -148,6 +148,20 @@
</select> </select>
</td> </td>
</tr> </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> <tr>
<td> <td>
<Label For="Icon" HelpText="Optionally provide an icon for this page which will be displayed in the site navigation">Icon: </Label> <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 { @code {
private Dictionary<string, string> _themes; private Dictionary<string, string> _themes;
private Dictionary<string, string> _panelayouts; private Dictionary<string, string> _panelayouts;
private Dictionary<string, string> _containers = new Dictionary<string, string>();
private List<Theme> _themeList; private List<Theme> _themeList;
private List<Page> _pageList; private List<Page> _pageList;
private int _pageId; private int _pageId;
@ -217,6 +232,7 @@
private string _mode; private string _mode;
private string _themetype = "-"; private string _themetype = "-";
private string _layouttype = "-"; private string _layouttype = "-";
private string _containertype = "-";
private string _icon; private string _icon;
private string _permissions; private string _permissions;
private string _createdby; private string _createdby;
@ -241,6 +257,7 @@
_children = PageState.Pages.Where(item => item.ParentId == null).ToList(); _children = PageState.Pages.Where(item => item.ParentId == null).ToList();
_themes = ThemeService.GetThemeTypes(_themeList); _themes = ThemeService.GetThemeTypes(_themeList);
_containers = ThemeService.GetContainerTypes(_themeList);
_pageId = Int32.Parse(PageState.QueryString["id"]); _pageId = Int32.Parse(PageState.QueryString["id"]);
var page = PageState.Pages.FirstOrDefault(item => item.PageId == _pageId); var page = PageState.Pages.FirstOrDefault(item => item.PageId == _pageId);
@ -270,16 +287,21 @@
_ispersonalizable = page.IsPersonalizable.ToString(); _ispersonalizable = page.IsPersonalizable.ToString();
_mode = (page.EditMode) ? "edit" : "view"; _mode = (page.EditMode) ? "edit" : "view";
_themetype = page.ThemeType; _themetype = page.ThemeType;
_panelayouts = ThemeService.GetPaneLayoutTypes(_themeList, _themetype);
_layouttype = page.LayoutType;
if (_themetype == PageState.Site.DefaultThemeType) if (_themetype == PageState.Site.DefaultThemeType)
{ {
_themetype = "-"; _themetype = "-";
} }
_panelayouts = ThemeService.GetPaneLayoutTypes(_themeList, _themetype);
_layouttype = page.LayoutType;
if (_layouttype == PageState.Site.DefaultLayoutType) if (_layouttype == PageState.Site.DefaultLayoutType)
{ {
_layouttype = "-"; _layouttype = "-";
} }
_containertype = page.DefaultContainerType;
if (string.IsNullOrEmpty(_containertype))
{
_containertype = "-";
}
_icon = page.Icon; _icon = page.Icon;
_permissions = page.Permissions; _permissions = page.Permissions;
_createdby = page.CreatedBy; _createdby = page.CreatedBy;
@ -426,15 +448,20 @@
page.Url = _url; page.Url = _url;
page.EditMode = (_mode == "edit"); page.EditMode = (_mode == "edit");
page.ThemeType = (_themetype != "-") ? _themetype : string.Empty; page.ThemeType = (_themetype != "-") ? _themetype : string.Empty;
page.LayoutType = (_layouttype != "-") ? _layouttype : string.Empty; if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType)
if (page.ThemeType == PageState.Site.DefaultThemeType)
{ {
page.ThemeType = string.Empty; 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.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.Icon = _icon ?? string.Empty;
page.Permissions = _permissionGrid.GetPermissions(); page.Permissions = _permissionGrid.GetPermissions();
page.IsPersonalizable = (_ispersonalizable != null && Boolean.Parse(_ispersonalizable)); 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> <Label For="logo" HelpText="Upload a logo for the site">Logo: </Label>
</td> </td>
<td> <td>
<FileManager FileId="@_logofileid.ToString()" Filter="@Constants.ImageFiles" @ref="_logofilemanager" /> <FileManager FileId="@_logofileid" Filter="@Constants.ImageFiles" @ref="_logofilemanager" />
</td> </td>
</tr> </tr>
<tr> <tr>
@ -47,7 +47,7 @@
<Label For="favicon" HelpText="Select Your default icon">Favicon: </Label> <Label For="favicon" HelpText="Select Your default icon">Favicon: </Label>
</td> </td>
<td> <td>
<FileManager FileId="@_faviconfileid.ToString()" Filter="ico" @ref="_faviconfilemanager" /> <FileManager FileId="@_faviconfileid" Filter="ico" @ref="_faviconfilemanager" />
</td> </td>
</tr> </tr>
<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> <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>
<td> <td>
<FileManager FileId="@_pwaappiconfileid.ToString()" Filter="png" @ref="_pwaappiconfilemanager" /> <FileManager FileId="@_pwaappiconfileid" Filter="png" @ref="_pwaappiconfilemanager" />
</td> </td>
</tr> </tr>
<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> <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>
<td> <td>
<FileManager FileId="@_pwasplashiconfileid.ToString()" Filter="png" @ref="_pwasplashiconfilemanager" /> <FileManager FileId="@_pwasplashiconfileid" Filter="png" @ref="_pwasplashiconfilemanager" />
</td> </td>
</tr> </tr>
</table> </table>

View File

@ -8,7 +8,7 @@
<Label For="version" HelpText="Framework Version">Framework Version: </Label> <Label For="version" HelpText="Framework Version">Framework Version: </Label>
</td> </td>
<td> <td>
<input id="version" class="form-control" @bind="@_version" disabled /> <input id="version" class="form-control" @bind="@_version" readonly />
</td> </td>
</tr> </tr>
<tr> <tr>
@ -16,7 +16,7 @@
<Label For="runtime" HelpText="Blazor Runtime (Server or WebAssembly)">Blazor Runtime: </Label> <Label For="runtime" HelpText="Blazor Runtime (Server or WebAssembly)">Blazor Runtime: </Label>
</td> </td>
<td> <td>
<input id="runtime" class="form-control" @bind="@_runtime" disabled /> <input id="runtime" class="form-control" @bind="@_runtime" readonly />
</td> </td>
</tr> </tr>
<tr> <tr>
@ -24,7 +24,7 @@
<Label For="clrversion" HelpText="Common Language Runtime Version">CLR Version: </Label> <Label For="clrversion" HelpText="Common Language Runtime Version">CLR Version: </Label>
</td> </td>
<td> <td>
<input id="clrversion" class="form-control" @bind="@_clrversion" disabled /> <input id="clrversion" class="form-control" @bind="@_clrversion" readonly />
</td> </td>
</tr> </tr>
<tr> <tr>
@ -32,7 +32,7 @@
<Label For="osversion" HelpText="Operating System Version">OS Version: </Label> <Label For="osversion" HelpText="Operating System Version">OS Version: </Label>
</td> </td>
<td> <td>
<input id="osversion" class="form-control" @bind="@_osversion" disabled /> <input id="osversion" class="form-control" @bind="@_osversion" readonly />
</td> </td>
</tr> </tr>
<tr> <tr>
@ -40,7 +40,7 @@
<Label For="serverpath" HelpText="Server Path">Server Path: </Label> <Label For="serverpath" HelpText="Server Path">Server Path: </Label>
</td> </td>
<td> <td>
<input id="serverpath" class="form-control" @bind="@_serverpath" disabled /> <input id="serverpath" class="form-control" @bind="@_serverpath" readonly />
</td> </td>
</tr> </tr>
<tr> <tr>
@ -48,7 +48,7 @@
<Label For="servertime" HelpText="Server Time">Server Time: </Label> <Label For="servertime" HelpText="Server Time">Server Time: </Label>
</td> </td>
<td> <td>
<input id="servertime" class="form-control" @bind="@_servertime" disabled /> <input id="servertime" class="form-control" @bind="@_servertime" readonly />
</td> </td>
</tr> </tr>
</table> </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> <Label HelpText="Upload one or more theme packages. Once they are uploaded click Install to complete the installation.">Theme: </Label>
</td> </td>
<td> <td>
<FileManager Filter="nupkg" ShowFiles="false" Folder="Themes" UploadMultiple="True" /> <FileManager Filter="nupkg" ShowFiles="false" Folder="Themes" UploadMultiple="@true" />
</td> </td>
</tr> </tr>
</table> </table>

View File

@ -11,29 +11,27 @@
<TabPanel Name="Download"> <TabPanel Name="Download">
@if (_upgradeavailable) @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> <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))>Download</button> @("Framework") @_package.Version <button type="button" class="btn btn-success" @onclick=@(async () => await Download(Constants.PackageId, Constants.Version))>Upgrade</button>
} }
else else
{ {
<ModuleMessage Type="MessageType.Info" Message="Framework Is Already Up To Date"></ModuleMessage> <ModuleMessage Type="MessageType.Info" Message="Framework Is Already Up To Date"></ModuleMessage>
} }
</TabPanel> </TabPanel>
@if (_upgradeavailable)
{
<TabPanel Name="Upload"> <TabPanel Name="Upload">
<table class="table table-borderless"> <table class="table table-borderless">
<tr> <tr>
<td> <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>
<td> <td>
<FileManager Filter="nupkg" ShowFiles="false" Folder="Framework" /> <FileManager Filter="nupkg" Folder="Framework" />
</td> </td>
</tr> </tr>
</table> </table>
<button type="button" class="btn btn-success" @onclick="Upgrade">Install</button>
</TabPanel> </TabPanel>
}
</TabStrip> </TabStrip>
} }

View File

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

View File

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

View File

@ -1,6 +1,6 @@
@namespace Oqtane.Modules.Controls @namespace Oqtane.Modules.Controls
@inherits ModuleBase @inherits ModuleBase
@attribute [OqtaneIgnore]
@if (_visible) @if (_visible)
{ {
<div class="app-admin-modal"> <div class="app-admin-modal">
@ -109,8 +109,8 @@
Type moduleType = Type.GetType(typename); Type moduleType = Type.GetType(typename);
if (moduleType != null) if (moduleType != null)
{ {
var moduleobject = Activator.CreateInstance(moduleType); var moduleobject = Activator.CreateInstance(moduleType) as IModuleControl;
security = (SecurityAccessLevel)moduleType.GetProperty("SecurityAccessLevel").GetValue(moduleobject, null); security = moduleobject.SecurityAccessLevel;
} }
else else
{ {

View File

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

View File

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

View File

@ -1,5 +1,7 @@
@namespace Oqtane.Modules.Controls @namespace Oqtane.Modules.Controls
@inherits ModuleBase @inherits ModuleBase
@attribute [OqtaneIgnore]
@inject IFolderService FolderService @inject IFolderService FolderService
@inject IFileService FileService @inject IFileService FileService
@inject IJSRuntime JsRuntime @inject IJSRuntime JsRuntime
@ -9,6 +11,8 @@
<div id="@Id" class="container-fluid px-0"> <div id="@Id" class="container-fluid px-0">
<div class="row"> <div class="row">
<div class="col"> <div class="col">
@if (ShowFolders || FolderId <= 0)
{
<div> <div>
<select class="form-control" @onchange="(e => FolderChanged(e))"> <select class="form-control" @onchange="(e => FolderChanged(e))">
@if (string.IsNullOrEmpty(Folder)) @if (string.IsNullOrEmpty(Folder))
@ -17,7 +21,7 @@
} }
@foreach (Folder folder in _folders) @foreach (Folder folder in _folders)
{ {
if (folder.FolderId == _folderid) if (folder.FolderId == FolderId)
{ {
<option value="@(folder.FolderId)" selected>@(new string('-', folder.Level * 2))@(folder.Name)</option> <option value="@(folder.FolderId)" selected>@(new string('-', folder.Level * 2))@(folder.Name)</option>
} }
@ -28,14 +32,15 @@
} }
</select> </select>
</div> </div>
@if (_showfiles) }
@if (ShowFiles)
{ {
<div> <div>
<select class="form-control" @onchange="(e => FileChanged(e))"> <select class="form-control" @onchange="(e => FileChanged(e))">
<option value="-1">&lt;Select File&gt;</option> <option value="-1">&lt;Select File&gt;</option>
@foreach (File file in _files) @foreach (File file in _files)
{ {
if (file.FileId == _fileid) if (file.FileId == FileId)
{ {
<option value="@(file.FileId)" selected>@(file.Name)</option> <option value="@(file.FileId)" selected>@(file.Name)</option>
} }
@ -47,10 +52,10 @@
</select> </select>
</div> </div>
} }
@if (_haseditpermission) @if (ShowUpload && _haseditpermission)
{ {
<div> <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/>
} }
@ -67,8 +72,8 @@
} }
</span> </span>
</div> </div>
@((MarkupString)_message)
} }
@((MarkupString) _message)
</div> </div>
@if (_image != string.Empty) @if (_image != string.Empty)
{ {
@ -83,15 +88,12 @@
@code { @code {
private string _id; private string _id;
private List<Folder> _folders; private List<Folder> _folders;
private int _folderid = -1;
private List<File> _files = new List<File>(); private List<File> _files = new List<File>();
private int _fileid = -1;
private bool _showfiles = true; private bool _showfiles = true;
private string _fileinputid = string.Empty; private string _fileinputid = string.Empty;
private string _progressinfoid = string.Empty; private string _progressinfoid = string.Empty;
private string _progressbarid = string.Empty; private string _progressbarid = string.Empty;
private string _filter = "*"; private string _filter = "*";
private bool _uploadmultiple = false;
private bool _haseditpermission = false; private bool _haseditpermission = false;
private string _message = string.Empty; private string _message = string.Empty;
private string _image = string.Empty; private string _image = string.Empty;
@ -104,19 +106,25 @@
public string Folder { get; set; } // optional - for setting a specific folder by default public string Folder { get; set; } // optional - for setting a specific folder by default
[Parameter] [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] [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] [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] [Parameter]
public string Filter { get; set; } // optional - comma delimited list of file types that can be selected or uploaded ie. "jpg,gif" public string Filter { get; set; } // optional - comma delimited list of file types that can be selected or uploaded ie. "jpg,gif"
[Parameter] [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() protected override async Task OnInitializedAsync()
{ {
@ -128,38 +136,26 @@
if (!string.IsNullOrEmpty(Folder)) if (!string.IsNullOrEmpty(Folder))
{ {
_folders = new List<Folder> {new Folder {FolderId = -1, Name = Folder}}; _folders = new List<Folder> {new Folder {FolderId = -1, Name = Folder}};
_folderid = -1; FolderId = -1;
} }
else else
{ {
_folders = await FolderService.GetFoldersAsync(ModuleState.SiteId); _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); File file = await FileService.GetFileAsync(FileId);
if (_fileid != -1)
{
File file = await FileService.GetFileAsync(int.Parse(FileId));
if (file != null) if (file != null)
{ {
_folderid = file.FolderId; FolderId = file.FolderId;
} }
else else
{ {
_fileid = -1; // file does not exist FileId = -1; // file does not exist
} }
} }
await SetImage(); await SetImage();
}
if (!string.IsNullOrEmpty(ShowFiles))
{
_showfiles = bool.Parse(ShowFiles);
}
if (!string.IsNullOrEmpty(Filter)) if (!string.IsNullOrEmpty(Filter))
{ {
@ -173,11 +169,6 @@
_fileinputid = _guid + "FileInput"; _fileinputid = _guid + "FileInput";
_progressinfoid = _guid + "ProgressInfo"; _progressinfoid = _guid + "ProgressInfo";
_progressbarid = _guid + "ProgressBar"; _progressbarid = _guid + "ProgressBar";
if (!string.IsNullOrEmpty(UploadMultiple))
{
_uploadmultiple = bool.Parse(UploadMultiple);
}
} }
private async Task GetFiles() private async Task GetFiles()
@ -190,11 +181,11 @@
} }
else else
{ {
Folder folder = _folders.FirstOrDefault(item => item.FolderId == _folderid); Folder folder = _folders.FirstOrDefault(item => item.FolderId == FolderId);
if (folder != null) if (folder != null)
{ {
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, folder.Permissions); _haseditpermission = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, folder.Permissions);
_files = await FileService.GetFilesAsync(_folderid); _files = await FileService.GetFilesAsync(FolderId);
} }
else else
{ {
@ -221,9 +212,9 @@
_message = string.Empty; _message = string.Empty;
try try
{ {
_folderid = int.Parse((string)e.Value); FolderId = int.Parse((string) e.Value);
await GetFiles(); await GetFiles();
_fileid = -1; FileId = -1;
_image = string.Empty; _image = string.Empty;
StateHasChanged(); StateHasChanged();
} }
@ -237,7 +228,7 @@
private async Task FileChanged(ChangeEventArgs e) private async Task FileChanged(ChangeEventArgs e)
{ {
_message = string.Empty; _message = string.Empty;
_fileid = int.Parse((string)e.Value); FileId = int.Parse((string) e.Value);
await SetImage(); await SetImage();
StateHasChanged(); StateHasChanged();
@ -246,9 +237,9 @@
private async Task SetImage() private async Task SetImage()
{ {
_image = string.Empty; _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) if (file != null && file.ImageHeight != 0 && file.ImageWidth != 0)
{ {
var maxwidth = 200; var maxwidth = 200;
@ -258,7 +249,7 @@
var ratioY = (double) maxheight / (double) file.ImageHeight; var ratioY = (double) maxheight / (double) file.ImageHeight;
var ratio = ratioX < ratioY ? ratioX : ratioY; var ratio = ratioX < ratioY ? ratioX : ratioY;
_image = "<img src=\"" + ContentUrl(_fileid) + "\" alt=\"" + file.Name + _image = "<img src=\"" + ContentUrl(FileId) + "\" alt=\"" + file.Name +
"\" width=\"" + Convert.ToInt32(file.ImageWidth * ratio).ToString() + "\" width=\"" + Convert.ToInt32(file.ImageWidth * ratio).ToString() +
"\" height=\"" + Convert.ToInt32(file.ImageHeight * ratio).ToString() + "\" />"; "\" height=\"" + Convert.ToInt32(file.ImageHeight * ratio).ToString() + "\" />";
} }
@ -280,7 +271,7 @@
} }
else else
{ {
result = await FileService.UploadFilesAsync(_folderid, upload, _guid); result = await FileService.UploadFilesAsync(FolderId, upload, _guid);
} }
if (result == string.Empty) if (result == string.Empty)
@ -294,7 +285,7 @@
var file = _files.Where(item => item.Name == upload[0]).FirstOrDefault(); var file = _files.Where(item => item.Name == upload[0]).FirstOrDefault();
if (file != null) if (file != null)
{ {
_fileid = file.FileId; FileId = file.FileId;
await SetImage(); await SetImage();
} }
} }
@ -324,21 +315,21 @@
try try
{ {
await FileService.DeleteFileAsync(_fileid); await FileService.DeleteFileAsync(FileId);
await logger.LogInformation("File Deleted {File}", _fileid); await logger.LogInformation("File Deleted {File}", FileId);
_message = "<br /><div class=\"alert alert-success\" role=\"alert\">File Deleted</div>"; _message = "<br /><div class=\"alert alert-success\" role=\"alert\">File Deleted</div>";
await GetFiles(); await GetFiles();
_fileid = -1; FileId = -1;
await SetImage(); await SetImage();
StateHasChanged(); StateHasChanged();
} }
catch (Exception ex) 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>"; _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 @namespace Oqtane.Modules.Controls
@inherits ModuleBase @inherits ModuleBase
@attribute [OqtaneIgnore]
@if (!string.IsNullOrEmpty(HelpText)) @if (!string.IsNullOrEmpty(HelpText))
{ {

View File

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

View File

@ -1,7 +1,9 @@
@namespace Oqtane.Modules.Controls @namespace Oqtane.Modules.Controls
@inherits ModuleBase @inherits ModuleBase
@attribute [OqtaneIgnore]
@typeparam TableItem @typeparam TableItem
<p> <p>
@if(Format == "Table") @if(Format == "Table")
{ {

View File

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

View File

@ -1,15 +1,21 @@
@namespace Oqtane.Modules.Controls @namespace Oqtane.Modules.Controls
@inherits ModuleBase @inherits ModuleBase
@attribute [OqtaneIgnore]
@inject IJSRuntime JsRuntime @inject IJSRuntime JsRuntime
<div class="row" style="margin-bottom: 50px;">
<div class="col">
<TabStrip>
<TabPanel Name="Rich" Heading="Rich Text Editor">
@if (_filemanagervisible) @if (_filemanagervisible)
{ {
<FileManager @ref="_fileManager" Filter="@Constants.ImageFiles" /> <FileManager @ref="_fileManager" Filter="@Constants.ImageFiles" />
@((MarkupString)_message) @((MarkupString)_message)
<br /> <br />
} }
<div class="row justify-content-center"> <div class="row justify-content-center" style="margin-bottom: 20px;">
<button type="button" class="btn btn-success" @onclick="InsertImage">Insert Image</button> <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) @if (_filemanagervisible)
{ {
@((MarkupString)"&nbsp;&nbsp;") @((MarkupString)"&nbsp;&nbsp;")
@ -19,22 +25,72 @@
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<div @ref="@_toolBar"> <div @ref="@_toolBar">
@if (ToolbarContent != null)
{
@ToolbarContent @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>
<div @ref="@_editorElement"> <div @ref="@_editorElement">
</div> </div>
</div> </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>
@code { @code {
private ElementReference _editorElement; private ElementReference _editorElement;
private ElementReference _toolBar; private ElementReference _toolBar;
private bool _filemanagervisible = false; private bool _filemanagervisible = false;
private FileManager _fileManager; private FileManager _fileManager;
private string _content = string.Empty;
private string _original = string.Empty;
private string _message = string.Empty; private string _message = string.Empty;
[Parameter] [Parameter]
public RenderFragment ToolbarContent { get; set; } public string Content { get; set; }
[Parameter] [Parameter]
public bool ReadOnly { get; set; } = false; public bool ReadOnly { get; set; } = false;
@ -42,60 +98,78 @@
[Parameter] [Parameter]
public string Placeholder { get; set; } = "Enter Your Content..."; public string Placeholder { get; set; } = "Enter Your Content...";
// parameters only applicable to rich text editor
[Parameter]
public RenderFragment ToolbarContent { get; set; }
[Parameter] [Parameter]
public string Theme { get; set; } = "snow"; public string Theme { get; set; } = "snow";
[Parameter] [Parameter]
public string DebugLevel { get; set; } = "info"; public string DebugLevel { get; set; } = "info";
protected override void OnInitialized()
{
_content = Content; // raw HTML
}
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
if (firstRender) if (firstRender)
{ {
await RichTextEditorInterop.CreateEditor( var interop = new RichTextEditorInterop(JsRuntime);
JsRuntime,
await interop.CreateEditor(
_editorElement, _editorElement,
_toolBar, _toolBar,
ReadOnly, ReadOnly,
Placeholder, Placeholder,
Theme, Theme,
DebugLevel); 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( _filemanagervisible = false;
JsRuntime, _message = string.Empty;
_editorElement); 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() public async Task<string> GetHtml()
{ {
return await RichTextEditorInterop.GetHtml( // get rich text content
JsRuntime, var interop = new RichTextEditorInterop(JsRuntime);
_editorElement); string content = await interop.GetHtml(_editorElement);
}
public async Task<string> GetContent() if (_original != content)
{ {
return await RichTextEditorInterop.GetContent( // rich text content has changed - return it
JsRuntime, return content;
_editorElement);
} }
else
public async Task LoadContent(string content)
{ {
await RichTextEditorInterop.LoadEditorContent( // return raw html content
JsRuntime, return _content;
_editorElement, content);
} }
public async Task EnableEditor(bool mode)
{
await RichTextEditorInterop.EnableEditor(
JsRuntime,
_editorElement, mode);
} }
public async Task InsertImage() public async Task InsertImage()
@ -105,9 +179,8 @@
var fileid = _fileManager.GetFileId(); var fileid = _fileManager.GetFileId();
if (fileid != -1) if (fileid != -1)
{ {
await RichTextEditorInterop.InsertImage( var interop = new RichTextEditorInterop(JsRuntime);
JsRuntime, await interop.InsertImage(_editorElement, ContentUrl(fileid));
_editorElement, ContentUrl(fileid));
_filemanagervisible = false; _filemanagervisible = false;
_message = string.Empty; _message = string.Empty;
} }
@ -121,16 +194,25 @@
_filemanagervisible = true; _filemanagervisible = true;
_message = string.Empty; _message = string.Empty;
} }
StateHasChanged(); StateHasChanged();
} }
public void CloseFileManager() // other rich text editor methods which can be used by developers
public async Task<string> GetText()
{ {
_filemanagervisible = false; var interop = new RichTextEditorInterop(JsRuntime);
_message = string.Empty; return await interop.GetText(_editorElement);
StateHasChanged();
} }
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 @namespace Oqtane.Modules.Controls
@inherits ModuleBase @inherits ModuleBase
@attribute [OqtaneIgnore]
<div class="d-flex"> <div class="d-flex">
<div> <div>

View File

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

View File

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

View File

@ -3,115 +3,62 @@
@using Oqtane.Modules.Controls @using Oqtane.Modules.Controls
@namespace Oqtane.Modules.HtmlText @namespace Oqtane.Modules.HtmlText
@inherits ModuleBase @inherits ModuleBase
@inject IHtmlTextService HtmlTextService
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject HttpClient http
@inject SiteState sitestate
<div class="row" style="margin-bottom: 50px;"> @if (_content != null)
<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> <RichTextEditor Content="@_content" @ref="@RichTextEditorHtml"></RichTextEditor>
}
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> <button type="button" class="btn btn-success" @onclick="SaveContent">Save</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">Cancel</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">Cancel</NavLink>
</div> @if (!string.IsNullOrEmpty(_content))
</div> {
<br />
<div class="row"> <br />
<div class="col"> <AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo> }
</div> }
</div>
@code { @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 SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Title => "Edit Html/Text"; public override string Title => "Edit Html/Text";
public bool RichTextEditorMode public override List<Resource> Resources => new List<Resource>()
{ {
get => _richTextEditorMode; new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" },
set // 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" },
_richTextEditorMode = value; 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" }
};
if (_richTextEditorMode) private RichTextEditor RichTextEditorHtml;
{ private string _content = null;
_visibleText = "d-none"; private string _createdby;
_visibleRich = string.Empty; private DateTime _createdon;
} private string _modifiedby;
else private DateTime _modifiedon;
{
_visibleText = string.Empty;
_visibleRich = "d-none";
}
}
}
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnInitializedAsync()
{ {
try try
{ {
if (firstRender) var htmltext = await HtmlTextService.GetHtmlTextAsync(ModuleState.ModuleId);
if (htmltext != null)
{ {
if (content == null) _content = htmltext.Content;
{ _content = _content.Replace(Constants.ContentUrl, "/" + PageState.Alias.AliasId.ToString() + Constants.ContentUrl);
RichTextEditorMode = true; _createdby = htmltext.CreatedBy;
await LoadText(); _createdon = htmltext.CreatedOn;
_modifiedby = htmltext.ModifiedBy;
_modifiedon = htmltext.ModifiedOn;
} }
else
{
_content = string.Empty;
} }
} }
catch (Exception ex) catch (Exception ex)
@ -121,64 +68,25 @@
} }
} }
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() private async Task SaveContent()
{ {
if (RichTextEditorMode) string content = await RichTextEditorHtml.GetHtml();
{ content = content.Replace("/" + PageState.Alias.AliasId.ToString() + Constants.ContentUrl, Constants.ContentUrl);
content = await RichTextEditorHtml.GetHtml();
}
content = content.Replace(((PageState.Alias.Path == string.Empty) ? "/~" : PageState.Alias.Path) + Constants.ContentUrl, Constants.ContentUrl);
try try
{ {
var htmltextservice = new HtmlTextService(http, sitestate); var htmltext = await HtmlTextService.GetHtmlTextAsync(ModuleState.ModuleId);
var htmltext = await htmltextservice.GetHtmlTextAsync(ModuleState.ModuleId);
if (htmltext != null) if (htmltext != null)
{ {
htmltext.Content = content; htmltext.Content = content;
await htmltextservice.UpdateHtmlTextAsync(htmltext); await HtmlTextService.UpdateHtmlTextAsync(htmltext);
} }
else else
{ {
htmltext = new HtmlTextInfo(); htmltext = new HtmlTextInfo();
htmltext.ModuleId = ModuleState.ModuleId; htmltext.ModuleId = ModuleState.ModuleId;
htmltext.Content = content; htmltext.Content = content;
await htmltextservice.AddHtmlTextAsync(htmltext); await HtmlTextService.AddHtmlTextAsync(htmltext);
} }
await logger.LogInformation("Html/Text Content Saved {HtmlText}", htmltext); await logger.LogInformation("Html/Text Content Saved {HtmlText}", htmltext);
@ -190,5 +98,4 @@
AddModuleMessage("Error Saving Content", MessageType.Error); AddModuleMessage("Error Saving Content", MessageType.Error);
} }
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -4,12 +4,16 @@ using System.Threading.Tasks;
using Oqtane.Services; using Oqtane.Services;
using System.Reflection; using System.Reflection;
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Json;
using Oqtane.Modules; using Oqtane.Modules;
using Oqtane.Shared; using Oqtane.Shared;
using Oqtane.Providers; using Oqtane.Providers;
using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Authorization;
using System.IO.Compression;
using System.IO;
namespace Oqtane.Client namespace Oqtane.Client
{ {
@ -19,10 +23,9 @@ namespace Oqtane.Client
{ {
var builder = WebAssemblyHostBuilder.CreateDefault(args); var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app"); builder.RootComponents.Add<App>("app");
HttpClient httpClient = new HttpClient {BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)};
builder.Services.AddSingleton( builder.Services.AddSingleton(httpClient);
new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }
);
builder.Services.AddOptions(); builder.Services.AddOptions();
// register auth services // register auth services
@ -57,14 +60,16 @@ namespace Oqtane.Client
builder.Services.AddScoped<ISqlService, SqlService>(); builder.Services.AddScoped<ISqlService, SqlService>();
builder.Services.AddScoped<ISystemService, SystemService>(); builder.Services.AddScoped<ISystemService, SystemService>();
await LoadClientAssemblies(httpClient);
// dynamically register module contexts and repository services // dynamically register module contexts and repository services
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies) foreach (Assembly assembly in assemblies)
{ {
Type[] implementationtypes = assembly.GetTypes() var implementationTypes = assembly.GetTypes()
.Where(item => item.GetInterfaces().Contains(typeof(IService))) .Where(item => item.GetInterfaces().Contains(typeof(IService)));
.ToArray();
foreach (Type implementationtype in implementationtypes) foreach (Type implementationtype in implementationTypes)
{ {
Type servicetype = Type.GetType(implementationtype.AssemblyQualifiedName.Replace(implementationtype.Name, "I" + implementationtype.Name)); Type servicetype = Type.GetType(implementationtype.AssemblyQualifiedName.Replace(implementationtype.Name, "I" + implementationtype.Name));
if (servicetype != null) if (servicetype != null)
@ -76,9 +81,62 @@ namespace Oqtane.Client
builder.Services.AddScoped(implementationtype, implementationtype); // no interface defined for service builder.Services.AddScoped(implementationtype, implementationtype); // no interface defined for service
} }
} }
assembly.GetInstances<IClientStartup>()
.ToList()
.ForEach(x => x.ConfigureServices(builder.Services));
} }
await builder.Build().RunAsync(); await builder.Build().RunAsync();
} }
private static async Task LoadClientAssemblies(HttpClient http)
{
// 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.Collections.Generic;
using System.Net; using System.Net;
using System; using System;
using Oqtane.Shared;
namespace Oqtane.Services namespace Oqtane.Services
{ {
public class AliasService : ServiceBase, IAliasService public class AliasService : ServiceBase, IAliasService
{ {
public AliasService(HttpClient http) :base(http) { } private readonly SiteState _siteState;
private string Apiurl => CreateApiUrl("Alias"); public AliasService(HttpClient http, SiteState siteState) : base(http)
{
_siteState = siteState;
}
private string Apiurl => CreateApiUrl(_siteState.Alias, "Alias");
public async Task<List<Alias>> GetAliasesAsync() public async Task<List<Alias>> GetAliasesAsync()
{ {

View File

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

View File

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

View File

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

View File

@ -49,46 +49,9 @@ namespace Oqtane.Services
await DeleteAsync($"{Apiurl}/{moduleDefinitionId}?siteid={siteId}"); 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) 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>(); var result = await response.Content.ReadFromJsonAsync<TResult>();
return result; return result;
} }
return default; return default;
} }
@ -121,6 +120,8 @@ namespace Oqtane.Services
if (response.StatusCode != HttpStatusCode.NoContent && response.StatusCode != HttpStatusCode.NotFound) if (response.StatusCode != HttpStatusCode.NoContent && response.StatusCode != HttpStatusCode.NotFound)
{ {
//TODO: Log errors here //TODO: Log errors here
Console.WriteLine($"Request: {response.RequestMessage.RequestUri}");
Console.WriteLine($"Response status: {response.StatusCode} {response.ReasonPhrase}"); Console.WriteLine($"Response status: {response.StatusCode} {response.ReasonPhrase}");
} }
@ -134,13 +135,13 @@ namespace Oqtane.Services
//TODO Missing content JSON validation //TODO Missing content JSON validation
} }
// create an API Url which is tenant agnostic ( for use with entities in the MasterDB ) // create an API Url which is tenant agnostic ( for use during installation )
public string CreateApiUrl(string serviceName) public string CreateApiUrl(string serviceName)
{ {
return CreateApiUrl(null, serviceName); return CreateApiUrl(null, serviceName);
} }
// create an API Url which is tenant aware ( for use with entities in the TenantDB ) // create an API Url which is tenant aware ( for use with repositories )
public string CreateApiUrl(Alias alias, string serviceName) public string CreateApiUrl(Alias alias, string serviceName)
{ {
string apiurl = "/"; string apiurl = "/";

View File

@ -1,4 +1,5 @@
using Oqtane.Models; using System;
using Oqtane.Models;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Net.Http; using System.Net.Http;
using System.Linq; using System.Linq;
@ -106,7 +107,7 @@ namespace Oqtane.Services
foreach (KeyValuePair<string, string> kvp in settings) 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) if (setting == null)
{ {
setting = new Setting(); setting = new Setting();

View File

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

View File

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

View File

@ -23,33 +23,6 @@ namespace Oqtane.Services
public async Task<List<Theme>> GetThemesAsync() public async Task<List<Theme>> GetThemesAsync()
{ {
List<Theme> themes = await GetJsonAsync<List<Theme>>(Apiurl); 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(); return themes.OrderBy(item => item.Name).ToList();
} }

View File

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

View File

@ -7,15 +7,7 @@
<div class="sidebar"> <div class="sidebar">
<nav class="navbar"> <nav class="navbar">
<Logo /> <Logo /><Menu Orientation="Vertical" />
<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>
</nav> </nav>
</div> </div>
@ -36,9 +28,8 @@
@code { @code {
public override string Panes => "Content"; 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] [CascadingParameter]
protected Module ModuleState { get; set; } protected Module ModuleState { get; set; }
public virtual string Name { get; set; }
public string ThemePath() public string ThemePath()
{ {

View File

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

View File

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

View File

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

View File

@ -1,21 +1,13 @@
@namespace Oqtane.Themes.Controls @namespace Oqtane.Themes.Controls
@inherits ThemeControlBase @inherits ThemeControlBase
@inject NavigationManager NavigationManager @attribute [OqtaneIgnore]
@if (PageState.Site.LogoFileId != null) @if (PageState.Site.LogoFileId != null)
{ {
<a href="@Href"> <span class="app-logo">
<a href="@PageState.Alias.Path">
<img class="img-fluid" src="@ContentUrl(PageState.Site.LogoFileId.Value)" alt="@PageState.Site.Name" /> <img class="img-fluid" src="@ContentUrl(PageState.Site.LogoFileId.Value)" alt="@PageState.Site.Name" />
</a> </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 @namespace Oqtane.Themes.Controls
@inherits MenuBase @inherits MenuBase
@attribute [OqtaneIgnore]
@if (MenuPages.Any()) @if (MenuPages.Any())
{ {
<div class="app-menu"> <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"> <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> <span class="navbar-toggler-icon"></span>
</button> </button>
</span>
<div class="app-menu">
<div class="collapse navbar-collapse" id="Menu"> <div class="collapse navbar-collapse" id="Menu">
<ul class="navbar-nav mr-auto"> <ul class="navbar-nav mr-auto">
@foreach (var p in MenuPages) @foreach (var p in MenuPages)

View File

@ -1,8 +1,15 @@
@namespace Oqtane.Themes.Controls @namespace Oqtane.Themes.Controls
@inherits MenuBase @inherits MenuBase
@attribute [OqtaneIgnore]
@if (MenuPages.Any()) @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"> <div class="app-menu">
<div class="collapse navbar-collapse" id="Menu">
<ul class="nav flex-column"> <ul class="nav flex-column">
@foreach (var p in MenuPages) @foreach (var p in MenuPages)
{ {
@ -18,10 +25,10 @@
<span class="oi oi-@p.Icon" aria-hidden="true"></span> <span class="oi oi-@p.Icon" aria-hidden="true"></span>
} }
@p.Name @p.Name
</a> </a>
</li> </li>
} }
</ul> </ul>
</div> </div>
</div>
} }

View File

@ -1,8 +1,10 @@
@namespace Oqtane.Themes.Controls @namespace Oqtane.Themes.Controls
@inherits ModuleActionsBase @inherits ModuleActionsBase
@attribute [OqtaneIgnore]
@if (PageState.EditMode && !PageState.Page.EditMode && UserSecurity.IsAuthorized(PageState.User,PermissionNames.Edit, ModuleState.Permissions)) @if (PageState.EditMode && !PageState.Page.EditMode && UserSecurity.IsAuthorized(PageState.User,PermissionNames.Edit, ModuleState.Permissions))
{ {
<div class="app-moduleactions">
<a class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"></a> <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);"> <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) @foreach (var action in Actions)
@ -17,4 +19,5 @@
} }
} }
</div> </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)}); 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) if (pane != ModuleState.Pane)
{ {

View File

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

View File

@ -1,7 +1,9 @@
@namespace Oqtane.Themes.Controls @namespace Oqtane.Themes.Controls
@inherits ThemeControlBase @inherits ThemeControlBase
@attribute [OqtaneIgnore]
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
<span class="app-profile">
<AuthorizeView> <AuthorizeView>
<Authorizing> <Authorizing>
<text>...</text> <text>...</text>
@ -16,7 +18,7 @@
} }
</NotAuthorized> </NotAuthorized>
</AuthorizeView> </AuthorizeView>
</span>
@code { @code {

View File

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

View File

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

View File

@ -3,9 +3,12 @@
<main role="main"> <main role="main">
<nav class="navbar navbar-expand-md navbar-dark bg-primary fixed-top"> <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> </nav>
<div class="container"> <div class="content container">
<PaneLayout /> <PaneLayout />
<div class="row px-4"> <div class="row px-4">
<Pane Name="Admin" /> <Pane Name="Admin" />
@ -16,8 +19,12 @@
@code { @code {
public override string Panes => string.Empty; 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 Microsoft.JSInterop;
using Oqtane.Models;
using Oqtane.Shared; using Oqtane.Shared;
using Oqtane.UI; using Oqtane.UI;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Oqtane.Themes namespace Oqtane.Themes
@ -11,24 +13,21 @@ namespace Oqtane.Themes
[Inject] [Inject]
protected IJSRuntime JSRuntime { get; set; } protected IJSRuntime JSRuntime { get; set; }
// optional interface properties
[CascadingParameter] [CascadingParameter]
protected PageState PageState { get; set; } protected PageState PageState { get; set; }
public virtual string Panes { get; set; } public virtual string Panes { get; set; }
public virtual List<Resource> Resources { get; set; }
// path method
public string ThemePath() public string ThemePath()
{ {
return "Themes/" + GetType().Namespace + "/"; return "Themes/" + GetType().Namespace + "/";
} }
public async Task IncludeCSS(string Url) // url methods
{
if (!Url.StartsWith("http"))
{
Url = ThemePath() + Url;
}
var interop = new Interop(JSRuntime);
await interop.IncludeCSS("Theme", Url);
}
public string NavigateUrl() public string NavigateUrl()
{ {

View File

@ -7,7 +7,8 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="mx-auto text-center"> <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>
</div> </div>
<hr class="app-rule" /> <hr class="app-rule" />

View File

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

View File

@ -32,7 +32,7 @@
} }
else else
{ {
_paneadminborder = ""; _paneadminborder = "container";
_panetitle = ""; _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 AuthenticationStateProvider AuthenticationStateProvider
@inject SiteState SiteState @inject SiteState SiteState
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@ -9,11 +11,7 @@
@inject IPageService PageService @inject IPageService PageService
@inject IUserService UserService @inject IUserService UserService
@inject IModuleService ModuleService @inject IModuleService ModuleService
@inject IModuleDefinitionService ModuleDefinitionService
@inject ILogService LogService @inject ILogService LogService
@using System.Diagnostics.CodeAnalysis
@using Oqtane.Enums
@using System.Runtime.InteropServices
@implements IHandleAfterRender @implements IHandleAfterRender
@DynamicComponent @DynamicComponent
@ -158,7 +156,6 @@
if (PageState == null || reload >= Reload.Site) if (PageState == null || reload >= Reload.Site)
{ {
await ModuleDefinitionService.LoadModuleDefinitionsAsync(site.SiteId, runtime);
pages = await PageService.GetPagesAsync(site.SiteId); pages = await PageService.GetPagesAsync(site.SiteId);
} }
else else
@ -182,7 +179,7 @@
// extract admin route elements from path // extract admin route elements from path
var segments = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); var segments = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
int result; 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)) if (segments.Length >= 2 && int.TryParse(segments[segments.Length - 2], out result))
{ {
action = segments[segments.Length - 1]; action = segments[segments.Length - 1];
@ -191,7 +188,7 @@
} }
else 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)) if (segments.Length >= 1 && int.TryParse(segments[segments.Length - 1], out result))
{ {
moduleid = result; moduleid = result;
@ -241,21 +238,7 @@
{ {
page = await ProcessPage(page, site, user); page = await ProcessPage(page, site, user);
_pagestate = new PageState if (PageState != null && (PageState.ModuleId != moduleid || PageState.Action != action))
{
Alias = alias,
Site = site,
Pages = pages,
Page = page,
User = user,
Uri = new Uri(_absoluteUri, UriKind.Absolute),
QueryString = querystring,
ModuleId = moduleid,
Action = action,
Runtime = runtime
};
if (PageState != null && (PageState.ModuleId != _pagestate.ModuleId || PageState.Action != _pagestate.Action))
{ {
reload = Reload.Page; reload = Reload.Page;
} }
@ -263,15 +246,29 @@
if (PageState == null || reload >= Reload.Page) if (PageState == null || reload >= Reload.Page)
{ {
modules = await ModuleService.GetModulesAsync(site.SiteId); modules = await ModuleService.GetModulesAsync(site.SiteId);
modules = ProcessModules(modules, page.PageId, _pagestate.ModuleId, _pagestate.Action, page.Panes, site.DefaultContainerType); (page, modules) = ProcessModules(page, modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType);
} }
else else
{ {
modules = PageState.Modules; modules = PageState.Modules;
} }
_pagestate.Modules = modules;
_pagestate.EditMode = editmode; _pagestate = new PageState
_pagestate.LastSyncDate = lastsyncdate; {
Alias = alias,
Site = site,
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
};
OnStateChange?.Invoke(_pagestate); OnStateChange?.Invoke(_pagestate);
} }
@ -358,19 +355,30 @@
page.ThemeType = site.DefaultThemeType; page.ThemeType = site.DefaultThemeType;
page.LayoutType = site.DefaultLayoutType; 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)) if (!string.IsNullOrEmpty(page.LayoutType))
{ {
type = Type.GetType(page.LayoutType); Type layouttype = Type.GetType(page.LayoutType);
} var layoutobject = Activator.CreateInstance(layouttype) as ILayoutControl;
else if (layoutobject != null)
{ {
type = Type.GetType(page.ThemeType); panes = layoutobject.Panes;
}
} }
var property = type.GetProperty("Panes"); page.Panes = panes.Replace(";", ",").Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();
page.Panes = (string)property.GetValue(Activator.CreateInstance(type), null);
} }
catch catch
{ {
@ -380,12 +388,12 @@
return page; 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>(); var paneindex = new Dictionary<string, int>();
foreach (Module module in modules) foreach (Module module in modules)
{ {
if (module.PageId == pageid || module.ModuleId == moduleid) if (module.PageId == page.PageId || module.ModuleId == moduleid)
{ {
var typename = string.Empty; var typename = string.Empty;
if (module.ModuleDefinition != null) if (module.ModuleDefinition != null)
@ -397,63 +405,72 @@
typename = Constants.ErrorModule; typename = Constants.ErrorModule;
} }
if (module.ModuleId == moduleid && control != "") if (module.ModuleId == moduleid && action != "")
{ {
// check if the module defines custom routes // check if the module defines custom routes
if (module.ModuleDefinition.ControlTypeRoutes != "") if (module.ModuleDefinition.ControlTypeRoutes != "")
{ {
foreach (string route in module.ModuleDefinition.ControlTypeRoutes.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) 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); module.ModuleType = typename.Replace(Constants.ActionToken, action);
// admin controls need to load additional metadata from the IModuleControl interface
if (moduleid == module.ModuleId)
{
typename = module.ModuleType;
// check for core module actions component
if (Constants.DefaultModuleActions.Contains(control))
{
typename = Constants.DefaultModuleActionsTemplate.Replace(Constants.ActionToken, control);
}
Type moduletype = Type.GetType(typename);
if (moduletype != null)
{
var moduleobject = Activator.CreateInstance(moduletype);
module.SecurityAccessLevel = (SecurityAccessLevel)moduletype.GetProperty("SecurityAccessLevel").GetValue(moduleobject, null);
module.ControlTitle = (string)moduletype.GetProperty("Title").GetValue(moduleobject);
module.Actions = (string)moduletype.GetProperty("Actions").GetValue(moduleobject);
module.UseAdminContainer = (bool)moduletype.GetProperty("UseAdminContainer").GetValue(moduleobject);
}
}
} }
else else
{ {
module.ModuleType = typename.Replace(Constants.ActionToken, Constants.DefaultAction); 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 // 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; module.Pane = Constants.AdminPane;
} }
// calculate module position within pane // 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 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)) if (string.IsNullOrEmpty(module.ContainerType))
{ {
@ -462,12 +479,28 @@
} }
} }
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() private Runtime GetRuntime()

View File

@ -12,6 +12,8 @@
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
var interop = new Interop(JsRuntime); var interop = new Interop(JsRuntime);
// set page title
if (!string.IsNullOrEmpty(PageState.Page.Title)) if (!string.IsNullOrEmpty(PageState.Page.Title))
{ {
await interop.UpdateTitle(PageState.Page.Title); await interop.UpdateTitle(PageState.Page.Title);
@ -20,10 +22,34 @@
{ {
await interop.UpdateTitle(PageState.Site.Name + " - " + PageState.Page.Name); 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) if (PageState.Site.FaviconFileId != null)
{ {
await interop.IncludeLink("fav-icon", "shortcut icon", Utilities.ContentUrl(PageState.Alias, PageState.Site.FaviconFileId.Value), "image/x-icon", "", ""); 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) if (PageState.Site.PwaIsEnabled)
{ {
await InitializePwa(interop); await InitializePwa(interop);

View File

@ -18,3 +18,4 @@
@using Oqtane.Themes @using Oqtane.Themes
@using Oqtane.Themes.Controls @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"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Framework</id> <id>Oqtane.Framework</id>
<version>0.9.0</version> <version>1.0.0</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>
@ -18,8 +18,11 @@
</metadata> </metadata>
<files> <files>
<file src="..\Oqtane.Client\bin\Release\netstandard2.1\Oqtane.Client.dll" target="lib" /> <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.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.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> </files>
</package> </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.Enums;
using Oqtane.Infrastructure; using Oqtane.Infrastructure;
using Oqtane.Repository; using Oqtane.Repository;
using Microsoft.AspNetCore.Routing.Constraints;
// ReSharper disable StringIndexOfIsCultureSpecific.1 // ReSharper disable StringIndexOfIsCultureSpecific.1
@ -188,14 +189,37 @@ namespace Oqtane.Controllers
{ {
Models.File file = null; Models.File file = null;
Folder folder = _folders.GetFolder(int.Parse(folderid)); Folder folder = _folders.GetFolder(int.Parse(folderid));
if (folder != null && _userPermissions.IsAuthorized(User, PermissionNames.Edit, folder.Permissions))
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);
HttpContext.Response.StatusCode = 401;
return file;
}
string folderPath = GetFolderPath(folder); string folderPath = GetFolderPath(folder);
CreateDirectory(folderPath); CreateDirectory(folderPath);
string filename = url.Substring(url.LastIndexOf("/", StringComparison.Ordinal) + 1); string filename = url.Substring(url.LastIndexOf("/", StringComparison.Ordinal) + 1);
// check for allowable file extensions // check for allowable file extensions
if (Constants.UploadableFiles.Contains(Path.GetExtension(filename).Replace(".", ""))) if (!Constants.UploadableFiles.Split(',')
.Contains(Path.GetExtension(filename).ToLower().Replace(".", "")))
{ {
_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 try
{ {
var client = new WebClient(); var client = new WebClient();
@ -207,22 +231,12 @@ namespace Oqtane.Controllers
} }
client.DownloadFile(url, targetPath); client.DownloadFile(url, targetPath);
_files.AddFile(CreateFile(filename, folder.FolderId, targetPath)); file = _files.AddFile(CreateFile(filename, folder.FolderId, targetPath));
} }
catch catch
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Create, "File Could Not Be Downloaded From Url {Url}", url); _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
{
_logger.Log(LogLevel.Error, this, LogFunction.Create, "User Not Authorized To Download File {Url} {FolderId}", url, folderid);
HttpContext.Response.StatusCode = 401;
} }
return file; return file;
@ -232,14 +246,24 @@ namespace Oqtane.Controllers
[HttpPost("upload")] [HttpPost("upload")]
public async Task UploadFile(string folder, IFormFile file) public async Task UploadFile(string folder, IFormFile file)
{ {
if (file.Length > 0) if (file.Length <= 0)
{ {
return;
}
if (!file.FileName.IsPathOrFileValid())
{
HttpContext.Response.StatusCode = (int)HttpStatusCode.Conflict;
return;
}
string folderPath = ""; string folderPath = "";
if (int.TryParse(folder, out int folderId)) if (int.TryParse(folder, out int folderId))
{ {
Folder virtualFolder = _folders.GetFolder(folderId); Folder virtualFolder = _folders.GetFolder(folderId);
if (virtualFolder != null && _userPermissions.IsAuthorized(User, PermissionNames.Edit, virtualFolder.Permissions)) if (virtualFolder != null &&
_userPermissions.IsAuthorized(User, PermissionNames.Edit, virtualFolder.Permissions))
{ {
folderPath = GetFolderPath(virtualFolder); folderPath = GetFolderPath(virtualFolder);
} }
@ -268,11 +292,11 @@ namespace Oqtane.Controllers
} }
else else
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Create, "User Not Authorized To Upload File {Folder} {File}", folder, file); _logger.Log(LogLevel.Error, this, LogFunction.Create,
"User Not Authorized To Upload File {Folder} {File}", folder, file);
HttpContext.Response.StatusCode = 401; HttpContext.Response.StatusCode = 401;
} }
} }
}
private async Task<string> MergeFile(string folder, string filename) private async Task<string> MergeFile(string folder, string filename)
{ {
@ -282,7 +306,8 @@ namespace Oqtane.Controllers
string token = ".part_"; string token = ".part_";
string parts = Path.GetExtension(filename)?.Replace(token, ""); // returns "x_y" string parts = Path.GetExtension(filename)?.Replace(token, ""); // returns "x_y"
int totalparts = int.Parse(parts?.Substring(parts.IndexOf("_") + 1)); 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 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 ) // 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 // check for allowable file extensions
if (!Constants.UploadableFiles.Contains(Path.GetExtension(filename)?.Replace(".", ""))) if (!Constants.UploadableFiles.Split(',').Contains(Path.GetExtension(filename)?.ToLower().Replace(".", "")))
{ {
System.IO.File.Delete(Path.Combine(folder, filename + ".tmp")); System.IO.File.Delete(Path.Combine(folder, filename + ".tmp"));
} }
@ -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 ) // 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 + "*"); var cleanupFiles = Directory.EnumerateFiles(folder, "*" + token + "*")
foreach (string filepart in fileParts) .Where(f => Path.GetExtension(f).StartsWith(token));
foreach (var file in cleanupFiles)
{ {
DateTime createddate = System.IO.File.GetCreationTime(filepart).ToUniversalTime(); var createdDate = System.IO.File.GetCreationTime(file).ToUniversalTime();
if (createddate < DateTime.UtcNow.AddHours(-2)) if (createdDate < DateTime.UtcNow.AddHours(-2))
{ {
System.IO.File.Delete(filepart); System.IO.File.Delete(file);
} }
} }
@ -396,6 +423,7 @@ namespace Oqtane.Controllers
[HttpGet("download/{id}")] [HttpGet("download/{id}")]
public IActionResult Download(int id) public IActionResult Download(int id)
{ {
string errorpath = Path.Combine(GetFolderPath("images"), "error.png");
Models.File file = _files.GetFile(id); Models.File file = _files.GetFile(id);
if (file != null) if (file != null)
{ {
@ -411,22 +439,29 @@ namespace Oqtane.Controllers
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Read, "File Does Not Exist {FileId} {FilePath}", id, filepath); _logger.Log(LogLevel.Error, this, LogFunction.Read, "File Does Not Exist {FileId} {FilePath}", id, filepath);
HttpContext.Response.StatusCode = 404; HttpContext.Response.StatusCode = 404;
return null; if (System.IO.File.Exists(errorpath))
{
byte[] filebytes = System.IO.File.ReadAllBytes(errorpath);
return File(filebytes, "application/octet-stream", file.Name);
}
} }
} }
else else
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Access File {FileId}", id); _logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Access File {FileId}", id);
HttpContext.Response.StatusCode = 401; HttpContext.Response.StatusCode = 401;
return null; byte[] filebytes = System.IO.File.ReadAllBytes(errorpath);
return File(filebytes, "application/octet-stream", file.Name);
} }
} }
else else
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Read, "File Not Found {FileId}", id); _logger.Log(LogLevel.Error, this, LogFunction.Read, "File Not Found {FileId}", id);
HttpContext.Response.StatusCode = 404; HttpContext.Response.StatusCode = 404;
return null; byte[] filebytes = System.IO.File.ReadAllBytes(errorpath);
return File(filebytes, "application/octet-stream", "error.png");
} }
return null;
} }
private string GetFolderPath(Folder folder) private string GetFolderPath(Folder folder)
@ -469,7 +504,7 @@ namespace Oqtane.Controllers
file.ImageHeight = 0; file.ImageHeight = 0;
file.ImageWidth = 0; file.ImageWidth = 0;
if (Constants.ImageFiles.Contains(file.Extension)) if (Constants.ImageFiles.Split(',').Contains(file.Extension.ToLower()))
{ {
FileStream stream = new FileStream(filepath, FileMode.Open, FileAccess.Read); FileStream stream = new FileStream(filepath, FileMode.Open, FileAccess.Read);
using (var image = Image.FromStream(stream)) using (var image = Image.FromStream(stream))

View File

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

View File

@ -4,6 +4,11 @@ using Microsoft.Extensions.Configuration;
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Shared; using Oqtane.Shared;
using Oqtane.Infrastructure; using Oqtane.Infrastructure;
using System;
using System.IO;
using System.Reflection;
using System.Linq;
using System.IO.Compression;
namespace Oqtane.Controllers namespace Oqtane.Controllers
{ {
@ -55,5 +60,56 @@ namespace Oqtane.Controllers
_installationManager.UpgradeFramework(); _installationManager.UpgradeFramework();
return installation; 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 IModuleRepository _modules;
private readonly IPageModuleRepository _pageModules; private readonly IPageModuleRepository _pageModules;
private readonly IPageRepository _pages;
private readonly IModuleDefinitionRepository _moduleDefinitions; private readonly IModuleDefinitionRepository _moduleDefinitions;
private readonly IUserPermissions _userPermissions; private readonly IUserPermissions _userPermissions;
private readonly ILogManager _logger; 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; _modules = modules;
_pageModules = pageModules; _pageModules = pageModules;
_pages = pages;
_moduleDefinitions = moduleDefinitions; _moduleDefinitions = moduleDefinitions;
_userPermissions = userPermissions; _userPermissions = userPermissions;
_logger = logger; _logger = logger;
@ -42,6 +44,7 @@ namespace Oqtane.Controllers
Module module = new Module(); Module module = new Module();
module.SiteId = pagemodule.Module.SiteId; module.SiteId = pagemodule.Module.SiteId;
module.ModuleDefinitionName = pagemodule.Module.ModuleDefinitionName; module.ModuleDefinitionName = pagemodule.Module.ModuleDefinitionName;
module.AllPages = pagemodule.Module.AllPages;
module.Permissions = pagemodule.Module.Permissions; module.Permissions = pagemodule.Module.Permissions;
module.CreatedBy = pagemodule.Module.CreatedBy; module.CreatedBy = pagemodule.Module.CreatedBy;
module.CreatedOn = pagemodule.Module.CreatedOn; module.CreatedOn = pagemodule.Module.CreatedOn;
@ -111,7 +114,20 @@ namespace Oqtane.Controllers
if (ModelState.IsValid && _userPermissions.IsAuthorized(User, EntityNames.Module, module.ModuleId, PermissionNames.Edit)) if (ModelState.IsValid && _userPermissions.IsAuthorized(User, EntityNames.Module, module.ModuleId, PermissionNames.Edit))
{ {
module = _modules.UpdateModule(module); module = _modules.UpdateModule(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); _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 else
{ {

View File

@ -13,7 +13,8 @@ using Oqtane.Repository;
using Oqtane.Security; using Oqtane.Security;
using System; using System;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
// ReSharper disable StringIndexOfIsCultureSpecific.1 using Microsoft.Extensions.Configuration;
using System.Xml.Linq;
namespace Oqtane.Controllers namespace Oqtane.Controllers
{ {
@ -23,20 +24,24 @@ namespace Oqtane.Controllers
private readonly IModuleDefinitionRepository _moduleDefinitions; private readonly IModuleDefinitionRepository _moduleDefinitions;
private readonly IModuleRepository _modules; private readonly IModuleRepository _modules;
private readonly ITenantRepository _tenants; private readonly ITenantRepository _tenants;
private readonly ISqlRepository _sql;
private readonly IUserPermissions _userPermissions; private readonly IUserPermissions _userPermissions;
private readonly IInstallationManager _installationManager; private readonly IInstallationManager _installationManager;
private readonly IWebHostEnvironment _environment; private readonly IWebHostEnvironment _environment;
private readonly IConfigurationRoot _config;
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly ILogManager _logger; 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; _moduleDefinitions = moduleDefinitions;
_modules = modules; _modules = modules;
_tenants = tenants; _tenants = tenants;
_sql = sql;
_userPermissions = userPermissions; _userPermissions = userPermissions;
_installationManager = installationManager; _installationManager = installationManager;
_environment = environment; _environment = environment;
_config = config;
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
_logger = logger; _logger = logger;
} }
@ -101,74 +106,59 @@ namespace Oqtane.Controllers
ModuleDefinition moduledefinition = _moduleDefinitions.GetModuleDefinition(id, 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); 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); var moduleobject = ActivatorUtilities.CreateInstance(_serviceProvider, moduletype);
((IInstallable)moduleobject).Uninstall(tenant); ((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)
}
// format root assembly name
string assemblyname = Utilities.GetAssemblyName(moduledefinition.ModuleDefinitionName);
if (assemblyname != "Oqtane.Client")
{ {
assemblyname = assemblyname.Replace(".Client", ""); _logger.Log(LogLevel.Error, this, LogFunction.Delete, "Error Uninstalling {ModuleDefinitionName} For Tenant {Tenant} {Error}", moduledefinition.ModuleDefinitionName, tenant.Name, ex.Message);
}
}
// clean up module static resource folder // 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)) if (Directory.Exists(folder))
{ {
Directory.Delete(folder, true); 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); string binfolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
foreach (string file in Directory.EnumerateFiles(binfolder, assemblyname + "*.*")) foreach (string file in Directory.EnumerateFiles(binfolder, assemblyname + "*.*"))
{ {
System.IO.File.Delete(file); 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 // remove module definition
_moduleDefinitions.DeleteModuleDefinition(id, siteid); _moduleDefinitions.DeleteModuleDefinition(id, siteid);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Definition Deleted {ModuleDefinitionName}", moduledefinition.Name); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Definition {ModuleDefinitionName} Deleted", moduledefinition.Name);
// restart application // restart application
_installationManager.RestartApplication(); _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;
}
} }
// POST api/<controller>?moduleid=x // POST api/<controller>?moduleid=x
@ -180,19 +170,19 @@ namespace Oqtane.Controllers
{ {
string rootPath; string rootPath;
DirectoryInfo rootFolder = Directory.GetParent(_environment.ContentRootPath); 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") if (moduleDefinition.Template == "internal")
{ {
rootPath = Utilities.PathCombine(rootFolder.FullName,"\\"); 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"; moduleDefinition.ServerManagerType = moduleDefinition.Owner + "." + moduleDefinition.Name + "s.Manager." + moduleDefinition.Name + "Manager, Oqtane.Server";
} }
else else
{ {
rootPath = Utilities.PathCombine(rootFolder.Parent.FullName , moduleDefinition.Owner + "." + moduleDefinition.Name + "s.Module","\\"); rootPath = Utilities.PathCombine(rootFolder.Parent.FullName , moduleDefinition.Owner + "." + moduleDefinition.Name + "s","\\");
moduleDefinition.ModuleDefinitionName = moduleDefinition.Owner + "." + moduleDefinition.Name + "s.Modules, " + moduleDefinition.Owner + "." + moduleDefinition.Name + "s.Module.Client"; 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.Module.Server"; 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); ProcessTemplatesRecursively(new DirectoryInfo(templatePath), rootPath, rootFolder.Name, templatePath, moduleDefinition);
@ -204,7 +194,11 @@ namespace Oqtane.Controllers
if (moduleDefinition.Template == "internal") 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(); _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); page = _pages.AddPage(page);
_syncManager.AddSyncEvent(_tenants.GetTenant().TenantId, EntityNames.Site, page.SiteId); _syncManager.AddSyncEvent(_tenants.GetTenant().TenantId, EntityNames.Site, page.SiteId);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Page Added {Page}", page); _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 else
{ {
@ -156,6 +166,7 @@ namespace Oqtane.Controllers
page.EditMode = false; page.EditMode = false;
page.ThemeType = parent.ThemeType; page.ThemeType = parent.ThemeType;
page.LayoutType = parent.LayoutType; page.LayoutType = parent.LayoutType;
page.DefaultContainerType = parent.DefaultContainerType;
page.Icon = parent.Icon; page.Icon = parent.Icon;
page.Permissions = new List<Permission> { page.Permissions = new List<Permission> {
new Permission(PermissionNames.View, userid, true), new Permission(PermissionNames.View, userid, true),
@ -174,6 +185,7 @@ namespace Oqtane.Controllers
module.SiteId = page.SiteId; module.SiteId = page.SiteId;
module.PageId = page.PageId; module.PageId = page.PageId;
module.ModuleDefinitionName = pm.Module.ModuleDefinitionName; module.ModuleDefinitionName = pm.Module.ModuleDefinitionName;
module.AllPages = false;
module.Permissions = new List<Permission> { module.Permissions = new List<Permission> {
new Permission(PermissionNames.View, userid, true), new Permission(PermissionNames.View, userid, true),
new Permission(PermissionNames.Edit, userid, true) new Permission(PermissionNames.Edit, userid, true)

View File

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

View File

@ -54,22 +54,20 @@ namespace Oqtane.Controllers
{ {
List<Theme> themes = _themes.GetThemes().ToList(); List<Theme> themes = _themes.GetThemes().ToList();
Theme theme = themes.Where(item => item.ThemeName == themename).FirstOrDefault(); 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(",")); // clean up theme static resource folder
string folder = Path.Combine(_environment.WebRootPath, "Themes" , Utilities.GetTypeName(theme.ThemeName));
string folder = Path.Combine(_environment.WebRootPath, "Themes" , themename);
if (Directory.Exists(folder)) if (Directory.Exists(folder))
{ {
Directory.Delete(folder, true); 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); string binfolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
foreach (string file in Directory.EnumerateFiles(binfolder, themename + "*.dll")) 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);
System.IO.File.Delete(file);
}
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Theme Deleted {ThemeName}", themename);
_installationManager.RestartApplication(); _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;
using System.Linq; using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Oqtane.Infrastructure;
// ReSharper disable once CheckNamespace // ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection namespace Microsoft.Extensions.DependencyInjection
@ -16,10 +18,11 @@ namespace Microsoft.Extensions.DependencyInjection
} }
// load MVC application parts from module assemblies // 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 // 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); var partFactory = ApplicationPartFactory.GetApplicationPartFactory(assembly);
foreach (var part in partFactory.GetApplicationParts(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; return mvcBuilder;
} }
} }

View File

@ -5,67 +5,37 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using System.Runtime.Loader; using System.Runtime.Loader;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Oqtane.Extensions;
using Oqtane.Infrastructure; using Oqtane.Infrastructure;
using Oqtane.Modules; using Oqtane.Modules;
using Oqtane.Services;
using Oqtane.Shared;
using Oqtane.UI;
// ReSharper disable once CheckNamespace // ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection namespace Microsoft.Extensions.DependencyInjection
{ {
public static class OqtaneServiceCollectionExtensions public static class OqtaneServiceCollectionExtensions
{ {
private static readonly IList<Assembly> OqtaneModuleAssemblies = new List<Assembly>(); public static IServiceCollection AddOqtaneParts(this IServiceCollection services, Runtime runtime)
private static Assembly[] Assemblies => AppDomain.CurrentDomain.GetAssemblies();
internal static IEnumerable<Assembly> GetOqtaneModuleAssemblies() => OqtaneModuleAssemblies;
public static IServiceCollection AddOqtaneModules(this IServiceCollection services)
{ {
if (services is null) LoadAssemblies();
{ services.AddOqtaneServices(runtime);
throw new ArgumentNullException(nameof(services));
}
LoadAssemblies("Module");
return services; return services;
} }
public static IServiceCollection AddOqtaneThemes(this IServiceCollection services) private static IServiceCollection AddOqtaneServices(this IServiceCollection services, Runtime runtime)
{ {
if (services is null) if (services is null)
{ {
throw new ArgumentNullException(nameof(services)); throw new ArgumentNullException(nameof(services));
} }
LoadAssemblies("Theme"); var hostedServiceType = typeof(IHostedService);
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
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();
foreach (var assembly in assemblies) foreach (var assembly in assemblies)
{ {
// dynamically register module services, contexts, and repository classes
var implementationTypes = assembly.GetInterfaces<IService>(); var implementationTypes = assembly.GetInterfaces<IService>();
foreach (var implementationType in implementationTypes) foreach (var implementationType in implementationTypes)
{ {
@ -75,22 +45,8 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddScoped(serviceType ?? implementationType, implementationType); 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 // dynamically register hosted services
var hostedServiceType = typeof(IHostedService);
foreach (var assembly in Assemblies)
{
var serviceTypes = assembly.GetTypes(hostedServiceType); var serviceTypes = assembly.GetTypes(hostedServiceType);
foreach (var serviceType in serviceTypes) foreach (var serviceType in serviceTypes)
{ {
@ -99,27 +55,55 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddSingleton(hostedServiceType, serviceType); services.AddSingleton(hostedServiceType, serviceType);
} }
} }
var startUps = assembly.GetInstances<IServerStartup>();
foreach (var startup in startUps)
{
startup.ConfigureServices(services);
} }
if (runtime == Runtime.Server)
{
assembly.GetInstances<IClientStartup>()
.ToList()
.ForEach(x => x.ConfigureServices(services));
}
}
return services; return services;
} }
private static void LoadAssemblies(string pattern)
private static void LoadAssemblies()
{ {
var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location); var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
if (assemblyPath == null) return; if (assemblyPath == null) return;
var assembliesFolder = new DirectoryInfo(assemblyPath); var assembliesFolder = new DirectoryInfo(assemblyPath);
// iterate through Oqtane assemblies in /bin ( filter is narrow to optimize loading process ) // 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 AssemblyName assemblyName;
var assembly = Assemblies.FirstOrDefault(a =>!a.IsDynamic && a.Location == dll.FullName); try
if (assembly == null)
{ {
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
{
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 ) // 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)) if (File.Exists(pdb))
{ {
assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(File.ReadAllBytes(dll.FullName)), new MemoryStream(File.ReadAllBytes(pdb))); assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(File.ReadAllBytes(dll.FullName)), new MemoryStream(File.ReadAllBytes(pdb)));
@ -128,10 +112,11 @@ namespace Microsoft.Extensions.DependencyInjection
{ {
assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(File.ReadAllBytes(dll.FullName))); assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(File.ReadAllBytes(dll.FullName)));
} }
if (pattern == "Module") Console.WriteLine($"Loaded : {assemblyName}");
}
catch (Exception e)
{ {
// build a list of module assemblies Console.WriteLine($"Failed : {assemblyName}\n{e}");
OqtaneModuleAssemblies.Add(assembly);
} }
} }
} }

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()) using (var scope = _serviceScopeFactory.CreateScope())
{ {
var moduledefinitions = scope.ServiceProvider.GetRequiredService<IModuleDefinitionRepository>(); var moduledefinitions = scope.ServiceProvider.GetRequiredService<IModuleDefinitionRepository>();
var sql = scope.ServiceProvider.GetRequiredService<ISqlRepository>();
foreach (var moduledefinition in moduledefinitions.GetModuleDefinitions()) 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); string[] versions = moduledefinition.ReleaseVersions.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
using (var db = new InstallationContext(NormalizeConnectionString(_config.GetConnectionString(SettingKeys.ConnectionStringKey)))) using (var db = new InstallationContext(NormalizeConnectionString(_config.GetConnectionString(SettingKeys.ConnectionStringKey))))
{ {
@ -350,19 +353,22 @@ namespace Oqtane.Infrastructure
{ {
if (index == -1) index = 0; if (index == -1) index = 0;
for (int i = index; i < versions.Length; i++) 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); var moduleobject = ActivatorUtilities.CreateInstance(scope.ServiceProvider, moduletype);
((IInstallable)moduleobject).Install(tenant, versions[i]); ((IInstallable)moduleobject).Install(tenant, versions[i]);
} }
else
{
sql.ExecuteScript(tenant, moduletype.Assembly, Utilities.GetTypeName(moduledefinition.ModuleDefinitionName) + "." + versions[i] + ".sql");
}
}
catch (Exception ex) catch (Exception ex)
{ {
result.Message = "An Error Occurred Installing " + moduledefinition.Name + " - " + ex.Message.ToString(); 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 webRootPath = _environment.WebRootPath;
var install = UnpackPackages(folders, webRootPath); var install = InstallPackages(folders, webRootPath);
if (install && restart) 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; bool install = false;
string binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location); string binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
@ -44,20 +44,14 @@ namespace Oqtane.Infrastructure
foreach (string folder in folders.Split(',')) foreach (string folder in folders.Split(','))
{ {
string sourceFolder = Path.Combine(webRootPath, folder); string sourceFolder = Path.Combine(webRootPath, folder);
// create folder if it does not exist
if (!Directory.Exists(sourceFolder)) if (!Directory.Exists(sourceFolder))
{ {
Directory.CreateDirectory(sourceFolder); Directory.CreateDirectory(sourceFolder);
} }
// iterate through packages // iterate through Nuget packages in source folder
foreach (string packagename in Directory.GetFiles(sourceFolder, "*.nupkg")) 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 // iterate through files
using (ZipArchive archive = ZipFile.OpenRead(packagename)) using (ZipArchive archive = ZipFile.OpenRead(packagename))
{ {
@ -86,31 +80,33 @@ namespace Oqtane.Infrastructure
// if compatible with framework version // if compatible with framework version
if (frameworkversion == "" || Version.Parse(Constants.Version).CompareTo(Version.Parse(frameworkversion)) >= 0) 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 // deploy to appropriate locations
foreach (ZipArchiveEntry entry in archive.Entries) foreach (ZipArchiveEntry entry in archive.Entries)
{ {
string foldername = Path.GetDirectoryName(entry.FullName).Split('\\')[0];
string filename = Path.GetFileName(entry.FullName); 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; break;
} }
} }
@ -126,6 +122,15 @@ namespace Oqtane.Infrastructure
return install; 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() public void UpgradeFramework()
{ {
string folder = Path.Combine(_environment.WebRootPath, "Framework"); string folder = Path.Combine(_environment.WebRootPath, "Framework");
@ -180,17 +185,17 @@ namespace Oqtane.Infrastructure
private void FinishUpgrade() private void FinishUpgrade()
{ {
string folder = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
// check if upgrade application exists // check if upgrade application exists
string folder = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
if (folder == null || !File.Exists(Path.Combine(folder, "Oqtane.Upgrade.exe"))) return; if (folder == null || !File.Exists(Path.Combine(folder, "Oqtane.Upgrade.exe"))) return;
// run upgrade application // run upgrade application
var process = new Process var process = new Process
{ {
StartInfo = StartInfo =
{ {
FileName = Path.Combine(folder, "Oqtane.Upgrade.exe"), FileName = Path.Combine(folder, "Oqtane.Upgrade.exe"),
Arguments = "", Arguments = "\"" + _environment.ContentRootPath + "\" \"" + _environment.WebRootPath + "\"",
ErrorDialog = false, ErrorDialog = false,
UseShellExecute = false, UseShellExecute = false,
CreateNoWindow = true, 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.View, Constants.AdminRole, true),
new Permission(PermissionNames.Edit, Constants.AdminRole, true) new Permission(PermissionNames.Edit, Constants.AdminRole, true)
}.EncodePermissions(), }.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>" + 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.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 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 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>" + "<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>" "<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", 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(),"\\"); string folderpath = Utilities.PathCombine(_environment.ContentRootPath, "Content", "Tenants", site.TenantId.ToString(), "Sites", site.SiteId.ToString(),"\\");
System.IO.Directory.CreateDirectory(folderpath); 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, ""); 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; site.LogoFileId = file.FileId;
_siteRepository.UpdateSite(site); _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> <PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<LangVersion>7.3</LangVersion> <LangVersion>7.3</LangVersion>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Version>0.9.0</Version> <Version>1.0.0</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -16,28 +16,31 @@
<PackageReleaseNotes>Not for production use.</PackageReleaseNotes> <PackageReleaseNotes>Not for production use.</PackageReleaseNotes>
<RootNamespace>Oqtane</RootNamespace> <RootNamespace>Oqtane</RootNamespace>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="Modules\HtmlText\Scripts\HtmlText.1.0.0.sql" /> <Compile Remove="wwwroot\Modules\Templates\**" />
<EmbeddedResource Include="Modules\HtmlText\Scripts\HtmlText.Uninstall.sql" /> <Content Remove="wwwroot\Modules\Templates\**" />
<EmbeddedResource Remove="wwwroot\Modules\Templates\**" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Scripts\Master.0.9.0.sql" /> <EmbeddedResource Include="Scripts\Master.0.9.0.sql" />
<EmbeddedResource Include="Scripts\Tenant.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>
<ItemGroup> <ItemGroup>
<PackageReference Include="dbup" Version="4.3.0" /> <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.Components.WebAssembly.Server" Version="3.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.2" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.4" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.2" /> <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.2" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.2" /> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.4" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.1.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="5.4.1" />
<PackageReference Include="System.Drawing.Common" Version="4.7.0" /> <PackageReference Include="System.Drawing.Common" Version="4.7.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Oqtane.Client\Oqtane.Client.csproj" /> <ProjectReference Include="..\Oqtane.Client\Oqtane.Client.csproj" />
<ProjectReference Include="..\Oqtane.Shared\Oqtane.Shared.csproj" /> <ProjectReference Include="..\Oqtane.Shared\Oqtane.Shared.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -14,8 +14,6 @@
<link id="fav-icon" rel="shortcut icon" type="image/x-icon" href="favicon.ico" /> <link id="fav-icon" rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
<!-- stub the PWA manifest but defer the assignment of href --> <!-- stub the PWA manifest but defer the assignment of href -->
<link id="pwa-manifest" rel="manifest" /> <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 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" /> <link href="css/app.css" rel="stylesheet" />
</head> </head>
@ -25,10 +23,7 @@
<component type="typeof(Oqtane.App)" render-mode="Server" /> <component type="typeof(Oqtane.App)" render-mode="Server" />
</app> </app>
<script src="js/site.js"></script>
<script src="js/interop.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://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://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> <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 // get module assemblies
_moduleDefinitions = LoadModuleDefinitionsFromAssemblies(); _moduleDefinitions = LoadModuleDefinitionsFromAssemblies();
} }
List<ModuleDefinition> moduleDefinitions = _moduleDefinitions; List<ModuleDefinition> moduleDefinitions = _moduleDefinitions;
List<Permission> permissions = new List<Permission>(); List<Permission> permissions = new List<Permission>();
@ -109,18 +110,22 @@ namespace Oqtane.Repository
{ {
moduledefinition.Name = moduledef.Name; moduledefinition.Name = moduledef.Name;
} }
if (!string.IsNullOrEmpty(moduledef.Description)) if (!string.IsNullOrEmpty(moduledef.Description))
{ {
moduledefinition.Description = moduledef.Description; moduledefinition.Description = moduledef.Description;
} }
if (!string.IsNullOrEmpty(moduledef.Categories)) if (!string.IsNullOrEmpty(moduledef.Categories))
{ {
moduledefinition.Categories = moduledef.Categories; moduledefinition.Categories = moduledef.Categories;
} }
if (!string.IsNullOrEmpty(moduledef.Version)) if (!string.IsNullOrEmpty(moduledef.Version))
{ {
moduledefinition.Version = moduledef.Version; moduledefinition.Version = moduledef.Version;
} }
if (siteId != -1) if (siteId != -1)
{ {
if (permissions.Count == 0) if (permissions.Count == 0)
@ -139,9 +144,11 @@ namespace Oqtane.Repository
} }
} }
} }
// remove module definition from list as it is already synced // remove module definition from list as it is already synced
moduledefs.Remove(moduledef); moduledefs.Remove(moduledef);
} }
moduledefinition.ModuleDefinitionId = moduledef.ModuleDefinitionId; moduledefinition.ModuleDefinitionId = moduledef.ModuleDefinitionId;
moduledefinition.SiteId = siteId; moduledefinition.SiteId = siteId;
moduledefinition.CreatedBy = moduledef.CreatedBy; moduledefinition.CreatedBy = moduledef.CreatedBy;
@ -157,6 +164,7 @@ namespace Oqtane.Repository
{ {
_permissions.DeletePermissions(siteId, EntityNames.ModuleDefinition, moduledefinition.ModuleDefinitionId); _permissions.DeletePermissions(siteId, EntityNames.ModuleDefinition, moduledefinition.ModuleDefinitionId);
} }
_db.ModuleDefinition.Remove(moduledefinition); // delete _db.ModuleDefinition.Remove(moduledefinition); // delete
_db.SaveChanges(); _db.SaveChanges();
} }
@ -168,12 +176,12 @@ namespace Oqtane.Repository
{ {
List<ModuleDefinition> moduleDefinitions = new List<ModuleDefinition>(); List<ModuleDefinition> moduleDefinitions = new List<ModuleDefinition>();
// iterate through Oqtane module assemblies // iterate through Oqtane module assemblies
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies() var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
.Where(item => item.FullName.StartsWith("Oqtane.") || item.FullName.Contains(".Module.")).ToArray();
foreach (Assembly assembly in assemblies) foreach (Assembly assembly in assemblies)
{ {
moduleDefinitions = LoadModuleDefinitionsFromAssembly(moduleDefinitions, assembly); moduleDefinitions = LoadModuleDefinitionsFromAssembly(moduleDefinitions, assembly);
} }
return moduleDefinitions; return moduleDefinitions;
} }
@ -183,13 +191,15 @@ namespace Oqtane.Repository
Type[] modulecontroltypes = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IModuleControl))).ToArray(); Type[] modulecontroltypes = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IModuleControl))).ToArray();
foreach (Type modulecontroltype in modulecontroltypes) foreach (Type modulecontroltype in modulecontroltypes)
{ {
if (modulecontroltype.Name != "ModuleBase" && !modulecontroltype.Namespace.EndsWith(".Controls")) // Check if type should be ignored
{ if (modulecontroltype.Name == "ModuleBase"
string[] typename = modulecontroltype.AssemblyQualifiedName?.Split(',').Select(item => item.Trim()).ToArray(); || modulecontroltype.IsGenericType
string[] segments = typename[0].Split('.'); || modulecontroltype.IsAbstract
Array.Resize(ref segments, segments.Length - 1); || Attribute.IsDefined(modulecontroltype, typeof(OqtaneIgnoreAttribute))
string moduleType = string.Join(".", segments); ) continue;
string qualifiedModuleType = moduleType + ", " + typename[1];
string moduleNamespace = modulecontroltype.Namespace;
string qualifiedModuleType = moduleNamespace + ", " + modulecontroltype.Assembly.GetName().Name;
int index = moduledefinitions.FindIndex(item => item.ModuleDefinitionName == qualifiedModuleType); int index = moduledefinitions.FindIndex(item => item.ModuleDefinitionName == qualifiedModuleType);
if (index == -1) if (index == -1)
@ -198,34 +208,36 @@ namespace Oqtane.Repository
Type moduletype = assembly Type moduletype = assembly
.GetTypes() .GetTypes()
.Where(item => item.Namespace != null) .Where(item => item.Namespace != null)
.Where(item => item.Namespace.StartsWith(moduleType)) .Where(item => item.Namespace.StartsWith(moduleNamespace))
.FirstOrDefault(item => item.GetInterfaces().Contains(typeof(IModule))); .FirstOrDefault(item => item.GetInterfaces().Contains(typeof(IModule)));
if (moduletype != null) if (moduletype != null)
{ {
// get property values from IModule // get property values from IModule
var moduleobject = Activator.CreateInstance(moduletype); var moduleobject = Activator.CreateInstance(moduletype) as IModule;
moduledefinition = (ModuleDefinition)moduletype.GetProperty("ModuleDefinition").GetValue(moduleobject); moduledefinition = moduleobject.ModuleDefinition;
} }
else else
{ {
// set default property values // set default property values
moduledefinition = new ModuleDefinition moduledefinition = new ModuleDefinition
{ {
Name = moduleType.Substring(moduleType.LastIndexOf(".") + 1), Name = moduleNamespace.Substring(moduleNamespace.LastIndexOf(".") + 1),
Description = "Manage " + moduleType.Substring(moduleType.LastIndexOf(".") + 1), Description = "Manage " + moduleNamespace.Substring(moduleNamespace.LastIndexOf(".") + 1),
Categories = ((qualifiedModuleType.StartsWith("Oqtane.Modules.Admin.")) ? "Admin" : "") Categories = ((qualifiedModuleType.StartsWith("Oqtane.Modules.Admin.")) ? "Admin" : "")
}; };
} }
// set internal properties // set internal properties
moduledefinition.ModuleDefinitionName = qualifiedModuleType; moduledefinition.ModuleDefinitionName = qualifiedModuleType;
moduledefinition.Version = ""; // will be populated from database moduledefinition.Version = ""; // will be populated from database
moduledefinition.ControlTypeTemplate = moduleType + "." + Constants.ActionToken + ", " + typename[1]; moduledefinition.ControlTypeTemplate = moduleNamespace + "." + Constants.ActionToken + ", " + modulecontroltype.Assembly.GetName().Name;
moduledefinition.AssemblyName = assembly.GetName().Name; moduledefinition.AssemblyName = assembly.GetName().Name;
if (string.IsNullOrEmpty(moduledefinition.Categories)) if (string.IsNullOrEmpty(moduledefinition.Categories))
{ {
moduledefinition.Categories = "Common"; moduledefinition.Categories = "Common";
} }
if (moduledefinition.Categories == "Admin") if (moduledefinition.Categories == "Admin")
{ {
moduledefinition.Permissions = new List<Permission> moduledefinition.Permissions = new List<Permission>
@ -241,26 +253,28 @@ namespace Oqtane.Repository
new Permission(PermissionNames.Utilize, Constants.RegisteredRole, true) new Permission(PermissionNames.Utilize, Constants.RegisteredRole, true)
}.EncodePermissions(); }.EncodePermissions();
} }
Console.WriteLine($"Registering module: {moduledefinition.ModuleDefinitionName}");
moduledefinitions.Add(moduledefinition); moduledefinitions.Add(moduledefinition);
index = moduledefinitions.FindIndex(item => item.ModuleDefinitionName == qualifiedModuleType); index = moduledefinitions.FindIndex(item => item.ModuleDefinitionName == qualifiedModuleType);
} }
moduledefinition = moduledefinitions[index]; moduledefinition = moduledefinitions[index];
// actions // actions
var modulecontrolobject = Activator.CreateInstance(modulecontroltype); var modulecontrolobject = Activator.CreateInstance(modulecontroltype) as IModuleControl;
string actions = (string)modulecontroltype.GetProperty("Actions")?.GetValue(modulecontrolobject); string actions = modulecontrolobject.Actions;
if (!string.IsNullOrEmpty(actions)) if (!string.IsNullOrEmpty(actions))
{ {
foreach (string action in actions.Split(',')) foreach (string action in actions.Split(','))
{ {
moduledefinition.ControlTypeRoutes += (action + "=" + modulecontroltype.FullName + ", " + typename[1] + ";"); moduledefinition.ControlTypeRoutes += (action + "=" + modulecontroltype.FullName + ", " + modulecontroltype.Assembly.GetName().Name + ";");
} }
} }
moduledefinitions[index] = moduledefinition; moduledefinitions[index] = moduledefinition;
} }
}
return moduledefinitions; return moduledefinitions;
} }
} }
} }

View File

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

View File

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

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Shared;
using Oqtane.Themes; using Oqtane.Themes;
namespace Oqtane.Repository namespace Oqtane.Repository
@ -31,8 +32,7 @@ namespace Oqtane.Repository
List<Theme> themes = new List<Theme>(); List<Theme> themes = new List<Theme>();
// iterate through Oqtane theme assemblies // iterate through Oqtane theme assemblies
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies() var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
.Where(item => item.FullName.StartsWith("Oqtane.") || item.FullName.Contains(".Theme.")).ToArray();
foreach (Assembly assembly in assemblies) foreach (Assembly assembly in assemblies)
{ {
themes = LoadThemesFromAssembly(themes, assembly); themes = LoadThemesFromAssembly(themes, assembly);
@ -47,25 +47,27 @@ namespace Oqtane.Repository
Type[] themeControlTypes = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IThemeControl))).ToArray(); Type[] themeControlTypes = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IThemeControl))).ToArray();
foreach (Type themeControlType in themeControlTypes) foreach (Type themeControlType in themeControlTypes)
{ {
if (themeControlType.Name != "ThemeBase") // Check if type should be ignored
{ if (themeControlType.Name == "ThemeBase"
string[] typename = themeControlType.AssemblyQualifiedName.Split(',').Select(item => item.Trim()).ToList().ToArray(); || themeControlType.IsGenericType
string[] segments = typename[0].Split('.'); || Attribute.IsDefined(themeControlType, typeof(OqtaneIgnoreAttribute))
Array.Resize(ref segments, segments.Length - 1); ) continue;
string @namespace = string.Join(".", segments);
int index = themes.FindIndex(item => item.ThemeName == @namespace); string themeNamespace = themeControlType.Namespace;
string qualifiedModuleType = themeNamespace + ", " + themeControlType.Assembly.GetName().Name;
int index = themes.FindIndex(item => item.ThemeName == themeNamespace);
if (index == -1) if (index == -1)
{ {
// determine if this theme implements ITheme // determine if this theme implements ITheme
Type themetype = assembly.GetTypes() Type themetype = assembly.GetTypes()
.Where(item => item.Namespace != null) .Where(item => item.Namespace != null)
.Where(item => item.Namespace.StartsWith(@namespace)) .Where(item => item.Namespace.StartsWith(themeNamespace))
.Where(item => item.GetInterfaces().Contains(typeof(ITheme))).FirstOrDefault(); .Where(item => item.GetInterfaces().Contains(typeof(ITheme))).FirstOrDefault();
if (themetype != null) if (themetype != null)
{ {
var themeobject = Activator.CreateInstance(themetype); var themeobject = Activator.CreateInstance(themetype) as ITheme;
theme = (Theme)themetype.GetProperty("Theme").GetValue(themeobject); theme = themeobject.Theme;
} }
else else
{ {
@ -76,25 +78,25 @@ namespace Oqtane.Repository
}; };
} }
// set internal properties // set internal properties
theme.ThemeName = @namespace; theme.ThemeName = themeNamespace;
theme.ThemeControls = ""; theme.ThemeControls = "";
theme.PaneLayouts = ""; theme.PaneLayouts = "";
theme.ContainerControls = ""; theme.ContainerControls = "";
theme.AssemblyName = assembly.FullName.Split(",")[0]; theme.AssemblyName = assembly.FullName.Split(",")[0];
themes.Add(theme); themes.Add(theme);
index = themes.FindIndex(item => item.ThemeName == @namespace); index = themes.FindIndex(item => item.ThemeName == themeNamespace);
} }
theme = themes[index]; theme = themes[index];
theme.ThemeControls += (themeControlType.FullName + ", " + typename[1] + ";"); theme.ThemeControls += (themeControlType.FullName + ", " + themeControlType.Assembly.GetName().Name + ";");
// layouts // layouts
Type[] layouttypes = assembly.GetTypes() Type[] layouttypes = assembly.GetTypes()
.Where(item => item.Namespace != null) .Where(item => item.Namespace != null)
.Where(item => item.Namespace.StartsWith(@namespace)) .Where(item => item.Namespace.StartsWith(themeNamespace))
.Where(item => item.GetInterfaces().Contains(typeof(ILayoutControl))).ToArray(); .Where(item => item.GetInterfaces().Contains(typeof(ILayoutControl))).ToArray();
foreach (Type layouttype in layouttypes) foreach (Type layouttype in layouttypes)
{ {
string panelayout = layouttype.FullName + ", " + typename[1] + ";"; string panelayout = layouttype.FullName + ", " + themeControlType.Assembly.GetName().Name + ";";
if (!theme.PaneLayouts.Contains(panelayout)) if (!theme.PaneLayouts.Contains(panelayout))
{ {
theme.PaneLayouts += panelayout; theme.PaneLayouts += panelayout;
@ -104,11 +106,11 @@ namespace Oqtane.Repository
// containers // containers
Type[] containertypes = assembly.GetTypes() Type[] containertypes = assembly.GetTypes()
.Where(item => item.Namespace != null) .Where(item => item.Namespace != null)
.Where(item => item.Namespace.StartsWith(@namespace)) .Where(item => item.Namespace.StartsWith(themeNamespace))
.Where(item => item.GetInterfaces().Contains(typeof(IContainerControl))).ToArray(); .Where(item => item.GetInterfaces().Contains(typeof(IContainerControl))).ToArray();
foreach (Type containertype in containertypes) foreach (Type containertype in containertypes)
{ {
string container = containertype.FullName + ", " + typename[1] + ";"; string container = containertype.FullName + ", " + themeControlType.Assembly.GetName().Name + ";";
if (!theme.ContainerControls.Contains(container)) if (!theme.ContainerControls.Contains(container))
{ {
theme.ContainerControls += container; theme.ContainerControls += container;
@ -117,7 +119,6 @@ namespace Oqtane.Repository
themes[index] = theme; themes[index] = theme;
} }
}
return themes; 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.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Oqtane.Extensions;
using Oqtane.Infrastructure; using Oqtane.Infrastructure;
using Oqtane.Repository; using Oqtane.Repository;
using Oqtane.Security; using Oqtane.Security;
using Oqtane.Services; using Oqtane.Services;
using Oqtane.Shared; using Oqtane.Shared;
using Oqtane.UI;
namespace Oqtane namespace Oqtane
{ {
@ -26,6 +28,7 @@ namespace Oqtane
{ {
public IConfigurationRoot Configuration { get; } public IConfigurationRoot Configuration { get; }
private string _webRoot; private string _webRoot;
private Runtime _runtime;
public Startup(IWebHostEnvironment env) public Startup(IWebHostEnvironment env)
{ {
@ -33,6 +36,9 @@ namespace Oqtane
.SetBasePath(env.ContentRootPath) .SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true); .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
Configuration = builder.Build(); Configuration = builder.Build();
_runtime = (Configuration.GetSection("Runtime").Value == "WebAssembly") ? Runtime.WebAssembly : Runtime.Server;
_webRoot = env.WebRootPath; _webRoot = env.WebRootPath;
AppDomain.CurrentDomain.SetData("DataDirectory", Path.Combine(env.ContentRootPath, "Data")); AppDomain.CurrentDomain.SetData("DataDirectory", Path.Combine(env.ContentRootPath, "Data"));
} }
@ -41,7 +47,7 @@ namespace Oqtane
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
services.AddMvc().AddNewtonsoftJson();
services.AddServerSideBlazor(); services.AddServerSideBlazor();
// setup HttpClient for server side in a client side compatible fashion ( with auth cookie ) // setup HttpClient for server side in a client side compatible fashion ( with auth cookie )
@ -157,7 +163,7 @@ namespace Oqtane
services.AddSingleton<IDatabaseManager, DatabaseManager>(); services.AddSingleton<IDatabaseManager, DatabaseManager>();
// install any modules or themes ( this needs to occur BEFORE the assemblies are loaded into the app domain ) // 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 // register transient scoped core services
services.AddTransient<IModuleDefinitionRepository, ModuleDefinitionRepository>(); services.AddTransient<IModuleDefinitionRepository, ModuleDefinitionRepository>();
@ -187,17 +193,13 @@ namespace Oqtane
services.AddTransient<ISqlRepository, SqlRepository>(); services.AddTransient<ISqlRepository, SqlRepository>();
services.AddTransient<IUpgradeManager, UpgradeManager>(); services.AddTransient<IUpgradeManager, UpgradeManager>();
// load the external assemblies into the app domain // load the external assemblies into the app domain, install services
services.AddOqtaneModules(); services.AddOqtaneParts(_runtime);
services.AddOqtaneThemes();
services.AddOqtaneSiteTemplates();
services.AddMvc() services.AddMvc()
.AddNewtonsoftJson()
.AddOqtaneApplicationParts() // register any Controllers from custom modules .AddOqtaneApplicationParts() // register any Controllers from custom modules
.AddNewtonsoftJson(); .ConfigureOqtaneMvc(); // any additional configuration from IStart classes.
services.AddOqtaneServices();
services.AddOqtaneHostedServices();
services.AddSwaggerGen(c => 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. // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts(); app.UseHsts();
} }
app.UseHttpsRedirection(); app.UseHttpsRedirection();
app.UseStaticFiles(); app.UseStaticFiles();
app.UseBlazorFrameworkFiles(); app.UseBlazorFrameworkFiles();
app.UseRouting(); app.UseRouting();
app.UseAuthentication(); app.UseAuthentication();
app.UseAuthorization(); app.UseAuthorization();
app.UseSwagger(); app.UseSwagger();
app.UseSwaggerUI(c => app.UseSwaggerUI(c =>
{ {
@ -240,6 +240,7 @@ namespace Oqtane
endpoints.MapControllers(); endpoints.MapControllers();
endpoints.MapFallbackToPage("/_Host"); 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.Services
@using [Owner].[Module]s.Models @using [Owner].[Module]s.Models
@namespace [Owner].[Module]s.Modules @namespace [Owner].[Module]s
@inherits ModuleBase @inherits ModuleBase
@inject I[Module]Service [Module]Service
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject HttpClient http
@inject SiteState sitestate
<table class="table table-borderless"> <table class="table table-borderless">
<tr> <tr>
@ -29,9 +28,14 @@
@code { @code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Actions => "Add,Edit"; public override string Actions => "Add,Edit";
I[Module]Service [Module]Service; public override List<Resource> Resources => new List<Resource>()
{
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }
};
int _id; int _id;
string _name; string _name;
string _createdby; string _createdby;
@ -43,7 +47,6 @@
{ {
try try
{ {
[Module]Service = new [Module]Service(http, sitestate);
if (PageState.Action == "Edit") if (PageState.Action == "Edit")
{ {
_id = Int32.Parse(PageState.QueryString["id"]); _id = Int32.Parse(PageState.QueryString["id"]);

View File

@ -1,11 +1,10 @@
@using [Owner].[Module]s.Services @using [Owner].[Module]s.Services
@using [Owner].[Module]s.Models @using [Owner].[Module]s.Models
@namespace [Owner].[Module]s.Modules @namespace [Owner].[Module]s
@inherits ModuleBase @inherits ModuleBase
@inject I[Module]Service [Module]Service
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject HttpClient http
@inject SiteState sitestate
@if (_[Module]s == null) @if (_[Module]s == null)
{ {
@ -42,7 +41,7 @@ else
<hr /> <hr />
[Module] Module Created Successfully. Use Edit Mode To Add A [Module]. You Can Access The Files At The Following Locations:<br /><br /> [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 /> [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 /> - _Imports.razor - global imports for module components<br />
- Edit.razor - component for adding or editing content<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 /> - 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\I[Module]Service.cs - interface for defining service API methods<br />
- Services\[Module]Service.cs - implements service API interface methods<br /><br /> - Services\[Module]Service.cs - implements service API interface methods<br /><br />
[RootPath]Package\<br /> [RootPath]Package\<br />
- [Owner].[Module]s.Module.nuspec - nuget manifest for packaging module<br /> - [Owner].[Module]s.nuspec - nuget manifest for packaging module<br />
- [Owner].[Module]s.Module.Package.csproj - packaging project<br /> - [Owner].[Module]s.Package.csproj - packaging project<br />
- debug.cmd - copies assemblies to Oqtane bin folder when in Debug mode<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 /> - release.cmd - creates nuget package and deploys to Oqtane wwwroot/modules folder when in Release mode<br /><br />
[RootPath]Server\<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 /> - 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 /> - 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\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]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 /> - 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]s.1.0.0.sql - database schema definition script<br />
- Scripts\[Owner].[Module].Uninstall.sql - database uninstall script<br /><br /> - Scripts\[Owner].[Module]s.Uninstall.sql - database uninstall script<br />
- wwwroot\Module.css - module style sheet<br /><br />
[RootPath]Shared\<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 /> - Models\[Module].cs - model definition<br /><br />
<!-- The content above is for informational purposes only and can be safely removed --> <!-- The content above is for informational purposes only and can be safely removed -->
@code { @code {
I[Module]Service [Module]Service; public override List<Resource> Resources => new List<Resource>()
{
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }
};
List<[Module]> _[Module]s; List<[Module]> _[Module]s;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
try try
{ {
[Module]Service = new [Module]Service(http, sitestate);
_[Module]s = await [Module]Service.Get[Module]sAsync(ModuleState.ModuleId); _[Module]s = await [Module]Service.Get[Module]sAsync(ModuleState.ModuleId);
} }
catch (Exception ex) catch (Exception ex)

View File

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

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