commit
56631a3e6b
@ -47,7 +47,7 @@ else
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId, Constants.PackageId);
|
||||
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId, Constants.ClientId);
|
||||
|
||||
var cultures = await LocalizationService.GetCulturesAsync();
|
||||
var culture = cultures.First(c => c.Name.Equals(Constants.DefaultCulture));
|
||||
@ -78,7 +78,7 @@ else
|
||||
private bool UpgradeAvailable(string code, string version)
|
||||
{
|
||||
var upgradeavailable = false;
|
||||
if (_packages != null)
|
||||
if (_packages != null && version != null)
|
||||
{
|
||||
var package = _packages.Where(item => item.PackageId == (Constants.PackageId + "." + code)).FirstOrDefault();
|
||||
if (package != null)
|
||||
|
@ -109,7 +109,7 @@ else
|
||||
// external link to log item will display Details component
|
||||
if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int id))
|
||||
{
|
||||
NavigationManager.NavigateTo(EditUrl(PageState.Page.Path, ModuleState.ModuleId, "Detail", $"id={id}"));
|
||||
NavigationManager.NavigateTo(EditUrl(PageState.Page.Path, ModuleState.ModuleId, "Detail", $"/{id}"));
|
||||
}
|
||||
|
||||
if (UrlParameters.ContainsKey("level"))
|
||||
|
@ -102,46 +102,43 @@
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
</TabPanel>
|
||||
<TabPanel Name="Translations" ResourceKey="Translations">
|
||||
@if (_languages != null)
|
||||
@if (_languages != null && _languages.Count > 0)
|
||||
{
|
||||
@if (_languages.Count > 0)
|
||||
{
|
||||
<Pager Items="@_languages">
|
||||
<Header>
|
||||
<th>@SharedLocalizer["Name"]</th>
|
||||
<th>@Localizer["Code"]</th>
|
||||
<th>@Localizer["Version"]</th>
|
||||
<th style="width: 1px;"> </th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td>@context.Name</td>
|
||||
<td>@context.Code</td>
|
||||
<td>@context.Version</td>
|
||||
<td>
|
||||
@if (context.IsDefault)
|
||||
<Pager Items="@_languages">
|
||||
<Header>
|
||||
<th>@SharedLocalizer["Name"]</th>
|
||||
<th>@Localizer["Code"]</th>
|
||||
<th>@Localizer["Version"]</th>
|
||||
<th style="width: 1px;"> </th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td>@context.Name</td>
|
||||
<td>@context.Code</td>
|
||||
<td>@context.Version</td>
|
||||
<td>
|
||||
@if (context.IsDefault)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(_packagename + "." + context.Code))>@SharedLocalizer["Download"]</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
if (UpgradeAvailable(_packagename + "." + context.Code, context.Version))
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(_packagename + "." + context.Code))>@SharedLocalizer["Download"]</button>
|
||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await DownloadPackage(_packagename + "." + context.Code))>@SharedLocalizer["Upgrade"]</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
if (UpgradeAvailable(_packagename + "." + context.Code, context.Version))
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await DownloadPackage(_packagename + "." + context.Code))>@SharedLocalizer["Upgrade"]</button>
|
||||
}
|
||||
}
|
||||
</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
<button type="button" class="btn btn-success" @onclick="InstallTranslations">@SharedLocalizer["Install"]</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<br />
|
||||
<div class="mx-auto text-center">
|
||||
@Localizer["Search.NoResults"]
|
||||
</div>
|
||||
<br />
|
||||
}
|
||||
}
|
||||
</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
<button type="button" class="btn btn-success" @onclick="InstallTranslations">@SharedLocalizer["Install"]</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<br />
|
||||
<div class="mx-auto text-center">
|
||||
@Localizer["Search.NoResults"]
|
||||
</div>
|
||||
<br />
|
||||
}
|
||||
</TabPanel>
|
||||
</TabStrip>
|
||||
@ -237,17 +234,20 @@
|
||||
_modifiedby = moduleDefinition.ModifiedBy;
|
||||
_modifiedon = moduleDefinition.ModifiedOn;
|
||||
|
||||
_packages = await PackageService.GetPackagesAsync("translation", "", "", _packagename);
|
||||
_languages = await LanguageService.GetLanguagesAsync(-1, _packagename);
|
||||
foreach (var package in _packages)
|
||||
if (!string.IsNullOrEmpty(_packagename))
|
||||
{
|
||||
var code = package.PackageId.Split('.').Last();
|
||||
if (!_languages.Any(item => item.Code == code))
|
||||
_packages = await PackageService.GetPackagesAsync("translation", "", "", _packagename);
|
||||
_languages = await LanguageService.GetLanguagesAsync(-1, _packagename);
|
||||
foreach (var package in _packages)
|
||||
{
|
||||
_languages.Add(new Language { Code = code, Name = CultureInfo.GetCultureInfo(code).DisplayName, Version = package.Version, IsDefault = true });
|
||||
var code = package.PackageId.Split('.').Last();
|
||||
if (!_languages.Any(item => item.Code == code))
|
||||
{
|
||||
_languages.Add(new Language { Code = code, Name = CultureInfo.GetCultureInfo(code).DisplayName, Version = package.Version, IsDefault = true });
|
||||
}
|
||||
}
|
||||
_languages = _languages.OrderBy(item => item.Name).ToList();
|
||||
}
|
||||
_languages = _languages.OrderBy(item => item.Name).ToList();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -50,7 +50,7 @@ else
|
||||
<Row>
|
||||
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ModuleDefinitionId.ToString())" ResourceKey="EditModule" /></td>
|
||||
<td>
|
||||
@if (context.AssemblyName != "Oqtane.Client")
|
||||
@if (context.AssemblyName != Constants.ClientId)
|
||||
{
|
||||
<ActionDialog Header="Delete Module" Message="@string.Format(Localizer["Confirm.Module.Delete", context.Name])" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" />
|
||||
}
|
||||
@ -58,7 +58,7 @@ else
|
||||
<td>@context.Name</td>
|
||||
<td>@context.Version</td>
|
||||
<td>
|
||||
@if(context.AssemblyName == "Oqtane.Client" || PageState.Modules.Where(m => m.ModuleDefinition?.ModuleDefinitionId == context.ModuleDefinitionId).FirstOrDefault() != null)
|
||||
@if (context.AssemblyName == Constants.ClientId || PageState.Modules.Where(m => m.ModuleDefinition?.ModuleDefinitionId == context.ModuleDefinitionId).FirstOrDefault() != null)
|
||||
{
|
||||
<span>@SharedLocalizer["Yes"]</span>
|
||||
}
|
||||
|
@ -7,75 +7,77 @@
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
<TabStrip>
|
||||
<TabPanel Name="Pages" ResourceKey="Pages">
|
||||
@if (_pages == null)
|
||||
{
|
||||
<br />
|
||||
<p>@Localizer["NoPage.Deleted"]</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<Pager Items="@_pages">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@SharedLocalizer["Name"]</th>
|
||||
<th>@Localizer["DeletedBy"]</th>
|
||||
<th>@Localizer["DeletedOn"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><button type="button" @onclick="@(() => RestorePage(context))" class="btn btn-success" title="Restore">Restore</button></td>
|
||||
<td><ActionDialog Header="Delete Page" Message="@string.Format(Localizer["Confirm.Page.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
|
||||
<td>@context.Name</td>
|
||||
<td>@context.DeletedBy</td>
|
||||
<td>@context.DeletedOn</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
@if (_pages.Any())
|
||||
{
|
||||
<br /><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" />
|
||||
}
|
||||
}
|
||||
</TabPanel>
|
||||
<TabPanel Name="Modules" ResourceKey="Modules">
|
||||
@if (_modules == null)
|
||||
{
|
||||
<br />
|
||||
<p>@Localizer["NoModule.Deleted"]</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<Pager Items="@_modules">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@Localizer["Page"]</th>
|
||||
<th>@Localizer["Module"]</th>
|
||||
<th>@Localizer["DeletedBy"]</th>
|
||||
<th>@Localizer["DeletedOn"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><button type="button" @onclick="@(() => RestoreModule(context))" class="btn btn-success" title="Restore">@Localizer["Restore"]</button></td>
|
||||
<td><ActionDialog Header="Delete Module" Message="@string.Format(Localizer["Confirm.Module.Delete"], context.Title)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" /></td>
|
||||
<td>@_pages.Find(item => item.PageId == context.PageId).Name</td>
|
||||
<td>@context.Title</td>
|
||||
<td>@context.DeletedBy</td>
|
||||
<td>@context.DeletedOn</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
@if (_modules.Any())
|
||||
{
|
||||
<br /><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" />
|
||||
}
|
||||
|
||||
}
|
||||
</TabPanel>
|
||||
</TabStrip>
|
||||
@if (_pages == null || _modules == null)
|
||||
{
|
||||
<p><em>@SharedLocalizer["Loading"]</em></p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<TabStrip>
|
||||
<TabPanel Name="Pages" ResourceKey="Pages">
|
||||
@if (!_pages.Where(item => item.IsDeleted).Any())
|
||||
{
|
||||
<br />
|
||||
<p>@Localizer["NoPage.Deleted"]</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<Pager Items="@_pages.Where(item => item.IsDeleted)">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@SharedLocalizer["Name"]</th>
|
||||
<th>@Localizer["DeletedBy"]</th>
|
||||
<th>@Localizer["DeletedOn"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><button type="button" @onclick="@(() => RestorePage(context))" class="btn btn-success" title="Restore">Restore</button></td>
|
||||
<td><ActionDialog Header="Delete Page" Message="@string.Format(Localizer["Confirm.Page.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
|
||||
<td>@context.Name</td>
|
||||
<td>@context.DeletedBy</td>
|
||||
<td>@context.DeletedOn</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
<br />
|
||||
<ActionDialog Header="Remove All Deleted Pages" Message="Are You Sure You Wish To Permanently Remove All Deleted Pages?" Action="Remove All Deleted Pages" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllPages())" ResourceKey="DeleteAllPages" />
|
||||
}
|
||||
</TabPanel>
|
||||
<TabPanel Name="Modules" ResourceKey="Modules">
|
||||
@if (!_modules.Where(item => item.IsDeleted).Any())
|
||||
{
|
||||
<br />
|
||||
<p>@Localizer["NoModule.Deleted"]</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<Pager Items="@_modules.Where(item => item.IsDeleted)">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@Localizer["Page"]</th>
|
||||
<th>@Localizer["Module"]</th>
|
||||
<th>@Localizer["DeletedBy"]</th>
|
||||
<th>@Localizer["DeletedOn"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><button type="button" @onclick="@(() => RestoreModule(context))" class="btn btn-success" title="Restore">@Localizer["Restore"]</button></td>
|
||||
<td><ActionDialog Header="Delete Module" Message="@string.Format(Localizer["Confirm.Module.Delete"], context.Title)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" /></td>
|
||||
<td>@_pages.Find(item => item.PageId == context.PageId).Name</td>
|
||||
<td>@context.Title</td>
|
||||
<td>@context.DeletedBy</td>
|
||||
<td>@context.DeletedOn</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
<br />
|
||||
<ActionDialog Header="Remove All Deleted Modules" Message="Are You Sure You Wish To Permanently Remove All Deleted Modules?" Action="Remove All Deleted Modules" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllModules())" ResourceKey="DeleteAllModules" />
|
||||
}
|
||||
</TabPanel>
|
||||
</TabStrip>
|
||||
}
|
||||
|
||||
@code {
|
||||
private List<Page> _pages;
|
||||
private List<Module> _modules;
|
||||
private List<Page> _pages;
|
||||
private List<Module> _modules;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||
|
||||
@ -95,10 +97,7 @@
|
||||
private async Task Load()
|
||||
{
|
||||
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||
_pages = _pages.Where(item => item.IsDeleted).ToList();
|
||||
|
||||
_modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
|
||||
_modules = _modules.Where(item => item.IsDeleted).ToList();
|
||||
}
|
||||
|
||||
private async Task RestorePage(Page page)
|
||||
@ -141,7 +140,7 @@
|
||||
try
|
||||
{
|
||||
ModuleInstance.ShowProgressIndicator();
|
||||
foreach (Page page in _pages)
|
||||
foreach (Page page in _pages.Where(item => item.IsDeleted))
|
||||
{
|
||||
await PageService.DeletePageAsync(page.PageId);
|
||||
await logger.LogInformation("Page Permanently Deleted {Page}", page);
|
||||
@ -184,9 +183,8 @@
|
||||
try
|
||||
{
|
||||
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
|
||||
// check if there are any remaining module instances in the site
|
||||
_modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
|
||||
|
||||
// check if there are any remaining module instances in the site
|
||||
if (!_modules.Exists(item => item.ModuleId == module.ModuleId))
|
||||
{
|
||||
await ModuleService.DeleteModuleAsync(module.ModuleId);
|
||||
@ -208,12 +206,11 @@
|
||||
try
|
||||
{
|
||||
ModuleInstance.ShowProgressIndicator();
|
||||
foreach (Module module in _modules)
|
||||
foreach (Module module in _modules.Where(item => item.IsDeleted))
|
||||
{
|
||||
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
|
||||
// check if there are any remaining module instances in the site
|
||||
_modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
|
||||
|
||||
// check if there are any remaining module instances in the site
|
||||
if (!_modules.Exists(item => item.ModuleId == module.ModuleId))
|
||||
{
|
||||
await ModuleService.DeleteModuleAsync(module.ModuleId);
|
||||
|
@ -29,7 +29,7 @@ else
|
||||
<Row>
|
||||
<td><ActionLink Action="View" Parameters="@($"name=" + WebUtility.UrlEncode(context.ThemeName))" ResourceKey="ViewTheme" /></td>
|
||||
<td>
|
||||
@if (context.AssemblyName != "Oqtane.Client")
|
||||
@if (context.AssemblyName != Constants.ClientId)
|
||||
{
|
||||
<ActionDialog Header="Delete Theme" Message="@string.Format(Localizer["Confirm.Theme.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteTheme(context))" ResourceKey="DeleteTheme" />
|
||||
}
|
||||
|
@ -286,6 +286,15 @@ else
|
||||
<input id="emailclaimtype" class="form-control" @bind="@_emailclaimtype" />
|
||||
</div>
|
||||
</div>
|
||||
@if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="roleclaimtype" HelpText="The name of the role claim provided by the provider" ResourceKey="RoleClaimType">Role Claim:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="roleclaimtype" class="form-control" @bind="@_roleclaimtype" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="domainfilter" HelpText="Provide any email domain filter criteria (separated by commas). Domains to exclude should be prefixed with an exclamation point (!). For example 'microsoft.com,!hotmail.com' would include microsoft.com email addresses but not hotmail.com email addresses." ResourceKey="DomainFilter">Domain Filter:</Label>
|
||||
<div class="col-sm-9">
|
||||
@ -385,6 +394,7 @@ else
|
||||
private string _redirecturl;
|
||||
private string _identifierclaimtype;
|
||||
private string _emailclaimtype;
|
||||
private string _roleclaimtype;
|
||||
private string _domainfilter;
|
||||
private string _createusers;
|
||||
|
||||
@ -436,8 +446,9 @@ else
|
||||
_parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", "");
|
||||
_pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false");
|
||||
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;
|
||||
_identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier");
|
||||
_emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress");
|
||||
_identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "sub");
|
||||
_emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "email");
|
||||
_roleclaimtype = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimType", "");
|
||||
_domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", "");
|
||||
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
|
||||
|
||||
@ -555,6 +566,7 @@ else
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:IdentifierClaimType", _identifierclaimtype, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:EmailClaimType", _emailclaimtype, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimType", _roleclaimtype, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true);
|
||||
|
||||
@ -590,14 +602,10 @@ else
|
||||
if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
|
||||
{
|
||||
_scopes = "openid,profile,email";
|
||||
_identifierclaimtype = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier";
|
||||
_emailclaimtype = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress";
|
||||
}
|
||||
else
|
||||
{
|
||||
_scopes = "";
|
||||
_identifierclaimtype = "sub";
|
||||
_emailclaimtype = "email";
|
||||
}
|
||||
}
|
||||
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;
|
||||
|
@ -70,6 +70,9 @@
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="@RowClass">@Footer</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
@ -185,6 +188,9 @@
|
||||
[Parameter]
|
||||
public RenderFragment<TableItem> Row { get; set; } = null; // required
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment Footer { get; set; } = null; // only applicable to Table layouts
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment<TableItem> Detail { get; set; } = null; // only applicable to Table layouts
|
||||
|
||||
@ -293,6 +299,7 @@
|
||||
{
|
||||
_page = 1;
|
||||
}
|
||||
if (_page < 1) _page = 1;
|
||||
|
||||
_startPage = 0;
|
||||
_endPage = 0;
|
||||
|
@ -262,7 +262,7 @@
|
||||
{
|
||||
var interop = new Interop(JSRuntime);
|
||||
int pos = await interop.GetCaretPosition("rawhtmleditor");
|
||||
var image = "<img src=\"" + file.Url + "\" alt=\"" + ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name) + "\">";
|
||||
var image = "<img src=\"" + file.Url + "\" alt=\"" + ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name) + "\" class=\"img-fluid\">";
|
||||
_rawhtml = _rawhtml.Substring(0, pos) + image + _rawhtml.Substring(pos);
|
||||
_rawfilemanager = false;
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ namespace Oqtane.Modules
|
||||
var scripts = new List<object>();
|
||||
foreach (Resource resource in Resources.Where(item => item.ResourceType == ResourceType.Script))
|
||||
{
|
||||
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + "/" + resource.Url;
|
||||
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url;
|
||||
scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module });
|
||||
}
|
||||
if (scripts.Any())
|
||||
@ -91,7 +91,7 @@ namespace Oqtane.Modules
|
||||
|
||||
public string ModulePath()
|
||||
{
|
||||
return "Modules/" + GetType().Namespace + "/";
|
||||
return PageState?.Alias.BaseUrl + "/Modules/" + GetType().Namespace + "/";
|
||||
}
|
||||
|
||||
// url methods
|
||||
@ -145,14 +145,23 @@ namespace Oqtane.Modules
|
||||
return Utilities.EditUrl(PageState.Alias.Path, path, moduleid, action, parameters);
|
||||
}
|
||||
|
||||
public string ContentUrl(int fileid)
|
||||
public string FileUrl(string folderpath, string filename)
|
||||
{
|
||||
return ContentUrl(fileid, false);
|
||||
return FileUrl(folderpath, filename, false);
|
||||
}
|
||||
|
||||
public string ContentUrl(int fileid, bool asAttachment)
|
||||
public string FileUrl(string folderpath, string filename, bool download)
|
||||
{
|
||||
return Utilities.ContentUrl(PageState.Alias, fileid, asAttachment);
|
||||
return Utilities.FileUrl(PageState.Alias, folderpath, filename, download);
|
||||
}
|
||||
public string FileUrl(int fileid)
|
||||
{
|
||||
return FileUrl(fileid, false);
|
||||
}
|
||||
|
||||
public string FileUrl(int fileid, bool download)
|
||||
{
|
||||
return Utilities.FileUrl(PageState.Alias, fileid, download);
|
||||
}
|
||||
|
||||
public string ImageUrl(int fileid, int width, int height)
|
||||
@ -407,5 +416,17 @@ namespace Oqtane.Modules
|
||||
await _moduleBase.Log(null, LogLevel.Critical, "", exception, message, args);
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("ContentUrl(int fileId) is deprecated. Use FileUrl(int fileId) instead.", false)]
|
||||
public string ContentUrl(int fileid)
|
||||
{
|
||||
return ContentUrl(fileid, false);
|
||||
}
|
||||
|
||||
[Obsolete("ContentUrl(int fileId, bool asAttachment) is deprecated. Use FileUrl(int fileId, bool download) instead.", false)]
|
||||
public string ContentUrl(int fileid, bool asAttachment)
|
||||
{
|
||||
return Utilities.FileUrl(PageState.Alias, fileid, asAttachment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
<OutputType>Exe</OutputType>
|
||||
<RazorLangVersion>3.0</RazorLangVersion>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Version>3.2.0</Version>
|
||||
<Version>3.2.1</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -13,7 +13,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<RootNamespace>Oqtane</RootNamespace>
|
||||
|
@ -63,7 +63,7 @@ namespace Oqtane.Client
|
||||
{
|
||||
var dlls = new Dictionary<string, byte[]>();
|
||||
var pdbs = new Dictionary<string, byte[]>();
|
||||
var filter = new List<string>();
|
||||
var list = new List<string>();
|
||||
|
||||
var jsRuntime = serviceProvider.GetRequiredService<IJSRuntime>();
|
||||
var interop = new Interop(jsRuntime);
|
||||
@ -81,14 +81,14 @@ namespace Oqtane.Client
|
||||
var file = files.FirstOrDefault(item => item.Contains(assembly));
|
||||
if (file == null)
|
||||
{
|
||||
filter.Add(assembly);
|
||||
list.Add(assembly);
|
||||
}
|
||||
else
|
||||
{
|
||||
// check if newer version available
|
||||
if (GetFileDate(assembly) > GetFileDate(file))
|
||||
{
|
||||
filter.Add(assembly);
|
||||
list.Add(assembly);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -96,7 +96,7 @@ namespace Oqtane.Client
|
||||
// get assemblies already downloaded
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (assemblies.Contains(file) && !filter.Contains(file))
|
||||
if (assemblies.Contains(file) && !list.Contains(file))
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -117,6 +117,7 @@ namespace Oqtane.Client
|
||||
try
|
||||
{
|
||||
await interop.RemoveIndexedDBItem(file);
|
||||
await interop.RemoveIndexedDBItem(file.Replace(".dll", ".pdb"));
|
||||
}
|
||||
catch
|
||||
{
|
||||
@ -127,13 +128,13 @@ namespace Oqtane.Client
|
||||
}
|
||||
else
|
||||
{
|
||||
filter.Add("*");
|
||||
list.Add("*");
|
||||
}
|
||||
|
||||
if (filter.Count != 0)
|
||||
if (list.Count != 0)
|
||||
{
|
||||
// get assemblies from server and load into client app domain
|
||||
var zip = await http.GetByteArrayAsync($"/api/Installation/load?list=" + string.Join(",", filter));
|
||||
var zip = await http.GetByteArrayAsync($"/api/Installation/load?list=" + string.Join(",", list));
|
||||
|
||||
// asemblies and debug symbols are packaged in a zip file
|
||||
using (ZipArchive archive = new ZipArchive(new MemoryStream(zip)))
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
@ -166,16 +166,22 @@
|
||||
<value>Error Permanently Deleting Modules</value>
|
||||
</data>
|
||||
<data name="DeleteAllPages.Header" xml:space="preserve">
|
||||
<value>Delete All Pages</value>
|
||||
<value>Remove All Deleted Pages</value>
|
||||
</data>
|
||||
<data name="DeleteAllPages.Message" xml:space="preserve">
|
||||
<value>Are You Sure You Wish To Permanently Delete All Pages?</value>
|
||||
<value>Are You Sure You Wish To Permanently Remove All Deleted Pages?</value>
|
||||
</data>
|
||||
<data name="DeleteAllPages.Text" xml:space="preserve">
|
||||
<value>Remove All Deleted Pages</value>
|
||||
</data>
|
||||
<data name="DeleteAllModules.Header" xml:space="preserve">
|
||||
<value>Delete All Modules</value>
|
||||
<value>Remove All Deleted Modules</value>
|
||||
</data>
|
||||
<data name="DeleteAllModules.Message" xml:space="preserve">
|
||||
<value>Are You Sure You Wish To Permanently Delete All Modules?</value>
|
||||
<value>Are You Sure You Wish To Permanently Remove All Deleted Modules?</value>
|
||||
</data>
|
||||
<data name="DeleteAllModules.Text" xml:space="preserve">
|
||||
<value>Remove All Deleted Modules</value>
|
||||
</data>
|
||||
<data name="Pages.Heading" xml:space="preserve">
|
||||
<value>Pages</value>
|
||||
|
@ -211,25 +211,25 @@
|
||||
<value>Allow Login?</value>
|
||||
</data>
|
||||
<data name="Authority.HelpText" xml:space="preserve">
|
||||
<value>The Authority Url or Issuer Url associated with the OpenID Connect provider</value>
|
||||
<value>The authority url or issuer url associated with the identity provider</value>
|
||||
</data>
|
||||
<data name="Authority.Text" xml:space="preserve">
|
||||
<value>Authority:</value>
|
||||
</data>
|
||||
<data name="AuthorizationUrl.HelpText" xml:space="preserve">
|
||||
<value>The endpoint for obtaining an Authorization Code</value>
|
||||
<value>The endpoint for obtaining an authorization code</value>
|
||||
</data>
|
||||
<data name="AuthorizationUrl.Text" xml:space="preserve">
|
||||
<value>Authorization Url:</value>
|
||||
</data>
|
||||
<data name="ClientID.HelpText" xml:space="preserve">
|
||||
<value>The Client ID from the provider</value>
|
||||
<value>The client id for the identity provider</value>
|
||||
</data>
|
||||
<data name="ClientID.Text" xml:space="preserve">
|
||||
<value>Client ID:</value>
|
||||
</data>
|
||||
<data name="ClientSecret.HelpText" xml:space="preserve">
|
||||
<value>The Client Secret from the provider</value>
|
||||
<value>The client secret for the identity provider</value>
|
||||
</data>
|
||||
<data name="ClientSecret.Text" xml:space="preserve">
|
||||
<value>Client Secret:</value>
|
||||
@ -247,7 +247,7 @@
|
||||
<value>Domain Filter:</value>
|
||||
</data>
|
||||
<data name="EmailClaimType.HelpText" xml:space="preserve">
|
||||
<value>The name of the email address claim provided by the provider</value>
|
||||
<value>The name of the email address claim provided by the identity provider</value>
|
||||
</data>
|
||||
<data name="EmailClaimType.Text" xml:space="preserve">
|
||||
<value>Email Claim:</value>
|
||||
@ -259,7 +259,7 @@
|
||||
<value>Lockout Settings</value>
|
||||
</data>
|
||||
<data name="MetadataUrl.HelpText" xml:space="preserve">
|
||||
<value>The discovery endpoint for obtaining metadata for this provider. Only specify if the OpenID Connect provider does not use the standard approach (ie. /.well-known/openid-configuration)</value>
|
||||
<value>The discovery endpoint for obtaining metadata for this identity provider. Only specify if the identity provider does not use the standard approach (ie. /.well-known/openid-configuration)</value>
|
||||
</data>
|
||||
<data name="MetadataUrl.Text" xml:space="preserve">
|
||||
<value>Metadata Url:</value>
|
||||
@ -268,7 +268,7 @@
|
||||
<value>Password Settings</value>
|
||||
</data>
|
||||
<data name="PKCE.HelpText" xml:space="preserve">
|
||||
<value>Indicate if the provider supports Proof Key for Code Exchange (PKCE)</value>
|
||||
<value>Indicate if the identity provider supports proof key for code exchange (PKCE)</value>
|
||||
</data>
|
||||
<data name="PKCE.Text" xml:space="preserve">
|
||||
<value>Use PKCE?</value>
|
||||
@ -286,25 +286,25 @@
|
||||
<value>Provider Type:</value>
|
||||
</data>
|
||||
<data name="RedirectUrl.HelpText" xml:space="preserve">
|
||||
<value>The Redirect Url (or Callback Url) which usually needs to be registered with the provider</value>
|
||||
<value>The redirect url (or callback url) which usually needs to be registered with the identity provider</value>
|
||||
</data>
|
||||
<data name="RedirectUrl.Text" xml:space="preserve">
|
||||
<value>Redirect Url:</value>
|
||||
</data>
|
||||
<data name="Scopes.HelpText" xml:space="preserve">
|
||||
<value>A list of Scopes to request from the provider (separated by commas). If none are specified, standard Scopes will be used by default.</value>
|
||||
<value>A list of scopes to request from the identity provider (separated by commas). If none are specified, standard Scopes will be used by default.</value>
|
||||
</data>
|
||||
<data name="Scopes.Text" xml:space="preserve">
|
||||
<value>Scopes:</value>
|
||||
</data>
|
||||
<data name="TokenUrl.HelpText" xml:space="preserve">
|
||||
<value>The endpoint for obtaining an Auth Token</value>
|
||||
<value>The endpoint for obtaining an auth token</value>
|
||||
</data>
|
||||
<data name="TokenUrl.Text" xml:space="preserve">
|
||||
<value>Token Url:</value>
|
||||
</data>
|
||||
<data name="UserInfoUrl.HelpText" xml:space="preserve">
|
||||
<value>The endpoint for obtaining user information. This should be an API or Page Url which contains the users email address.</value>
|
||||
<value>The endpoint for obtaining user information. This should be an API endpoint or page url which contains the users email address.</value>
|
||||
</data>
|
||||
<data name="UserInfoUrl.Text" xml:space="preserve">
|
||||
<value>User Info Url:</value>
|
||||
@ -373,15 +373,21 @@
|
||||
<value>Last Login</value>
|
||||
</data>
|
||||
<data name="IdentifierClaimType.HelpText" xml:space="preserve">
|
||||
<value>The name of the unique user identifier claim provided by the provider</value>
|
||||
<value>The name of the unique user identifier claim provided by the identity provider</value>
|
||||
</data>
|
||||
<data name="IdentifierClaimType.Text" xml:space="preserve">
|
||||
<value>Identifier Claim:</value>
|
||||
</data>
|
||||
<data name="Parameters.HelpText" xml:space="preserve">
|
||||
<value>Optionally specify any additional parameters as name/value pairs to send to the provider (separated by commas if there are multiple).</value>
|
||||
<value>Optionally specify any additional parameters as name/value pairs to send to the identity provider (separated by commas if there are multiple).</value>
|
||||
</data>
|
||||
<data name="Parameters.Text" xml:space="preserve">
|
||||
<value>Parameters:</value>
|
||||
</data>
|
||||
<data name="RoleClaimType.HelpText" xml:space="preserve">
|
||||
<value>Optionally provide the name of the role claim provided by the identity provider. These roles will be used in addition to any internal user roles assigned within the site.</value>
|
||||
</data>
|
||||
<data name="RoleClaimType.Text" xml:space="preserve">
|
||||
<value>Role Claim Type:</value>
|
||||
</data>
|
||||
</root>
|
@ -3,6 +3,7 @@ using Microsoft.JSInterop;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Shared;
|
||||
using Oqtane.UI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@ -34,7 +35,8 @@ namespace Oqtane.Themes
|
||||
var scripts = new List<object>();
|
||||
foreach (Resource resource in Resources.Where(item => item.ResourceType == ResourceType.Script))
|
||||
{
|
||||
scripts.Add(new { href = resource.Url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module });
|
||||
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url;
|
||||
scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module });
|
||||
}
|
||||
if (scripts.Any())
|
||||
{
|
||||
@ -49,7 +51,7 @@ namespace Oqtane.Themes
|
||||
|
||||
public string ThemePath()
|
||||
{
|
||||
return "Themes/" + GetType().Namespace + "/";
|
||||
return PageState?.Alias.BaseUrl + "/Themes/" + GetType().Namespace + "/";
|
||||
}
|
||||
|
||||
// url methods
|
||||
@ -94,14 +96,23 @@ namespace Oqtane.Themes
|
||||
return Utilities.EditUrl(PageState.Alias.Path, path, moduleid, action, parameters);
|
||||
}
|
||||
|
||||
public string ContentUrl(int fileid)
|
||||
public string FileUrl(string folderpath, string filename)
|
||||
{
|
||||
return Utilities.ContentUrl(PageState.Alias, fileid);
|
||||
return FileUrl(folderpath, filename, false);
|
||||
}
|
||||
|
||||
public string ContentUrl(int fileid, bool asAttachment)
|
||||
public string FileUrl(string folderpath, string filename, bool download)
|
||||
{
|
||||
return Utilities.ContentUrl(PageState.Alias, fileid, asAttachment);
|
||||
return Utilities.FileUrl(PageState.Alias, folderpath, filename, download);
|
||||
}
|
||||
public string FileUrl(int fileid)
|
||||
{
|
||||
return FileUrl(fileid, false);
|
||||
}
|
||||
|
||||
public string FileUrl(int fileid, bool download)
|
||||
{
|
||||
return Utilities.FileUrl(PageState.Alias, fileid, download);
|
||||
}
|
||||
|
||||
public string ImageUrl(int fileid, int width, int height)
|
||||
@ -118,5 +129,17 @@ namespace Oqtane.Themes
|
||||
{
|
||||
return Utilities.ImageUrl(PageState.Alias, fileid, width, height, mode, position, background, rotate, recreate);
|
||||
}
|
||||
|
||||
[Obsolete("ContentUrl(int fileId) is deprecated. Use FileUrl(int fileId) instead.", false)]
|
||||
public string ContentUrl(int fileid)
|
||||
{
|
||||
return ContentUrl(fileid, false);
|
||||
}
|
||||
|
||||
[Obsolete("ContentUrl(int fileId, bool asAttachment) is deprecated. Use FileUrl(int fileId, bool download) instead.", false)]
|
||||
public string ContentUrl(int fileid, bool asAttachment)
|
||||
{
|
||||
return Utilities.FileUrl(PageState.Alias, fileid, asAttachment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +0,0 @@
|
||||
namespace Oqtane.UI
|
||||
{
|
||||
public enum Refresh
|
||||
{
|
||||
None,
|
||||
Site,
|
||||
Application
|
||||
}
|
||||
}
|
@ -79,7 +79,7 @@
|
||||
Page page;
|
||||
User user = null;
|
||||
var editmode = false;
|
||||
var refresh = UI.Refresh.None;
|
||||
var refresh = false;
|
||||
var lastsyncdate = DateTime.UtcNow.AddHours(-1);
|
||||
var runtime = (Shared.Runtime)Enum.Parse(typeof(Shared.Runtime), Runtime);
|
||||
_error = "";
|
||||
@ -116,7 +116,7 @@
|
||||
// the refresh parameter is used to refresh the client-side PageState
|
||||
if (querystring.ContainsKey("refresh"))
|
||||
{
|
||||
refresh = UI.Refresh.Site;
|
||||
refresh = true;
|
||||
}
|
||||
|
||||
if (PageState != null)
|
||||
@ -126,7 +126,7 @@
|
||||
}
|
||||
|
||||
// get user
|
||||
if (PageState == null || refresh == UI.Refresh.Site || PageState.Alias.SiteId != SiteState.Alias.SiteId)
|
||||
if (PageState == null || refresh || PageState.Alias.SiteId != SiteState.Alias.SiteId)
|
||||
{
|
||||
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
|
||||
if (authState.User.Identity.IsAuthenticated)
|
||||
@ -149,27 +149,27 @@
|
||||
if (sync.SyncEvents.Any())
|
||||
{
|
||||
// reload client application if server was restarted or site runtime/rendermode was modified
|
||||
if (PageState != null && sync.SyncEvents.Exists(item => (item.TenantId == -1 || item.EntityName == EntityNames.Site && item.EntityId == SiteState.Alias.SiteId) && item.Reload))
|
||||
if (PageState != null && sync.SyncEvents.Exists(item => (item.Action == SyncEventActions.Reload)))
|
||||
{
|
||||
NavigationManager.NavigateTo(_absoluteUri, true);
|
||||
return;
|
||||
}
|
||||
// when a site has changed the state needs to be refreshed
|
||||
// when site information has changed the PageState needs to be refreshed
|
||||
if (sync.SyncEvents.Exists(item => item.EntityName == EntityNames.Site && item.EntityId == SiteState.Alias.SiteId))
|
||||
{
|
||||
refresh = UI.Refresh.Site;
|
||||
refresh = true;
|
||||
}
|
||||
// when a user changed the site needs to be refreshed as the list of pages/modules may have changed
|
||||
// when user information has changed the PageState needs to be refreshed as the list of pages/modules may have changed
|
||||
if (user != null && sync.SyncEvents.Exists(item => item.EntityName == EntityNames.User && item.EntityId == user.UserId))
|
||||
{
|
||||
refresh = UI.Refresh.Site;
|
||||
refresh = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (PageState == null || refresh == UI.Refresh.Site || PageState.Alias.SiteId != SiteState.Alias.SiteId)
|
||||
if (PageState == null || refresh || PageState.Alias.SiteId != SiteState.Alias.SiteId)
|
||||
{
|
||||
site = await SiteService.GetSiteAsync(SiteState.Alias.SiteId);
|
||||
refresh = UI.Refresh.Site;
|
||||
refresh = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -178,7 +178,7 @@
|
||||
|
||||
if (site != null)
|
||||
{
|
||||
if (PageState == null || refresh == UI.Refresh.Site || PageState.Page.Path != route.PagePath)
|
||||
if (PageState == null || refresh || PageState.Page.Path != route.PagePath)
|
||||
{
|
||||
page = site.Pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase));
|
||||
editmode = false;
|
||||
|
@ -36,7 +36,7 @@
|
||||
foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet))
|
||||
{
|
||||
var prefix = "app-stylesheet-" + resource.Level.ToString().ToLower();
|
||||
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + "/" + resource.Url;
|
||||
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url;
|
||||
links.Add(new { id = prefix + "-" + batch + "-" + (links.Count + 1).ToString("00"), rel = "stylesheet", href = url, type = "text/css", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", insertbefore = prefix });
|
||||
}
|
||||
if (links.Any())
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Version>3.2.0</Version>
|
||||
<Version>3.2.1</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -10,7 +10,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Database.MySQL</id>
|
||||
<version>3.2.0</version>
|
||||
<version>3.2.1</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane MySQL Provider</title>
|
||||
@ -12,7 +12,7 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Version>3.2.0</Version>
|
||||
<Version>3.2.1</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -10,7 +10,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Database.PostgreSQL</id>
|
||||
<version>3.2.0</version>
|
||||
<version>3.2.1</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane PostgreSQL Provider</title>
|
||||
@ -12,7 +12,7 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Version>3.2.0</Version>
|
||||
<Version>3.2.1</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -10,7 +10,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Database.SqlServer</id>
|
||||
<version>3.2.0</version>
|
||||
<version>3.2.1</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane SQL Server Provider</title>
|
||||
@ -12,7 +12,7 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Version>3.2.0</Version>
|
||||
<Version>3.2.1</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -10,7 +10,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Database.Sqlite</id>
|
||||
<version>3.2.0</version>
|
||||
<version>3.2.1</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane SQLite Provider</title>
|
||||
@ -12,7 +12,7 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
|
@ -12,7 +12,8 @@ namespace Oqtane.Maui;
|
||||
public static class MauiProgram
|
||||
{
|
||||
// the API service url
|
||||
static string apiurl = "http://localhost:44357";
|
||||
static string apiurl = "https://www.oqtane.org"; // for testing
|
||||
//static string apiurl = "http://localhost:44357"; // for local development (Oqtane.Server must be already running for MAUI client to connect)
|
||||
|
||||
public static MauiApp CreateMauiApp()
|
||||
{
|
||||
@ -72,7 +73,7 @@ public static class MauiProgram
|
||||
|
||||
var dlls = new Dictionary<string, byte[]>();
|
||||
var pdbs = new Dictionary<string, byte[]>();
|
||||
var filter = new List<string>();
|
||||
var list = new List<string>();
|
||||
|
||||
var files = new List<string>();
|
||||
foreach (var file in Directory.EnumerateFiles(folder, "*.dll", SearchOption.AllDirectories))
|
||||
@ -92,14 +93,14 @@ public static class MauiProgram
|
||||
var file = files.FirstOrDefault(item => item.Contains(assembly));
|
||||
if (file == null)
|
||||
{
|
||||
filter.Add(assembly);
|
||||
list.Add(assembly);
|
||||
}
|
||||
else
|
||||
{
|
||||
// check if newer version available
|
||||
if (GetFileDate(assembly) > GetFileDate(file))
|
||||
{
|
||||
filter.Add(assembly);
|
||||
list.Add(assembly);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -107,7 +108,7 @@ public static class MauiProgram
|
||||
// get assemblies already downloaded
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (assemblies.Contains(file) && !filter.Contains(file))
|
||||
if (assemblies.Contains(file) && !list.Contains(file))
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -127,7 +128,10 @@ public static class MauiProgram
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(Path.Combine(folder, file));
|
||||
foreach (var path in Directory.EnumerateFiles(folder, Path.GetFileNameWithoutExtension(file) + ".*"))
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
@ -138,13 +142,13 @@ public static class MauiProgram
|
||||
}
|
||||
else
|
||||
{
|
||||
filter.Add("*");
|
||||
list.Add("*");
|
||||
}
|
||||
|
||||
if (filter.Count != 0)
|
||||
if (list.Count != 0)
|
||||
{
|
||||
// get assemblies from server
|
||||
var zip = Task.Run(() => http.GetByteArrayAsync("/api/Installation/load?list=" + string.Join(",", filter))).GetAwaiter().GetResult();
|
||||
var zip = Task.Run(() => http.GetByteArrayAsync("/api/Installation/load?list=" + string.Join(",", list))).GetAwaiter().GetResult();
|
||||
|
||||
// asemblies and debug symbols are packaged in a zip file
|
||||
using (ZipArchive archive = new ZipArchive(new MemoryStream(zip)))
|
||||
@ -159,11 +163,6 @@ public static class MauiProgram
|
||||
// save assembly to local folder
|
||||
try
|
||||
{
|
||||
int subfolder = entry.FullName.IndexOf('/');
|
||||
if (subfolder != -1 && !Directory.Exists(Path.Combine(folder, entry.FullName.Substring(0, subfolder))))
|
||||
{
|
||||
Directory.CreateDirectory(Path.Combine(folder, entry.FullName.Substring(0, subfolder)));
|
||||
}
|
||||
using var stream = File.Create(Path.Combine(folder, entry.FullName));
|
||||
stream.Write(file, 0, file.Length);
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
|
||||
<!-- <TargetFrameworks>$(TargetFrameworks);net6.0-tizen</TargetFrameworks> -->
|
||||
<OutputType>Exe</OutputType>
|
||||
<Version>3.2.0</Version>
|
||||
<Version>3.2.1</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -14,7 +14,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<RootNamespace>Oqtane.Maui</RootNamespace>
|
||||
@ -31,7 +31,7 @@
|
||||
<ApplicationIdGuid>0E29FC31-1B83-48ED-B6E0-9F3C67B775D4</ApplicationIdGuid>
|
||||
|
||||
<!-- Versions -->
|
||||
<ApplicationDisplayVersion>3.2.0</ApplicationDisplayVersion>
|
||||
<ApplicationDisplayVersion>3.2.1</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>1</ApplicationVersion>
|
||||
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Client</id>
|
||||
<version>3.2.0</version>
|
||||
<version>3.2.1</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -12,7 +12,7 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Framework</id>
|
||||
<version>3.2.0</version>
|
||||
<version>3.2.1</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -11,8 +11,8 @@
|
||||
<copyright>.NET Foundation</copyright>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v3.2.0/Oqtane.Framework.3.2.0.Upgrade.zip</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0</releaseNotes>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v3.2.1/Oqtane.Framework.3.2.1.Upgrade.zip</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane framework</tags>
|
||||
</metadata>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Server</id>
|
||||
<version>3.2.0</version>
|
||||
<version>3.2.1</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -12,7 +12,7 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Shared</id>
|
||||
<version>3.2.0</version>
|
||||
<version>3.2.1</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -12,7 +12,7 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Updater</id>
|
||||
<version>3.2.0</version>
|
||||
<version>3.2.1</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -12,7 +12,7 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
|
@ -1 +1 @@
|
||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.2.0.Install.zip" -Force
|
||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.2.1.Install.zip" -Force
|
@ -1 +1 @@
|
||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.2.0.Upgrade.zip" -Force
|
||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.2.1.Upgrade.zip" -Force
|
@ -17,13 +17,15 @@ namespace Oqtane.Controllers
|
||||
{
|
||||
private readonly IAliasRepository _aliases;
|
||||
private readonly ITenantRepository _tenants;
|
||||
private readonly ISyncManager _syncManager;
|
||||
private readonly ILogManager _logger;
|
||||
private readonly Alias _alias;
|
||||
|
||||
public AliasController(IAliasRepository aliases, ITenantRepository tenants, ILogManager logger, ITenantManager tenantManager)
|
||||
public AliasController(IAliasRepository aliases, ITenantRepository tenants, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager)
|
||||
{
|
||||
_aliases = aliases;
|
||||
_tenants = tenants;
|
||||
_syncManager = syncManager;
|
||||
_logger = logger;
|
||||
_alias = tenantManager.GetAlias();
|
||||
}
|
||||
@ -57,6 +59,7 @@ namespace Oqtane.Controllers
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
alias = _aliases.AddAlias(alias);
|
||||
_syncManager.AddSyncEvent(alias.TenantId, EntityNames.Alias, alias.AliasId, SyncEventActions.Create);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Alias Added {Alias}", alias);
|
||||
}
|
||||
else
|
||||
@ -76,6 +79,7 @@ namespace Oqtane.Controllers
|
||||
if (ModelState.IsValid && _aliases.GetAlias(alias.AliasId, false) != null)
|
||||
{
|
||||
alias = _aliases.UpdateAlias(alias);
|
||||
_syncManager.AddSyncEvent(alias.TenantId, EntityNames.Alias, alias.AliasId, SyncEventActions.Update);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Alias Updated {Alias}", alias);
|
||||
}
|
||||
else
|
||||
@ -96,6 +100,7 @@ namespace Oqtane.Controllers
|
||||
if (alias != null)
|
||||
{
|
||||
_aliases.DeleteAlias(id);
|
||||
_syncManager.AddSyncEvent(alias.TenantId, EntityNames.Alias, alias.AliasId, SyncEventActions.Delete);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Alias Deleted {AliasId}", id);
|
||||
|
||||
var aliases = _aliases.GetAliases();
|
||||
|
@ -32,15 +32,17 @@ namespace Oqtane.Controllers
|
||||
private readonly IFileRepository _files;
|
||||
private readonly IFolderRepository _folders;
|
||||
private readonly IUserPermissions _userPermissions;
|
||||
private readonly ISyncManager _syncManager;
|
||||
private readonly ILogManager _logger;
|
||||
private readonly Alias _alias;
|
||||
|
||||
public FileController(IWebHostEnvironment environment, IFileRepository files, IFolderRepository folders, IUserPermissions userPermissions, ILogManager logger, ITenantManager tenantManager)
|
||||
public FileController(IWebHostEnvironment environment, IFileRepository files, IFolderRepository folders, IUserPermissions userPermissions, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager)
|
||||
{
|
||||
_environment = environment;
|
||||
_files = files;
|
||||
_folders = folders;
|
||||
_userPermissions = userPermissions;
|
||||
_syncManager = syncManager;
|
||||
_logger = logger;
|
||||
_alias = tenantManager.GetAlias();
|
||||
}
|
||||
@ -148,6 +150,7 @@ namespace Oqtane.Controllers
|
||||
|
||||
file.Extension = Path.GetExtension(file.Name).ToLower().Replace(".", "");
|
||||
file = _files.UpdateFile(file);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.File, file.FileId, SyncEventActions.Update);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "File Updated {File}", file);
|
||||
}
|
||||
else
|
||||
@ -168,8 +171,6 @@ namespace Oqtane.Controllers
|
||||
Models.File file = _files.GetFile(id);
|
||||
if (file != null && file.Folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Folder, file.Folder.FolderId, PermissionNames.Edit))
|
||||
{
|
||||
_files.DeleteFile(id);
|
||||
|
||||
string filepath = _files.GetFilePath(file);
|
||||
if (System.IO.File.Exists(filepath))
|
||||
{
|
||||
@ -180,6 +181,8 @@ namespace Oqtane.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
_files.DeleteFile(id);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.File, file.FileId, SyncEventActions.Delete);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "File Deleted {File}", file);
|
||||
}
|
||||
else
|
||||
@ -251,6 +254,7 @@ namespace Oqtane.Controllers
|
||||
if (file != null)
|
||||
{
|
||||
file = _files.AddFile(file);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.File, file.FileId, SyncEventActions.Create);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -324,7 +328,8 @@ namespace Oqtane.Controllers
|
||||
var file = CreateFile(upload, FolderId, Path.Combine(folderPath, upload));
|
||||
if (file != null)
|
||||
{
|
||||
_files.AddFile(file);
|
||||
file = _files.AddFile(file);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.File, file.FileId, SyncEventActions.Create);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -486,10 +491,15 @@ namespace Oqtane.Controllers
|
||||
var filepath = _files.GetFilePath(file);
|
||||
if (System.IO.File.Exists(filepath))
|
||||
{
|
||||
var result = asAttachment
|
||||
? PhysicalFile(filepath, file.GetMimeType(), file.Name)
|
||||
: PhysicalFile(filepath, file.GetMimeType());
|
||||
return result;
|
||||
if (asAttachment)
|
||||
{
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.File, file.FileId, "Download");
|
||||
return PhysicalFile(filepath, file.GetMimeType(), file.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
return PhysicalFile(filepath, file.GetMimeType());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -20,13 +20,15 @@ namespace Oqtane.Controllers
|
||||
{
|
||||
private readonly IFolderRepository _folders;
|
||||
private readonly IUserPermissions _userPermissions;
|
||||
private readonly ISyncManager _syncManager;
|
||||
private readonly ILogManager _logger;
|
||||
private readonly Alias _alias;
|
||||
|
||||
public FolderController(IFolderRepository folders, IUserPermissions userPermissions, ILogManager logger, ITenantManager tenantManager)
|
||||
public FolderController(IFolderRepository folders, IUserPermissions userPermissions, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager)
|
||||
{
|
||||
_folders = folders;
|
||||
_userPermissions = userPermissions;
|
||||
_syncManager = syncManager;
|
||||
_logger = logger;
|
||||
_alias = tenantManager.GetAlias();
|
||||
}
|
||||
@ -124,6 +126,7 @@ namespace Oqtane.Controllers
|
||||
}
|
||||
folder.Path = folder.Path + "/";
|
||||
folder = _folders.AddFolder(folder);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Folder, folder.FolderId, SyncEventActions.Create);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Folder Added {Folder}", folder);
|
||||
}
|
||||
else
|
||||
@ -172,6 +175,7 @@ namespace Oqtane.Controllers
|
||||
}
|
||||
|
||||
folder = _folders.UpdateFolder(folder);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Folder, folder.FolderId, SyncEventActions.Update);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Folder Updated {Folder}", folder);
|
||||
}
|
||||
else
|
||||
@ -205,6 +209,7 @@ namespace Oqtane.Controllers
|
||||
{
|
||||
folder.Order = order;
|
||||
_folders.UpdateFolder(folder);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Folder, folder.FolderId, SyncEventActions.Update);
|
||||
}
|
||||
order += 2;
|
||||
}
|
||||
@ -230,6 +235,7 @@ namespace Oqtane.Controllers
|
||||
Directory.Delete(_folders.GetFolderPath(folder));
|
||||
}
|
||||
_folders.DeleteFolder(id);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Folder, folder.FolderId, SyncEventActions.Delete);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Folder Deleted {FolderId}", id);
|
||||
}
|
||||
else
|
||||
|
@ -103,10 +103,7 @@ namespace Oqtane.Controllers
|
||||
[HttpGet("list")]
|
||||
public List<string> List()
|
||||
{
|
||||
return _cache.GetOrCreate("assemblieslist", entry =>
|
||||
{
|
||||
return GetAssemblyList();
|
||||
});
|
||||
return GetAssemblyList().Select(item => item.HashedName).ToList();
|
||||
}
|
||||
|
||||
// GET api/<controller>/load?list=x,y
|
||||
@ -116,81 +113,90 @@ namespace Oqtane.Controllers
|
||||
return File(GetAssemblies(list), System.Net.Mime.MediaTypeNames.Application.Octet, "oqtane.dll");
|
||||
}
|
||||
|
||||
private List<string> GetAssemblyList()
|
||||
private List<ClientAssembly> GetAssemblyList()
|
||||
{
|
||||
// get list of assemblies which should be downloaded to client
|
||||
var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
|
||||
var assemblies = AppDomain.CurrentDomain.GetOqtaneClientAssemblies();
|
||||
var list = assemblies.Select(a => a.GetName().Name).ToList();
|
||||
|
||||
// include version numbers
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
return _cache.GetOrCreate("assemblieslist", entry =>
|
||||
{
|
||||
list[i] = Path.GetFileName(AddFileDate(Path.Combine(binFolder, list[i] + ".dll")));
|
||||
}
|
||||
var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
|
||||
var assemblyList = new List<ClientAssembly>();
|
||||
|
||||
// insert satellite assemblies at beginning of list
|
||||
foreach (var culture in _localizationManager.GetInstalledCultures())
|
||||
{
|
||||
var assembliesFolderPath = Path.Combine(binFolder, culture);
|
||||
if (culture == Constants.DefaultCulture)
|
||||
// get list of assemblies which should be downloaded to client
|
||||
var assemblies = AppDomain.CurrentDomain.GetOqtaneClientAssemblies();
|
||||
var list = assemblies.Select(a => a.GetName().Name).ToList();
|
||||
|
||||
// populate assemblies
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
continue;
|
||||
assemblyList.Add(new ClientAssembly(Path.Combine(binFolder, list[i] + ".dll")));
|
||||
}
|
||||
|
||||
if (Directory.Exists(assembliesFolderPath))
|
||||
// insert satellite assemblies at beginning of list
|
||||
foreach (var culture in _localizationManager.GetInstalledCultures())
|
||||
{
|
||||
foreach (var resourceFile in Directory.EnumerateFiles(assembliesFolderPath))
|
||||
var assembliesFolderPath = Path.Combine(binFolder, culture);
|
||||
if (culture == Constants.DefaultCulture)
|
||||
{
|
||||
list.Insert(0, culture + "/" + Path.GetFileName(AddFileDate(resourceFile)));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Directory.Exists(assembliesFolderPath))
|
||||
{
|
||||
foreach (var resourceFile in Directory.EnumerateFiles(assembliesFolderPath))
|
||||
{
|
||||
assemblyList.Insert(0, new ClientAssembly(resourceFile));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_filelogger.LogError(Utilities.LogMessage(this, $"The Satellite Assembly Folder For {culture} Does Not Exist"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_filelogger.LogError(Utilities.LogMessage(this, $"The Satellite Assembly Folder For {culture} Does Not Exist"));
|
||||
}
|
||||
}
|
||||
|
||||
// insert module and theme dependencies at beginning of list
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
foreach (var type in assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IModule))))
|
||||
// insert module and theme dependencies at beginning of list
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
var instance = Activator.CreateInstance(type) as IModule;
|
||||
foreach (string name in instance.ModuleDefinition.Dependencies.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
foreach (var type in assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IModule))))
|
||||
{
|
||||
var path = Path.Combine(binFolder, name + ".dll");
|
||||
if (System.IO.File.Exists(path))
|
||||
var instance = Activator.CreateInstance(type) as IModule;
|
||||
foreach (string name in instance.ModuleDefinition.Dependencies.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Reverse())
|
||||
{
|
||||
path = Path.GetFileName(AddFileDate(path));
|
||||
if (!list.Contains(path)) list.Insert(0, path);
|
||||
var filepath = Path.Combine(binFolder, name + ".dll");
|
||||
if (System.IO.File.Exists(filepath))
|
||||
{
|
||||
if (!assemblyList.Exists(item => item.FilePath == filepath))
|
||||
{
|
||||
assemblyList.Insert(0, new ClientAssembly(filepath));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_filelogger.LogError(Utilities.LogMessage(this, $"Module {instance.ModuleDefinition.ModuleDefinitionName} Dependency {name}.dll Does Not Exist"));
|
||||
}
|
||||
}
|
||||
else
|
||||
}
|
||||
foreach (var type in assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(ITheme))))
|
||||
{
|
||||
var instance = Activator.CreateInstance(type) as ITheme;
|
||||
foreach (string name in instance.Theme.Dependencies.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Reverse())
|
||||
{
|
||||
_filelogger.LogError(Utilities.LogMessage(this, $"Module {instance.ModuleDefinition.ModuleDefinitionName} Dependency {name}.dll Does Not Exist"));
|
||||
var filepath = Path.Combine(binFolder, name + ".dll");
|
||||
if (System.IO.File.Exists(filepath))
|
||||
{
|
||||
if (!assemblyList.Exists(item => item.FilePath == filepath))
|
||||
{
|
||||
assemblyList.Insert(0, new ClientAssembly(filepath));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_filelogger.LogError(Utilities.LogMessage(this, $"Theme {instance.Theme.ThemeName} Dependency {name}.dll Does Not Exist"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var type in assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(ITheme))))
|
||||
{
|
||||
var instance = Activator.CreateInstance(type) as ITheme;
|
||||
foreach (string name in instance.Theme.Dependencies.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
var path = Path.Combine(binFolder, name + ".dll");
|
||||
if (System.IO.File.Exists(path))
|
||||
{
|
||||
path = Path.GetFileName(AddFileDate(path));
|
||||
if (!list.Contains(path)) list.Insert(0, path);
|
||||
}
|
||||
else
|
||||
{
|
||||
_filelogger.LogError(Utilities.LogMessage(this, $"Theme {instance.Theme.ThemeName} Dependency {name}.dll Does Not Exist"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
return assemblyList;
|
||||
});
|
||||
}
|
||||
|
||||
private byte[] GetAssemblies(string list)
|
||||
@ -213,14 +219,11 @@ namespace Oqtane.Controllers
|
||||
var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
|
||||
|
||||
// get list of assemblies which should be downloaded to client
|
||||
List<string> assemblies;
|
||||
if (list == "*")
|
||||
List<ClientAssembly> assemblies = GetAssemblyList();
|
||||
if (list != "*")
|
||||
{
|
||||
assemblies = GetAssemblyList();
|
||||
}
|
||||
else
|
||||
{
|
||||
assemblies = list.Split(',').ToList();
|
||||
var filter = list.Split(',').ToList();
|
||||
assemblies.RemoveAll(item => !filter.Contains(item.HashedName));
|
||||
}
|
||||
|
||||
// create zip file containing assemblies and debug symbols
|
||||
@ -228,22 +231,21 @@ namespace Oqtane.Controllers
|
||||
{
|
||||
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
|
||||
{
|
||||
foreach (string file in assemblies)
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
var filename = RemoveFileDate(file);
|
||||
if (System.IO.File.Exists(Path.Combine(binFolder, filename)))
|
||||
if (System.IO.File.Exists(assembly.FilePath))
|
||||
{
|
||||
using (var filestream = new FileStream(Path.Combine(binFolder, filename), FileMode.Open, FileAccess.Read))
|
||||
using (var entrystream = archive.CreateEntry(file).Open())
|
||||
using (var filestream = new FileStream(assembly.FilePath, FileMode.Open, FileAccess.Read))
|
||||
using (var entrystream = archive.CreateEntry(assembly.HashedName).Open())
|
||||
{
|
||||
filestream.CopyTo(entrystream);
|
||||
}
|
||||
}
|
||||
filename = filename.Replace(".dll", ".pdb");
|
||||
if (System.IO.File.Exists(Path.Combine(binFolder, filename)))
|
||||
var pdb = assembly.FilePath.Replace(".dll", ".pdb");
|
||||
if (System.IO.File.Exists(pdb))
|
||||
{
|
||||
using (var filestream = new FileStream(Path.Combine(binFolder, filename), FileMode.Open, FileAccess.Read))
|
||||
using (var entrystream = archive.CreateEntry(file.Replace(".dll", ".pdb")).Open())
|
||||
using (var filestream = new FileStream(pdb, FileMode.Open, FileAccess.Read))
|
||||
using (var entrystream = archive.CreateEntry(assembly.HashedName.Replace(".dll", ".pdb")).Open())
|
||||
{
|
||||
filestream.CopyTo(entrystream);
|
||||
}
|
||||
@ -255,18 +257,6 @@ namespace Oqtane.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
private string AddFileDate(string filepath)
|
||||
{
|
||||
DateTime lastwritetime = System.IO.File.GetLastWriteTime(filepath);
|
||||
return Path.GetFileNameWithoutExtension(filepath) + "." + lastwritetime.ToString("yyyyMMddHHmmss") + Path.GetExtension(filepath);
|
||||
}
|
||||
|
||||
private string RemoveFileDate(string filepath)
|
||||
{
|
||||
var segments = filepath.Split(".");
|
||||
return string.Join(".", segments, 0, segments.Length - 2) + Path.GetExtension(filepath);
|
||||
}
|
||||
|
||||
private async Task RegisterContact(string email)
|
||||
{
|
||||
try
|
||||
@ -292,5 +282,38 @@ namespace Oqtane.Controllers
|
||||
{
|
||||
await RegisterContact(email);
|
||||
}
|
||||
|
||||
public struct ClientAssembly
|
||||
{
|
||||
public ClientAssembly(string filepath)
|
||||
{
|
||||
FilePath = filepath;
|
||||
DateTime lastwritetime = System.IO.File.GetLastWriteTime(filepath);
|
||||
HashedName = GetDeterministicHashCode(filepath).ToString("X8") + "." + lastwritetime.ToString("yyyyMMddHHmmss") + Path.GetExtension(filepath);
|
||||
}
|
||||
|
||||
public string FilePath { get; private set; }
|
||||
public string HashedName { get; private set; }
|
||||
}
|
||||
|
||||
private static int GetDeterministicHashCode(string value)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hash1 = (5381 << 16) + 5381;
|
||||
int hash2 = hash1;
|
||||
|
||||
for (int i = 0; i < value.Length; i += 2)
|
||||
{
|
||||
hash1 = ((hash1 << 5) + hash1) ^ value[i];
|
||||
if (i == value.Length - 1)
|
||||
break;
|
||||
hash2 = ((hash2 << 5) + hash2) ^ value[i + 1];
|
||||
}
|
||||
|
||||
return hash1 + (hash2 * 1566083941);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -42,10 +42,13 @@ namespace Oqtane.Controllers
|
||||
{
|
||||
if (!string.IsNullOrEmpty(packagename))
|
||||
{
|
||||
foreach (var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"{packagename}.*{Constants.SatelliteAssemblyExtension}", SearchOption.AllDirectories))
|
||||
foreach (var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"{packagename}*{Constants.SatelliteAssemblyExtension}", SearchOption.AllDirectories))
|
||||
{
|
||||
var code = Path.GetFileName(Path.GetDirectoryName(file));
|
||||
languages.Add(new Language { Code = code, Name = CultureInfo.GetCultureInfo(code).DisplayName, Version = FileVersionInfo.GetVersionInfo(file).FileVersion, IsDefault = false });
|
||||
if (!languages.Any(item => item.Code == code))
|
||||
{
|
||||
languages.Add(new Language { Code = code, Name = CultureInfo.GetCultureInfo(code).DisplayName, Version = FileVersionInfo.GetVersionInfo(file).FileVersion, IsDefault = false });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -54,7 +57,7 @@ namespace Oqtane.Controllers
|
||||
languages = _languages.GetLanguages(SiteId).ToList();
|
||||
if (!string.IsNullOrEmpty(packagename))
|
||||
{
|
||||
foreach (var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"{packagename}.*{Constants.SatelliteAssemblyExtension}", SearchOption.AllDirectories))
|
||||
foreach (var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"{packagename}*{Constants.SatelliteAssemblyExtension}", SearchOption.AllDirectories))
|
||||
{
|
||||
var code = Path.GetFileName(Path.GetDirectoryName(file));
|
||||
if (languages.Any(item => item.Code == code))
|
||||
@ -99,7 +102,8 @@ namespace Oqtane.Controllers
|
||||
if (ModelState.IsValid && language.SiteId == _alias.SiteId)
|
||||
{
|
||||
language = _languages.AddLanguage(language);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Language, language.LanguageId, SyncEventActions.Create);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Language Added {Language}", language);
|
||||
}
|
||||
else
|
||||
@ -119,7 +123,8 @@ namespace Oqtane.Controllers
|
||||
if (language != null && language.SiteId == _alias.SiteId)
|
||||
{
|
||||
_languages.DeleteLanguage(id);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Language, language.LanguageId, SyncEventActions.Delete);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Language Deleted {LanguageId}", id);
|
||||
}
|
||||
else
|
||||
|
@ -124,7 +124,8 @@ namespace Oqtane.Controllers
|
||||
if (ModelState.IsValid && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Page, module.PageId, PermissionNames.Edit))
|
||||
{
|
||||
module = _modules.AddModule(module);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Module, module.ModuleId, SyncEventActions.Create);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Module Added {Module}", module);
|
||||
}
|
||||
else
|
||||
@ -174,7 +175,8 @@ namespace Oqtane.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Module, module.ModuleId, SyncEventActions.Update);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Module Updated {Module}", module);
|
||||
}
|
||||
else
|
||||
@ -195,7 +197,8 @@ namespace Oqtane.Controllers
|
||||
if (module != null && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Module, module.ModuleId, PermissionNames.Edit))
|
||||
{
|
||||
_modules.DeleteModule(id);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Module, module.ModuleId, SyncEventActions.Delete);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Deleted {ModuleId}", id);
|
||||
}
|
||||
else
|
||||
|
@ -29,10 +29,11 @@ namespace Oqtane.Controllers
|
||||
private readonly IWebHostEnvironment _environment;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly ITenantManager _tenantManager;
|
||||
private readonly ISyncManager _syncManager;
|
||||
private readonly ILogManager _logger;
|
||||
private readonly Alias _alias;
|
||||
|
||||
public ModuleDefinitionController(IModuleDefinitionRepository moduleDefinitions, ITenantRepository tenants, ISqlRepository sql, IUserPermissions userPermissions, IInstallationManager installationManager, IWebHostEnvironment environment, IServiceProvider serviceProvider, ITenantManager tenantManager, ILogManager logger)
|
||||
public ModuleDefinitionController(IModuleDefinitionRepository moduleDefinitions, ITenantRepository tenants, ISqlRepository sql, IUserPermissions userPermissions, IInstallationManager installationManager, IWebHostEnvironment environment, IServiceProvider serviceProvider, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger)
|
||||
{
|
||||
_moduleDefinitions = moduleDefinitions;
|
||||
_tenants = tenants;
|
||||
@ -42,6 +43,7 @@ namespace Oqtane.Controllers
|
||||
_environment = environment;
|
||||
_serviceProvider = serviceProvider;
|
||||
_tenantManager = tenantManager;
|
||||
_syncManager = syncManager;
|
||||
_logger = logger;
|
||||
_alias = tenantManager.GetAlias();
|
||||
}
|
||||
@ -145,6 +147,7 @@ namespace Oqtane.Controllers
|
||||
if (ModelState.IsValid && moduleDefinition.SiteId == _alias.SiteId && _moduleDefinitions.GetModuleDefinition(moduleDefinition.ModuleDefinitionId, moduleDefinition.SiteId) != null)
|
||||
{
|
||||
_moduleDefinitions.UpdateModuleDefinition(moduleDefinition);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.ModuleDefinition, moduleDefinition.ModuleDefinitionId, SyncEventActions.Update);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Module Definition Updated {ModuleDefinition}", moduleDefinition);
|
||||
}
|
||||
else
|
||||
@ -227,6 +230,7 @@ namespace Oqtane.Controllers
|
||||
|
||||
// remove module definition
|
||||
_moduleDefinitions.DeleteModuleDefinition(id);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.ModuleDefinition, moduledefinition.ModuleDefinitionId, SyncEventActions.Delete);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Definition {ModuleDefinitionName} Deleted", moduledefinition.Name);
|
||||
}
|
||||
else
|
||||
|
@ -8,6 +8,7 @@ using Oqtane.Infrastructure;
|
||||
using Oqtane.Repository;
|
||||
using Oqtane.Security;
|
||||
using System.Net;
|
||||
using System.Reflection.Metadata;
|
||||
|
||||
namespace Oqtane.Controllers
|
||||
{
|
||||
@ -16,13 +17,15 @@ namespace Oqtane.Controllers
|
||||
{
|
||||
private readonly INotificationRepository _notifications;
|
||||
private readonly IUserPermissions _userPermissions;
|
||||
private readonly ISyncManager _syncManager;
|
||||
private readonly ILogManager _logger;
|
||||
private readonly Alias _alias;
|
||||
|
||||
public NotificationController(INotificationRepository notifications, IUserPermissions userPermissions, ILogManager logger, ITenantManager tenantManager)
|
||||
public NotificationController(INotificationRepository notifications, IUserPermissions userPermissions, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager)
|
||||
{
|
||||
_notifications = notifications;
|
||||
_userPermissions = userPermissions;
|
||||
_syncManager = syncManager;
|
||||
_logger = logger;
|
||||
_alias = tenantManager.GetAlias();
|
||||
}
|
||||
@ -83,6 +86,7 @@ namespace Oqtane.Controllers
|
||||
if (ModelState.IsValid && notification.SiteId == _alias.SiteId && IsAuthorized(notification.FromUserId))
|
||||
{
|
||||
notification = _notifications.AddNotification(notification);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Notification, notification.NotificationId, SyncEventActions.Create);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Notification Added {NotificationId}", notification.NotificationId);
|
||||
}
|
||||
else
|
||||
@ -102,6 +106,7 @@ namespace Oqtane.Controllers
|
||||
if (ModelState.IsValid && notification.SiteId == _alias.SiteId && _notifications.GetNotification(notification.NotificationId, false) != null && IsAuthorized(notification.FromUserId))
|
||||
{
|
||||
notification = _notifications.UpdateNotification(notification);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Notification, notification.NotificationId, SyncEventActions.Update);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Notification Updated {NotificationId}", notification.NotificationId);
|
||||
}
|
||||
else
|
||||
@ -122,6 +127,7 @@ namespace Oqtane.Controllers
|
||||
if (notification != null && notification.SiteId == _alias.SiteId && (IsAuthorized(notification.FromUserId) || IsAuthorized(notification.ToUserId)))
|
||||
{
|
||||
_notifications.DeleteNotification(id);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Notification, notification.NotificationId, SyncEventActions.Delete);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Notification Deleted {NotificationId}", id);
|
||||
}
|
||||
else
|
||||
|
@ -1,7 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Oqtane.Models;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
@ -12,6 +11,7 @@ using Oqtane.Shared;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Enums;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.Json;
|
||||
// ReSharper disable PartialTypeWithSinglePart
|
||||
|
||||
namespace Oqtane.Controllers
|
||||
@ -106,11 +106,7 @@ namespace Oqtane.Controllers
|
||||
var stream = await response.Content.ReadAsStreamAsync();
|
||||
using (var streamReader = new StreamReader(stream))
|
||||
{
|
||||
using (var jsonTextReader = new JsonTextReader(streamReader))
|
||||
{
|
||||
var serializer = new JsonSerializer();
|
||||
return serializer.Deserialize<T>(jsonTextReader);
|
||||
}
|
||||
return await JsonSerializer.DeserializeAsync<T>(stream, new JsonSerializerOptions(JsonSerializerDefaults.Web));
|
||||
}
|
||||
}
|
||||
return default(T);
|
||||
|
@ -143,7 +143,8 @@ namespace Oqtane.Controllers
|
||||
if (_userPermissions.IsAuthorized(User,PermissionNames.Edit, permissions))
|
||||
{
|
||||
page = _pages.AddPage(page);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, page.SiteId);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Page, page.PageId, SyncEventActions.Create);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, page.SiteId, SyncEventActions.Refresh);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Page Added {Page}", page);
|
||||
|
||||
if (!page.Path.StartsWith("admin/"))
|
||||
@ -200,7 +201,8 @@ namespace Oqtane.Controllers
|
||||
page.IsPersonalizable = false;
|
||||
page.UserId = int.Parse(userid);
|
||||
page = _pages.AddPage(page);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, page.SiteId);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Page, page.PageId, SyncEventActions.Create);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, page.SiteId, SyncEventActions.Refresh);
|
||||
|
||||
// copy modules
|
||||
List<PageModule> pagemodules = _pageModules.GetPageModules(page.SiteId).ToList();
|
||||
@ -313,7 +315,8 @@ namespace Oqtane.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, page.SiteId);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Page, page.PageId, SyncEventActions.Update);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, page.SiteId, SyncEventActions.Refresh);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Page Updated {Page}", page);
|
||||
}
|
||||
else
|
||||
@ -353,10 +356,12 @@ namespace Oqtane.Controllers
|
||||
{
|
||||
page.Order = order;
|
||||
_pages.UpdatePage(page);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Page, page.PageId, SyncEventActions.Update);
|
||||
}
|
||||
order += 2;
|
||||
}
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, siteid);
|
||||
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, siteid, SyncEventActions.Refresh);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Page Order Updated {SiteId} {PageId} {ParentId}", siteid, pageid, parentid);
|
||||
}
|
||||
else
|
||||
@ -375,7 +380,8 @@ namespace Oqtane.Controllers
|
||||
if (page != null && page.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Page, page.PageId, PermissionNames.Edit))
|
||||
{
|
||||
_pages.DeletePage(page.PageId);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, page.SiteId);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Page, page.PageId, SyncEventActions.Delete);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, page.SiteId, SyncEventActions.Refresh);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Page Deleted {PageId}", page.PageId);
|
||||
}
|
||||
else
|
||||
|
@ -9,6 +9,7 @@ using Oqtane.Infrastructure;
|
||||
using Oqtane.Repository;
|
||||
using Oqtane.Security;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace Oqtane.Controllers
|
||||
{
|
||||
@ -75,7 +76,8 @@ namespace Oqtane.Controllers
|
||||
if (ModelState.IsValid && page != null && page.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Page, pageModule.PageId, PermissionNames.Edit))
|
||||
{
|
||||
pageModule = _pageModules.AddPageModule(pageModule);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.PageModule, pageModule.PageModuleId, SyncEventActions.Create);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Page Module Added {PageModule}", pageModule);
|
||||
}
|
||||
else
|
||||
@ -96,7 +98,8 @@ namespace Oqtane.Controllers
|
||||
if (ModelState.IsValid && page != null && page.SiteId == _alias.SiteId && _pageModules.GetPageModule(pageModule.PageModuleId, false) != null && _userPermissions.IsAuthorized(User, EntityNames.Page, pageModule.PageId, PermissionNames.Edit))
|
||||
{
|
||||
pageModule = _pageModules.UpdatePageModule(pageModule);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.PageModule, pageModule.PageModuleId, SyncEventActions.Update);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Page Module Updated {PageModule}", pageModule);
|
||||
}
|
||||
else
|
||||
@ -124,10 +127,11 @@ namespace Oqtane.Controllers
|
||||
{
|
||||
pagemodule.Order = order;
|
||||
_pageModules.UpdatePageModule(pagemodule);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.PageModule, pagemodule.PageModuleId, SyncEventActions.Update);
|
||||
}
|
||||
order += 2;
|
||||
}
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Page Module Order Updated {PageId} {Pane}", pageid, pane);
|
||||
}
|
||||
else
|
||||
@ -146,7 +150,8 @@ namespace Oqtane.Controllers
|
||||
if (pagemodule != null && pagemodule.Module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Page, pagemodule.PageId, PermissionNames.Edit))
|
||||
{
|
||||
_pageModules.DeletePageModule(id);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.PageModule, pagemodule.PageModuleId, SyncEventActions.Delete);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Page Module Deleted {PageModuleId}", id);
|
||||
}
|
||||
else
|
||||
|
@ -14,12 +14,14 @@ namespace Oqtane.Controllers
|
||||
public class ProfileController : Controller
|
||||
{
|
||||
private readonly IProfileRepository _profiles;
|
||||
private readonly ISyncManager _syncManager;
|
||||
private readonly ILogManager _logger;
|
||||
private readonly Alias _alias;
|
||||
|
||||
public ProfileController(IProfileRepository profiles, ILogManager logger, ITenantManager tenantManager)
|
||||
public ProfileController(IProfileRepository profiles, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager)
|
||||
{
|
||||
_profiles = profiles;
|
||||
_syncManager = syncManager;
|
||||
_logger = logger;
|
||||
_alias = tenantManager.GetAlias();
|
||||
}
|
||||
@ -66,6 +68,7 @@ namespace Oqtane.Controllers
|
||||
if (ModelState.IsValid && profile.SiteId == _alias.SiteId)
|
||||
{
|
||||
profile = _profiles.AddProfile(profile);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Profile, profile.ProfileId, SyncEventActions.Create);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Profile Added {Profile}", profile);
|
||||
}
|
||||
else
|
||||
@ -85,6 +88,7 @@ namespace Oqtane.Controllers
|
||||
if (ModelState.IsValid && profile.SiteId == _alias.SiteId && _profiles.GetProfile(profile.ProfileId, false) != null)
|
||||
{
|
||||
profile = _profiles.UpdateProfile(profile);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Profile, profile.ProfileId, SyncEventActions.Update);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Profile Updated {Profile}", profile);
|
||||
}
|
||||
else
|
||||
@ -105,6 +109,7 @@ namespace Oqtane.Controllers
|
||||
if (profile != null && profile.SiteId == _alias.SiteId)
|
||||
{
|
||||
_profiles.DeleteProfile(id);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Profile, profile.ProfileId, SyncEventActions.Delete);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Profile Deleted {ProfileId}", id);
|
||||
}
|
||||
else
|
||||
|
@ -14,12 +14,14 @@ namespace Oqtane.Controllers
|
||||
public class RoleController : Controller
|
||||
{
|
||||
private readonly IRoleRepository _roles;
|
||||
private readonly ISyncManager _syncManager;
|
||||
private readonly ILogManager _logger;
|
||||
private readonly Alias _alias;
|
||||
|
||||
public RoleController(IRoleRepository roles, ILogManager logger, ITenantManager tenantManager)
|
||||
public RoleController(IRoleRepository roles, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager)
|
||||
{
|
||||
_roles = roles;
|
||||
_syncManager = syncManager;
|
||||
_logger = logger;
|
||||
_alias = tenantManager.GetAlias();
|
||||
}
|
||||
@ -72,6 +74,7 @@ namespace Oqtane.Controllers
|
||||
if (ModelState.IsValid && role.SiteId == _alias.SiteId)
|
||||
{
|
||||
role = _roles.AddRole(role);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Role, role.RoleId, SyncEventActions.Create);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Role Added {Role}", role);
|
||||
}
|
||||
else
|
||||
@ -91,6 +94,7 @@ namespace Oqtane.Controllers
|
||||
if (ModelState.IsValid && role.SiteId == _alias.SiteId && _roles.GetRole(role.RoleId, false) != null)
|
||||
{
|
||||
role = _roles.UpdateRole(role);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Role, role.RoleId, SyncEventActions.Update);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Role Updated {Role}", role);
|
||||
}
|
||||
else
|
||||
@ -111,6 +115,7 @@ namespace Oqtane.Controllers
|
||||
if (role != null && !role.IsSystem && role.SiteId == _alias.SiteId)
|
||||
{
|
||||
_roles.DeleteRole(id);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Role, role.RoleId, SyncEventActions.Delete);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Role Deleted {RoleId}", id);
|
||||
}
|
||||
else
|
||||
|
@ -98,7 +98,7 @@ namespace Oqtane.Controllers
|
||||
if (ModelState.IsValid && IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.Edit))
|
||||
{
|
||||
setting = _settings.AddSetting(setting);
|
||||
AddSyncEvent(setting.EntityName);
|
||||
AddSyncEvent(setting.EntityName, setting.SettingId, SyncEventActions.Create);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Setting Added {Setting}", setting);
|
||||
}
|
||||
else
|
||||
@ -117,7 +117,7 @@ namespace Oqtane.Controllers
|
||||
if (ModelState.IsValid && IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.Edit))
|
||||
{
|
||||
setting = _settings.UpdateSetting(setting);
|
||||
AddSyncEvent(setting.EntityName);
|
||||
AddSyncEvent(setting.EntityName, setting.SettingId, SyncEventActions.Update);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Setting Updated {Setting}", setting);
|
||||
}
|
||||
else
|
||||
@ -137,7 +137,7 @@ namespace Oqtane.Controllers
|
||||
if (IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.Edit))
|
||||
{
|
||||
_settings.DeleteSetting(setting.EntityName, id);
|
||||
AddSyncEvent(setting.EntityName);
|
||||
AddSyncEvent(setting.EntityName, setting.SettingId, SyncEventActions.Delete);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Setting Deleted {Setting}", setting);
|
||||
}
|
||||
else
|
||||
@ -277,14 +277,16 @@ namespace Oqtane.Controllers
|
||||
return filter;
|
||||
}
|
||||
|
||||
private void AddSyncEvent(string EntityName)
|
||||
private void AddSyncEvent(string EntityName, int SettingId, string Action)
|
||||
{
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityName + "Setting", SettingId, Action);
|
||||
|
||||
switch (EntityName)
|
||||
{
|
||||
case EntityNames.Module:
|
||||
case EntityNames.Page:
|
||||
case EntityNames.Site:
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -160,6 +160,7 @@ namespace Oqtane.Controllers
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
site = _sites.AddSite(site);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, site.SiteId, SyncEventActions.Create);
|
||||
_logger.Log(site.SiteId, LogLevel.Information, this, LogFunction.Create, "Site Added {Site}", site);
|
||||
}
|
||||
else
|
||||
@ -180,12 +181,13 @@ namespace Oqtane.Controllers
|
||||
if (ModelState.IsValid && site.SiteId == _alias.SiteId && site.TenantId == _alias.TenantId && current != null)
|
||||
{
|
||||
site = _sites.UpdateSite(site);
|
||||
bool reload = false;
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, site.SiteId, SyncEventActions.Update);
|
||||
string action = SyncEventActions.Refresh;
|
||||
if (current.Runtime != site.Runtime || current.RenderMode != site.RenderMode)
|
||||
{
|
||||
reload = true;
|
||||
action = SyncEventActions.Reload;
|
||||
}
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, site.SiteId, reload);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, site.SiteId, action);
|
||||
_logger.Log(site.SiteId, LogLevel.Information, this, LogFunction.Update, "Site Updated {Site}", site);
|
||||
}
|
||||
else
|
||||
@ -206,6 +208,7 @@ namespace Oqtane.Controllers
|
||||
if (site != null && site.SiteId == _alias.SiteId)
|
||||
{
|
||||
_sites.DeleteSite(id);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, site.SiteId, SyncEventActions.Delete);
|
||||
_logger.Log(id, LogLevel.Information, this, LogFunction.Delete, "Site Deleted {SiteId}", id);
|
||||
}
|
||||
else
|
||||
|
@ -2,9 +2,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Oqtane.Models;
|
||||
using System.Collections.Generic;
|
||||
using Oqtane.Enums;
|
||||
using Oqtane.Shared;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Repository;
|
||||
|
||||
namespace Oqtane.Controllers
|
||||
@ -13,12 +11,10 @@ namespace Oqtane.Controllers
|
||||
public class TenantController : Controller
|
||||
{
|
||||
private readonly ITenantRepository _tenants;
|
||||
private readonly ILogManager _logger;
|
||||
|
||||
public TenantController(ITenantRepository tenants, ILogManager logger)
|
||||
public TenantController(ITenantRepository tenants)
|
||||
{
|
||||
_tenants = tenants;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
// GET: api/<controller>
|
||||
|
@ -56,7 +56,7 @@ namespace Oqtane.Controllers
|
||||
{
|
||||
List<Theme> themes = _themes.GetThemes().ToList();
|
||||
Theme theme = themes.Where(item => item.ThemeName == themename).FirstOrDefault();
|
||||
if (theme != null && Utilities.GetAssemblyName(theme.ThemeName) != "Oqtane.Client")
|
||||
if (theme != null && Utilities.GetAssemblyName(theme.ThemeName) != Constants.ClientId)
|
||||
{
|
||||
// remove theme assets
|
||||
if (_installationManager.UninstallPackage(theme.PackageName))
|
||||
|
@ -14,12 +14,14 @@ namespace Oqtane.Controllers
|
||||
public class UrlMappingController : Controller
|
||||
{
|
||||
private readonly IUrlMappingRepository _urlMappings;
|
||||
private readonly ISyncManager _syncManager;
|
||||
private readonly ILogManager _logger;
|
||||
private readonly Alias _alias;
|
||||
|
||||
public UrlMappingController(IUrlMappingRepository urlMappings, ILogManager logger, ITenantManager tenantManager)
|
||||
public UrlMappingController(IUrlMappingRepository urlMappings, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager)
|
||||
{
|
||||
_urlMappings = urlMappings;
|
||||
_syncManager = syncManager;
|
||||
_logger = logger;
|
||||
_alias = tenantManager.GetAlias();
|
||||
}
|
||||
@ -85,6 +87,7 @@ namespace Oqtane.Controllers
|
||||
if (ModelState.IsValid && urlMapping.SiteId == _alias.SiteId)
|
||||
{
|
||||
urlMapping = _urlMappings.AddUrlMapping(urlMapping);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.UrlMapping, urlMapping.UrlMappingId, SyncEventActions.Create);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "UrlMapping Added {UrlMapping}", urlMapping);
|
||||
}
|
||||
else
|
||||
@ -104,6 +107,7 @@ namespace Oqtane.Controllers
|
||||
if (ModelState.IsValid && urlMapping.SiteId == _alias.SiteId && _urlMappings.GetUrlMapping(urlMapping.UrlMappingId, false) != null)
|
||||
{
|
||||
urlMapping = _urlMappings.UpdateUrlMapping(urlMapping);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.UrlMapping, urlMapping.UrlMappingId, SyncEventActions.Update);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "UrlMapping Updated {UrlMapping}", urlMapping);
|
||||
}
|
||||
else
|
||||
@ -124,6 +128,7 @@ namespace Oqtane.Controllers
|
||||
if (urlMapping != null && urlMapping.SiteId == _alias.SiteId)
|
||||
{
|
||||
_urlMappings.DeleteUrlMapping(id);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.UrlMapping, urlMapping.UrlMappingId, SyncEventActions.Delete);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "UrlMapping Deleted {UrlMappingId}", id);
|
||||
}
|
||||
else
|
||||
|
@ -23,7 +23,6 @@ namespace Oqtane.Controllers
|
||||
public class UserController : Controller
|
||||
{
|
||||
private readonly IUserRepository _users;
|
||||
private readonly IRoleRepository _roles;
|
||||
private readonly IUserRoleRepository _userRoles;
|
||||
private readonly UserManager<IdentityUser> _identityUserManager;
|
||||
private readonly SignInManager<IdentityUser> _identitySignInManager;
|
||||
@ -35,10 +34,9 @@ namespace Oqtane.Controllers
|
||||
private readonly IJwtManager _jwtManager;
|
||||
private readonly ILogManager _logger;
|
||||
|
||||
public UserController(IUserRepository users, IRoleRepository roles, IUserRoleRepository userRoles, UserManager<IdentityUser> identityUserManager, SignInManager<IdentityUser> identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, ISyncManager syncManager, ISiteRepository sites, IJwtManager jwtManager, ILogManager logger)
|
||||
public UserController(IUserRepository users, IUserRoleRepository userRoles, UserManager<IdentityUser> identityUserManager, SignInManager<IdentityUser> identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, ISyncManager syncManager, ISiteRepository sites, IJwtManager jwtManager, ILogManager logger)
|
||||
{
|
||||
_users = users;
|
||||
_roles = roles;
|
||||
_userRoles = userRoles;
|
||||
_identityUserManager = identityUserManager;
|
||||
_identitySignInManager = identitySignInManager;
|
||||
@ -165,6 +163,7 @@ namespace Oqtane.Controllers
|
||||
if (allowregistration)
|
||||
{
|
||||
bool succeeded;
|
||||
string errors = "";
|
||||
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username);
|
||||
if (identityuser == null)
|
||||
{
|
||||
@ -174,12 +173,20 @@ namespace Oqtane.Controllers
|
||||
identityuser.EmailConfirmed = verified;
|
||||
var result = await _identityUserManager.CreateAsync(identityuser, user.Password);
|
||||
succeeded = result.Succeeded;
|
||||
if (!succeeded)
|
||||
{
|
||||
errors = string.Join(", ", result.Errors.Select(e => e.Description));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = await _identitySignInManager.CheckPasswordSignInAsync(identityuser, user.Password, false);
|
||||
succeeded = result.Succeeded;
|
||||
verified = true;
|
||||
if (!succeeded)
|
||||
{
|
||||
errors = "Password Not Valid For User";
|
||||
}
|
||||
verified = succeeded;
|
||||
}
|
||||
|
||||
if (succeeded)
|
||||
@ -187,6 +194,11 @@ namespace Oqtane.Controllers
|
||||
user.LastLoginOn = null;
|
||||
user.LastIPAddress = "";
|
||||
newUser = _users.AddUser(user);
|
||||
_syncManager.AddSyncEvent(_tenantManager.GetAlias().TenantId, EntityNames.User, newUser.UserId, SyncEventActions.Create);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(user.SiteId, LogLevel.Error, this, LogFunction.Create, "Unable To Add User {Username} - {Errors}", user.Username, errors);
|
||||
}
|
||||
|
||||
if (newUser != null)
|
||||
@ -242,7 +254,8 @@ namespace Oqtane.Controllers
|
||||
await _identityUserManager.UpdateAsync(identityuser);
|
||||
}
|
||||
user = _users.UpdateUser(user);
|
||||
_syncManager.AddSyncEvent(_tenantManager.GetAlias().TenantId, EntityNames.User, user.UserId);
|
||||
_syncManager.AddSyncEvent(_tenantManager.GetAlias().TenantId, EntityNames.User, user.UserId, SyncEventActions.Update);
|
||||
_syncManager.AddSyncEvent(_tenantManager.GetAlias().TenantId, EntityNames.User, user.UserId, SyncEventActions.Refresh);
|
||||
user.Password = ""; // remove sensitive information
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "User Updated {User}", user);
|
||||
}
|
||||
@ -297,6 +310,7 @@ namespace Oqtane.Controllers
|
||||
{
|
||||
// delete user
|
||||
_users.DeleteUser(user.UserId);
|
||||
_syncManager.AddSyncEvent(_tenantManager.GetAlias().TenantId, EntityNames.User, user.UserId, SyncEventActions.Delete);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "User Deleted {UserId}", user.UserId);
|
||||
}
|
||||
else
|
||||
|
@ -122,9 +122,10 @@ namespace Oqtane.Controllers
|
||||
if (ModelState.IsValid && role != null && SiteValid(role.SiteId) && RoleValid(role.Name))
|
||||
{
|
||||
userRole = _userRoles.AddUserRole(userRole);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.UserRole, userRole.UserRoleId, SyncEventActions.Create);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "User Role Added {UserRole}", userRole);
|
||||
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.User, userRole.UserId);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.User, userRole.UserId, SyncEventActions.Refresh);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -144,7 +145,8 @@ namespace Oqtane.Controllers
|
||||
if (ModelState.IsValid && role != null && SiteValid(role.SiteId) && RoleValid(role.Name) && _userRoles.GetUserRole(userRole.UserRoleId, false) != null)
|
||||
{
|
||||
userRole = _userRoles.UpdateUserRole(userRole);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.User, userRole.UserId);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.UserRole, userRole.UserRoleId, SyncEventActions.Update);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.User, userRole.UserId, SyncEventActions.Refresh);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "User Role Updated {UserRole}", userRole);
|
||||
}
|
||||
else
|
||||
@ -165,6 +167,7 @@ namespace Oqtane.Controllers
|
||||
if (userrole != null && SiteValid(userrole.Role.SiteId) && RoleValid(userrole.Role.Name))
|
||||
{
|
||||
_userRoles.DeleteUserRole(id);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.UserRole, userrole.UserRoleId, SyncEventActions.Delete);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "User Role Deleted {UserRole}", userrole);
|
||||
|
||||
if (userrole.Role.Name == RoleNames.Host)
|
||||
@ -178,7 +181,7 @@ namespace Oqtane.Controllers
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "User Role Added {UserRole}", userrole);
|
||||
}
|
||||
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.User, userrole.UserId);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.User, userrole.UserId, SyncEventActions.Refresh);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
@ -157,6 +158,9 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
|
||||
public static IServiceCollection ConfigureOqtaneAuthenticationOptions(this IServiceCollection services, IConfigurationRoot Configuration)
|
||||
{
|
||||
// prevent remapping of claims
|
||||
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
|
||||
|
||||
// settings defined in appsettings
|
||||
services.Configure<OAuthOptions>(Configuration);
|
||||
services.Configure<OpenIdConnectOptions>(Configuration);
|
||||
@ -276,7 +280,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
var serviceTypes = assembly.GetTypes(hostedServiceType);
|
||||
foreach (var serviceType in serviceTypes)
|
||||
{
|
||||
if (serviceType.IsSubclassOf(typeof(HostedServiceBase)))
|
||||
if (!services.Any(item => item.ServiceType == serviceType))
|
||||
{
|
||||
services.AddSingleton(hostedServiceType, serviceType);
|
||||
}
|
||||
|
@ -56,6 +56,10 @@ namespace Oqtane.Extensions
|
||||
options.ClientId = sitesettings.GetValue("ExternalLogin:ClientId", "");
|
||||
options.ClientSecret = sitesettings.GetValue("ExternalLogin:ClientSecret", "");
|
||||
options.UsePkce = bool.Parse(sitesettings.GetValue("ExternalLogin:PKCE", "false"));
|
||||
if (!string.IsNullOrEmpty(sitesettings.GetValue("ExternalLogin:RoleClaimType", "")))
|
||||
{
|
||||
options.TokenValidationParameters.RoleClaimType = sitesettings.GetValue("ExternalLogin:RoleClaimType", "");
|
||||
}
|
||||
options.Scope.Clear();
|
||||
foreach (var scope in sitesettings.GetValue("ExternalLogin:Scopes", "openid,profile,email").Split(',', StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
@ -230,6 +234,18 @@ namespace Oqtane.Extensions
|
||||
var identity = await ValidateUser(email, id, claims, context.HttpContext);
|
||||
if (identity.Label == ExternalLoginStatus.Success)
|
||||
{
|
||||
// external roles
|
||||
if (!string.IsNullOrEmpty(context.HttpContext.GetSiteSettings().GetValue("ExternalLogin:RoleClaimType", "")))
|
||||
{
|
||||
foreach (var claim in context.Principal.Claims.Where(item => item.Type == ClaimTypes.Role))
|
||||
{
|
||||
if (!identity.Claims.Any(item => item.Type == ClaimTypes.Role && item.Value == claim.Value))
|
||||
{
|
||||
identity.AddClaim(new Claim(ClaimTypes.Role, claim.Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
identity.AddClaim(new Claim("access_token", context.SecurityToken.RawData));
|
||||
context.Principal = new ClaimsPrincipal(identity);
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace Oqtane.Infrastructure
|
||||
@ -52,9 +52,9 @@ namespace Oqtane.Infrastructure
|
||||
try
|
||||
{
|
||||
var path = Path.Combine(Directory.GetCurrentDirectory(), file);
|
||||
dynamic jsonObj = JsonConvert.DeserializeObject(File.ReadAllText(path));
|
||||
SetValueRecursively(key, jsonObj, value, "set");
|
||||
File.WriteAllText(path, JsonConvert.SerializeObject(jsonObj, Formatting.Indented));
|
||||
JsonNode node = JsonNode.Parse(File.ReadAllText(path));
|
||||
SetValueRecursively(node, key, value);
|
||||
File.WriteAllText(path, JsonSerializer.Serialize(node, new JsonSerializerOptions() { WriteIndented = true }));
|
||||
if (reload) Reload();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -73,9 +73,9 @@ namespace Oqtane.Infrastructure
|
||||
try
|
||||
{
|
||||
var path = Path.Combine(Directory.GetCurrentDirectory(), file);
|
||||
dynamic jsonObj = JsonConvert.DeserializeObject(File.ReadAllText(path));
|
||||
SetValueRecursively(key, jsonObj, "", "remove");
|
||||
File.WriteAllText(path, JsonConvert.SerializeObject(jsonObj, Formatting.Indented));
|
||||
JsonNode node = JsonNode.Parse(File.ReadAllText(path));
|
||||
RemovePropertyRecursively(node, key);
|
||||
File.WriteAllText(path, JsonSerializer.Serialize(node, new JsonSerializerOptions() { WriteIndented = true }));
|
||||
if (reload) Reload();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -84,30 +84,53 @@ namespace Oqtane.Infrastructure
|
||||
}
|
||||
}
|
||||
|
||||
private void SetValueRecursively<T>(string key, dynamic jsonObj, T value, string action)
|
||||
private void SetValueRecursively<T>(JsonNode json, string key, T value)
|
||||
{
|
||||
var remainingSections = key.Split(":", 2);
|
||||
if (json != null && key != null && value != null)
|
||||
{
|
||||
var remainingSections = key.Split(":", 2);
|
||||
|
||||
var currentSection = remainingSections[0];
|
||||
if (remainingSections.Length > 1)
|
||||
{
|
||||
var nextSection = remainingSections[1];
|
||||
jsonObj[currentSection] ??= new JObject();
|
||||
SetValueRecursively(nextSection, jsonObj[currentSection], value, action);
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (action)
|
||||
var currentSection = remainingSections[0];
|
||||
if (remainingSections.Length > 1)
|
||||
{
|
||||
case "set":
|
||||
jsonObj[currentSection] = JToken.FromObject(value);
|
||||
break;
|
||||
case "remove":
|
||||
if (jsonObj.Property(currentSection) != null)
|
||||
{
|
||||
jsonObj.Property(currentSection).Remove();
|
||||
}
|
||||
break;
|
||||
var nextSection = remainingSections[1];
|
||||
SetValueRecursively(json[currentSection] ??= new JsonObject(), nextSection, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (value.GetType() == typeof(string) && (value.ToString()!.StartsWith("[") || value.ToString()!.StartsWith("{")))
|
||||
{
|
||||
json[currentSection] = JsonNode.Parse(value.ToString()!);
|
||||
}
|
||||
else
|
||||
{
|
||||
json[currentSection] = JsonValue.Create(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RemovePropertyRecursively(JsonNode json, string key)
|
||||
{
|
||||
if (json != null && key != null)
|
||||
{
|
||||
var remainingSections = key.Split(":", 2);
|
||||
|
||||
var currentSection = remainingSections[0];
|
||||
if (remainingSections.Length > 1)
|
||||
{
|
||||
var nextSection = remainingSections[1];
|
||||
if (json[currentSection] != null)
|
||||
{
|
||||
RemovePropertyRecursively(json[currentSection], nextSection);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (json.AsObject().ContainsKey(currentSection))
|
||||
{
|
||||
json.AsObject().Remove(currentSection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ using Oqtane.Models;
|
||||
using Oqtane.Repository;
|
||||
using Oqtane.Shared;
|
||||
using Oqtane.Enums;
|
||||
using Newtonsoft.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
@ -51,7 +50,6 @@ namespace Oqtane.Infrastructure
|
||||
|
||||
if (!string.IsNullOrEmpty(_config.GetConnectionString(SettingKeys.ConnectionStringKey)))
|
||||
{
|
||||
result.Success = true;
|
||||
using (var db = GetInstallationContext())
|
||||
{
|
||||
if (db.Database.CanConnect())
|
||||
@ -60,6 +58,7 @@ namespace Oqtane.Infrastructure
|
||||
{
|
||||
// verify master database contains a Tenant table ( ie. validate schema is properly provisioned )
|
||||
var provisioned = db.Tenant.Any();
|
||||
result.Success = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -715,7 +714,7 @@ namespace Oqtane.Infrastructure
|
||||
foreach (var upgrade in siteupgrades)
|
||||
{
|
||||
var aliasname = upgrade.Key.Split(' ').First();
|
||||
// in the future this equality condition could use RegEx to allow for more flexible matching
|
||||
// in the future this equality condition could use RegEx to allow for more flexible matching
|
||||
if (string.Equals(alias.Name, aliasname, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
tenantManager.SetTenant(alias.TenantId);
|
||||
@ -825,7 +824,7 @@ namespace Oqtane.Infrastructure
|
||||
databases += "{ \"Name\": \"MySQL\", \"ControlType\": \"Oqtane.Installer.Controls.MySQLConfig, Oqtane.Client\", \"DBTYpe\": \"Oqtane.Database.MySQL.SqlServerDatabase, Oqtane.Database.MySQL\" },";
|
||||
databases += "{ \"Name\": \"PostgreSQL\", \"ControlType\": \"Oqtane.Installer.Controls.PostgreSQLConfig, Oqtane.Client\", \"DBTYpe\": \"Oqtane.Database.PostgreSQL.PostgreSQLDatabase, Oqtane.Database.PostgreSQL\" }";
|
||||
databases += "]";
|
||||
_configManager.AddOrUpdateSetting(SettingKeys.AvailableDatabasesSection, JsonConvert.DeserializeObject<dynamic>(databases), true);
|
||||
_configManager.AddOrUpdateSetting(SettingKeys.AvailableDatabasesSection, databases, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,45 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Oqtane.Models;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace Oqtane.Infrastructure
|
||||
{
|
||||
public class CacheInvalidationHostedService : IHostedService
|
||||
{
|
||||
private readonly ISyncManager _syncManager;
|
||||
private readonly IMemoryCache _cache;
|
||||
|
||||
public CacheInvalidationHostedService(ISyncManager syncManager, IMemoryCache cache)
|
||||
{
|
||||
_syncManager = syncManager;
|
||||
_cache = cache;
|
||||
}
|
||||
|
||||
void EntityChanged(object sender, SyncEvent e)
|
||||
{
|
||||
if (e.EntityName == "Site" && e.Action == SyncEventActions.Refresh)
|
||||
{
|
||||
_cache.Remove($"site:{e.TenantId}:{e.EntityId}");
|
||||
}
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_syncManager.EntityChanged += EntityChanged;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
_syncManager.EntityChanged -= EntityChanged;
|
||||
}
|
||||
}
|
||||
}
|
@ -6,8 +6,8 @@ namespace Oqtane.Infrastructure
|
||||
{
|
||||
public interface ISyncManager
|
||||
{
|
||||
event EventHandler<SyncEvent> EntityChanged;
|
||||
List<SyncEvent> GetSyncEvents(int tenantId, DateTime lastSyncDate);
|
||||
void AddSyncEvent(int tenantId, string entityName, int entityId);
|
||||
void AddSyncEvent(int tenantId, string entityName, int entityId, bool reload);
|
||||
void AddSyncEvent(int tenantId, string entityName, int entityId, string action);
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ namespace Oqtane.Infrastructure
|
||||
public string[] GetInstalledCultures()
|
||||
{
|
||||
var cultures = new List<string>();
|
||||
foreach (var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"Oqtane.Client{Constants.SatelliteAssemblyExtension}", SearchOption.AllDirectories))
|
||||
foreach (var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"{Constants.ClientId}{Constants.SatelliteAssemblyExtension}", SearchOption.AllDirectories))
|
||||
{
|
||||
cultures.Add(Path.GetFileName(Path.GetDirectoryName(file)));
|
||||
}
|
||||
|
@ -1,21 +1,19 @@
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Shared;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Oqtane.Infrastructure
|
||||
{
|
||||
public class SyncManager : ISyncManager
|
||||
{
|
||||
private readonly IMemoryCache _cache;
|
||||
private List<SyncEvent> SyncEvents { get; set; }
|
||||
|
||||
public SyncManager(IMemoryCache cache)
|
||||
public event EventHandler<SyncEvent> EntityChanged;
|
||||
|
||||
public SyncManager()
|
||||
{
|
||||
_cache = cache;
|
||||
SyncEvents = new List<SyncEvent>();
|
||||
}
|
||||
|
||||
@ -24,20 +22,22 @@ namespace Oqtane.Infrastructure
|
||||
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, string action)
|
||||
{
|
||||
AddSyncEvent(tenantId, entityName, entityId, false);
|
||||
}
|
||||
var syncevent = new SyncEvent { TenantId = tenantId, EntityName = entityName, EntityId = entityId, Action = action, ModifiedOn = DateTime.UtcNow };
|
||||
|
||||
public void AddSyncEvent(int tenantId, string entityName, int entityId, bool reload)
|
||||
{
|
||||
SyncEvents.Add(new SyncEvent { TenantId = tenantId, EntityName = entityName, EntityId = entityId, Reload = reload, ModifiedOn = DateTime.UtcNow });
|
||||
if (entityName == EntityNames.Site)
|
||||
{
|
||||
_cache.Remove($"site:{tenantId}:{entityId}");
|
||||
// client actions for PageState management
|
||||
if (action == SyncEventActions.Refresh || action == SyncEventActions.Reload)
|
||||
{
|
||||
// trim sync events
|
||||
SyncEvents.RemoveAll(item => item.ModifiedOn < DateTime.UtcNow.AddHours(-1));
|
||||
|
||||
// add sync event
|
||||
SyncEvents.Add(syncevent);
|
||||
}
|
||||
// trim sync events
|
||||
SyncEvents.RemoveAll(item => item.ModifiedOn < DateTime.UtcNow.AddHours(-1));
|
||||
|
||||
// raise event
|
||||
EntityChanged?.Invoke(this, syncevent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,6 +57,9 @@ namespace Oqtane.Infrastructure
|
||||
case "3.2.0":
|
||||
Upgrade_3_2_0(tenant, scope);
|
||||
break;
|
||||
case "3.2.1":
|
||||
Upgrade_3_2_1(tenant, scope);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -264,5 +267,41 @@ namespace Oqtane.Infrastructure
|
||||
}
|
||||
}
|
||||
|
||||
private void Upgrade_3_2_1(Tenant tenant, IServiceScope scope)
|
||||
{
|
||||
try
|
||||
{
|
||||
// convert Identifier Claim Type and Email Claim Type
|
||||
var settingRepository = scope.ServiceProvider.GetRequiredService<ISettingRepository>();
|
||||
var siteRepository = scope.ServiceProvider.GetRequiredService<ISiteRepository>();
|
||||
foreach (Site site in siteRepository.GetSites().ToList())
|
||||
{
|
||||
var settings = settingRepository.GetSettings(EntityNames.Site, site.SiteId).ToList();
|
||||
var setting = settings.FirstOrDefault(item => item.SettingName == "ExternalLogin:IdentifierClaimType");
|
||||
if (setting != null)
|
||||
{
|
||||
if (setting.SettingValue == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")
|
||||
{
|
||||
setting.SettingValue = "sub";
|
||||
settingRepository.UpdateSetting(setting);
|
||||
}
|
||||
}
|
||||
setting = settings.FirstOrDefault(item => item.SettingName == "ExternalLogin:EmailClaimType");
|
||||
if (setting != null)
|
||||
{
|
||||
if (setting.SettingValue == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress")
|
||||
{
|
||||
setting.SettingValue = "email";
|
||||
settingRepository.UpdateSetting(setting);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Oqtane Error: Error In 3.2.1 Upgrade Logic - {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Version>3.2.0</Version>
|
||||
<Version>3.2.1</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -11,7 +11,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<RootNamespace>Oqtane</RootNamespace>
|
||||
@ -34,7 +34,6 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="6.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="6.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.ViewFeatures" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="4.1.0" />
|
||||
|
@ -23,15 +23,17 @@ namespace Oqtane.Pages
|
||||
private readonly IFileRepository _files;
|
||||
private readonly IUserPermissions _userPermissions;
|
||||
private readonly IUrlMappingRepository _urlMappings;
|
||||
private readonly ISyncManager _syncManager;
|
||||
private readonly ILogManager _logger;
|
||||
private readonly Alias _alias;
|
||||
|
||||
public FilesModel(IWebHostEnvironment environment, IFileRepository files, IUserPermissions userPermissions, IUrlMappingRepository urlMappings, ILogManager logger, ITenantManager tenantManager)
|
||||
public FilesModel(IWebHostEnvironment environment, IFileRepository files, IUserPermissions userPermissions, IUrlMappingRepository urlMappings, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager)
|
||||
{
|
||||
_environment = environment;
|
||||
_files = files;
|
||||
_userPermissions = userPermissions;
|
||||
_urlMappings = urlMappings;
|
||||
_syncManager = syncManager;
|
||||
_logger = logger;
|
||||
_alias = tenantManager.GetAlias();
|
||||
}
|
||||
@ -42,6 +44,12 @@ namespace Oqtane.Pages
|
||||
var folderpath = "";
|
||||
var filename = "";
|
||||
|
||||
bool download = false;
|
||||
if (Request.Query.ContainsKey("download"))
|
||||
{
|
||||
download = true;
|
||||
}
|
||||
|
||||
var segments = path.Split('/');
|
||||
if (segments.Length > 0)
|
||||
{
|
||||
@ -52,15 +60,32 @@ namespace Oqtane.Pages
|
||||
}
|
||||
}
|
||||
|
||||
var file = _files.GetFile(_alias.SiteId, folderpath, filename);
|
||||
Models.File file;
|
||||
if (folderpath == "id/" && int.TryParse(filename, out int fileid))
|
||||
{
|
||||
file = _files.GetFile(fileid, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
file = _files.GetFile(_alias.SiteId, folderpath, filename);
|
||||
}
|
||||
|
||||
if (file != null)
|
||||
{
|
||||
if (_userPermissions.IsAuthorized(User, PermissionNames.View, file.Folder.Permissions))
|
||||
if (file.Folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.View, file.Folder.Permissions))
|
||||
{
|
||||
var filepath = _files.GetFilePath(file);
|
||||
if (System.IO.File.Exists(filepath))
|
||||
{
|
||||
return PhysicalFile(filepath, file.GetMimeType());
|
||||
if (download)
|
||||
{
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.File, file.FileId, "Download");
|
||||
return PhysicalFile(filepath, file.GetMimeType(), file.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
return PhysicalFile(filepath, file.GetMimeType());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -91,7 +116,7 @@ namespace Oqtane.Pages
|
||||
}
|
||||
|
||||
// broken link
|
||||
string errorPath = Path.Combine(Utilities.PathCombine(_environment.ContentRootPath, "wwwroot\\images"), "error.png");
|
||||
string errorPath = Path.Combine(Utilities.PathCombine(_environment.ContentRootPath, "wwwroot/images"), "error.png");
|
||||
return PhysicalFile(errorPath, MimeUtilities.GetMimeType(errorPath));
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +50,10 @@
|
||||
{
|
||||
<script src="_framework/blazor.server.js"></script>
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(Model.ReconnectScript))
|
||||
{
|
||||
@Html.Raw(Model.ReconnectScript)
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(Model.PWAScript))
|
||||
{
|
||||
@Html.Raw(Model.PWAScript)
|
||||
|
@ -20,6 +20,7 @@ using Oqtane.Enums;
|
||||
using Oqtane.Security;
|
||||
using Oqtane.Extensions;
|
||||
using Oqtane.Themes;
|
||||
using Oqtane.UI;
|
||||
|
||||
namespace Oqtane.Pages
|
||||
{
|
||||
@ -69,6 +70,7 @@ namespace Oqtane.Pages
|
||||
public string Meta = "";
|
||||
public string FavIcon = "favicon.ico";
|
||||
public string PWAScript = "";
|
||||
public string ReconnectScript = "";
|
||||
public string Message = "";
|
||||
|
||||
public IActionResult OnGet()
|
||||
@ -126,7 +128,11 @@ namespace Oqtane.Pages
|
||||
}
|
||||
if (site.FaviconFileId != null)
|
||||
{
|
||||
FavIcon = Utilities.ContentUrl(alias, site.FaviconFileId.Value);
|
||||
FavIcon = Utilities.FileUrl(alias, site.FaviconFileId.Value);
|
||||
}
|
||||
if (Runtime == "Server")
|
||||
{
|
||||
ReconnectScript = CreateReconnectScript();
|
||||
}
|
||||
if (site.PwaIsEnabled && site.PwaAppIconFileId != null && site.PwaSplashIconFileId != null)
|
||||
{
|
||||
@ -174,7 +180,7 @@ namespace Oqtane.Pages
|
||||
{
|
||||
ThemeType = page.ThemeType;
|
||||
}
|
||||
ProcessThemeResources(ThemeType);
|
||||
ProcessThemeResources(ThemeType, alias);
|
||||
}
|
||||
else // page not found
|
||||
{
|
||||
@ -198,7 +204,7 @@ namespace Oqtane.Pages
|
||||
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
|
||||
foreach (Assembly assembly in assemblies)
|
||||
{
|
||||
ProcessHostResources(assembly);
|
||||
ProcessHostResources(assembly, alias);
|
||||
}
|
||||
|
||||
// set culture if not specified
|
||||
@ -374,44 +380,64 @@ namespace Oqtane.Pages
|
||||
private string CreatePWAScript(Alias alias, Site site, Route route)
|
||||
{
|
||||
return
|
||||
"<script id=\"app-pwa\">" +
|
||||
"setTimeout(() => { " +
|
||||
"var manifest = { " +
|
||||
"\"name\": \"" + site.Name + "\", " +
|
||||
"\"short_name\": \"" + site.Name + "\", " +
|
||||
"\"start_url\": \"" + route.SiteUrl + "/\", " +
|
||||
"\"display\": \"standalone\", " +
|
||||
"\"background_color\": \"#fff\", " +
|
||||
"\"description\": \"" + site.Name + "\", " +
|
||||
"\"icons\": [{ " +
|
||||
"\"src\": \"" + route.RootUrl + Utilities.ContentUrl(alias, site.PwaAppIconFileId.Value) + "\", " +
|
||||
"\"sizes\": \"192x192\", " +
|
||||
"\"type\": \"image/png\" " +
|
||||
"}, { " +
|
||||
"\"src\": \"" + route.RootUrl + Utilities.ContentUrl(alias, site.PwaSplashIconFileId.Value) + "\", " +
|
||||
"\"sizes\": \"512x512\", " +
|
||||
"\"type\": \"image/png\" " +
|
||||
"}] " +
|
||||
"}; " +
|
||||
"const serialized = JSON.stringify(manifest); " +
|
||||
"const blob = new Blob([serialized], {type: 'application/javascript'}); " +
|
||||
"const url = URL.createObjectURL(blob); " +
|
||||
"document.getElementById('app-manifest').setAttribute('href', url); " +
|
||||
"} " +
|
||||
", 1000);" +
|
||||
"<script>" + Environment.NewLine +
|
||||
" // PWA Manifest" + Environment.NewLine +
|
||||
" setTimeout(() => {" + Environment.NewLine +
|
||||
" var manifest = {" + Environment.NewLine +
|
||||
" \"name\": \"" + site.Name + "\"," + Environment.NewLine +
|
||||
" \"short_name\": \"" + site.Name + "\"," + Environment.NewLine +
|
||||
" \"start_url\": \"" + route.SiteUrl + "/\"," + Environment.NewLine +
|
||||
" \"display\": \"standalone\"," + Environment.NewLine +
|
||||
" \"background_color\": \"#fff\"," + Environment.NewLine +
|
||||
" \"description\": \"" + site.Name + "\"," + Environment.NewLine +
|
||||
" \"icons\": [{" + Environment.NewLine +
|
||||
" \"src\": \"" + route.RootUrl + Utilities.FileUrl(alias, site.PwaAppIconFileId.Value) + "\"," + Environment.NewLine +
|
||||
" \"sizes\": \"192x192\"," + Environment.NewLine +
|
||||
" \"type\": \"image/png\"" + Environment.NewLine +
|
||||
" }, {" + Environment.NewLine +
|
||||
" \"src\": \"" + route.RootUrl + Utilities.FileUrl(alias, site.PwaSplashIconFileId.Value) + "\"," + Environment.NewLine +
|
||||
" \"sizes\": \"512x512\"," + Environment.NewLine +
|
||||
" \"type\": \"image/png\"" + Environment.NewLine +
|
||||
" }]" + Environment.NewLine +
|
||||
" };" + Environment.NewLine +
|
||||
" const serialized = JSON.stringify(manifest);" + Environment.NewLine +
|
||||
" const blob = new Blob([serialized], {type: 'application/javascript'});" + Environment.NewLine +
|
||||
" const url = URL.createObjectURL(blob);" + Environment.NewLine +
|
||||
" document.getElementById('app-manifest').setAttribute('href', url);" + Environment.NewLine +
|
||||
" }, 1000);" + Environment.NewLine +
|
||||
"</script>" + Environment.NewLine +
|
||||
"<script id=\"app-serviceworker\">" +
|
||||
"if ('serviceWorker' in navigator) { " +
|
||||
"navigator.serviceWorker.register('/service-worker.js').then(function(registration) { " +
|
||||
"console.log('ServiceWorker Registration Successful'); " +
|
||||
"}).catch (function(err) { " +
|
||||
"console.log('ServiceWorker Registration Failed ', err); " +
|
||||
"}); " +
|
||||
"};" +
|
||||
"<script>" + Environment.NewLine +
|
||||
" // PWA Service Worker" + Environment.NewLine +
|
||||
" if ('serviceWorker' in navigator) {" + Environment.NewLine +
|
||||
" navigator.serviceWorker.register('/service-worker.js').then(function(registration) {" + Environment.NewLine +
|
||||
" console.log('ServiceWorker Registration Successful');" + Environment.NewLine +
|
||||
" }).catch (function(err) {" + Environment.NewLine +
|
||||
" console.log('ServiceWorker Registration Failed ', err);" + Environment.NewLine +
|
||||
" });" + Environment.NewLine +
|
||||
" };" + Environment.NewLine +
|
||||
"</script>";
|
||||
}
|
||||
|
||||
private void ProcessHostResources(Assembly assembly)
|
||||
private string CreateReconnectScript()
|
||||
{
|
||||
return
|
||||
"<script>" + Environment.NewLine +
|
||||
" // Blazor Server Reconnect" + Environment.NewLine +
|
||||
" new MutationObserver((mutations, observer) => {" + Environment.NewLine +
|
||||
" if (document.querySelector('#components-reconnect-modal h5 a')) {" + Environment.NewLine +
|
||||
" async function attemptReload() {" + Environment.NewLine +
|
||||
" await fetch('');" + Environment.NewLine +
|
||||
" location.reload();" + Environment.NewLine +
|
||||
" }" + Environment.NewLine +
|
||||
" observer.disconnect();" + Environment.NewLine +
|
||||
" attemptReload();" + Environment.NewLine +
|
||||
" setInterval(attemptReload, 5000);" + Environment.NewLine +
|
||||
" }" + Environment.NewLine +
|
||||
" }).observe(document.body, { childList: true, subtree: true });" + Environment.NewLine +
|
||||
"</script>";
|
||||
}
|
||||
|
||||
private void ProcessHostResources(Assembly assembly, Alias alias)
|
||||
{
|
||||
var types = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IHostResources)));
|
||||
foreach (var type in types)
|
||||
@ -420,12 +446,12 @@ namespace Oqtane.Pages
|
||||
foreach (var resource in obj.Resources)
|
||||
{
|
||||
resource.Level = ResourceLevel.App;
|
||||
ProcessResource(resource, 0);
|
||||
ProcessResource(resource, 0, alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessThemeResources(string ThemeType)
|
||||
private void ProcessThemeResources(string ThemeType, Alias alias)
|
||||
{
|
||||
var type = Type.GetType(ThemeType);
|
||||
if (type != null)
|
||||
@ -437,40 +463,41 @@ namespace Oqtane.Pages
|
||||
foreach (var resource in obj.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet))
|
||||
{
|
||||
resource.Level = ResourceLevel.Page;
|
||||
ProcessResource(resource, count++);
|
||||
ProcessResource(resource, count++, alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessResource(Resource resource, int count)
|
||||
private void ProcessResource(Resource resource, int count, Alias alias)
|
||||
{
|
||||
var url = (resource.Url.Contains("://")) ? resource.Url : alias.BaseUrl + resource.Url;
|
||||
switch (resource.ResourceType)
|
||||
{
|
||||
case ResourceType.Stylesheet:
|
||||
if (!HeadResources.Contains(resource.Url, StringComparison.OrdinalIgnoreCase))
|
||||
if (!HeadResources.Contains(url, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string id = "";
|
||||
if (resource.Level == ResourceLevel.Page)
|
||||
{
|
||||
id = "id=\"app-stylesheet-" + resource.Level.ToString().ToLower() + "-" + DateTime.UtcNow.ToString("yyyyMMddHHmmssfff") + "-" + count.ToString("00") + "\" ";
|
||||
}
|
||||
HeadResources += "<link " + id + "rel=\"stylesheet\" href=\"" + resource.Url + "\"" + CrossOrigin(resource.CrossOrigin) + Integrity(resource.Integrity) + " />" + Environment.NewLine;
|
||||
HeadResources += "<link " + id + "rel=\"stylesheet\" href=\"" + url + "\"" + CrossOrigin(resource.CrossOrigin) + Integrity(resource.Integrity) + " type=\"text/css\"/>" + Environment.NewLine;
|
||||
}
|
||||
break;
|
||||
case ResourceType.Script:
|
||||
if (resource.Location == Shared.ResourceLocation.Body)
|
||||
{
|
||||
if (!BodyResources.Contains(resource.Url, StringComparison.OrdinalIgnoreCase))
|
||||
if (!BodyResources.Contains(url, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
BodyResources += "<script src=\"" + resource.Url + "\"" + CrossOrigin(resource.CrossOrigin) + Integrity(resource.Integrity) + "></script>" + Environment.NewLine;
|
||||
BodyResources += "<script src=\"" + url + "\"" + CrossOrigin(resource.CrossOrigin) + Integrity(resource.Integrity) + "></script>" + Environment.NewLine;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!HeadResources.Contains(resource.Url, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
HeadResources += "<script src=\"" + resource.Url + "\"" + CrossOrigin(resource.CrossOrigin) + Integrity(resource.Integrity) + "></script>" + Environment.NewLine;
|
||||
HeadResources += "<script src=\"" + url + "\"" + CrossOrigin(resource.CrossOrigin) + Integrity(resource.Integrity) + "></script>" + Environment.NewLine;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -133,7 +133,6 @@ namespace Oqtane
|
||||
{
|
||||
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
|
||||
})
|
||||
.AddNewtonsoftJson()
|
||||
.AddOqtaneApplicationParts() // register any Controllers from custom modules
|
||||
.ConfigureOqtaneMvc(); // any additional configuration from IStartup classes
|
||||
|
||||
@ -191,7 +190,7 @@ namespace Oqtane
|
||||
});
|
||||
|
||||
// create a global sync event to identify server application startup
|
||||
sync.AddSyncEvent(-1, "Application", -1, true);
|
||||
sync.AddSyncEvent(-1, EntityNames.Host, -1, SyncEventActions.Reload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -356,10 +356,15 @@ Oqtane.Interop = {
|
||||
}
|
||||
}
|
||||
},
|
||||
refreshBrowser: function (reload, wait) {
|
||||
setInterval(function () {
|
||||
window.location.reload(reload);
|
||||
}, wait * 1000);
|
||||
refreshBrowser: function (verify, wait) {
|
||||
async function attemptReload (verify) {
|
||||
if (verify) {
|
||||
await fetch('');
|
||||
}
|
||||
window.location.reload();
|
||||
}
|
||||
attemptReload(verify);
|
||||
setInterval(attemptReload, wait * 1000);
|
||||
},
|
||||
redirectBrowser: function (url, wait) {
|
||||
setInterval(function () {
|
||||
|
@ -79,7 +79,7 @@ namespace System.Reflection
|
||||
{
|
||||
return appDomain.GetOqtaneAssemblies()
|
||||
.Where(a => a.GetTypes<IModuleControl>().Any() || a.GetTypes<IThemeControl>().Any() || a.GetTypes<IClientStartup>().Any())
|
||||
.Where(a => Utilities.GetFullTypeName(a.GetName().Name) != "Oqtane.Client");
|
||||
.Where(a => Utilities.GetFullTypeName(a.GetName().Name) != Constants.ClientId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -8,6 +8,8 @@ namespace Oqtane.Models
|
||||
/// </summary>
|
||||
public class Resource
|
||||
{
|
||||
private string _url;
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="ResourceType"/> so the Interop can properly create `script` or `link` tags
|
||||
/// </summary>
|
||||
@ -16,7 +18,14 @@ namespace Oqtane.Models
|
||||
/// <summary>
|
||||
/// Path to the resources.
|
||||
/// </summary>
|
||||
public string Url { get; set; }
|
||||
public string Url
|
||||
{
|
||||
get => _url;
|
||||
set
|
||||
{
|
||||
_url = (value.Contains("://")) ? value : (!value.StartsWith("/") ? "/" : "") + value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Integrity checks to increase the security of resources accessed. Especially common in CDN resources.
|
||||
|
@ -9,12 +9,12 @@ namespace Oqtane.Models
|
||||
public List<SyncEvent> SyncEvents { get; set; }
|
||||
}
|
||||
|
||||
public class SyncEvent
|
||||
public class SyncEvent : EventArgs
|
||||
{
|
||||
public int TenantId { get; set; }
|
||||
public string EntityName { get; set; }
|
||||
public int EntityId { get; set; }
|
||||
public bool Reload { get; set; }
|
||||
public string Action { get; set; }
|
||||
public DateTime ModifiedOn { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Version>3.2.0</Version>
|
||||
<Version>3.2.1</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -11,7 +11,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<RootNamespace>Oqtane</RootNamespace>
|
||||
|
@ -4,9 +4,10 @@ namespace Oqtane.Shared
|
||||
{
|
||||
public class Constants
|
||||
{
|
||||
public static readonly string Version = "3.2.0";
|
||||
public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0";
|
||||
public static readonly string Version = "3.2.1";
|
||||
public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1";
|
||||
public const string PackageId = "Oqtane.Framework";
|
||||
public const string ClientId = "Oqtane.Client";
|
||||
public const string UpdaterPackageId = "Oqtane.Updater";
|
||||
public const string PackageRegistryUrl = "https://www.oqtane.net";
|
||||
|
||||
@ -16,17 +17,12 @@ namespace Oqtane.Shared
|
||||
public const string ContainerComponent = "Oqtane.UI.ContainerBuilder, Oqtane.Client";
|
||||
|
||||
public const string DefaultTheme = "Oqtane.Themes.OqtaneTheme.Default, Oqtane.Client";
|
||||
[Obsolete("DefaultLayout is deprecated")]
|
||||
public const string DefaultLayout = "";
|
||||
public const string DefaultContainer = "Oqtane.Themes.OqtaneTheme.Container, Oqtane.Client";
|
||||
public const string DefaultAdminContainer = "Oqtane.Themes.AdminContainer, Oqtane.Client";
|
||||
|
||||
public const string ActionToken = "{Action}";
|
||||
public const string DefaultAction = "Index";
|
||||
|
||||
[Obsolete("Use PaneNames.Admin")]
|
||||
public const string AdminPane = PaneNames.Admin;
|
||||
|
||||
public static readonly string[] ReservedRoutes = { "api", "pages", "files" };
|
||||
public const string ModuleDelimiter = "*";
|
||||
public const string UrlParametersDelimiter = "!";
|
||||
@ -42,29 +38,13 @@ namespace Oqtane.Shared
|
||||
|
||||
public const string DefaultSiteTemplate = "Oqtane.SiteTemplates.DefaultSiteTemplate, Oqtane.Server";
|
||||
|
||||
public const string ContentUrl = "/api/file/download/";
|
||||
public const string FileUrl = "/files/";
|
||||
public const string ImageUrl = "/api/file/image/";
|
||||
public const int UserFolderCapacity = 20; // megabytes
|
||||
public const string PackagesFolder = "Packages";
|
||||
|
||||
[Obsolete("Use UserNames.Host instead.")]
|
||||
public const string HostUser = UserNames.Host;
|
||||
|
||||
[Obsolete("Use TenantNames.Master instead")]
|
||||
public const string MasterTenant = TenantNames.Master;
|
||||
public const string DefaultSite = "Default Site";
|
||||
|
||||
const string RoleObsoleteMessage = "Use the corresponding member from Oqtane.Shared.RoleNames";
|
||||
|
||||
[Obsolete(RoleObsoleteMessage)]
|
||||
public const string AllUsersRole = RoleNames.Everyone;
|
||||
[Obsolete(RoleObsoleteMessage)]
|
||||
public const string HostRole = RoleNames.Host;
|
||||
[Obsolete(RoleObsoleteMessage)]
|
||||
public const string AdminRole = RoleNames.Admin;
|
||||
[Obsolete(RoleObsoleteMessage)]
|
||||
public const string RegisteredRole = RoleNames.Registered;
|
||||
|
||||
public const string ImageFiles = "jpg,jpeg,jpe,gif,bmp,png,ico,webp";
|
||||
public const string UploadableFiles = ImageFiles + ",mov,wmv,avi,mp4,mp3,doc,docx,xls,xlsx,ppt,pptx,pdf,txt,zip,nupkg,csv,json,xml,xslt,rss,html,htm,css";
|
||||
public const string ReservedDevices = "CON,NUL,PRN,COM0,COM1,COM2,COM3,COM4,COM5,COM6,COM7,COM8,COM9,LPT0,LPT1,LPT2,LPT3,LPT4,LPT5,LPT6,LPT7,LPT8,LPT9,CONIN$,CONOUT$";
|
||||
@ -93,5 +73,33 @@ namespace Oqtane.Shared
|
||||
public static readonly string HttpContextSiteSettingsKey = "SiteSettings";
|
||||
|
||||
public static readonly string MauiUserAgent = "MAUI";
|
||||
|
||||
// Obsolete constants
|
||||
|
||||
const string RoleObsoleteMessage = "Use the corresponding member from Oqtane.Shared.RoleNames";
|
||||
|
||||
[Obsolete(RoleObsoleteMessage)]
|
||||
public const string AllUsersRole = RoleNames.Everyone;
|
||||
[Obsolete(RoleObsoleteMessage)]
|
||||
public const string HostRole = RoleNames.Host;
|
||||
[Obsolete(RoleObsoleteMessage)]
|
||||
public const string AdminRole = RoleNames.Admin;
|
||||
[Obsolete(RoleObsoleteMessage)]
|
||||
public const string RegisteredRole = RoleNames.Registered;
|
||||
|
||||
[Obsolete("DefaultLayout is deprecated")]
|
||||
public const string DefaultLayout = "";
|
||||
|
||||
[Obsolete("Use PaneNames.Admin")]
|
||||
public const string AdminPane = PaneNames.Admin;
|
||||
|
||||
[Obsolete("Use UserNames.Host instead.")]
|
||||
public const string HostUser = UserNames.Host;
|
||||
|
||||
[Obsolete("Use TenantNames.Master instead")]
|
||||
public const string MasterTenant = TenantNames.Master;
|
||||
|
||||
// [Obsolete("Use FileUrl instead")]
|
||||
public const string ContentUrl = "/api/file/download/";
|
||||
}
|
||||
}
|
||||
|
@ -2,15 +2,25 @@ namespace Oqtane.Shared
|
||||
{
|
||||
public class EntityNames
|
||||
{
|
||||
public const string Alias = "Alias";
|
||||
public const string File = "File";
|
||||
public const string Folder = "Folder";
|
||||
public const string Job = "Job";
|
||||
public const string Language = "Language";
|
||||
public const string Module = "Module";
|
||||
public const string ModuleDefinition = "ModuleDefinition";
|
||||
public const string PageModule = "PageModule";
|
||||
public const string Tenant = "Tenant";
|
||||
public const string Site = "Site";
|
||||
public const string Notification = "Notification";
|
||||
public const string Page = "Page";
|
||||
public const string Folder = "Folder";
|
||||
public const string PageModule = "PageModule";
|
||||
public const string Profile = "Profile";
|
||||
public const string Role = "Role";
|
||||
public const string Setting = "Setting";
|
||||
public const string Site = "Site";
|
||||
public const string Tenant = "Tenant";
|
||||
public const string UrlMapping = "UrlMapping";
|
||||
public const string User = "User";
|
||||
public const string UserRole = "UserRole";
|
||||
public const string Visitor = "Visitor";
|
||||
public const string Host = "Host";
|
||||
public const string Host = "Host"; // a conceptual entity
|
||||
}
|
||||
}
|
||||
|
12
Oqtane.Shared/Shared/SyncEventActions.cs
Normal file
12
Oqtane.Shared/Shared/SyncEventActions.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Oqtane.Shared {
|
||||
public class SyncEventActions {
|
||||
// client actions for PageState management
|
||||
public const string Refresh = "Refresh";
|
||||
public const string Reload = "Reload";
|
||||
|
||||
// server actions for raising Events
|
||||
public const string Create = "Create";
|
||||
public const string Update = "Update";
|
||||
public const string Delete = "Delete";
|
||||
}
|
||||
}
|
@ -98,23 +98,28 @@ namespace Oqtane.Shared
|
||||
return NavigateUrl(alias, path, parameters);
|
||||
}
|
||||
|
||||
public static string ContentUrl(Alias alias, int fileId)
|
||||
{
|
||||
return ContentUrl(alias, fileId, false);
|
||||
}
|
||||
|
||||
public static string ContentUrl(Alias alias, int fileId, bool asAttachment)
|
||||
{
|
||||
var aliasUrl = (alias != null && !string.IsNullOrEmpty(alias.Path)) ? "/" + alias.Path : "";
|
||||
var method = asAttachment ? "/attach" : "";
|
||||
|
||||
return $"{alias.BaseUrl}{aliasUrl}{Constants.ContentUrl}{fileId}{method}";
|
||||
}
|
||||
|
||||
public static string FileUrl(Alias alias, string folderpath, string filename)
|
||||
{
|
||||
return FileUrl(alias, folderpath, filename, false);
|
||||
}
|
||||
|
||||
public static string FileUrl(Alias alias, string folderpath, string filename, bool download)
|
||||
{
|
||||
var aliasUrl = (alias != null && !string.IsNullOrEmpty(alias.Path)) ? "/" + alias.Path : "";
|
||||
return $"{alias.BaseUrl}{aliasUrl}/files/{folderpath.Replace("\\", "/")}{filename}";
|
||||
var querystring = (download) ? "?download" : "";
|
||||
return $"{alias?.BaseUrl}{aliasUrl}{Constants.FileUrl}{folderpath.Replace("\\", "/")}{filename}{querystring}";
|
||||
}
|
||||
|
||||
public static string FileUrl(Alias alias, int fileid)
|
||||
{
|
||||
return FileUrl(alias, fileid, false);
|
||||
}
|
||||
|
||||
public static string FileUrl(Alias alias, int fileid, bool download)
|
||||
{
|
||||
var aliasUrl = (alias != null && !string.IsNullOrEmpty(alias.Path)) ? "/" + alias.Path : "";
|
||||
var querystring = (download) ? "?download" : "";
|
||||
return $"{alias?.BaseUrl}{aliasUrl}{Constants.FileUrl}id/{fileid}{querystring}";
|
||||
}
|
||||
|
||||
public static string ImageUrl(Alias alias, int fileId, int width, int height, string mode)
|
||||
@ -128,25 +133,30 @@ namespace Oqtane.Shared
|
||||
mode = string.IsNullOrEmpty(mode) ? "crop" : mode;
|
||||
position = string.IsNullOrEmpty(position) ? "center" : position;
|
||||
background = string.IsNullOrEmpty(background) ? "000000" : background;
|
||||
return $"{alias.BaseUrl}{url}{Constants.ImageUrl}{fileId}/{width}/{height}/{mode}/{position}/{background}/{rotate}/{recreate}";
|
||||
return $"{alias?.BaseUrl}{url}{Constants.ImageUrl}{fileId}/{width}/{height}/{mode}/{position}/{background}/{rotate}/{recreate}";
|
||||
}
|
||||
|
||||
public static string TenantUrl(Alias alias, string url)
|
||||
{
|
||||
url = (!url.StartsWith("/")) ? "/" + url : url;
|
||||
url = (alias != null && !string.IsNullOrEmpty(alias.Path)) ? "/" + alias.Path + url : url;
|
||||
return $"{alias.BaseUrl}{url}";
|
||||
return $"{alias?.BaseUrl}{url}";
|
||||
}
|
||||
|
||||
public static string FormatContent(string content, Alias alias, string operation)
|
||||
{
|
||||
var aliasUrl = (alias != null && !string.IsNullOrEmpty(alias.Path)) ? "/" + alias.Path : "";
|
||||
switch (operation)
|
||||
{
|
||||
case "save":
|
||||
content = content.Replace(alias?.BaseUrl + aliasUrl + Constants.FileUrl, Constants.FileUrl);
|
||||
// legacy
|
||||
content = content.Replace(UrlCombine("Content", "Tenants", alias.TenantId.ToString(), "Sites", alias.SiteId.ToString()), "[siteroot]");
|
||||
content = content.Replace(alias.Path + Constants.ContentUrl, Constants.ContentUrl);
|
||||
break;
|
||||
case "render":
|
||||
content = content.Replace(Constants.FileUrl, alias?.BaseUrl + aliasUrl + Constants.FileUrl);
|
||||
// legacy
|
||||
content = content.Replace("[siteroot]", UrlCombine("Content", "Tenants", alias.TenantId.ToString(), "Sites", alias.SiteId.ToString()));
|
||||
content = content.Replace(Constants.ContentUrl, alias.Path + Constants.ContentUrl);
|
||||
break;
|
||||
@ -491,5 +501,19 @@ namespace Oqtane.Shared
|
||||
return (localDateTime?.Date, localTime);
|
||||
}
|
||||
|
||||
[Obsolete("ContentUrl(Alias alias, int fileId) is deprecated. Use FileUrl(Alias alias, int fileId) instead.", false)]
|
||||
public static string ContentUrl(Alias alias, int fileId)
|
||||
{
|
||||
return ContentUrl(alias, fileId, false);
|
||||
}
|
||||
|
||||
[Obsolete("ContentUrl(Alias alias, int fileId, bool asAttachment) is deprecated. Use FileUrl(Alias alias, int fileId, bool download) instead.", false)]
|
||||
public static string ContentUrl(Alias alias, int fileId, bool asAttachment)
|
||||
{
|
||||
var aliasUrl = (alias != null && !string.IsNullOrEmpty(alias.Path)) ? "/" + alias.Path : "";
|
||||
var method = asAttachment ? "/attach" : "";
|
||||
|
||||
return $"{alias?.BaseUrl}{aliasUrl}{Constants.ContentUrl}{fileId}{method}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Version>3.2.0</Version>
|
||||
<Version>3.2.1</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -11,7 +11,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<RootNamespace>Oqtane</RootNamespace>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<Version>3.2.0</Version>
|
||||
<Version>3.2.1</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -11,7 +11,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<RootNamespace>Oqtane</RootNamespace>
|
||||
|
10
README.md
10
README.md
@ -41,15 +41,15 @@ There is a separate [Documentation repository](https://github.com/oqtane/oqtane.
|
||||
# Roadmap
|
||||
This project is open source, and therefore is a work in progress...
|
||||
|
||||
V.4.0.0 ( Q4 2022 )
|
||||
4.0.0 ( Q4 2022 )
|
||||
- [ ] Migration to .NET 7
|
||||
- [ ] Folder Providers
|
||||
|
||||
V.3.2.0 ( Q3 2022 )
|
||||
[3.2.0](https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0) ( Sep 13, 2022 )
|
||||
- [x] MAUI / Blazor Hybrid support
|
||||
- [x] Upgrade to Bootstrap 5.2
|
||||
|
||||
[3.1.3](https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.3) ( June 27, 2022 )
|
||||
[3.1.3](https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.3) ( Jun 27, 2022 )
|
||||
- [x] Stabilization improvements
|
||||
|
||||
[3.1.2](https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.2) ( May 14, 2022 )
|
||||
@ -58,7 +58,7 @@ V.3.2.0 ( Q3 2022 )
|
||||
[3.1.1](https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.1) ( May 3, 2022 )
|
||||
- [x] Stabilization improvements
|
||||
|
||||
[3.1.0](https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.0) ( April 5, 2022 )
|
||||
[3.1.0](https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.0) ( Apr 5, 2022 )
|
||||
- [x] User account lockout support
|
||||
- [x] Two factor authentication support
|
||||
- [x] Per-site configuration of password complexity, lockout criteria
|
||||
@ -156,6 +156,8 @@ Oqtane was created by [Shaun Walker](https://www.linkedin.com/in/shaunbrucewalke
|
||||
|
||||
# Release Announcements
|
||||
|
||||
[Oqtane 3.2](https://www.oqtane.org/blog/!/50/oqtane-3-2-for-net-maui-blazor-hybrid)
|
||||
|
||||
[Oqtane 3.1](https://www.oqtane.org/blog/!/41/oqtane-3-1-released)
|
||||
|
||||
[Oqtane 3.0](https://www.oqtane.org/Resources/Blog/PostId/551/announcing-oqtane-30-for-net-6)
|
||||
|
Loading…
x
Reference in New Issue
Block a user