Merge remote-tracking branch 'upstream/dev' into dev

This commit is contained in:
Charles Nurse 2021-04-19 11:08:35 -07:00
commit 74c403cef1
112 changed files with 1696 additions and 1232 deletions

View File

@ -1,12 +1,14 @@
@inject IInstallationService InstallationService @inject IInstallationService InstallationService
@if (_initialized) @if (_initialized)
{ {
@if (!_installed) @if (!_installation.Success)
{ {
<Installer /> <Installer />
} }
else else
{
@if (string.IsNullOrEmpty(_installation.Message))
{ {
<CascadingAuthenticationState> <CascadingAuthenticationState>
<CascadingValue Value="@PageState"> <CascadingValue Value="@PageState">
@ -14,18 +16,24 @@
</CascadingValue> </CascadingValue>
</CascadingAuthenticationState> </CascadingAuthenticationState>
} }
else
{
<div class="app-alert">
@_installation.Message
</div>
}
}
} }
@code { @code {
private Installation _installation;
private bool _initialized; private bool _initialized;
private bool _installed;
private PageState PageState { get; set; } private PageState PageState { get; set; }
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
var installation = await InstallationService.IsInstalled(); _installation = await InstallationService.IsInstalled();
_installed = installation.Success;
_initialized = true; _initialized = true;
} }

View File

@ -36,7 +36,7 @@
<tr> <tr>
<td colspan="2" align="center"> <td colspan="2" align="center">
<Label For="permissions" HelpText="Select the permissions you want for the folder" ResourceKey="Permissions">Permissions: </Label> <Label For="permissions" HelpText="Select the permissions you want for the folder" ResourceKey="Permissions">Permissions: </Label>
<PermissionGrid EntityName="@EntityNames.Folder" PermissionNames="Browse,View,Edit" Permissions="@_permissions" @ref="_permissionGrid" /> <PermissionGrid EntityName="@EntityNames.Folder" PermissionNames="@(PermissionNames.Browse + "," + PermissionNames.View + "," + PermissionNames.Edit)" Permissions="@_permissions" @ref="_permissionGrid" />
</td> </td>
</tr> </tr>
</table> </table>

View File

@ -11,14 +11,15 @@
} }
<AuthorizeView> <AuthorizeView>
<NotAuthorized> <NotAuthorized>
<div class="container Oqtane-Modules-Admin-Login"> <form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container Oqtane-Modules-Admin-Login" @onkeypress="@(e => KeyPressed(e))">
<div class="form-group"> <div class="form-group">
<label for="Username" class="control-label">@Localizer["Username:"] </label> <label for="Username" class="control-label">@Localizer["Username:"] </label>
<input type="text" name="Username" class="form-control username" placeholder="Username" @bind="@_username" id="Username" /> <input type="text" @ref="username" name="Username" class="form-control username" placeholder="Username" @bind="@_username" id="Username" required />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="Password" class="control-label">@Localizer["Password:"] </label> <label for="Password" class="control-label">@Localizer["Password:"] </label>
<input type="password" name="Password" class="form-control password" placeholder="Password" @bind="@_password" id="Password" /> <input type="password" name="Password" class="form-control password" placeholder="Password" @bind="@_password" id="Password" required />
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
@ -31,6 +32,7 @@
<br /><br /> <br /><br />
<button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["Forgot Password"]</button> <button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["Forgot Password"]</button>
</div> </div>
</form>
</NotAuthorized> </NotAuthorized>
</AuthorizeView> </AuthorizeView>
@ -41,6 +43,10 @@
private string _username = string.Empty; private string _username = string.Empty;
private string _password = string.Empty; private string _password = string.Empty;
private bool _remember = false; private bool _remember = false;
private bool validated = false;
private ElementReference login;
private ElementReference username;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
@ -80,7 +86,19 @@
} }
} }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await username.FocusAsync();
}
}
private async Task Login() private async Task Login()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(login))
{ {
if (PageState.Runtime == Oqtane.Shared.Runtime.Server) if (PageState.Runtime == Oqtane.Shared.Runtime.Server)
{ {
@ -95,7 +113,6 @@
{ {
await logger.LogInformation("Login Successful For Username {Username}", _username); await logger.LogInformation("Login Successful For Username {Username}", _username);
// complete the login on the server so that the cookies are set correctly on SignalR // complete the login on the server so that the cookies are set correctly on SignalR
var interop = new Interop(JSRuntime);
string antiforgerytoken = await interop.GetElementByName("__RequestVerificationToken"); string antiforgerytoken = await interop.GetElementByName("__RequestVerificationToken");
var fields = new { __RequestVerificationToken = antiforgerytoken, username = _username, password = _password, remember = _remember, returnurl = _returnUrl }; var fields = new { __RequestVerificationToken = antiforgerytoken, username = _username, password = _password, remember = _remember, returnurl = _returnUrl };
await interop.SubmitForm($"/{PageState.Alias.AliasId}/pages/login/", fields); await interop.SubmitForm($"/{PageState.Alias.AliasId}/pages/login/", fields);
@ -103,7 +120,7 @@
else else
{ {
await logger.LogInformation("Login Failed For Username {Username}", _username); await logger.LogInformation("Login Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Login Failed. Please Remember That Passwords Are Case Sensitive And User Accounts Require Email Verification When They Initially Created."], MessageType.Error); AddModuleMessage(Localizer["Login Failed. Please Remember That Passwords Are Case Sensitive And User Accounts Require Verification When They Are Initially Created So You May Wish To Check Your Email."], MessageType.Error);
} }
} }
else else
@ -128,6 +145,11 @@
} }
} }
} }
else
{
AddModuleMessage(Localizer["Please Provide Your Username And Password"], MessageType.Warning);
}
}
private void Cancel() private void Cancel()
{ {
@ -157,4 +179,12 @@
StateHasChanged(); StateHasChanged();
} }
private async Task KeyPressed(KeyboardEventArgs e)
{
if (e.Code == "Enter" || e.Code == "NumpadEnter")
{
await Login();
}
}
} }

View File

@ -12,8 +12,8 @@ else
<table class="table table-borderless"> <table class="table table-borderless">
<tr> <tr>
<td> <td>
<label>@Localizer["Level:"] </label> <Label For="level" HelpText="Select the log level for event log items" ResourceKey="Level">Level: </Label><br /><br />
<select class="form-control" @onchange="(e => LevelChanged(e))"> <select id="level" class="form-control" @onchange="(e => LevelChanged(e))">
<option value="-">&lt;@Localizer["All Levels"]&gt;</option> <option value="-">&lt;@Localizer["All Levels"]&gt;</option>
<option value="Trace">@Localizer["Trace"]</option> <option value="Trace">@Localizer["Trace"]</option>
<option value="Debug">@Localizer["Debug"]</option> <option value="Debug">@Localizer["Debug"]</option>
@ -24,8 +24,8 @@ else
</select> </select>
</td> </td>
<td> <td>
<label>@Localizer["Function:"] </label> <Label For="function" HelpText="Select the function for event log items" ResourceKey="Function">Function: </Label><br /><br />
<select class="form-control" @onchange="(e => FunctionChanged(e))"> <select id="function" class="form-control" @onchange="(e => FunctionChanged(e))">
<option value="-">&lt;@Localizer["All Functions"]&gt;</option> <option value="-">&lt;@Localizer["All Functions"]&gt;</option>
<option value="Create">@Localizer["Create"]</option> <option value="Create">@Localizer["Create"]</option>
<option value="Read">@Localizer["Read"]</option> <option value="Read">@Localizer["Read"]</option>
@ -36,8 +36,8 @@ else
</select> </select>
</td> </td>
<td> <td>
<label>@Localizer["Rows:"] </label> <Label For="rows" HelpText="Select the maximum number of event log items to review. Please note that if you choose more than 10 items the information will be split into pages." ResourceKey="Rows">Maximum Items: </Label><br /><br />
<select class="form-control" @onchange="(e => RowsChanged(e))"> <select id="rows" class="form-control" @onchange="(e => RowsChanged(e))">
<option value="10">10</option> <option value="10">10</option>
<option value="50">50</option> <option value="50">50</option>
<option value="100">100</option> <option value="100">100</option>

View File

@ -9,7 +9,7 @@
@using System.Text.RegularExpressions @using System.Text.RegularExpressions
@using System.IO; @using System.IO;
@if (string.IsNullOrEmpty(_moduledefinitionname)) @if (string.IsNullOrEmpty(_moduledefinitionname) && _systeminfo != null && _templates != null)
{ {
<table class="table table-borderless"> <table class="table table-borderless">
<tr> <tr>
@ -38,13 +38,15 @@
</tr> </tr>
<tr> <tr>
<td> <td>
<Label For="template" HelpText="Select a module template. Internal modules are created inside of the framework solution. External modules are created outside of the framework solution." ResourceKey="Template">Template: </Label> <Label For="template" HelpText="Select a module template. Templates are located in the wwwroot/Modules/Templates folder on the server." ResourceKey="Template">Template: </Label>
</td> </td>
<td> <td>
<select id="template" class="form-control" @onchange="(e => TemplateChanged(e))"> <select id="template" class="form-control" @onchange="(e => TemplateChanged(e))">
<option value="-">&lt;@Localizer["Select Template"]&gt;</option> <option value="-">&lt;@Localizer["Select Template"]&gt;</option>
<option value="internal">@Localizer["Internal"]</option> @foreach (string template in _templates)
<option value="external">@Localizer["External"]</option> {
<option value="@template">@template</option>
}
</select> </select>
</td> </td>
</tr> </tr>
@ -90,9 +92,11 @@ else
private string _module = string.Empty; private string _module = string.Empty;
private string _description = string.Empty; private string _description = string.Empty;
private string _template = "-"; private string _template = "-";
public string _reference = Constants.Version; private string _reference = Constants.Version;
private string _location = string.Empty; private string _location = string.Empty;
private Dictionary<string, string> _systeminfo; private Dictionary<string, string> _systeminfo;
private List<string> _templates;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
@ -102,12 +106,11 @@ else
{ {
_moduledefinitionname = SettingService.GetSetting(ModuleState.Settings, "ModuleDefinitionName", ""); _moduledefinitionname = SettingService.GetSetting(ModuleState.Settings, "ModuleDefinitionName", "");
_systeminfo = await SystemService.GetSystemInfoAsync(); _systeminfo = await SystemService.GetSystemInfoAsync();
_templates = await ModuleDefinitionService.GetModuleDefinitionTemplatesAsync();
if (string.IsNullOrEmpty(_moduledefinitionname)) if (string.IsNullOrEmpty(_moduledefinitionname))
{ {
_owner = ModuleState.Title; AddModuleMessage(Localizer["Please Note That The Module Creator Is Only Intended To Be Used In A Development Environment"], MessageType.Info);
_module = ModuleState.Title;
_description = ModuleState.Title;
} }
else else
{ {
@ -185,19 +188,9 @@ else
if (_template != "-" && _systeminfo != null && _systeminfo.ContainsKey("serverpath")) if (_template != "-" && _systeminfo != null && _systeminfo.ContainsKey("serverpath"))
{ {
string[] path = _systeminfo["serverpath"].Split(Path.DirectorySeparatorChar); string[] path = _systeminfo["serverpath"].Split(Path.DirectorySeparatorChar);
if (_template == "internal")
{
_location = string.Join(Path.DirectorySeparatorChar, path, 0, path.Length - 1) +
Path.DirectorySeparatorChar + "Oqtane.Client" +
Path.DirectorySeparatorChar + "Modules" +
Path.DirectorySeparatorChar + _owner + "." + _module;
}
else
{
_location = string.Join(Path.DirectorySeparatorChar, path, 0, path.Length - 2) + _location = string.Join(Path.DirectorySeparatorChar, path, 0, path.Length - 2) +
Path.DirectorySeparatorChar + _owner + "." + _module; Path.DirectorySeparatorChar + _owner + "." + _module;
} }
}
StateHasChanged(); StateHasChanged();
} }
} }

View File

@ -26,7 +26,7 @@ else
<td> <td>
@if (context.AssemblyName != "Oqtane.Client") @if (context.AssemblyName != "Oqtane.Client")
{ {
<ActionDialog Header="Delete Module" Message="@("Are You Sure You Wish To Delete The " + context.Name + " Module?")" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" /> <ActionDialog Header="Delete Module" Message="@Localizer["Are You Sure You Wish To Delete The {0} Module?", context.Name]" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" />
} }
</td> </td>
<td>@context.Name</td> <td>@context.Name</td>

View File

@ -101,19 +101,12 @@
<Label For="Theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label> <Label For="Theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
</td> </td>
<td> <td>
<select id="Theme" class="form-control" @onchange="(e => ThemeChanged(e))"> <select id="Theme" class="form-control" value="@_themetype" @onchange="(e => ThemeChanged(e))">
<option value="-">&lt;@Localizer["Inherit From Site"]&gt;</option> <option value="-">&lt;@Localizer["Inherit From Site"]&gt;</option>
@foreach (var theme in _themes) @foreach (var theme in _themes)
{
if (theme.TypeName == _themetype)
{
<option value="@theme.TypeName" selected>@theme.Name</option>
}
else
{ {
<option value="@theme.TypeName">@theme.Name</option> <option value="@theme.TypeName">@theme.Name</option>
} }
}
</select> </select>
</td> </td>
</tr> </tr>

View File

@ -23,15 +23,11 @@
<Label For="Parent" HelpText="Select the parent for the page in the site hierarchy" ResourceKey="Parent">Parent: </Label> <Label For="Parent" HelpText="Select the parent for the page in the site hierarchy" ResourceKey="Parent">Parent: </Label>
</td> </td>
<td> <td>
<select id="Parent" class="form-control" @onchange="(e => ParentChanged(e))"> <select id="Parent" class="form-control" value="@_parentid" @onchange="(e => ParentChanged(e))">
<option value="-1">&lt;@Localizer["Site Root"]&gt;</option> <option value="-1">&lt;@Localizer["Site Root"]&gt;</option>
@foreach (Page page in _pageList) @foreach (Page page in _pageList)
{ {
if (page.PageId.ToString() == _parentid) if (page.PageId != _pageId)
{
<option value="@(page.PageId)" selected>@(new string('-', page.Level * 2))@(page.Name)</option>
}
else
{ {
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option> <option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option>
} }
@ -112,19 +108,12 @@
<Label For="Theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label> <Label For="Theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
</td> </td>
<td> <td>
<select id="Theme" class="form-control" @onchange="(e => ThemeChanged(e))"> <select id="Theme" class="form-control" value="@_themetype" @onchange="(e => ThemeChanged(e))">
<option value="-">&lt;Inherit From Site&gt;</option> <option value="-">&lt;@Localizer["Inherit From Site"]&gt;</option>
@foreach (var theme in _themes) @foreach (var theme in _themes)
{
if (theme.TypeName == _themetype)
{
<option value="@theme.TypeName" selected>@theme.Name</option>
}
else
{ {
<option value="@theme.TypeName">@theme.Name</option> <option value="@theme.TypeName">@theme.Name</option>
} }
}
</select> </select>
</td> </td>
</tr> </tr>

View File

@ -16,7 +16,7 @@
</Header> </Header>
<Row> <Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.PageId.ToString())" ResourceKey="EditPage" /></td> <td><ActionLink Action="Edit" Parameters="@($"id=" + context.PageId.ToString())" ResourceKey="EditPage" /></td>
<td><ActionDialog Header="Delete Page" Message="@("Are You Sure You Wish To Delete The " + context.Name + " Page?")" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td> <td><ActionDialog Header="Delete Page" Message="@Localizer["Are You Sure You Wish To Delete The {0} Page?", context.Name]" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
<td>@(new string('-', context.Level * 2))@(context.Name)</td> <td>@(new string('-', context.Level * 2))@(context.Name)</td>
</Row> </Row>
</Pager> </Pager>

View File

@ -19,7 +19,7 @@ else
</Header> </Header>
<Row> <Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ProfileId.ToString())" ResourceKey="EditProfile" /></td> <td><ActionLink Action="Edit" Parameters="@($"id=" + context.ProfileId.ToString())" ResourceKey="EditProfile" /></td>
<td><ActionDialog Header="Delete Profile" Message="@("Are You Sure You Wish To Delete " + context.Name + "?")" Action="Delete" Class="btn btn-danger" OnClick="@(async () => await DeleteProfile(context.ProfileId))" ResourceKey="DeleteProfile" /></td> <td><ActionDialog Header="Delete Profile" Message="@Localizer["Are You Sure You Wish To Delete {0}?", context.Name]" Action="Delete" Class="btn btn-danger" OnClick="@(async () => await DeleteProfile(context.ProfileId))" ResourceKey="DeleteProfile" /></td>
<td>@context.Name</td> <td>@context.Name</td>
</Row> </Row>
</Pager> </Pager>
@ -30,9 +30,9 @@ else
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnInitializedAsync() protected override async Task OnParametersSetAsync()
{ {
_profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId); await GetProfilesAsync();
} }
private async Task DeleteProfile(int profileId) private async Task DeleteProfile(int profileId)
@ -41,7 +41,12 @@ else
{ {
await ProfileService.DeleteProfileAsync(profileId); await ProfileService.DeleteProfileAsync(profileId);
await logger.LogInformation("Profile Deleted {ProfileId}", profileId); await logger.LogInformation("Profile Deleted {ProfileId}", profileId);
AddModuleMessage(Localizer["Profile Deleted"], MessageType.Success); AddModuleMessage(Localizer["Profile Deleted"], MessageType.Success);
await GetProfilesAsync();
StateHasChanged();
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -49,4 +54,9 @@ else
AddModuleMessage(Localizer["Error Deleting Profile"], MessageType.Error); AddModuleMessage(Localizer["Error Deleting Profile"], MessageType.Error);
} }
} }
private async Task GetProfilesAsync()
{
_profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
}
} }

View File

@ -34,7 +34,7 @@
@if (_pages.Any()) @if (_pages.Any())
{ {
<div style="text-align:right;"> <div style="text-align:right;">
<ActionDialog Header="Delete All Pages" Message="@Localizer["Are You Sure You Wish To Permanently Delete All Pages?", "Delete All Pages"]" Action="Delete All Pages" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllPages())" ResourceKey="DeleteAllPages" /> <ActionDialog Header="Delete All Pages" Message="Are You Sure You Wish To Permanently Delete All Pages?" Action="Delete All Pages" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllPages())" ResourceKey="DeleteAllPages" />
</div> </div>
} }
} }
@ -68,7 +68,7 @@
@if (_modules.Any()) @if (_modules.Any())
{ {
<div style="text-align:right;"> <div style="text-align:right;">
<ActionDialog Header="Delete All Modules" Message="@Localizer["Are You Sure You Wish To Permanently Delete All Modules?", "Delete All Modules"]" Action="Delete All Modules" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllModules())" ResourceKey="DeleteAllModules" /> <ActionDialog Header="Delete All Modules" Message="Are You Sure You Wish To Permanently Delete All Modules?" Action="Delete All Modules" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllModules())" ResourceKey="DeleteAllModules" />
</div> </div>
} }

View File

@ -7,10 +7,11 @@
@inject IThemeService ThemeService @inject IThemeService ThemeService
@inject ISettingService SettingService @inject ISettingService SettingService
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject INotificationService NotificationService
@if (_initialized) @if (_initialized)
{ {
<table class="table table-borderless"> <table class="table table-borderless">
<tr> <tr>
<td> <td>
<Label For="name" HelpText="Enter the site name" ResourceKey="Name">Name: </Label> <Label For="name" HelpText="Enter the site name" ResourceKey="Name">Name: </Label>
@ -37,7 +38,32 @@
</tr> </tr>
<tr> <tr>
<td> <td>
<Label For="logo" HelpText="Upload a logo for the site" ResourceKey="Logo">Logo: </Label> <Label For="allowRegister" HelpText="Do you want the users to be able to register for an account on the site" ResourceKey="AllowRegistration">Allow User Registration? </Label>
</td>
<td>
<select id="allowRegister" class="form-control" @bind="@_allowregistration">
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</select>
</td>
</tr>
<tr>
<td>
<Label For="isDeleted" HelpText="Is this site deleted?" ResourceKey="IsDeleted">Is Deleted? </Label>
</td>
<td>
<select id="isDeleted" class="form-control" @bind="@_isdeleted">
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</select>
</td>
</tr>
</table>
<Section Name="Appearance" Heading="Appearance" ResourceKey="Appearance">
<table class="table table-borderless">
<tr>
<td>
<Label For="logo" HelpText="Specify a logo for the site" ResourceKey="Logo">Logo: </Label>
</td> </td>
<td> <td>
<FileManager FileId="@_logofileid" Filter="@Constants.ImageFiles" @ref="_logofilemanager" /> <FileManager FileId="@_logofileid" Filter="@Constants.ImageFiles" @ref="_logofilemanager" />
@ -45,7 +71,7 @@
</tr> </tr>
<tr> <tr>
<td> <td>
<Label For="favicon" HelpText="Select Your default icon" ResourceKey="FavoriteIcon">Favicon: </Label> <Label For="favicon" HelpText="Specify a Favicon" ResourceKey="FavoriteIcon">Favicon: </Label>
</td> </td>
<td> <td>
<FileManager FileId="@_faviconfileid" Filter="ico" @ref="_faviconfilemanager" /> <FileManager FileId="@_faviconfileid" Filter="ico" @ref="_faviconfilemanager" />
@ -56,19 +82,12 @@
<Label For="defaultTheme" HelpText="Select the sites default theme" ResourceKey="DefaultTheme">Default Theme: </Label> <Label For="defaultTheme" HelpText="Select the sites default theme" ResourceKey="DefaultTheme">Default Theme: </Label>
</td> </td>
<td> <td>
<select id="defaultTheme" class="form-control" @onchange="(e => ThemeChanged(e))"> <select id="defaultTheme" class="form-control" value="@_themetype" @onchange="(e => ThemeChanged(e))">
<option value="-">&lt;@Localizer["Select Theme"]&gt;</option> <option value="-">&lt;@Localizer["Select Theme"]&gt;</option>
@foreach (var theme in _themes) @foreach (var theme in _themes)
{
if (theme.TypeName == _themetype)
{
<option value="@theme.TypeName" selected>@theme.Name</option>
}
else
{ {
<option value="@theme.TypeName">@theme.Name</option> <option value="@theme.TypeName">@theme.Name</option>
} }
}
</select> </select>
</td> </td>
</tr> </tr>
@ -118,35 +137,13 @@
</select> </select>
</td> </td>
</tr> </tr>
<tr> </table>
<td> </Section>
<Label For="allowRegister" HelpText="Do you want the users to be able to register for an account on the site" ResourceKey="AllowRegistration">Allow User Registration? </Label>
</td>
<td>
<select id="allowRegister" class="form-control" @bind="@_allowregistration">
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</select>
</td>
</tr>
<tr>
<td>
<Label For="isDeleted" HelpText="Is this site deleted?" ResourceKey="IsDeleted">Is Deleted? </Label>
</td>
<td>
<select id="isDeleted" class="form-control" @bind="@_isdeleted">
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</select>
</td>
</tr>
</table>
<Section Name="SMTP" Heading="SMTP Settings" ResourceKey="SMTPSettings"> <Section Name="SMTP" Heading="SMTP Settings" ResourceKey="SMTPSettings">
<table class="table table-borderless"> <table class="table table-borderless">
<tr> <tr>
<td colspan="2"> <td colspan="2">
@Localizer["Please Note That SMTP Requires The Notification Job To Be Enabled In the Scheduled Jobs"] <strong>@Localizer["Please Note That SMTP Requires The Notification Job To Be Enabled In Scheduled Jobs"]</strong>
</td> </td>
</tr> </tr>
<tr> <tr>
@ -201,6 +198,8 @@
</td> </td>
</tr> </tr>
</table> </table>
<button type="button" class="btn btn-secondary" @onclick="SendEmail">@Localizer["Test SMTP Configuration"]</button>
<br /><br />
</Section> </Section>
<Section Name="PWA" Heading="Progressive Web Application Settings" ResourceKey="PWASettings"> <Section Name="PWA" Heading="Progressive Web Application Settings" ResourceKey="PWASettings">
<table class="table table-borderless"> <table class="table table-borderless">
@ -482,7 +481,6 @@
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId); await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
await logger.LogInformation("Site Settings Saved {Site}", site); await logger.LogInformation("Site Settings Saved {Site}", site);
AddModuleMessage(Localizer["Site Settings Saved"], MessageType.Success); AddModuleMessage(Localizer["Site Settings Saved"], MessageType.Success);
} }
} }
@ -502,4 +500,36 @@
AddModuleMessage(Localizer["Error Saving Site"], MessageType.Error); AddModuleMessage(Localizer["Error Saving Site"], MessageType.Error);
} }
} }
private async Task SendEmail()
{
if (_smtphost != "" && _smtpport != "" && _smtpsender != "")
{
try
{
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
SettingService.SetSetting(settings, "SMTPHost", _smtphost);
SettingService.SetSetting(settings, "SMTPPort", _smtpport);
SettingService.SetSetting(settings, "SMTPSSL", _smtpssl);
SettingService.SetSetting(settings, "SMTPUsername", _smtpusername);
SettingService.SetSetting(settings, "SMTPPassword", _smtppassword);
SettingService.SetSetting(settings, "SMTPSender", _smtpsender);
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
await logger.LogInformation("Site SMTP Settings Saved");
await NotificationService.AddNotificationAsync(new Notification(PageState.Site.SiteId, PageState.User.DisplayName, PageState.User.Email, PageState.User.DisplayName, PageState.User.Email, PageState.Site.Name + " SMTP Configuration Test", "SMTP Server Is Configured Correctly."));
AddModuleMessage(Localizer["SMTP Settings Saved And A Message Has Been Sent To The Email Address Associated To Your User Account... Please Wait A Few Minutes For Delivery. If You Do Not Receive The Email Please Review The Notification Job In Scheduled Jobs For Any Log Details."], MessageType.Info);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Testing SMTP Configuration");
AddModuleMessage(Localizer["Error Testing SMTP Configuration"], MessageType.Error);
}
}
else
{
AddModuleMessage(Localizer["You Must Specify The SMTP Host, Port, And Sender"], MessageType.Warning);
}
}
} }

View File

@ -31,19 +31,12 @@
<Label For="defaultTheme" HelpText="Select the default theme for the website" ResourceKey="DefaultTheme">Default Theme: </Label> <Label For="defaultTheme" HelpText="Select the default theme for the website" ResourceKey="DefaultTheme">Default Theme: </Label>
</td> </td>
<td> <td>
<select id="defaultTheme" class="form-control" @onchange="(e => ThemeChanged(e))"> <select id="defaultTheme" class="form-control" value="@_themetype" @onchange="(e => ThemeChanged(e))">
<option value="-">&lt;@Localizer["Select Theme"]&gt;</option> <option value="-">&lt;@Localizer["Select Theme"]&gt;</option>
@foreach (var theme in _themes) @foreach (var theme in _themes)
{
if (theme.TypeName == _themetype)
{
<option value="@theme.TypeName" selected>@theme.Name</option>
}
else
{ {
<option value="@theme.TypeName">@theme.Name</option> <option value="@theme.TypeName">@theme.Name</option>
} }
}
</select> </select>
</td> </td>
</tr> </tr>

View File

@ -22,7 +22,7 @@ else
<Row> <Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.AliasId.ToString())" ResourceKey="EditSite" /></td> <td><ActionLink Action="Edit" Parameters="@($"id=" + context.AliasId.ToString())" ResourceKey="EditSite" /></td>
<td><ActionDialog Header="Delete Site" Message="@Localizer["Are You Sure You Wish To Delete The {0} Site?", context.Name]" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteSite(context))" ResourceKey="DeleteSite" /></td> <td><ActionDialog Header="Delete Site" Message="@Localizer["Are You Sure You Wish To Delete The {0} Site?", context.Name]" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteSite(context))" ResourceKey="DeleteSite" /></td>
<td><a href="@(_scheme + context.Name)">@context.Name</a></td> <td><a href="@(_scheme + context.Name +"?reload")">@context.Name</a></td>
</Row> </Row>
</Pager> </Pager>
} }

View File

@ -0,0 +1,186 @@
@namespace Oqtane.Modules.Admin.ThemeCreator
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IThemeService ThemeService
@inject IModuleService ModuleService
@inject IPageModuleService PageModuleService
@inject ISystemService SystemService
@inject ISettingService SettingService
@inject IStringLocalizer<Index> Localizer
@using System.Text.RegularExpressions
@using System.IO;
@if (string.IsNullOrEmpty(_themename) && _systeminfo != null && _templates != null)
{
<table class="table table-borderless">
<tr>
<td>
<Label For="owner" HelpText="Enter the name of the organization who is developing this theme. It should not contain spaces or punctuation." ResourceKey="OwnerName">Owner Name: </Label>
</td>
<td>
<input id="owner" class="form-control" @bind="@_owner" />
</td>
</tr>
<tr>
<td>
<Label For="module" HelpText="Enter a name for this theme. It should not contain spaces or punctuation." ResourceKey="ThemeName">Theme Name: </Label>
</td>
<td>
<input id="module" class="form-control" @bind="@_theme" />
</td>
</tr>
<tr>
<td>
<Label For="template" HelpText="Select a theme template. Templates are located in the wwwroot/Themes/Templates folder on the server." ResourceKey="Template">Template: </Label>
</td>
<td>
<select id="template" class="form-control" @onchange="(e => TemplateChanged(e))">
<option value="-">&lt;@Localizer["Select Template"]&gt;</option>
@foreach (string template in _templates)
{
<option value="@template">@template</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
</td>
<td>
<select id="reference" class="form-control" @bind="@_reference">
@foreach (string version in Constants.ReleaseVersions.Split(','))
{
if (Version.Parse(version).CompareTo(Version.Parse("2.0.0")) >= 0)
{
<option value="@(version)">@(version)</option>
}
}
<option value="local">@Localizer["Local Version"]</option>
</select>
</td>
</tr>
@if (!string.IsNullOrEmpty(_location))
{
<tr>
<td>
<Label For="location" HelpText="Location where the theme will be created" ResourceKey="Location">Location: </Label>
</td>
<td>
<input id="module" class="form-control" @bind="@_location" readonly />
</td>
</tr>
}
</table>
<button type="button" class="btn btn-success" @onclick="CreateTheme">@Localizer["Create Theme"]</button>
}
else
{
<button type="button" class="btn btn-success" @onclick="ActivateTheme">@Localizer["Activate Theme"]</button>
}
@code {
private string _themename = string.Empty;
private string _owner = string.Empty;
private string _theme = string.Empty;
private string _template = "-";
private string _reference = Constants.Version;
private string _location = string.Empty;
private Dictionary<string, string> _systeminfo;
private List<string> _templates;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync()
{
try
{
_themename = SettingService.GetSetting(ModuleState.Settings, "ThemeName", "");
_systeminfo = await SystemService.GetSystemInfoAsync();
_templates = await ThemeService.GetThemeTemplatesAsync();
if (string.IsNullOrEmpty(_themename))
{
AddModuleMessage(Localizer["Please Note That The Theme Creator Is Only Intended To Be Used In A Development Environment"], MessageType.Info);
}
else
{
AddModuleMessage(Localizer["Once You Have Compiled The Theme And Restarted The Application You Can Activate The Theme Below"], MessageType.Info);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Theme Creator");
}
}
private async Task CreateTheme()
{
try
{
if (IsValid(_owner) && IsValid(_theme) && _owner != _theme && _template != "-")
{
var theme = new Theme { Owner = _owner, Name = _theme, Template = _template, Version = _reference };
theme = await ThemeService.CreateThemeAsync(theme);
var settings = ModuleState.Settings;
SettingService.SetSetting(settings, "ThemeName", theme.ThemeName);
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
GetLocation();
AddModuleMessage(Localizer["The Source Code For Your Theme Has Been Created At The Location Specified Below And Must Be Compiled In Order To Make It Functional. Once It Has Been Compiled You Must <a href=\"{0}\">Restart</a> Your Application To Apply These Changes.", NavigateUrl("admin/system")], MessageType.Success);
}
else
{
AddModuleMessage(Localizer["You Must Provide A Valid Owner Name And Theme Name ( ie. No Punctuation Or Spaces And The Values Cannot Be The Same ) And Choose A Template"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Creating Theme");
}
}
private async Task ActivateTheme()
{
try
{
if (!string.IsNullOrEmpty(_themename))
{
await PageModuleService.DeletePageModuleAsync(ModuleState.PageModuleId);
await ModuleService.DeleteModuleAsync(ModuleState.ModuleId);
NavigationManager.NavigateTo(NavigateUrl());
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Activating Theme");
}
}
private bool IsValid(string name)
{
// must contain letters, underscores and digits and first character must be letter or underscore
return !string.IsNullOrEmpty(name) && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
}
private void TemplateChanged(ChangeEventArgs e)
{
_template = (string)e.Value;
GetLocation();
}
private void GetLocation()
{
_location = string.Empty;
if (_template != "-" && _systeminfo != null && _systeminfo.ContainsKey("serverpath"))
{
string[] path = _systeminfo["serverpath"].Split(Path.DirectorySeparatorChar);
_location = string.Join(Path.DirectorySeparatorChar, path, 0, path.Length - 2) +
Path.DirectorySeparatorChar + _owner + "." + _theme;
}
StateHasChanged();
}
}

View File

@ -0,0 +1,15 @@
using Oqtane.Models;
namespace Oqtane.Modules.Admin.ThemeCreator
{
public class ModuleInfo : IModule
{
public ModuleDefinition ModuleDefinition => new ModuleDefinition
{
Name = "Theme Creator",
Description = "Enables software developers to quickly create themes by automating many of the initial theme creation tasks",
Version = "1.0.0",
Categories = "Developer"
};
}
}

View File

@ -24,7 +24,7 @@
</tr> </tr>
<tr> <tr>
<td> <td>
<Label For="version" HelpText="The version of the thene" ResourceKey="Version">Version: </Label> <Label For="version" HelpText="The version of the theme" ResourceKey="Version">Version: </Label>
</td> </td>
<td> <td>
<input id="version" class="form-control" @bind="@_version" disabled /> <input id="version" class="form-control" @bind="@_version" disabled />

View File

@ -55,7 +55,7 @@
{ {
var notification = new Notification(PageState.Site.SiteId, PageState.User, user, subject, body, null); var notification = new Notification(PageState.Site.SiteId, PageState.User, user, subject, body, null);
notification = await NotificationService.AddNotificationAsync(notification); notification = await NotificationService.AddNotificationAsync(notification);
await logger.LogInformation("Notification Created {Notification}", notification); await logger.LogInformation("Notification Created {NotificationId}", notification.NotificationId);
NavigationManager.NavigateTo(NavigateUrl()); NavigationManager.NavigateTo(NavigateUrl());
} }
else else

View File

@ -183,7 +183,7 @@
{ {
var notification = new Notification(PageState.Site.SiteId, PageState.User, user, subject, body, notificationid); var notification = new Notification(PageState.Site.SiteId, PageState.User, user, subject, body, notificationid);
notification = await NotificationService.AddNotificationAsync(notification); notification = await NotificationService.AddNotificationAsync(notification);
await logger.LogInformation("Notification Created {Notification}", notification); await logger.LogInformation("Notification Created {NotificationId}", notification.NotificationId);
NavigationManager.NavigateTo(NavigateUrl()); NavigationManager.NavigateTo(NavigateUrl());
} }
else else

View File

@ -85,7 +85,7 @@ else
var user = await UserService.GetUserAsync(UserRole.UserId, PageState.Site.SiteId); var user = await UserService.GetUserAsync(UserRole.UserId, PageState.Site.SiteId);
if (user != null) if (user != null)
{ {
await UserService.DeleteUserAsync(user.UserId); await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId);
await logger.LogInformation("User Deleted {User}", UserRole.User); await logger.LogInformation("User Deleted {User}", UserRole.User);
StateHasChanged(); StateHasChanged();
} }

View File

@ -12,41 +12,27 @@
@if (ShowFolders || FolderId <= 0) @if (ShowFolders || FolderId <= 0)
{ {
<div> <div>
<select class="form-control" @onchange="(e => FolderChanged(e))"> <select class="form-control" value="@FolderId" @onchange="(e => FolderChanged(e))">
@if (string.IsNullOrEmpty(Folder)) @if (string.IsNullOrEmpty(Folder))
{ {
<option value="-1">&lt;@Localizer["Select Folder"]&gt;</option> <option value="-1">&lt;@Localizer["Select Folder"]&gt;</option>
} }
@foreach (Folder folder in _folders) @foreach (Folder folder in _folders)
{
if (folder.FolderId == FolderId)
{
<option value="@(folder.FolderId)" selected>@(new string('-', folder.Level * 2))@(folder.Name)</option>
}
else
{ {
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option> <option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
} }
}
</select> </select>
</div> </div>
} }
@if (ShowFiles) @if (ShowFiles)
{ {
<div> <div>
<select class="form-control" @onchange="(e => FileChanged(e))"> <select class="form-control" value="@FileId" @onchange="(e => FileChanged(e))">
<option value="-1">&lt;@Localizer["Select File"]&gt;</option> <option value="-1">&lt;@Localizer["Select File"]&gt;</option>
@foreach (File file in _files) @foreach (File file in _files)
{
if (file.FileId == FileId)
{
<option value="@(file.FileId)" selected>@(file.Name)</option>
}
else
{ {
<option value="@(file.FileId)">@(file.Name)</option> <option value="@(file.FileId)">@(file.Name)</option>
} }
}
</select> </select>
</div> </div>
} }

View File

@ -1,4 +1,4 @@
@namespace Oqtane.Modules.Controls @namespace Oqtane.Modules.Controls
@inherits ModuleControlBase @inherits ModuleControlBase
@typeparam TableItem @typeparam TableItem
@ -114,10 +114,10 @@
@code { @code {
private int _pages = 0; private int _pages = 0;
private int _page = 1; private int _page = 1;
private int _maxItems; private int _maxItems = 10;
private int _maxPages; private int _maxPages = 5;
private int _startPage; private int _startPage = 0;
private int _endPage; private int _endPage = 0;
[Parameter] [Parameter]
public string Format { get; set; } public string Format { get; set; }
@ -172,24 +172,20 @@
} }
} }
if (string.IsNullOrEmpty(PageSize)) if (!string.IsNullOrEmpty(PageSize))
{
_maxItems = 10;
}
else
{ {
_maxItems = int.Parse(PageSize); _maxItems = int.Parse(PageSize);
} }
if (string.IsNullOrEmpty(DisplayPages)) if (!string.IsNullOrEmpty(DisplayPages))
{
_maxPages = 5;
}
else
{ {
_maxPages = int.Parse(DisplayPages); _maxPages = int.Parse(DisplayPages);
} }
_page = 1;
_startPage = 0;
_endPage = 0;
if (Items != null) if (Items != null)
{ {
ItemList = Items.Skip((_page - 1) * _maxItems).Take(_maxItems); ItemList = Items.Skip((_page - 1) * _maxItems).Take(_maxItems);

View File

View File

@ -35,13 +35,7 @@ namespace Oqtane.Services
public async Task<Folder> GetFolderAsync(int siteId, [NotNull] string folderPath) public async Task<Folder> GetFolderAsync(int siteId, [NotNull] string folderPath)
{ {
if (!(folderPath.EndsWith(System.IO.Path.DirectorySeparatorChar) || folderPath.EndsWith(System.IO.Path.AltDirectorySeparatorChar)))
{
folderPath = Utilities.PathCombine(folderPath, System.IO.Path.DirectorySeparatorChar.ToString());
}
var path = WebUtility.UrlEncode(folderPath); var path = WebUtility.UrlEncode(folderPath);
return await GetJsonAsync<Folder>($"{ApiUrl}/{siteId}/{path}"); return await GetJsonAsync<Folder>($"{ApiUrl}/{siteId}/{path}");
} }

View File

@ -13,5 +13,6 @@ namespace Oqtane.Services
Task InstallModuleDefinitionsAsync(); Task InstallModuleDefinitionsAsync();
Task DeleteModuleDefinitionAsync(int moduleDefinitionId, int siteId); Task DeleteModuleDefinitionAsync(int moduleDefinitionId, int siteId);
Task<ModuleDefinition> CreateModuleDefinitionAsync(ModuleDefinition moduleDefinition); Task<ModuleDefinition> CreateModuleDefinitionAsync(ModuleDefinition moduleDefinition);
Task<List<string>> GetModuleDefinitionTemplatesAsync();
} }
} }

View File

@ -1,4 +1,4 @@
using Oqtane.Models; using Oqtane.Models;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -12,5 +12,7 @@ namespace Oqtane.Services
List<ThemeControl> GetContainerControls(List<Theme> themes, string themeName); List<ThemeControl> GetContainerControls(List<Theme> themes, string themeName);
Task InstallThemesAsync(); Task InstallThemesAsync();
Task DeleteThemeAsync(string themeName); Task DeleteThemeAsync(string themeName);
Task<Theme> CreateThemeAsync(Theme theme);
Task<List<string>> GetThemeTemplatesAsync();
} }
} }

View File

@ -1,4 +1,4 @@
using Oqtane.Models; using Oqtane.Models;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Oqtane.Services namespace Oqtane.Services
@ -13,7 +13,7 @@ namespace Oqtane.Services
Task<User> UpdateUserAsync(User user); Task<User> UpdateUserAsync(User user);
Task DeleteUserAsync(int userId); Task DeleteUserAsync(int userId, int siteId);
Task<User> LoginUserAsync(User user, bool setCookie, bool isPersistent); Task<User> LoginUserAsync(User user, bool setCookie, bool isPersistent);

View File

@ -53,5 +53,11 @@ namespace Oqtane.Services
{ {
return await PostJsonAsync($"{Apiurl}", moduleDefinition); return await PostJsonAsync($"{Apiurl}", moduleDefinition);
} }
public async Task<List<string>> GetModuleDefinitionTemplatesAsync()
{
List<string> templates = await GetJsonAsync<List<string>>($"{Apiurl}/templates");
return templates;
}
} }
} }

View File

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -50,5 +50,16 @@ namespace Oqtane.Services
{ {
await DeleteAsync($"{ApiUrl}/{themeName}"); await DeleteAsync($"{ApiUrl}/{themeName}");
} }
public async Task<Theme> CreateThemeAsync(Theme theme)
{
return await PostJsonAsync($"{ApiUrl}", theme);
}
public async Task<List<string>> GetThemeTemplatesAsync()
{
List<string> templates = await GetJsonAsync<List<string>>($"{ApiUrl}/templates");
return templates;
}
} }
} }

View File

@ -1,4 +1,4 @@
using Oqtane.Shared; using Oqtane.Shared;
using Oqtane.Models; using Oqtane.Models;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -36,9 +36,9 @@ namespace Oqtane.Services
return await PutJsonAsync<User>($"{Apiurl}/{user.UserId}", user); return await PutJsonAsync<User>($"{Apiurl}/{user.UserId}", user);
} }
public async Task DeleteUserAsync(int userId) public async Task DeleteUserAsync(int userId, int siteId)
{ {
await DeleteAsync($"{Apiurl}/{userId}"); await DeleteAsync($"{Apiurl}/{userId}?siteid={siteId}");
} }
public async Task<User> LoginUserAsync(User user, bool setCookie, bool isPersistent) public async Task<User> LoginUserAsync(User user, bool setCookie, bool isPersistent)

View File

@ -1,4 +1,4 @@
@namespace Oqtane.Themes.BlazorTheme @namespace Oqtane.Themes.BlazorTheme
@inherits ThemeBase @inherits ThemeBase
<div class="breadcrumbs"> <div class="breadcrumbs">
@ -19,9 +19,6 @@
<div class="row px-4"> <div class="row px-4">
<Pane Name="Content" /> <Pane Name="Content" />
</div> </div>
<div class="row px-4">
<Pane Name="Admin" />
</div>
</div> </div>
</div> </div>

View File

@ -8,15 +8,15 @@
{ {
if (childPage.PageId == PageState.Page.PageId) if (childPage.PageId == PageState.Page.PageId)
{ {
<a class="dropdown-item active" href="@GetUrl(childPage)" target="@GetTarget(childPage)"> <a class="nav-link active px-3" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<FontIcon Value="@childPage.Icon" /> <span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name <span class="sr-only">(current)</span> @childPage.Name <span class="sr-only">(current)</span>
</a> </a>
} }
else else
{ {
<a class="dropdown-item" href="@GetUrl(childPage)" target="@GetTarget(childPage)"> <a class="nav-link px-3" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<FontIcon Value="@childPage.Icon" /> <span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name @childPage.Name
</a> </a>
} }
@ -34,7 +34,7 @@ else
{ {
<li class="nav-item active"> <li class="nav-item active">
<a class="nav-link" href="@GetUrl(childPage)" target="@GetTarget(childPage)"> <a class="nav-link" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<FontIcon Value="@childPage.Icon" /> <span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name <span class="sr-only">(current)</span> @childPage.Name <span class="sr-only">(current)</span>
</a> </a>
</li> </li>
@ -43,7 +43,7 @@ else
{ {
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="@GetUrl(childPage)" target="@GetTarget(childPage)"> <a class="nav-link" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<FontIcon Value="@childPage.Icon" /> <span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name @childPage.Name
</a> </a>
</li> </li>
@ -55,7 +55,7 @@ else
{ {
<li class="nav-item dropdown active"> <li class="nav-item dropdown active">
<a class="nav-link dropdown-toggle" href="@GetUrl(childPage)" target="@GetTarget(childPage)" id="@($"navbarDropdown{childPage.PageId}")" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <a class="nav-link dropdown-toggle" href="@GetUrl(childPage)" target="@GetTarget(childPage)" id="@($"navbarDropdown{childPage.PageId}")" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<FontIcon Value="@childPage.Icon" /> <span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name <span class="sr-only">(current)</span> @childPage.Name <span class="sr-only">(current)</span>
</a> </a>
<MenuItemsHorizontal ParentPage="childPage" Pages="Pages" /> <MenuItemsHorizontal ParentPage="childPage" Pages="Pages" />
@ -65,7 +65,7 @@ else
{ {
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="@GetUrl(childPage)" target="@GetTarget(childPage)" id="@($"navbarDropdown{childPage.PageId}")" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <a class="nav-link dropdown-toggle" href="@GetUrl(childPage)" target="@GetTarget(childPage)" id="@($"navbarDropdown{childPage.PageId}")" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<FontIcon Value="@childPage.Icon" /> <span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name @childPage.Name
</a> </a>
<MenuItemsHorizontal ParentPage="childPage" Pages="Pages" /> <MenuItemsHorizontal ParentPage="childPage" Pages="Pages" />

View File

@ -9,7 +9,7 @@
{ {
<li class="nav-item px-3" style="margin-left: @(childPage.Level * 15)px;"> <li class="nav-item px-3" style="margin-left: @(childPage.Level * 15)px;">
<a class="nav-link active" href="@GetUrl(childPage)" target="@GetTarget(childPage)"> <a class="nav-link active" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<FontIcon Value="@childPage.Icon" /> <span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name <span class="sr-only">(current)</span> @childPage.Name <span class="sr-only">(current)</span>
</a> </a>
</li> </li>
@ -18,7 +18,7 @@
{ {
<li class="nav-item px-3" style="margin-left: @(childPage.Level * 15)px;"> <li class="nav-item px-3" style="margin-left: @(childPage.Level * 15)px;">
<a class="nav-link" href="@GetUrl(childPage)" target="@GetTarget(childPage)"> <a class="nav-link" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<FontIcon Value="@childPage.Icon" /> <span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name @childPage.Name
</a> </a>
</li> </li>
@ -38,7 +38,7 @@ else
{ {
<li class="nav-item px-3" style="margin-left: @(childPage.Level * 15)px;"> <li class="nav-item px-3" style="margin-left: @(childPage.Level * 15)px;">
<a class="nav-link active" href="@GetUrl(childPage)" target="@GetTarget(childPage)"> <a class="nav-link active" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<FontIcon Value="@childPage.Icon" /> <span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name <span class="sr-only">(current)</span> @childPage.Name <span class="sr-only">(current)</span>
</a> </a>
</li> </li>
@ -47,7 +47,7 @@ else
{ {
<li class="nav-item px-3" style="margin-left: @(childPage.Level * 15)px;"> <li class="nav-item px-3" style="margin-left: @(childPage.Level * 15)px;">
<a class="nav-link" href="@GetUrl(childPage)" target="@GetTarget(childPage)"> <a class="nav-link" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<FontIcon Value="@childPage.Icon" /> <span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name @childPage.Name
</a> </a>
</li> </li>

View File

@ -2,33 +2,41 @@
@inherits ModuleActionsBase @inherits ModuleActionsBase
@attribute [OqtaneIgnore] @attribute [OqtaneIgnore]
@if (PageState.EditMode && UserSecurity.IsAuthorized(PageState.User,PermissionNames.Edit, ModuleState.Permissions)) @if (PageState.EditMode && UserSecurity.IsAuthorized(PageState.User,PermissionNames.Edit, ModuleState.Permissions) && PageState.Action == Constants.DefaultAction)
{ {
<div class="app-moduleactions"> <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);"> <ul 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.Where(item => !item.Name.Contains("Pane")))
{ {
if (string.IsNullOrEmpty(action.Name)) if (string.IsNullOrEmpty(action.Name))
{ {
<div class="dropdown-divider"></div> <li class="dropdown-divider"></li>
} }
else else
{ {
<li>
<a class="dropdown-item" @onclick="(async () => await ModuleAction(action))"> <a class="dropdown-item" @onclick="(async () => await ModuleAction(action))">
@if (string.IsNullOrEmpty(action.Icon)) <span class="@action.Icon" aria-hidden="true"></span>&nbsp;@action.Name
{
@((MarkupString) "&nbsp;&nbsp;");
}
else
{
<span class="@action.Icon" aria-hidden="true"></span>
}
&nbsp;
@action.Name
</a> </a>
</li>
} }
} }
</div> <li class="dropdown-submenu">
<a class="dropdown-item" onclick="return subMenu(this)">
<span class="@Icons.AccountLogin" aria-hidden="true"></span>&nbsp;Move To &gt;
</a>
<ul class="dropdown-menu">
@foreach (var action in Actions.Where(item => item.Name.Contains("Pane")))
{
<li>
<a class="dropdown-item" @onclick="(async () => await ModuleAction(action))">
<span class="@action.Icon" aria-hidden="true"></span>&nbsp;@action.Name
</a>
</li>
}
</ul>
</li>
</ul>
</div> </div>
} }

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -76,7 +76,7 @@ namespace Oqtane.Themes.Controls
{ {
if (pane != ModuleState.Pane) if (pane != ModuleState.Pane)
{ {
actionList.Add(new ActionViewModel {Icon = Icons.AccountLogin, Name = "Move To " + pane + " Pane", Action = async (s, m) => await MoveToPane(s, pane, m)}); actionList.Add(new ActionViewModel {Icon = Icons.AccountLogin, Name = pane + " Pane", Action = async (s, m) => await MoveToPane(s, pane, m)});
} }
} }
} }

View File

@ -0,0 +1,10 @@
@namespace Oqtane.Themes.OqtaneTheme
@inherits ContainerBase
<div class="container">
<ModuleActions /><ModuleInstance />
</div>
@code {
public override string Name => "No Background Color - No Title";
}

View File

@ -0,0 +1,20 @@
@namespace Oqtane.Themes.OqtaneTheme
@inherits ContainerBase
<div class="container">
<div class="row px-4">
<div class="d-flex flex-nowrap">
<ModuleActions /><h2><ModuleTitle /></h2>
</div>
<hr class="app-rule" />
</div>
<div class="row px-4">
<div class="container">
<ModuleInstance />
</div>
</div>
</div>
@code {
public override string Name => "No Background Color - With Title";
}

View File

@ -0,0 +1,10 @@
@namespace Oqtane.Themes.OqtaneTheme
@inherits ContainerBase
<div class="container bg-primary">
<ModuleActions /><ModuleInstance />
</div>
@code {
public override string Name => "Primary Background Color - No Title";
}

View File

@ -0,0 +1,20 @@
@namespace Oqtane.Themes.OqtaneTheme
@inherits ContainerBase
<div class="container bg-primary">
<div class="row px-4">
<div class="d-flex flex-nowrap">
<ModuleActions /><h2><ModuleTitle /></h2>
</div>
<hr class="app-rule" />
</div>
<div class="row px-4">
<div class="container">
<ModuleInstance />
</div>
</div>
</div>
@code {
public override string Name => "Primary Background Color - With Title";
}

View File

@ -0,0 +1,10 @@
@namespace Oqtane.Themes.OqtaneTheme
@inherits ContainerBase
<div class="container bg-secondary">
<ModuleActions /><ModuleInstance />
</div>
@code {
public override string Name => "Secondary Background Color - No Title";
}

View File

@ -0,0 +1,20 @@
@namespace Oqtane.Themes.OqtaneTheme
@inherits ContainerBase
<div class="container bg-secondary">
<div class="row px-4">
<div class="d-flex flex-nowrap">
<ModuleActions /><h2><ModuleTitle /></h2>
</div>
<hr class="app-rule" />
</div>
<div class="row px-4">
<div class="container">
<ModuleInstance />
</div>
</div>
</div>
@code {
public override string Name => "Secondary Background Color - With Title";
}

View File

@ -0,0 +1,90 @@
@namespace Oqtane.Themes.OqtaneTheme
@inherits LayoutBase
<div class="container">
<div class="row">
<div class="col-md-12">
<Pane Name="Content" />
</div>
</div>
</div>
<Pane Name="Top Full Width" />
<div class="container">
<div class="row">
<div class="col-md-12">
<Pane Name="Top 100%" />
</div>
</div>
<div class="row">
<div class="col-md-6">
<Pane Name="Left 50%" />
</div>
<div class="col-md-6">
<Pane Name="Right 50%" />
</div>
</div>
<div class="row">
<div class="col-md-4">
<Pane Name="Left 33%" />
</div>
<div class="col-md-4">
<Pane Name="Center 33%" />
</div>
<div class="col-md-4">
<Pane Name="Right 33%" />
</div>
</div>
<div class="row">
<div class="col-md-3">
<Pane Name="Left Outer 25%" />
</div>
<div class="col-md-3">
<Pane Name="Left Inner 25%" />
</div>
<div class="col-md-3">
<Pane Name="Right Inner 25%" />
</div>
<div class="col-md-3">
<Pane Name="Right Outer 25%" />
</div>
</div>
<div class="row">
<div class="col-md-3">
<Pane Name="Left 25%" />
</div>
<div class="col-md-6">
<Pane Name="Center 50%" />
</div>
<div class="col-md-3">
<Pane Name="Right 25%" />
</div>
</div>
<div class="row">
<div class="col-md-8">
<Pane Name="Left Sidebar 66%" />
</div>
<div class="col-md-4">
<Pane Name="Right Sidebar 33%" />
</div>
</div>
<div class="row">
<div class="col-md-4">
<Pane Name="Left Sidebar 33%" />
</div>
<div class="col-md-8">
<Pane Name="Right Sidebar 66%" />
</div>
</div>
<div class="row">
<div class="col-md-12">
<Pane Name="Bottom 100%" />
</div>
</div>
</div>
<Pane Name="Bottom Full Width" />
@code {
public override string Name => "Multiple Panes";
public override string Panes => "Content, Top Full Width,Top 100%,Left 50%,Right 50%,Left 33%,Center 33%,Right 33%,Left Outer 25%,Left Inner 25%,Right Inner 25%,Right Outer 25%,Left 25%,Center 50%,Right 25%,Left Sidebar 66%,Right Sidebar 33%,Left Sidebar 33%,Right Sidebar 66%,Bottom 100%,Bottom Full Width";
}

View File

@ -0,0 +1,16 @@
@namespace Oqtane.Themes.OqtaneTheme
@inherits LayoutBase
<div class="container">
<div class="row">
<div class="col-md-12">
<Pane Name="Content" />
</div>
</div>
</div>
@code {
public override string Name => "Single Pane";
public override string Panes => "Content";
}

View File

@ -1,20 +0,0 @@
@namespace Oqtane.Themes.OqtaneTheme
@inherits LayoutBase
<div class="row px-4">
<Pane Name="Top" />
</div>
<div class="row px-4">
<div class="col-sm"><Pane Name="Left" /></div>
<div class="col-sm"><Pane Name="Content" /></div>
<div class="col-sm"><Pane Name="Right" /></div>
</div>
<div class="row px-4">
<Pane Name="Bottom" />
</div>
@code {
public override string Name => "Multiple Panes";
public override string Panes => "Top,Left,Content,Right,Bottom";
}

View File

@ -1,13 +0,0 @@
@namespace Oqtane.Themes.OqtaneTheme
@inherits ContainerBase
<div class="container">
@if (PageState.EditMode)
{
<ModuleActions />
}
<ModuleInstance />
</div>
@code {
public override string Name => "No Header";
}

View File

@ -1,12 +0,0 @@
@namespace Oqtane.Themes.OqtaneTheme
@inherits LayoutBase
<div class="row px-4">
<Pane Name="Content" />
</div>
@code {
public override string Name => "Single Pane";
public override string Panes => "Content";
}

View File

@ -1,4 +1,4 @@
@namespace Oqtane.Themes.OqtaneTheme @namespace Oqtane.Themes.OqtaneTheme
@inherits ThemeBase @inherits ThemeBase
<main role="main"> <main role="main">
@ -8,16 +8,11 @@
<div class="controls-group"><UserProfile /> <Login /> <ControlPanel /></div> <div class="controls-group"><UserProfile /> <Login /> <ControlPanel /></div>
</div> </div>
</nav> </nav>
<div class="content container">
<PaneLayout /> <PaneLayout />
<div class="row px-4">
<Pane Name="Admin" />
</div>
</div>
</main> </main>
@code { @code {
public override string Name => "Default"; public override string Name => "Default Theme";
public override string Panes => string.Empty; public override string Panes => string.Empty;

View File

@ -1,11 +1,21 @@
@namespace Oqtane.UI @namespace Oqtane.UI
<CascadingValue Value="@_moduleState" IsFixed="true"> <CascadingValue Value="@_moduleState" IsFixed="true">
@if (_useadminborder)
{
<div class="app-pane-admin-border">
@DynamicComponent @DynamicComponent
</div>
}
else
{
@DynamicComponent
}
</CascadingValue> </CascadingValue>
@code { @code {
private Module _moduleState; private Module _moduleState;
private bool _useadminborder = false;
[CascadingParameter] [CascadingParameter]
protected PageState PageState { get; set; } protected PageState PageState { get; set; }
@ -24,6 +34,15 @@
container = (!string.IsNullOrEmpty(PageState.Site.AdminContainerType)) ? PageState.Site.AdminContainerType : Constants.DefaultAdminContainer; container = (!string.IsNullOrEmpty(PageState.Site.AdminContainerType)) ? PageState.Site.AdminContainerType : Constants.DefaultAdminContainer;
} }
if (PageState.EditMode && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions) && PageState.Action == Constants.DefaultAction)
{
_useadminborder = true;
}
else
{
_useadminborder = false;
}
DynamicComponent = builder => DynamicComponent = builder =>
{ {
Type containerType = Type.GetType(container); Type containerType = Type.GetType(container);

View File

@ -1,3 +1,4 @@
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop; using Microsoft.JSInterop;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -232,5 +233,19 @@ namespace Oqtane.UI
return Task.CompletedTask; return Task.CompletedTask;
} }
} }
public ValueTask<bool> FormValid(ElementReference form)
{
try
{
return _jsRuntime.InvokeAsync<bool>(
"Oqtane.Interop.formValid",
form);
}
catch
{
return new ValueTask<bool>(Task.FromResult(false));
}
}
} }
} }

View File

@ -6,7 +6,7 @@
@if (_useadminborder) @if (_useadminborder)
{ {
<div class="@_paneadminborder"> <div class="app-pane-admin-border">
@((MarkupString)_panetitle) @((MarkupString)_panetitle)
@DynamicComponent @DynamicComponent
</div> </div>
@ -18,7 +18,6 @@ else
@code { @code {
private bool _useadminborder = false; private bool _useadminborder = false;
private string _paneadminborder = "container";
private string _panetitle = ""; private string _panetitle = "";
[CascadingParameter] [CascadingParameter]
@ -31,15 +30,14 @@ else
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
if (PageState.EditMode && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions) && Name != PaneNames.Admin) if (PageState.EditMode && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions) && PageState.Action == Constants.DefaultAction)
{ {
_useadminborder = true; _useadminborder = true;
_paneadminborder = "app-pane-admin-border";
_panetitle = "<div class=\"app-pane-admin-title\">" + Name + " Pane</div>"; _panetitle = "<div class=\"app-pane-admin-title\">" + Name + " Pane</div>";
} }
else else
{ {
_paneadminborder = "container"; _useadminborder = false;
_panetitle = ""; _panetitle = "";
} }

View File

@ -80,7 +80,7 @@
var urlparameters = string.Empty; var urlparameters = string.Empty;
var editmode = false; var editmode = false;
var reload = Reload.None; var reload = Reload.None;
var lastsyncdate = DateTime.UtcNow; var lastsyncdate = DateTime.UtcNow.AddHours(-1);
var runtime = GetRuntime(); var runtime = GetRuntime();
Uri uri = new Uri(_absoluteUri); Uri uri = new Uri(_absoluteUri);
@ -107,9 +107,14 @@
SiteState.Alias = alias; // set state for services SiteState.Alias = alias; // set state for services
lastsyncdate = alias.SyncDate; lastsyncdate = alias.SyncDate;
// process any sync events for site // process any sync events
if (reload != Reload.Site && alias.SyncEvents.Any()) if (reload != Reload.Site && alias.SyncEvents.Any())
{ {
// if running on WebAssembly reload the client application if the server application was restarted
if (runtime == Shared.Runtime.WebAssembly && PageState != null && alias.SyncEvents.Exists(item => item.TenantId == -1))
{
NavigationManager.NavigateTo(_absoluteUri + (!_absoluteUri.Contains("?") ? "?" : "&") + "reload", true);
}
if (alias.SyncEvents.Exists(item => item.EntityName == EntityNames.Site && item.EntityId == alias.SiteId)) if (alias.SyncEvents.Exists(item => item.EntityName == EntityNames.Site && item.EntityId == alias.SiteId))
{ {
reload = Reload.Site; reload = Reload.Site;

View File

@ -0,0 +1,83 @@
/* Oqtane Styles */
body {
padding-top: 7rem;
}
.controls {
z-index: 2000;
padding-top: 15px;
padding-bottom: 15px;
margin-right: 10px;
}
.app-menu .nav-item {
font-size: 0.9rem;
padding-bottom: 0.5rem;
}
.app-menu .nav-item a {
border-radius: 4px;
height: 3rem;
display: flex;
align-items: center;
line-height: 3rem;
}
.app-menu .nav-item a.active {
background-color: rgba(255,255,255,0.25);
color: white;
}
.app-menu .nav-item a:hover {
background-color: rgba(255,255,255,0.1);
color: white;
}
.app-menu .nav-link .oi {
width: 1.5rem;
font-size: 1.1rem;
vertical-align: text-top;
top: -2px;
}
.navbar-toggler {
background-color: rgba(255, 255, 255, 0.1);
margin-left: auto;
}
div.app-moduleactions a.dropdown-toggle, div.app-moduleactions div.dropdown-menu {
color:#ffffff;
}
@media (max-width: 767px) {
.app-menu {
width: 100%
}
.navbar {
position: fixed;
top: 60px;
width: 100%;
}
.controls {
height: 60px;
top: 15px;
position: fixed;
top: 0px;
width: 100%;
background-color: rgb(0, 0, 0);
}
.controls-group {
float: right;
margin-right: 25px;
}
.content {
position: relative;
top: 60px;
}
}

View File

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@ -69,6 +69,10 @@ namespace Oqtane.Controllers
public Folder GetByPath(int siteId, string path) public Folder GetByPath(int siteId, string path)
{ {
var folderPath = WebUtility.UrlDecode(path); var folderPath = WebUtility.UrlDecode(path);
if (!(folderPath.EndsWith(System.IO.Path.DirectorySeparatorChar) || folderPath.EndsWith(System.IO.Path.AltDirectorySeparatorChar)))
{
folderPath = Utilities.PathCombine(folderPath, System.IO.Path.DirectorySeparatorChar.ToString());
}
Folder folder = _folders.GetFolder(siteId, folderPath); Folder folder = _folders.GetFolder(siteId, folderPath);
if (folder != null) if (folder != null)
if (_userPermissions.IsAuthorized(User, PermissionNames.Browse, folder.Permissions)) if (_userPermissions.IsAuthorized(User, PermissionNames.Browse, folder.Permissions))

View File

@ -55,8 +55,7 @@ namespace Oqtane.Controllers
[HttpGet("installed")] [HttpGet("installed")]
public Installation IsInstalled() public Installation IsInstalled()
{ {
bool isInstalled = _databaseManager.IsInstalled(); return _databaseManager.IsInstalled();
return new Installation {Success = isInstalled, Message = string.Empty};
} }
[HttpGet("upgrade")] [HttpGet("upgrade")]
@ -129,17 +128,31 @@ namespace Oqtane.Controllers
{ {
var instance = Activator.CreateInstance(type) as IModule; var instance = Activator.CreateInstance(type) as IModule;
foreach (string name in instance.ModuleDefinition.Dependencies.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) foreach (string name in instance.ModuleDefinition.Dependencies.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
if (System.IO.File.Exists(Path.Combine(binFolder, name + ".dll")))
{ {
if (!list.Contains(name)) list.Insert(0, name); if (!list.Contains(name)) list.Insert(0, name);
} }
else
{
Console.WriteLine("Module " + instance.ModuleDefinition.ModuleDefinitionName + " dependency " + name + ".dll does not exist");
}
}
} }
foreach (var type in assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(ITheme)))) foreach (var type in assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(ITheme))))
{ {
var instance = Activator.CreateInstance(type) as ITheme; var instance = Activator.CreateInstance(type) as ITheme;
foreach (string name in instance.Theme.Dependencies.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) foreach (string name in instance.Theme.Dependencies.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
if (System.IO.File.Exists(Path.Combine(binFolder, name + ".dll")))
{ {
if (!list.Contains(name)) list.Insert(0, name); if (!list.Contains(name)) list.Insert(0, name);
} }
else
{
Console.WriteLine("Theme " + instance.Theme.ThemeName + " dependency " + name + ".dll does not exist" );
}
}
} }
} }

View File

@ -13,8 +13,6 @@ using Oqtane.Repository;
using Oqtane.Security; using Oqtane.Security;
using System; using System;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using System.Xml.Linq;
using System.Text.Json; using System.Text.Json;
namespace Oqtane.Controllers namespace Oqtane.Controllers
@ -174,7 +172,21 @@ namespace Oqtane.Controllers
} }
} }
// POST api/<controller>?moduleid=x // GET: api/<controller>/templates
[HttpGet("templates")]
[Authorize(Roles = RoleNames.Host)]
public List<string> GetTemplates()
{
var templates = new List<string>();
string templatePath = Utilities.PathCombine(_environment.WebRootPath, "Modules", "Templates", Path.DirectorySeparatorChar.ToString());
foreach (string directory in Directory.GetDirectories(templatePath))
{
templates.Add(directory.Replace(templatePath, ""));
}
return templates;
}
// POST api/<controller>
[HttpPost] [HttpPost]
[Authorize(Roles = RoleNames.Host)] [Authorize(Roles = RoleNames.Host)]
public ModuleDefinition Post([FromBody] ModuleDefinition moduleDefinition) public ModuleDefinition Post([FromBody] ModuleDefinition moduleDefinition)
@ -185,30 +197,12 @@ namespace Oqtane.Controllers
DirectoryInfo rootFolder = Directory.GetParent(_environment.ContentRootPath); DirectoryInfo rootFolder = Directory.GetParent(_environment.ContentRootPath);
string templatePath = Utilities.PathCombine(_environment.WebRootPath, "Modules", "Templates", moduleDefinition.Template,Path.DirectorySeparatorChar.ToString()); string templatePath = Utilities.PathCombine(_environment.WebRootPath, "Modules", "Templates", moduleDefinition.Template,Path.DirectorySeparatorChar.ToString());
if (moduleDefinition.Template == "internal")
{
rootPath = Utilities.PathCombine(rootFolder.FullName,Path.DirectorySeparatorChar.ToString());
moduleDefinition.ModuleDefinitionName = moduleDefinition.Owner + "." + moduleDefinition.Name + ", Oqtane.Client";
moduleDefinition.ServerManagerType = moduleDefinition.Owner + "." + moduleDefinition.Name + ".Manager." + moduleDefinition.Name + "Manager, Oqtane.Server";
}
else
{
rootPath = Utilities.PathCombine(rootFolder.Parent.FullName , moduleDefinition.Owner + "." + moduleDefinition.Name,Path.DirectorySeparatorChar.ToString()); rootPath = Utilities.PathCombine(rootFolder.Parent.FullName , moduleDefinition.Owner + "." + moduleDefinition.Name,Path.DirectorySeparatorChar.ToString());
moduleDefinition.ModuleDefinitionName = moduleDefinition.Owner + "." + moduleDefinition.Name + ", " + moduleDefinition.Owner + "." + moduleDefinition.Name + ".Client.Oqtane"; moduleDefinition.ModuleDefinitionName = moduleDefinition.Owner + "." + moduleDefinition.Name + ", " + moduleDefinition.Owner + "." + moduleDefinition.Name + ".Client.Oqtane";
moduleDefinition.ServerManagerType = moduleDefinition.Owner + "." + moduleDefinition.Name + ".Manager." + moduleDefinition.Name + "Manager, " + moduleDefinition.Owner + "." + moduleDefinition.Name + ".Server.Oqtane"; moduleDefinition.ServerManagerType = moduleDefinition.Owner + "." + moduleDefinition.Name + ".Manager." + moduleDefinition.Name + "Manager, " + moduleDefinition.Owner + "." + moduleDefinition.Name + ".Server.Oqtane";
}
ProcessTemplatesRecursively(new DirectoryInfo(templatePath), rootPath, rootFolder.Name, templatePath, moduleDefinition); ProcessTemplatesRecursively(new DirectoryInfo(templatePath), rootPath, rootFolder.Name, templatePath, moduleDefinition);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Module Definition Created {ModuleDefinition}", moduleDefinition); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Module Definition Created {ModuleDefinition}", moduleDefinition);
if (moduleDefinition.Template == "internal")
{
// add embedded resources to project file
List<string> resources = new List<string>();
resources.Add(Utilities.PathCombine("Modules", moduleDefinition.Owner + "." + moduleDefinition.Name, "Scripts", moduleDefinition.Owner + "." + moduleDefinition.Name + ".1.0.0.sql"));
resources.Add(Utilities.PathCombine("Modules", moduleDefinition.Owner + "." + moduleDefinition.Name, "Scripts", moduleDefinition.Owner + "." + moduleDefinition.Name + ".Uninstall.sql"));
EmbedResourceFiles(Utilities.PathCombine(rootPath, "Oqtane.Server", "Oqtane.Server.csproj"), resources);
}
} }
return moduleDefinition; return moduleDefinition;
@ -269,19 +263,5 @@ 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

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Oqtane.Enums; using Oqtane.Enums;
@ -65,7 +65,7 @@ namespace Oqtane.Controllers
if (IsAuthorized(notification.FromUserId)) if (IsAuthorized(notification.FromUserId))
{ {
notification = _notifications.AddNotification(notification); notification = _notifications.AddNotification(notification);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Notification Added {Notification}", notification); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Notification Added {NotificationId}", notification.NotificationId);
} }
return notification; return notification;
} }
@ -78,7 +78,7 @@ namespace Oqtane.Controllers
if (IsAuthorized(notification.FromUserId)) if (IsAuthorized(notification.FromUserId))
{ {
notification = _notifications.UpdateNotification(notification); notification = _notifications.UpdateNotification(notification);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Notification Updated {Folder}", notification); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Notification Updated {NotificationId}", notification.NotificationId);
} }
return notification; return notification;
} }

View File

@ -103,5 +103,91 @@ namespace Oqtane.Controllers
} }
} }
// GET: api/<controller>/templates
[HttpGet("templates")]
[Authorize(Roles = RoleNames.Host)]
public List<string> GetTemplates()
{
var templates = new List<string>();
string templatePath = Utilities.PathCombine(_environment.WebRootPath, "Themes", "Templates", Path.DirectorySeparatorChar.ToString());
foreach (string directory in Directory.GetDirectories(templatePath))
{
templates.Add(directory.Replace(templatePath, ""));
}
return templates;
}
// POST api/<controller>
[HttpPost]
[Authorize(Roles = RoleNames.Host)]
public Theme Post([FromBody] Theme theme)
{
if (ModelState.IsValid)
{
string rootPath;
DirectoryInfo rootFolder = Directory.GetParent(_environment.ContentRootPath);
string templatePath = Utilities.PathCombine(_environment.WebRootPath, "Themes", "Templates", theme.Template, Path.DirectorySeparatorChar.ToString());
rootPath = Utilities.PathCombine(rootFolder.Parent.FullName, theme.Owner + "." + theme.Name, Path.DirectorySeparatorChar.ToString());
theme.ThemeName = theme.Owner + "." + theme.Name + ", " + theme.Owner + "." + theme.Name + ".Client.Oqtane";
ProcessTemplatesRecursively(new DirectoryInfo(templatePath), rootPath, rootFolder.Name, templatePath, theme);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Theme Created {Theme}", theme);
}
return theme;
}
private void ProcessTemplatesRecursively(DirectoryInfo current, string rootPath, string rootFolder, string templatePath, Theme theme)
{
// process folder
string folderPath = Utilities.PathCombine(rootPath, current.FullName.Replace(templatePath, ""));
folderPath = folderPath.Replace("[Owner]", theme.Owner);
folderPath = folderPath.Replace("[Theme]", theme.Name);
if (!Directory.Exists(folderPath))
{
Directory.CreateDirectory(folderPath);
}
FileInfo[] files = current.GetFiles("*.*");
if (files != null)
{
foreach (FileInfo file in files)
{
// process file
string filePath = Path.Combine(folderPath, file.Name);
filePath = filePath.Replace("[Owner]", theme.Owner);
filePath = filePath.Replace("[Theme]", theme.Name);
string text = System.IO.File.ReadAllText(file.FullName);
text = text.Replace("[Owner]", theme.Owner);
text = text.Replace("[Theme]", theme.Name);
text = text.Replace("[RootPath]", rootPath);
text = text.Replace("[RootFolder]", rootFolder);
text = text.Replace("[Folder]", folderPath);
text = text.Replace("[File]", Path.GetFileName(filePath));
if (theme.Version == "local")
{
text = text.Replace("[FrameworkVersion]", Constants.Version);
text = text.Replace("[ClientReference]", "<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\oqtane.framework\\Oqtane.Server\\bin\\Debug\\net5.0\\Oqtane.Client.dll</HintPath></Reference>");
text = text.Replace("[SharedReference]", "<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\oqtane.framework\\Oqtane.Server\\bin\\Debug\\net5.0\\Oqtane.Shared.dll</HintPath></Reference>");
}
else
{
text = text.Replace("[FrameworkVersion]", theme.Version);
text = text.Replace("[ClientReference]", "<PackageReference Include=\"Oqtane.Client\" Version=\"" + theme.Version + "\" />");
text = text.Replace("[SharedReference]", "<PackageReference Include=\"Oqtane.Shared\" Version=\"" + theme.Version + "\" />");
}
System.IO.File.WriteAllText(filePath, text);
}
DirectoryInfo[] folders = current.GetDirectories();
foreach (DirectoryInfo folder in folders.Reverse())
{
ProcessTemplatesRecursively(folder, rootPath, rootFolder, templatePath, theme);
}
}
}
} }
} }

View File

@ -111,7 +111,6 @@ namespace Oqtane.Controllers
return null; return null;
} }
//TODO shoud be moved to another layer
private async Task<User> CreateUser(User user) private async Task<User> CreateUser(User user)
{ {
User newUser = null; User newUser = null;
@ -261,18 +260,50 @@ namespace Oqtane.Controllers
// DELETE api/<controller>/5?siteid=x // DELETE api/<controller>/5?siteid=x
[HttpDelete("{id}")] [HttpDelete("{id}")]
[Authorize(Roles = RoleNames.Admin)] [Authorize(Roles = RoleNames.Admin)]
public async Task Delete(int id) public async Task Delete(int id, string siteid)
{ {
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(_users.GetUser(id).Username); User user = _users.GetUser(id);
if (user != null)
{
// remove user roles for site
foreach (UserRole userrole in _userRoles.GetUserRoles(user.UserId, Int32.Parse(siteid)).ToList())
{
_userRoles.DeleteUserRole(userrole.UserRoleId);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "User Role Deleted {UserRole}", userrole);
}
// remove user folder for site
var folder = _folders.GetFolder(Int32.Parse(siteid), Utilities.PathCombine("Users", user.UserId.ToString(), Path.DirectorySeparatorChar.ToString()));
if (folder != null)
{
if (Directory.Exists(_folders.GetFolderPath(folder)))
{
Directory.Delete(_folders.GetFolderPath(folder), true);
}
_folders.DeleteFolder(folder.FolderId);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "User Folder Deleted {Folder}", folder);
}
// delete user if they are not a member of any other sites
if (!_userRoles.GetUserRoles(user.UserId, -1).Any())
{
// get identity user
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username);
if (identityuser != null) if (identityuser != null)
{ {
// delete identity user
var result = await _identityUserManager.DeleteAsync(identityuser); var result = await _identityUserManager.DeleteAsync(identityuser);
if (result != null) if (result != null)
{ {
_users.DeleteUser(id); // delete user
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "User Deleted {UserId}", id); _users.DeleteUser(user.UserId);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "User Deleted {UserId}", user.UserId);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Delete, "Error Deleting User {UserId}", user.UserId, result.ToString());
}
}
} }
} }
} }

View File

@ -37,27 +37,30 @@ namespace Oqtane.Infrastructure
_cache = cache; _cache = cache;
} }
public bool IsInstalled() public Installation IsInstalled()
{ {
var defaultConnectionString = NormalizeConnectionString(_config.GetConnectionString(SettingKeys.ConnectionStringKey)); var result = new Installation { Success = false, Message = string.Empty };
var result = !string.IsNullOrEmpty(defaultConnectionString); if (!string.IsNullOrEmpty(_config.GetConnectionString(SettingKeys.ConnectionStringKey)))
if (result)
{ {
result.Success = true;
using (var scope = _serviceScopeFactory.CreateScope()) using (var scope = _serviceScopeFactory.CreateScope())
{ {
var db = scope.ServiceProvider.GetRequiredService<MasterDBContext>(); var db = scope.ServiceProvider.GetRequiredService<MasterDBContext>();
result = db.Database.CanConnect(); if (db.Database.CanConnect())
if (result)
{ {
try try
{ {
result = db.Tenant.Any(); var provisioned = db.Tenant.Any();
} }
catch catch
{ {
result = false; result.Message = "Master Database Not Installed Correctly";
} }
} }
else
{
result.Message = "Cannot Connect To Master Database";
}
} }
} }
return result; return result;
@ -84,7 +87,8 @@ namespace Oqtane.Infrastructure
IsNewTenant = false IsNewTenant = false
}; };
if (!IsInstalled()) var installation = IsInstalled();
if (!installation.Success)
{ {
install.Aliases = GetInstallationConfig(SettingKeys.DefaultAliasKey, string.Empty); install.Aliases = GetInstallationConfig(SettingKeys.DefaultAliasKey, string.Empty);
install.HostPassword = GetInstallationConfig(SettingKeys.HostPasswordKey, string.Empty); install.HostPassword = GetInstallationConfig(SettingKeys.HostPasswordKey, string.Empty);
@ -107,6 +111,14 @@ namespace Oqtane.Infrastructure
install.ConnectionString = ""; install.ConnectionString = "";
} }
} }
else
{
if (!string.IsNullOrEmpty(installation.Message))
{
// problem with prior installation
install.ConnectionString = "";
}
}
} }
else else
{ {

View File

@ -1,11 +1,11 @@
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Shared; using Oqtane.Shared;
namespace Oqtane.Infrastructure namespace Oqtane.Infrastructure
{ {
public interface IDatabaseManager public interface IDatabaseManager
{ {
bool IsInstalled(); Installation IsInstalled();
Installation Install(); Installation Install();
Installation Install(InstallConfig install); Installation Install(InstallConfig install);
} }

View File

@ -48,7 +48,7 @@ namespace Oqtane.SiteTemplates
new Permission(PermissionNames.Edit, RoleNames.Admin, true) new Permission(PermissionNames.Edit, RoleNames.Admin, true)
}.EncodePermissions() , }.EncodePermissions() ,
PageTemplateModules = new List<PageTemplateModule> { PageTemplateModules = new List<PageTemplateModule> {
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "Welcome To Oqtane...", Pane = "Content", new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "Welcome To Oqtane...", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission> { ModulePermissions = new List<Permission> {
new Permission(PermissionNames.View, RoleNames.Everyone, true), new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.View, RoleNames.Admin, true), new Permission(PermissionNames.View, RoleNames.Admin, true),
@ -70,7 +70,7 @@ namespace Oqtane.SiteTemplates
"<p>The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.</p>" + "<p>The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.</p>" +
"<p>THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</p>" "<p>THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</p>"
}, },
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "Secure Content", Pane = "Content", new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "Secure Content", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission> { ModulePermissions = new List<Permission> {
new Permission(PermissionNames.View, RoleNames.Registered, true), new Permission(PermissionNames.View, RoleNames.Registered, true),
new Permission(PermissionNames.View, RoleNames.Admin, true), new Permission(PermissionNames.View, RoleNames.Admin, true),
@ -94,7 +94,7 @@ namespace Oqtane.SiteTemplates
new Permission(PermissionNames.Edit, RoleNames.Admin, true) new Permission(PermissionNames.Edit, RoleNames.Admin, true)
}.EncodePermissions(), }.EncodePermissions(),
PageTemplateModules = new List<PageTemplateModule> { PageTemplateModules = new List<PageTemplateModule> {
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "Secure Content", Pane = "Content", new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "Secure Content", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission> { ModulePermissions = new List<Permission> {
new Permission(PermissionNames.View, RoleNames.Registered, true), new Permission(PermissionNames.View, RoleNames.Registered, true),
new Permission(PermissionNames.View, RoleNames.Admin, true), new Permission(PermissionNames.View, RoleNames.Admin, true),
@ -118,7 +118,7 @@ namespace Oqtane.SiteTemplates
new Permission(PermissionNames.Edit, RoleNames.Admin, true) new Permission(PermissionNames.Edit, RoleNames.Admin, true)
}.EncodePermissions(), }.EncodePermissions(),
PageTemplateModules = new List<PageTemplateModule> { PageTemplateModules = new List<PageTemplateModule> {
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "My Page", Pane = "Content", new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "My Page", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission> { ModulePermissions = new List<Permission> {
new Permission(PermissionNames.View, RoleNames.Everyone, true), new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.View, RoleNames.Admin, true), new Permission(PermissionNames.View, RoleNames.Admin, true),

View File

@ -1,4 +1,4 @@
using Oqtane.Models; using Oqtane.Models;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -17,7 +17,7 @@ namespace Oqtane.Infrastructure
public List<SyncEvent> GetSyncEvents(int tenantId, DateTime lastSyncDate) public List<SyncEvent> GetSyncEvents(int tenantId, DateTime lastSyncDate)
{ {
return SyncEvents.Where(item => item.TenantId == tenantId && item.ModifiedOn >= lastSyncDate).ToList(); return SyncEvents.Where(item => (item.TenantId == tenantId || item.TenantId == -1) && item.ModifiedOn >= lastSyncDate).ToList();
} }
public void AddSyncEvent(int tenantId, string entityName, int entityId) public void AddSyncEvent(int tenantId, string entityName, int entityId)

View File

@ -1,9 +1,10 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.AspNetCore.Hosting;
using Oqtane.Extensions; using Microsoft.Extensions.DependencyInjection;
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Repository; using Oqtane.Repository;
using Oqtane.Shared; using Oqtane.Shared;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
namespace Oqtane.Infrastructure namespace Oqtane.Infrastructure
@ -12,22 +13,41 @@ namespace Oqtane.Infrastructure
{ {
private readonly IAliasRepository _aliases; private readonly IAliasRepository _aliases;
private readonly IServiceScopeFactory _serviceScopeFactory; private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly IWebHostEnvironment _environment;
public UpgradeManager(IAliasRepository aliases, IServiceScopeFactory serviceScopeFactory) public UpgradeManager(IAliasRepository aliases, IServiceScopeFactory serviceScopeFactory, IWebHostEnvironment environment)
{ {
_aliases = aliases; _aliases = aliases;
_serviceScopeFactory = serviceScopeFactory; _serviceScopeFactory = serviceScopeFactory;
_environment = environment;
} }
public void Upgrade(Tenant tenant, string version) public void Upgrade(Tenant tenant, string version)
{ {
// core framework upgrade logic - note that you can check if current tenant is Master if you only want to execute logic once // core framework upgrade logic - executed for every tenant
var pageTemplates = new List<PageTemplate>(); using (var scope = _serviceScopeFactory.CreateScope())
{
// set SiteState based on tenant
var siteState = scope.ServiceProvider.GetRequiredService<SiteState>();
siteState.Alias = new Alias { TenantId = tenant.TenantId };
switch (version) switch (version)
{ {
case "0.9.0": case "1.0.0":
// add a page to all existing sites on upgrade Upgrade_1_0_0(tenant, scope);
break;
case "2.0.2":
Upgrade_2_0_2(tenant, scope);
break;
}
}
}
private void Upgrade_1_0_0(Tenant tenant, IServiceScope scope)
{
var pageTemplates = new List<PageTemplate>();
// **Note: this code is commented out on purpose - it provides an example of how to programmatically add a page to all existing sites on upgrade
//pageTemplates.Add(new PageTemplate //pageTemplates.Add(new PageTemplate
//{ //{
@ -59,34 +79,40 @@ namespace Oqtane.Infrastructure
// } // }
// } // }
//}); //});
CreateSitePages(tenant, pageTemplates);
break; CreateSitePages(scope, pageTemplates);
}
private void Upgrade_2_0_2(Tenant tenant, IServiceScope scope)
{
if (tenant.Name == TenantNames.Master)
{
// remove Internal module template files as they are no longer supported
var internalTemplatePath = Utilities.PathCombine(_environment.WebRootPath, "Modules", "Templates", "Internal", Path.DirectorySeparatorChar.ToString());
if (Directory.Exists(internalTemplatePath))
{
Directory.Delete(internalTemplatePath, true);
} }
} }
private void CreateSitePages(Tenant tenant, List<PageTemplate> pageTemplates) // initialize SiteGuid
var sites = scope.ServiceProvider.GetRequiredService<ISiteRepository>();
foreach (Site site in sites.GetSites().ToList())
{
site.SiteGuid = System.Guid.NewGuid().ToString();
sites.UpdateSite(site);
}
}
private void CreateSitePages(IServiceScope scope, List<PageTemplate> pageTemplates)
{ {
if (pageTemplates.Count != 0) if (pageTemplates.Count != 0)
{ {
var processed = new List<Site>();
foreach (Alias alias in _aliases.GetAliases().Where(item => item.TenantId == tenant.TenantId))
{
if (!processed.Exists(item => item.SiteId == alias.SiteId))
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var siteState = scope.ServiceProvider.GetRequiredService<SiteState>();
siteState.Alias = alias;
var sites = scope.ServiceProvider.GetRequiredService<ISiteRepository>(); var sites = scope.ServiceProvider.GetRequiredService<ISiteRepository>();
var site = sites.GetSite(alias.SiteId); foreach (Site site in sites.GetSites().ToList())
if (site != null)
{ {
sites.CreatePages(site, pageTemplates); sites.CreatePages(site, pageTemplates);
} }
processed.Add(site);
}
}
}
} }
} }
} }

View File

@ -1,4 +1,4 @@
@page "/" @page "/"
@namespace Oqtane.Pages @namespace Oqtane.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@using Microsoft.Extensions.Configuration @using Microsoft.Extensions.Configuration
@ -15,6 +15,7 @@
<!-- stub the PWA manifest but defer the assignment of href --> <!-- stub the PWA manifest but defer the assignment of href -->
<link id="app-manifest" rel="manifest" /> <link id="app-manifest" rel="manifest" />
<link rel="stylesheet" href="css/app.css" /> <link rel="stylesheet" href="css/app.css" />
<script src="js/app.js"></script>
<script src="js/loadjs.min.js"></script> <script src="js/loadjs.min.js"></script>
@Html.Raw(@Model.HeadResources) @Html.Raw(@Model.HeadResources)
</head> </head>
@ -35,6 +36,13 @@
<a class="dismiss">🗙</a> <a class="dismiss">🗙</a>
</div> </div>
@if (Model.Message != "")
{
<div class="app-alert">
@Model.Message
</div>
}
<script src="js/interop.js"></script> <script src="js/interop.js"></script>
@if (Configuration.GetSection("Runtime").Value == "WebAssembly") @if (Configuration.GetSection("Runtime").Value == "WebAssembly")

View File

@ -39,6 +39,7 @@ namespace Oqtane.Pages
public string HeadResources = ""; public string HeadResources = "";
public string BodyResources = ""; public string BodyResources = "";
public string Message = "";
public void OnGet() public void OnGet()
{ {
@ -54,7 +55,10 @@ namespace Oqtane.Pages
if (HttpContext.Request.Cookies[CookieRequestCultureProvider.DefaultCookieName] == null && !string.IsNullOrEmpty(_configuration.GetConnectionString("DefaultConnection"))) if (HttpContext.Request.Cookies[CookieRequestCultureProvider.DefaultCookieName] == null && !string.IsNullOrEmpty(_configuration.GetConnectionString("DefaultConnection")))
{ {
var uri = new Uri(Request.GetDisplayUrl()); var uri = new Uri(Request.GetDisplayUrl());
var alias = _aliases.GetAlias(uri.Authority + "/" + uri.LocalPath.Substring(1)); var hostname = uri.Authority + "/" + uri.LocalPath.Substring(1);
var alias = _aliases.GetAlias(hostname);
if (alias != null)
{
_state.Alias = alias; _state.Alias = alias;
// set default language for site if the culture is not supported // set default language for site if the culture is not supported
@ -70,6 +74,11 @@ namespace Oqtane.Pages
SetLocalizationCookie(_localizationManager.GetDefaultCulture()); SetLocalizationCookie(_localizationManager.GetDefaultCulture());
} }
} }
else
{
Message = $"No Matching Alias For Host Name {hostname}";
}
}
} }
private void ProcessHostResources(Assembly assembly) private void ProcessHostResources(Assembly assembly)

View File

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using Oqtane.Models; using Oqtane.Models;
namespace Oqtane.Repository namespace Oqtane.Repository

View File

@ -68,7 +68,7 @@ namespace Oqtane.Repository
{ {
new PageTemplateModule new PageTemplateModule
{ {
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Login.Index).ToModuleDefinitionName(), Title = "User Login", Pane = "Content", ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Login.Index).ToModuleDefinitionName(), Title = "User Login", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission> ModulePermissions = new List<Permission>
{ {
new Permission(PermissionNames.View, RoleNames.Admin, true), new Permission(PermissionNames.View, RoleNames.Admin, true),
@ -97,7 +97,7 @@ namespace Oqtane.Repository
{ {
new PageTemplateModule new PageTemplateModule
{ {
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Register.Index).ToModuleDefinitionName(), Title = "User Registration", Pane = "Content", ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Register.Index).ToModuleDefinitionName(), Title = "User Registration", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission> ModulePermissions = new List<Permission>
{ {
new Permission(PermissionNames.View, RoleNames.Admin, true), new Permission(PermissionNames.View, RoleNames.Admin, true),
@ -127,7 +127,7 @@ namespace Oqtane.Repository
{ {
new PageTemplateModule new PageTemplateModule
{ {
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Reset.Index).ToModuleDefinitionName(), Title = "Password Reset", Pane = "Content", ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Reset.Index).ToModuleDefinitionName(), Title = "Password Reset", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission> ModulePermissions = new List<Permission>
{ {
new Permission(PermissionNames.View, RoleNames.Admin, true), new Permission(PermissionNames.View, RoleNames.Admin, true),
@ -156,7 +156,7 @@ namespace Oqtane.Repository
{ {
new PageTemplateModule new PageTemplateModule
{ {
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.UserProfile.Index).ToModuleDefinitionName(), Title = "User Profile", Pane = "Content", ModuleDefinitionName = typeof(Oqtane.Modules.Admin.UserProfile.Index).ToModuleDefinitionName(), Title = "User Profile", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission> ModulePermissions = new List<Permission>
{ {
new Permission(PermissionNames.View, RoleNames.Admin, true), new Permission(PermissionNames.View, RoleNames.Admin, true),
@ -181,7 +181,7 @@ namespace Oqtane.Repository
{ {
new PageTemplateModule new PageTemplateModule
{ {
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Dashboard.Index).ToModuleDefinitionName(), Title = "Admin Dashboard", Pane = "Content", ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Dashboard.Index).ToModuleDefinitionName(), Title = "Admin Dashboard", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission> ModulePermissions = new List<Permission>
{ {
new Permission(PermissionNames.View, RoleNames.Admin, true), new Permission(PermissionNames.View, RoleNames.Admin, true),
@ -208,7 +208,7 @@ namespace Oqtane.Repository
{ {
new PageTemplateModule new PageTemplateModule
{ {
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Site.Index).ToModuleDefinitionName(), Title = "Site Settings", Pane = "Content", ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Site.Index).ToModuleDefinitionName(), Title = "Site Settings", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission> ModulePermissions = new List<Permission>
{ {
new Permission(PermissionNames.View, RoleNames.Admin, true), new Permission(PermissionNames.View, RoleNames.Admin, true),
@ -235,7 +235,7 @@ namespace Oqtane.Repository
{ {
new PageTemplateModule new PageTemplateModule
{ {
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Pages.Index).ToModuleDefinitionName(), Title = "Page Management", Pane = "Content", ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Pages.Index).ToModuleDefinitionName(), Title = "Page Management", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission> ModulePermissions = new List<Permission>
{ {
new Permission(PermissionNames.View, RoleNames.Admin, true), new Permission(PermissionNames.View, RoleNames.Admin, true),
@ -262,7 +262,7 @@ namespace Oqtane.Repository
{ {
new PageTemplateModule new PageTemplateModule
{ {
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Users.Index).ToModuleDefinitionName(), Title = "User Management", Pane = "Content", ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Users.Index).ToModuleDefinitionName(), Title = "User Management", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission> ModulePermissions = new List<Permission>
{ {
new Permission(PermissionNames.View, RoleNames.Admin, true), new Permission(PermissionNames.View, RoleNames.Admin, true),
@ -289,7 +289,7 @@ namespace Oqtane.Repository
{ {
new PageTemplateModule new PageTemplateModule
{ {
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Profiles.Index).ToModuleDefinitionName(), Title = "Profile Management", Pane = "Content", ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Profiles.Index).ToModuleDefinitionName(), Title = "Profile Management", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission> ModulePermissions = new List<Permission>
{ {
new Permission(PermissionNames.View, RoleNames.Admin, true), new Permission(PermissionNames.View, RoleNames.Admin, true),
@ -316,7 +316,7 @@ namespace Oqtane.Repository
{ {
new PageTemplateModule new PageTemplateModule
{ {
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Roles.Index).ToModuleDefinitionName(), Title = "Role Management", Pane = "Content", ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Roles.Index).ToModuleDefinitionName(), Title = "Role Management", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission> ModulePermissions = new List<Permission>
{ {
new Permission(PermissionNames.View, RoleNames.Admin, true), new Permission(PermissionNames.View, RoleNames.Admin, true),
@ -343,7 +343,7 @@ namespace Oqtane.Repository
{ {
new PageTemplateModule new PageTemplateModule
{ {
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Files.Index).ToModuleDefinitionName(), Title = "File Management", Pane = "Content", ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Files.Index).ToModuleDefinitionName(), Title = "File Management", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission> ModulePermissions = new List<Permission>
{ {
new Permission(PermissionNames.View, RoleNames.Admin, true), new Permission(PermissionNames.View, RoleNames.Admin, true),
@ -370,7 +370,7 @@ namespace Oqtane.Repository
{ {
new PageTemplateModule new PageTemplateModule
{ {
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.RecycleBin.Index).ToModuleDefinitionName(), Title = "Recycle Bin", Pane = "Content", ModuleDefinitionName = typeof(Oqtane.Modules.Admin.RecycleBin.Index).ToModuleDefinitionName(), Title = "Recycle Bin", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission> ModulePermissions = new List<Permission>
{ {
new Permission(PermissionNames.View, RoleNames.Admin, true), new Permission(PermissionNames.View, RoleNames.Admin, true),
@ -399,7 +399,7 @@ namespace Oqtane.Repository
{ {
new PageTemplateModule new PageTemplateModule
{ {
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Logs.Index).ToModuleDefinitionName(), Title = "Event Log", Pane = "Content", ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Logs.Index).ToModuleDefinitionName(), Title = "Event Log", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission> ModulePermissions = new List<Permission>
{ {
new Permission(PermissionNames.View, RoleNames.Host, true), new Permission(PermissionNames.View, RoleNames.Host, true),
@ -421,7 +421,7 @@ namespace Oqtane.Repository
{ {
new PageTemplateModule new PageTemplateModule
{ {
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Sites.Index).ToModuleDefinitionName(), Title = "Site Management", Pane = "Content", ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Sites.Index).ToModuleDefinitionName(), Title = "Site Management", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission> ModulePermissions = new List<Permission>
{ {
new Permission(PermissionNames.View, RoleNames.Host, true), new Permission(PermissionNames.View, RoleNames.Host, true),
@ -443,7 +443,7 @@ namespace Oqtane.Repository
{ {
new PageTemplateModule new PageTemplateModule
{ {
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.ModuleDefinitions.Index).ToModuleDefinitionName(), Title = "Module Management", Pane = "Content", ModuleDefinitionName = typeof(Oqtane.Modules.Admin.ModuleDefinitions.Index).ToModuleDefinitionName(), Title = "Module Management", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission> ModulePermissions = new List<Permission>
{ {
new Permission(PermissionNames.View, RoleNames.Host, true), new Permission(PermissionNames.View, RoleNames.Host, true),
@ -465,7 +465,7 @@ namespace Oqtane.Repository
{ {
new PageTemplateModule new PageTemplateModule
{ {
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Themes.Index).ToModuleDefinitionName(), Title = "Theme Management", Pane = "Content", ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Themes.Index).ToModuleDefinitionName(), Title = "Theme Management", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission> ModulePermissions = new List<Permission>
{ {
new Permission(PermissionNames.View, RoleNames.Host, true), new Permission(PermissionNames.View, RoleNames.Host, true),
@ -494,7 +494,7 @@ namespace Oqtane.Repository
{ {
new PageTemplateModule new PageTemplateModule
{ {
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Languages.Index).ToModuleDefinitionName(), Title = "Language Management", Pane = "Content", ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Languages.Index).ToModuleDefinitionName(), Title = "Language Management", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission> ModulePermissions = new List<Permission>
{ {
new Permission(PermissionNames.View, RoleNames.Host, true), new Permission(PermissionNames.View, RoleNames.Host, true),
@ -518,7 +518,7 @@ namespace Oqtane.Repository
{ {
new PageTemplateModule new PageTemplateModule
{ {
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Jobs.Index).ToModuleDefinitionName(), Title = "Scheduled Jobs", Pane = "Content", ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Jobs.Index).ToModuleDefinitionName(), Title = "Scheduled Jobs", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission> ModulePermissions = new List<Permission>
{ {
new Permission(PermissionNames.View, RoleNames.Host, true), new Permission(PermissionNames.View, RoleNames.Host, true),
@ -540,7 +540,7 @@ namespace Oqtane.Repository
{ {
new PageTemplateModule new PageTemplateModule
{ {
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Sql.Index).ToModuleDefinitionName(), Title = "Sql Management", Pane = "Content", ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Sql.Index).ToModuleDefinitionName(), Title = "Sql Management", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission> ModulePermissions = new List<Permission>
{ {
new Permission(PermissionNames.View, RoleNames.Host, true), new Permission(PermissionNames.View, RoleNames.Host, true),
@ -562,7 +562,7 @@ namespace Oqtane.Repository
{ {
new PageTemplateModule new PageTemplateModule
{ {
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.SystemInfo.Index).ToModuleDefinitionName(), Title = "System Info", Pane = "Content", ModuleDefinitionName = typeof(Oqtane.Modules.Admin.SystemInfo.Index).ToModuleDefinitionName(), Title = "System Info", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission> ModulePermissions = new List<Permission>
{ {
new Permission(PermissionNames.View, RoleNames.Host, true), new Permission(PermissionNames.View, RoleNames.Host, true),
@ -584,7 +584,7 @@ namespace Oqtane.Repository
{ {
new PageTemplateModule new PageTemplateModule
{ {
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Upgrade.Index).ToModuleDefinitionName(), Title = "System Update", Pane = "Content", ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Upgrade.Index).ToModuleDefinitionName(), Title = "System Update", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission> ModulePermissions = new List<Permission>
{ {
new Permission(PermissionNames.View, RoleNames.Host, true), new Permission(PermissionNames.View, RoleNames.Host, true),
@ -605,7 +605,7 @@ namespace Oqtane.Repository
public Site AddSite(Site site) public Site AddSite(Site site)
{ {
site.SiteGuid = System.Guid.NewGuid().ToString();
_db.Site.Add(site); _db.Site.Add(site);
_db.SaveChanges(); _db.SaveChanges();
CreateSite(site); CreateSite(site);

View File

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Oqtane.Models; using Oqtane.Models;
@ -27,7 +27,7 @@ namespace Oqtane.Repository
return _db.UserRole.Where(item => item.UserId == userId) return _db.UserRole.Where(item => item.UserId == userId)
.Include(item => item.Role) // eager load roles .Include(item => item.Role) // eager load roles
.Include(item => item.User) // eager load users .Include(item => item.User) // eager load users
.Where(item => item.Role.SiteId == siteId || item.Role.SiteId == null); .Where(item => item.Role.SiteId == siteId || item.Role.SiteId == null || siteId == -1);
} }
public UserRole AddUserRole(UserRole userRole) public UserRole AddUserRole(UserRole userRole)

View File

@ -0,0 +1,10 @@
/*
Version 2.0.2 Tenant migration script
*/
ALTER TABLE [dbo].[Site] ADD
[SiteGuid] [char](36) NULL
GO

View File

@ -0,0 +1,20 @@
/*
Version 2.0.2 Tenant migration script
*/
UPDATE [dbo].[Site] SET DefaultContainerType = 'Oqtane.Themes.OqtaneTheme.DefaultTitle, Oqtane.Client' WHERE DefaultContainerType = 'Oqtane.Themes.OqtaneTheme.Container, Oqtane.Client';
GO
UPDATE [dbo].[Site] SET DefaultContainerType = 'Oqtane.Themes.OqtaneTheme.DefaultNoTitle, Oqtane.Client' WHERE DefaultContainerType = 'Oqtane.Themes.OqtaneTheme.NoTitle, Oqtane.Client';
GO
UPDATE [dbo].[Page] SET DefaultContainerType = 'Oqtane.Themes.OqtaneTheme.DefaultTitle, Oqtane.Client' WHERE DefaultContainerType = 'Oqtane.Themes.OqtaneTheme.Container, Oqtane.Client';
GO
UPDATE [dbo].[Page] SET DefaultContainerType = 'Oqtane.Themes.OqtaneTheme.DefaultNoTitle, Oqtane.Client' WHERE DefaultContainerType = 'Oqtane.Themes.OqtaneTheme.NoTitle, Oqtane.Client';
GO
UPDATE [dbo].[PageModule] SET ContainerType = 'Oqtane.Themes.OqtaneTheme.DefaultTitle, Oqtane.Client' WHERE ContainerType = 'Oqtane.Themes.OqtaneTheme.Container, Oqtane.Client';
GO
UPDATE [dbo].[PageModule] SET ContainerType = 'Oqtane.Themes.OqtaneTheme.DefaultNoTitle, Oqtane.Client' WHERE ContainerType = 'Oqtane.Themes.OqtaneTheme.NoTitle, Oqtane.Client';
GO

View File

@ -227,7 +227,7 @@ namespace Oqtane
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ISyncManager sync)
{ {
ServiceActivator.Configure(app.ApplicationServices); ServiceActivator.Configure(app.ApplicationServices);
@ -265,6 +265,9 @@ namespace Oqtane
endpoints.MapControllers(); endpoints.MapControllers();
endpoints.MapFallbackToPage("/_Host"); endpoints.MapFallbackToPage("/_Host");
}); });
// create a sync event to identify server application startup
sync.AddSyncEvent(-1, "Application", -1);
} }
} }
} }

View File

@ -73,6 +73,8 @@
private async Task Save() private async Task Save()
{ {
try try
{
if (string.IsNullOrEmpty(_name))
{ {
if (PageState.Action == "Add") if (PageState.Action == "Add")
{ {
@ -91,6 +93,11 @@
} }
NavigationManager.NavigateTo(NavigateUrl()); NavigationManager.NavigateTo(NavigateUrl());
} }
else
{
AddModuleMessage("The Name Is Required", MessageType.Warning);
}
}
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Saving [Module] {Error}", ex.Message); await logger.LogError(ex, "Error Saving [Module] {Error}", ex.Message);

View File

@ -1,100 +0,0 @@
@using Oqtane.Modules.Controls
@using [Owner].[Module].Services
@using [Owner].[Module].Models
@namespace [Owner].[Module]
@inherits ModuleBase
@inject I[Module]Service [Module]Service
@inject NavigationManager NavigationManager
<table class="table table-borderless">
<tr>
<td>
<Label For="name" HelpText="Enter a name">Name: </Label>
</td>
<td>
<input id="name" class="form-control" @bind="@_name" />
</td>
</tr>
</table>
<button type="button" class="btn btn-success" @onclick="Save">Save</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">Cancel</NavLink>
<br />
<br />
@if (PageState.Action == "Edit")
{
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
}
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Actions => "Add,Edit";
public override string Title => "Manage [Module]";
public override List<Resource> Resources => new List<Resource>()
{
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }
};
int _id;
string _name;
string _createdby;
DateTime _createdon;
string _modifiedby;
DateTime _modifiedon;
protected override async Task OnInitializedAsync()
{
try
{
if (PageState.Action == "Edit")
{
_id = Int32.Parse(PageState.QueryString["id"]);
[Module] [Module] = await [Module]Service.Get[Module]Async(_id, ModuleState.ModuleId);
if ([Module] != null)
{
_name = [Module].Name;
_createdby = [Module].CreatedBy;
_createdon = [Module].CreatedOn;
_modifiedby = [Module].ModifiedBy;
_modifiedon = [Module].ModifiedOn;
}
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading [Module] {[Module]Id} {Error}", _id, ex.Message);
AddModuleMessage("Error Loading [Module]", MessageType.Error);
}
}
private async Task Save()
{
try
{
if (PageState.Action == "Add")
{
[Module] [Module] = new [Module]();
[Module].ModuleId = ModuleState.ModuleId;
[Module].Name = _name;
[Module] = await [Module]Service.Add[Module]Async([Module]);
await logger.LogInformation("[Module] Added {[Module]}", [Module]);
}
else
{
[Module] [Module] = await [Module]Service.Get[Module]Async(_id, ModuleState.ModuleId);
[Module].Name = _name;
await [Module]Service.Update[Module]Async([Module]);
await logger.LogInformation("[Module] Updated {[Module]}", [Module]);
}
NavigationManager.NavigateTo(NavigateUrl());
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving [Module] {Error}", ex.Message);
AddModuleMessage("Error Saving [Module]", MessageType.Error);
}
}
}

View File

@ -1,100 +0,0 @@
@using [Owner].[Module].Services
@using [Owner].[Module].Models
@namespace [Owner].[Module]
@inherits ModuleBase
@inject I[Module]Service [Module]Service
@inject NavigationManager NavigationManager
@if (_[Module]s == null)
{
<p><em>Loading...</em></p>
}
else
{
<ActionLink Action="Add" Security="SecurityAccessLevel.Edit" Text="Add [Module]" />
<br />
<br />
@if (@_[Module]s.Count != 0)
{
<Pager Items="@_[Module]s">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>Name</th>
</Header>
<Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.[Module]Id.ToString())" /></td>
<td><ActionDialog Header="Delete [Module]" Message="@("Are You Sure You Wish To Delete The " + context.Name + " [Module]?")" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" /></td>
<td>@context.Name</td>
</Row>
</Pager>
}
else
{
<p>No [Module]s To Display</p>
}
}
<!-- The content below is for informational purposes only and can be safely removed -->
<hr />
[Module] Module Created Successfully. Use Edit Mode To Add A [Module]. You Can Access The Files At The Following Locations:<br /><br />
[RootPath]Oqtane.Client\Modules\[Module]\<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 />
- ModuleInfo.cs - implements IModule interface to provide configuration settings for your module<br />
- Settings.razor - component for managing module settings<br />
- Services\I[Module]Service.cs - interface for defining service API methods<br />
- Services\[Module]Service.cs - implements service API interface methods<br /><br />
[RootPath]Oqtane.Server\Modules\[Module]\<br />
- Controllers\[Module]Controller.cs - API methods implemented using a REST pattern<br />
- Manager\[Module]Manager.cs - implements optional module interfaces for features such as import/export of content<br />
- Repository\I[Module]Repository.cs - interface for defining repository methods<br />
- Repository\[Module]Respository.cs - implements repository interface methods for data access using EF Core<br />
- Repository\[Module]Context.cs - provides a DB Context for data access<br />
- Scripts\[Owner].[Module]s.1.0.0.sql - database schema definition script<br />
- Scripts\[Owner].[Module]s.Uninstall.sql - database uninstall script<br /><br />
[RootPath]Oqtane.Shared\Modules\[Module]\<br />
- Models\[Module].cs - model definition<br /><br />
<!-- The content above is for informational purposes only and can be safely removed -->
@code {
public override List<Resource> Resources => new List<Resource>()
{
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" },
new Resource { ResourceType = ResourceType.Script, Url = ModulePath() + "Module.js" }
};
List<[Module]> _[Module]s;
protected override async Task OnInitializedAsync()
{
try
{
_[Module]s = await [Module]Service.Get[Module]sAsync(ModuleState.ModuleId);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading [Module] {Error}", ex.Message);
AddModuleMessage("Error Loading [Module]", MessageType.Error);
}
}
private async Task Delete([Module] [Module])
{
try
{
await [Module]Service.Delete[Module]Async([Module].[Module]Id, ModuleState.ModuleId);
await logger.LogInformation("[Module] Deleted {[Module]}", [Module]);
_[Module]s = await [Module]Service.Get[Module]sAsync(ModuleState.ModuleId);
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting [Module] {[Module]} {Error}", [Module], ex.Message);
AddModuleMessage("Error Deleting [Module]", MessageType.Error);
}
}
}

View File

@ -1,15 +0,0 @@
using Microsoft.JSInterop;
using System.Threading.Tasks;
namespace [Owner].[Module]
{
public class Interop
{
private readonly IJSRuntime _jsRuntime;
public Interop(IJSRuntime jsRuntime)
{
_jsRuntime = jsRuntime;
}
}
}

View File

@ -1,17 +0,0 @@
using Oqtane.Models;
using Oqtane.Modules;
namespace [Owner].[Module]
{
public class ModuleInfo : IModule
{
public ModuleDefinition ModuleDefinition => new ModuleDefinition
{
Name = "[Module]",
Description = "[Module]",
Version = "1.0.0",
ServerManagerType = "[ServerManagerType]",
ReleaseVersions = "1.0.0"
};
}
}

View File

@ -1,19 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using [Owner].[Module].Models;
namespace [Owner].[Module].Services
{
public interface I[Module]Service
{
Task<List<Models.[Module]>> Get[Module]sAsync(int ModuleId);
Task<Models.[Module]> Get[Module]Async(int [Module]Id, int ModuleId);
Task<Models.[Module]> Add[Module]Async(Models.[Module] [Module]);
Task<Models.[Module]> Update[Module]Async(Models.[Module] [Module]);
Task Delete[Module]Async(int [Module]Id, int ModuleId);
}
}

View File

@ -1,49 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Oqtane.Modules;
using Oqtane.Services;
using Oqtane.Shared;
using [Owner].[Module].Models;
namespace [Owner].[Module].Services
{
public class [Module]Service : ServiceBase, I[Module]Service, IService
{
private readonly SiteState _siteState;
public [Module]Service(HttpClient http, SiteState siteState) : base(http)
{
_siteState = siteState;
}
private string Apiurl => CreateApiUrl(_siteState.Alias, "[Module]");
public async Task<List<Models.[Module]>> Get[Module]sAsync(int ModuleId)
{
List<Models.[Module]> [Module]s = await GetJsonAsync<List<Models.[Module]>>(CreateAuthorizationPolicyUrl($"{Apiurl}?moduleid={ModuleId}", ModuleId));
return [Module]s.OrderBy(item => item.Name).ToList();
}
public async Task<Models.[Module]> Get[Module]Async(int [Module]Id, int ModuleId)
{
return await GetJsonAsync<Models.[Module]>(CreateAuthorizationPolicyUrl($"{Apiurl}/{[Module]Id}", ModuleId));
}
public async Task<Models.[Module]> Add[Module]Async(Models.[Module] [Module])
{
return await PostJsonAsync<Models.[Module]>(CreateAuthorizationPolicyUrl($"{Apiurl}", [Module].ModuleId), [Module]);
}
public async Task<Models.[Module]> Update[Module]Async(Models.[Module] [Module])
{
return await PutJsonAsync<Models.[Module]>(CreateAuthorizationPolicyUrl($"{Apiurl}/{[Module].[Module]Id}", [Module].ModuleId), [Module]);
}
public async Task Delete[Module]Async(int [Module]Id, int ModuleId)
{
await DeleteAsync(CreateAuthorizationPolicyUrl($"{Apiurl}/{[Module]Id}", ModuleId));
}
}
}

View File

@ -1,47 +0,0 @@
@namespace [Owner].[Module]
@inherits ModuleBase
@inject ISettingService SettingService
<table class="table table-borderless">
<tr>
<td>
<Label For="value" HelpText="Enter a value">Name: </Label>
</td>
<td>
<input id="value" type="text" class="form-control" @bind="@_value" />
</td>
</tr>
</table>
@code {
public override string Title => "[Module] Settings";
string _value;
protected override async Task OnInitializedAsync()
{
try
{
Dictionary<string, string> settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
_value = SettingService.GetSetting(settings, "SettingName", "");
}
catch (Exception ex)
{
ModuleInstance.AddModuleMessage(ex.Message, MessageType.Error);
}
}
public async Task UpdateSettings()
{
try
{
Dictionary<string, string> settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
SettingService.SetSetting(settings, "SettingName", _value);
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
}
catch (Exception ex)
{
ModuleInstance.AddModuleMessage(ex.Message, MessageType.Error);
}
}
}

View File

@ -1,91 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Oqtane.Shared;
using Oqtane.Enums;
using Oqtane.Infrastructure;
using [Owner].[Module].Models;
using [Owner].[Module].Repository;
namespace [Owner].[Module].Controllers
{
[Route(ControllerRoutes.Default)]
public class [Module]Controller : Controller
{
private readonly I[Module]Repository _[Module]Repository;
private readonly ILogManager _logger;
protected int _entityId = -1;
public [Module]Controller(I[Module]Repository [Module]Repository, ILogManager logger, IHttpContextAccessor accessor)
{
_[Module]Repository = [Module]Repository;
_logger = logger;
if (accessor.HttpContext.Request.Query.ContainsKey("entityid"))
{
_entityId = int.Parse(accessor.HttpContext.Request.Query["entityid"]);
}
}
// GET: api/<controller>?moduleid=x
[HttpGet]
[Authorize(Policy = PolicyNames.ViewModule)]
public IEnumerable<Models.[Module]> Get(string moduleid)
{
return _[Module]Repository.Get[Module]s(int.Parse(moduleid));
}
// GET api/<controller>/5
[HttpGet("{id}")]
[Authorize(Policy = PolicyNames.ViewModule)]
public Models.[Module] Get(int id)
{
Models.[Module] [Module] = _[Module]Repository.Get[Module](id);
if ([Module] != null && [Module].ModuleId != _entityId)
{
[Module] = null;
}
return [Module];
}
// POST api/<controller>
[HttpPost]
[Authorize(Policy = PolicyNames.EditModule)]
public Models.[Module] Post([FromBody] Models.[Module] [Module])
{
if (ModelState.IsValid && [Module].ModuleId == _entityId)
{
[Module] = _[Module]Repository.Add[Module]([Module]);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "[Module] Added {[Module]}", [Module]);
}
return [Module];
}
// PUT api/<controller>/5
[HttpPut("{id}")]
[Authorize(Policy = PolicyNames.EditModule)]
public Models.[Module] Put(int id, [FromBody] Models.[Module] [Module])
{
if (ModelState.IsValid && [Module].ModuleId == _entityId)
{
[Module] = _[Module]Repository.Update[Module]([Module]);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "[Module] Updated {[Module]}", [Module]);
}
return [Module];
}
// DELETE api/<controller>/5
[HttpDelete("{id}")]
[Authorize(Policy = PolicyNames.EditModule)]
public void Delete(int id)
{
Models.[Module] [Module] = _[Module]Repository.Get[Module](id);
if ([Module] != null && [Module].ModuleId == _entityId)
{
_[Module]Repository.Delete[Module](id);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "[Module] Deleted {[Module]Id}", id);
}
}
}
}

View File

@ -1,61 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Oqtane.Modules;
using Oqtane.Models;
using Oqtane.Infrastructure;
using Oqtane.Repository;
using [Owner].[Module].Models;
using [Owner].[Module].Repository;
namespace [Owner].[Module].Manager
{
public class [Module]Manager : IInstallable, IPortable
{
private I[Module]Repository _[Module]Repository;
private ISqlRepository _sql;
public [Module]Manager(I[Module]Repository [Module]Repository, ISqlRepository sql)
{
_[Module]Repository = [Module]Repository;
_sql = sql;
}
public bool Install(Tenant tenant, string version)
{
return _sql.ExecuteScript(tenant, GetType().Assembly, "[Owner].[Module]." + version + ".sql");
}
public bool Uninstall(Tenant tenant)
{
return _sql.ExecuteScript(tenant, GetType().Assembly, "[Owner].[Module].Uninstall.sql");
}
public string ExportModule(Module module)
{
string content = "";
List<Models.[Module]> [Module]s = _[Module]Repository.Get[Module]s(module.ModuleId).ToList();
if ([Module]s != null)
{
content = JsonSerializer.Serialize([Module]s);
}
return content;
}
public void ImportModule(Module module, string content, string version)
{
List<Models.[Module]> [Module]s = null;
if (!string.IsNullOrEmpty(content))
{
[Module]s = JsonSerializer.Deserialize<List<Models.[Module]>>(content);
}
if ([Module]s != null)
{
foreach(var [Module] in [Module]s)
{
_[Module]Repository.Add[Module](new Models.[Module] { ModuleId = module.ModuleId, Name = [Module].Name });
}
}
}
}
}

View File

@ -1,14 +0,0 @@
using System.Collections.Generic;
using [Owner].[Module].Models;
namespace [Owner].[Module].Repository
{
public interface I[Module]Repository
{
IEnumerable<Models.[Module]> Get[Module]s(int ModuleId);
Models.[Module] Get[Module](int [Module]Id);
Models.[Module] Add[Module](Models.[Module] [Module]);
Models.[Module] Update[Module](Models.[Module] [Module]);
void Delete[Module](int [Module]Id);
}
}

View File

@ -1,18 +0,0 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Http;
using Oqtane.Modules;
using Oqtane.Repository;
using [Owner].[Module].Models;
namespace [Owner].[Module].Repository
{
public class [Module]Context : DBContextBase, IService
{
public virtual DbSet<Models.[Module]> [Module] { get; set; }
public [Module]Context(ITenantResolver tenantResolver, IHttpContextAccessor accessor) : base(tenantResolver, accessor)
{
// ContextBase handles multi-tenant database connections
}
}
}

View File

@ -1,49 +0,0 @@
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Collections.Generic;
using Oqtane.Modules;
using [Owner].[Module].Models;
namespace [Owner].[Module].Repository
{
public class [Module]Repository : I[Module]Repository, IService
{
private readonly [Module]Context _db;
public [Module]Repository([Module]Context context)
{
_db = context;
}
public IEnumerable<Models.[Module]> Get[Module]s(int ModuleId)
{
return _db.[Module].Where(item => item.ModuleId == ModuleId);
}
public Models.[Module] Get[Module](int [Module]Id)
{
return _db.[Module].Find([Module]Id);
}
public Models.[Module] Add[Module](Models.[Module] [Module])
{
_db.[Module].Add([Module]);
_db.SaveChanges();
return [Module];
}
public Models.[Module] Update[Module](Models.[Module] [Module])
{
_db.Entry([Module]).State = EntityState.Modified;
_db.SaveChanges();
return [Module];
}
public void Delete[Module](int [Module]Id)
{
Models.[Module] [Module] = _db.[Module].Find([Module]Id);
_db.[Module].Remove([Module]);
_db.SaveChanges();
}
}
}

View File

@ -1,26 +0,0 @@
/*
Create [Owner][Module] table
*/
CREATE TABLE [dbo].[[Owner][Module]](
[[Module]Id] [int] IDENTITY(1,1) NOT NULL,
[ModuleId] [int] NOT NULL,
[Name] [nvarchar](256) NOT NULL,
[CreatedBy] [nvarchar](256) NOT NULL,
[CreatedOn] [datetime] NOT NULL,
[ModifiedBy] [nvarchar](256) NOT NULL,
[ModifiedOn] [datetime] NOT NULL,
CONSTRAINT [PK_[Owner][Module]] PRIMARY KEY CLUSTERED
(
[[Module]Id] ASC
)
)
GO
/*
Create foreign key relationships
*/
ALTER TABLE [dbo].[[Owner][Module]] WITH CHECK ADD CONSTRAINT [FK_[Owner][Module]_Module] FOREIGN KEY([ModuleId])
REFERENCES [dbo].Module ([ModuleId])
ON DELETE CASCADE
GO

View File

@ -1,6 +0,0 @@
/*
Remove [Owner][Module] table
*/
DROP TABLE [dbo].[[Owner][Module]]
GO

View File

@ -1,5 +0,0 @@
/* Module Script */
var [Owner] = [Owner] || {};
[Owner].[Module] = {
};

View File

@ -1,19 +0,0 @@
using System;
using System.ComponentModel.DataAnnotations.Schema;
using Oqtane.Models;
namespace [Owner].[Module].Models
{
[Table("[Owner][Module]")]
public class [Module] : IAuditable
{
public int [Module]Id { get; set; }
public int ModuleId { get; set; }
public string Name { get; set; }
public string CreatedBy { get; set; }
public DateTime CreatedOn { get; set; }
public string ModifiedBy { get; set; }
public DateTime ModifiedOn { get; set; }
}
}

View File

@ -0,0 +1,4 @@
using System.Resources;
using Microsoft.Extensions.Localization;
[assembly: RootNamespace("[Owner].[Theme].Client")]

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Themes.OqtaneTheme @namespace [Owner].[Theme]
@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"> <div class="d-flex flex-nowrap">
@ -15,5 +16,5 @@
</div> </div>
@code { @code {
public override string Name => "Standard Header"; public override string Name => "Container1";
} }

View File

@ -0,0 +1,15 @@
using Oqtane.Models;
using Oqtane.Themes;
namespace [Owner].[Theme]
{
public class ThemeInfo : ITheme
{
public Theme Theme => new Theme
{
Name = "[Theme]",
Version = "1.0.0"
};
}
}

View File

@ -0,0 +1,107 @@
@namespace [Owner].[Theme]
@inherits ThemeBase
<main role="main">
<nav class="navbar navbar-expand-md navbar-dark bg-primary fixed-top">
<Logo /><Menu Orientation="Horizontal" />
<div class="controls ml-md-auto">
<div class="controls-group"><UserProfile /> <Login /> <ControlPanel /></div>
</div>
</nav>
<div class="container">
<div class="row">
<div class="col-md-12">
<Pane Name="Content" />
</div>
</div>
</div>
<Pane Name="Top Full Width" />
<div class="container">
<div class="row">
<div class="col-md-12">
<Pane Name="Top 100%" />
</div>
</div>
<div class="row">
<div class="col-md-6">
<Pane Name="Left 50%" />
</div>
<div class="col-md-6">
<Pane Name="Right 50%" />
</div>
</div>
<div class="row">
<div class="col-md-4">
<Pane Name="Left 33%" />
</div>
<div class="col-md-4">
<Pane Name="Center 33%" />
</div>
<div class="col-md-4">
<Pane Name="Right 33%" />
</div>
</div>
<div class="row">
<div class="col-md-3">
<Pane Name="Left Outer 25%" />
</div>
<div class="col-md-3">
<Pane Name="Left Inner 25%" />
</div>
<div class="col-md-3">
<Pane Name="Right Inner 25%" />
</div>
<div class="col-md-3">
<Pane Name="Right Outer 25%" />
</div>
</div>
<div class="row">
<div class="col-md-3">
<Pane Name="Left 25%" />
</div>
<div class="col-md-6">
<Pane Name="Center 50%" />
</div>
<div class="col-md-3">
<Pane Name="Right 25%" />
</div>
</div>
<div class="row">
<div class="col-md-8">
<Pane Name="Left Sidebar 66%" />
</div>
<div class="col-md-4">
<Pane Name="Right Sidebar 33%" />
</div>
</div>
<div class="row">
<div class="col-md-4">
<Pane Name="Left Sidebar 33%" />
</div>
<div class="col-md-8">
<Pane Name="Right Sidebar 66%" />
</div>
</div>
<div class="row">
<div class="col-md-12">
<Pane Name="Bottom 100%" />
</div>
</div>
</div>
<Pane Name="Bottom Full Width" />
</main>
@code {
public override string Name => "Theme1";
public override string Panes => "Content,Top Full Width,Top 100%,Left 50%,Right 50%,Left 33%,Center 33%,Right 33%,Left Outer 25%,Left Inner 25%,Right Inner 25%,Right Outer 25%,Left 25%,Center 50%,Right 25%,Left Sidebar 66%,Right Sidebar 33%,Left Sidebar 33%,Right Sidebar 66%,Bottom 100%,Bottom Full Width";
public override List<Resource> Resources => new List<Resource>()
{
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css", Integrity = "sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk", CrossOrigin = "anonymous" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "Theme.css" },
new Resource { ResourceType = ResourceType.Script, Bundle = "Bootstrap", Url = "https://code.jquery.com/jquery-3.5.1.slim.min.js", Integrity = "sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj", CrossOrigin = "anonymous" },
new Resource { ResourceType = ResourceType.Script, Bundle = "Bootstrap", Url = "https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js", Integrity = "sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo", CrossOrigin = "anonymous" },
new Resource { ResourceType = ResourceType.Script, Bundle = "Bootstrap", Url = "https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js", Integrity = "sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI", CrossOrigin = "anonymous" }
};
}

View File

@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<RazorLangVersion>3.0</RazorLangVersion>
<Version>1.0.0</Version>
<Authors>[Owner]</Authors>
<Company>[Owner]</Company>
<Description>[Description]</Description>
<Product>[Owner].[Theme]</Product>
<Copyright>[Owner]</Copyright>
<AssemblyName>[Owner].[Theme].Client.Oqtane</AssemblyName>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="5.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="5.0.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="5.0.0" />
<PackageReference Include="System.Net.Http.Json" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
[ClientReference]
[SharedReference]
</ItemGroup>
<PropertyGroup>
<!-- there may be other elements here -->
<BlazorWebAssemblyEnableLinking>false</BlazorWebAssemblyEnableLinking>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,21 @@
@using System
@using System.Linq
@using System.Collections.Generic
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.JSInterop
@using Oqtane.Models
@using Oqtane.Modules
@using Oqtane.Modules.Controls
@using Oqtane.Providers
@using Oqtane.Security
@using Oqtane.Services
@using Oqtane.Shared
@using Oqtane.Themes
@using Oqtane.Themes.Controls
@using Oqtane.UI
@using Oqtane.Enums

View File

@ -0,0 +1,83 @@
/* Oqtane Styles */
body {
padding-top: 7rem;
}
.controls {
z-index: 2000;
padding-top: 15px;
padding-bottom: 15px;
margin-right: 10px;
}
.app-menu .nav-item {
font-size: 0.9rem;
padding-bottom: 0.5rem;
}
.app-menu .nav-item a {
border-radius: 4px;
height: 3rem;
display: flex;
align-items: center;
line-height: 3rem;
}
.app-menu .nav-item a.active {
background-color: rgba(255,255,255,0.25);
color: white;
}
.app-menu .nav-item a:hover {
background-color: rgba(255,255,255,0.1);
color: white;
}
.app-menu .nav-link .oi {
width: 1.5rem;
font-size: 1.1rem;
vertical-align: text-top;
top: -2px;
}
.navbar-toggler {
background-color: rgba(255, 255, 255, 0.1);
margin-left: auto;
}
div.app-moduleactions a.dropdown-toggle, div.app-moduleactions div.dropdown-menu {
color:#ffffff;
}
@media (max-width: 767px) {
.app-menu {
width: 100%
}
.navbar {
position: fixed;
top: 60px;
width: 100%;
}
.controls {
height: 60px;
top: 15px;
position: fixed;
top: 0px;
width: 100%;
background-color: rgb(0, 0, 0);
}
.controls-group {
float: right;
margin-right: 25px;
}
.content {
position: relative;
top: 60px;
}
}

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