Compare commits

..

42 Commits

Author SHA1 Message Date
c8f56e1659 Merge pull request #2461 from oqtane/master
Merge pull request #2460 from oqtane/dev
2022-10-17 16:46:03 -04:00
56631a3e6b Merge pull request #2460 from oqtane/dev
3.2.1 release
2022-10-17 16:45:47 -04:00
84ee9de18c Merge pull request #2459 from sbwalker/dev
Changed default service url in MAUI so users can immediately run client app
2022-10-17 08:12:53 -04:00
0aeb4e9173 Changed default service url in MAUI so users can immediately run client app 2022-10-17 08:12:04 -04:00
bb65e5c373 Merge pull request #2455 from sbwalker/dev
prepare for 3.2.1 release
2022-10-13 13:35:36 -04:00
45e2027c56 prepare for 3.2.1 release 2022-10-13 13:34:43 -04:00
6dc5ef44b7 Merge pull request #2454 from sbwalker/dev
Resolve deserialization issue with System.Text.Json when accessing remote services
2022-10-12 12:38:08 -04:00
e88d3cca07 Resolve deserialization issue with System.Text.Json when accessing remote services 2022-10-12 12:37:03 -04:00
a4b7381141 Merge pull request #2451 from sbwalker/dev
fix #2435 - remove NewtonSoft.Json dependency
2022-10-11 08:35:44 -04:00
2ea054dc72 fix #2435 - remove NewtonSoft.Json dependency 2022-10-11 08:34:33 -04:00
13ec726ab2 Merge pull request #2450 from sbwalker/dev
add file download event
2022-10-05 08:02:22 -04:00
2e32b65421 add file download event 2022-10-05 08:00:45 -04:00
f48ca53cdb Merge pull request #2449 from sbwalker/dev
Enhance SyncManager to raise events which can be handled on the server within hosted services. Raise create, update, delete events for all major entities. Include support for refresh and reload events to synchronize client state. Move client state cache invalidation to a hosted service to separate concerns and demonstrate events.
2022-10-04 19:21:35 -04:00
c5b632cb24 Enhance SyncManager to raise events which can be handled on the server within hosted services. Raise create, update, delete events for all major entities. Include support for refresh and reload events to synchronize client state. Move client state cache invalidation to a hosted service to separate concerns and demonstrate events. 2022-10-04 19:20:02 -04:00
422e9ae99e Update README.md 2022-09-30 15:24:15 -04:00
68ada8fbe4 Merge pull request #2431 from chlupac/InstallFix
Unattended installation fix
2022-09-30 11:49:06 -04:00
e40bf08691 Merge pull request #2446 from sbwalker/dev
add upgrade logic for sites using remapped identifier and email claim…
2022-09-30 09:54:59 -04:00
a04c7222b2 add upgrade logic for sites using remapped identifier and email claim types 2022-09-30 09:53:37 -04:00
172faec5a0 Merge pull request #2445 from sbwalker/dev
log any user creation errors from .NET Identity
2022-09-29 17:18:48 -04:00
e01c3e7e4a log any user creation errors from .NET Identity 2022-09-29 17:16:29 -04:00
7a3d5d0429 Merge pull request #2444 from sbwalker/dev
fix #2432 - add support for roles as part of external login via OIDC
2022-09-29 16:34:18 -04:00
ddf1caaaaa fix #2432 - add support for roles as part of external login via OIDC 2022-09-29 16:32:50 -04:00
021293cbb0 Merge pull request #2443 from sbwalker/dev
fix #2427 - issue with upgrade available in Language Management
2022-09-28 16:18:08 -04:00
1438e61f1b fix #2427 - issue with upgrade available in Language Management 2022-09-28 16:16:46 -04:00
182f4dbae7 Merge pull request #2442 from sbwalker/dev
fix #2426 - error in recycle bin
2022-09-28 13:56:46 -04:00
26ec3fc7cf fix #2426 - error in recycle bin 2022-09-28 13:55:12 -04:00
225c758795 Merge pull request #2440 from leigh-pointer/PagerFooter
Add footer to the Pager control
2022-09-28 09:45:48 -04:00
5e653250f3 Merge pull request #2441 from sbwalker/dev
Fix #2439 - ensure resource urls are constructed consistently on client and server
2022-09-28 09:44:32 -04:00
b7a3713946 Fix #2439 - ensure resource urls are constructed consistently on client and server 2022-09-28 09:43:02 -04:00
44242fdb4a Add footer to the Pager control
Mirrored the Head functionality for <tfoot> tag
2022-09-28 14:00:53 +02:00
45515b2c06 Unattented instalation fix 2022-09-24 15:44:20 +02:00
2d95fe294c Merge pull request #2430 from sbwalker/dev
Add Blazor Server reconnect script, fix event log direct link from notification email, add more validation to Pager, improve browser refresh script to wait for server availability
2022-09-24 08:38:54 -04:00
72cc44641b Add Blazor Server reconnect script, fix event log direct link from notification email, add more validation to Pager, improve browser refresh script to wait for server availability 2022-09-24 08:37:18 -04:00
9ac3fa5269 Merge pull request #2425 from sbwalker/dev
fix new id convention in file server
2022-09-21 15:39:20 -04:00
d1ea141165 fix new id convention in file server 2022-09-21 15:37:52 -04:00
dca21fbb8c Merge pull request #2424 from sbwalker/dev
improve BaseUrl handling for MAUI, replace ContentUrl with FileUrl and improve file server
2022-09-21 13:39:51 -04:00
06812d5df8 improve BaseUrl handling for MAUI, replace ContentUrl with FileUrl and improve file server 2022-09-21 13:38:21 -04:00
564410d3cd Update README.md 2022-09-19 17:10:11 -04:00
4b1cec979a Merge pull request #2422 from sbwalker/dev
rename list name to match intent
2022-09-14 22:30:05 -04:00
a5f1bc3895 rename list name to match intent 2022-09-14 22:28:31 -04:00
b097d031b5 Merge pull request #2421 from sbwalker/dev
clean up pdb files on client, hash assembly file names
2022-09-14 10:11:22 -04:00
45df729711 clean up pdb files on client, hash assembly file names 2022-09-14 10:09:50 -04:00
85 changed files with 969 additions and 558 deletions

View File

@ -47,7 +47,7 @@ else
protected override async Task OnParametersSetAsync() 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 cultures = await LocalizationService.GetCulturesAsync();
var culture = cultures.First(c => c.Name.Equals(Constants.DefaultCulture)); var culture = cultures.First(c => c.Name.Equals(Constants.DefaultCulture));
@ -78,7 +78,7 @@ else
private bool UpgradeAvailable(string code, string version) private bool UpgradeAvailable(string code, string version)
{ {
var upgradeavailable = false; var upgradeavailable = false;
if (_packages != null) if (_packages != null && version != null)
{ {
var package = _packages.Where(item => item.PackageId == (Constants.PackageId + "." + code)).FirstOrDefault(); var package = _packages.Where(item => item.PackageId == (Constants.PackageId + "." + code)).FirstOrDefault();
if (package != null) if (package != null)

View File

@ -109,7 +109,7 @@ else
// external link to log item will display Details component // external link to log item will display Details component
if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int id)) 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")) if (UrlParameters.ContainsKey("level"))

View File

@ -102,46 +102,43 @@
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</TabPanel> </TabPanel>
<TabPanel Name="Translations" ResourceKey="Translations"> <TabPanel Name="Translations" ResourceKey="Translations">
@if (_languages != null) @if (_languages != null && _languages.Count > 0)
{ {
@if (_languages.Count > 0) <Pager Items="@_languages">
{ <Header>
<Pager Items="@_languages"> <th>@SharedLocalizer["Name"]</th>
<Header> <th>@Localizer["Code"]</th>
<th>@SharedLocalizer["Name"]</th> <th>@Localizer["Version"]</th>
<th>@Localizer["Code"]</th> <th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Version"]</th> </Header>
<th style="width: 1px;">&nbsp;</th> <Row>
</Header> <td>@context.Name</td>
<Row> <td>@context.Code</td>
<td>@context.Name</td> <td>@context.Version</td>
<td>@context.Code</td> <td>
<td>@context.Version</td> @if (context.IsDefault)
<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 }
{ </td>
if (UpgradeAvailable(_packagename + "." + context.Code, context.Version)) </Row>
{ </Pager>
<button type="button" class="btn btn-primary" @onclick=@(async () => await DownloadPackage(_packagename + "." + context.Code))>@SharedLocalizer["Upgrade"]</button> <button type="button" class="btn btn-success" @onclick="InstallTranslations">@SharedLocalizer["Install"]</button>
} }
} else
</td> {
</Row> <br />
</Pager> <div class="mx-auto text-center">
<button type="button" class="btn btn-success" @onclick="InstallTranslations">@SharedLocalizer["Install"]</button> @Localizer["Search.NoResults"]
} </div>
else <br />
{
<br />
<div class="mx-auto text-center">
@Localizer["Search.NoResults"]
</div>
<br />
}
} }
</TabPanel> </TabPanel>
</TabStrip> </TabStrip>
@ -237,17 +234,20 @@
_modifiedby = moduleDefinition.ModifiedBy; _modifiedby = moduleDefinition.ModifiedBy;
_modifiedon = moduleDefinition.ModifiedOn; _modifiedon = moduleDefinition.ModifiedOn;
_packages = await PackageService.GetPackagesAsync("translation", "", "", _packagename); if (!string.IsNullOrEmpty(_packagename))
_languages = await LanguageService.GetLanguagesAsync(-1, _packagename);
foreach (var package in _packages)
{ {
var code = package.PackageId.Split('.').Last(); _packages = await PackageService.GetPackagesAsync("translation", "", "", _packagename);
if (!_languages.Any(item => item.Code == code)) _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) catch (Exception ex)

View File

@ -50,7 +50,7 @@ else
<Row> <Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ModuleDefinitionId.ToString())" ResourceKey="EditModule" /></td> <td><ActionLink Action="Edit" Parameters="@($"id=" + context.ModuleDefinitionId.ToString())" ResourceKey="EditModule" /></td>
<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" /> <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.Name</td>
<td>@context.Version</td> <td>@context.Version</td>
<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> <span>@SharedLocalizer["Yes"]</span>
} }

View File

@ -7,75 +7,77 @@
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
<TabStrip> @if (_pages == null || _modules == null)
<TabPanel Name="Pages" ResourceKey="Pages"> {
@if (_pages == null) <p><em>@SharedLocalizer["Loading"]</em></p>
{ }
<br /> else
<p>@Localizer["NoPage.Deleted"]</p> {
} <TabStrip>
else <TabPanel Name="Pages" ResourceKey="Pages">
{ @if (!_pages.Where(item => item.IsDeleted).Any())
<Pager Items="@_pages"> {
<Header> <br />
<th style="width: 1px;">&nbsp;</th> <p>@Localizer["NoPage.Deleted"]</p>
<th style="width: 1px;">&nbsp;</th> }
<th>@SharedLocalizer["Name"]</th> else
<th>@Localizer["DeletedBy"]</th> {
<th>@Localizer["DeletedOn"]</th> <Pager Items="@_pages.Where(item => item.IsDeleted)">
</Header> <Header>
<Row> <th style="width: 1px;">&nbsp;</th>
<td><button type="button" @onclick="@(() => RestorePage(context))" class="btn btn-success" title="Restore">Restore</button></td> <th style="width: 1px;">&nbsp;</th>
<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> <th>@SharedLocalizer["Name"]</th>
<td>@context.Name</td> <th>@Localizer["DeletedBy"]</th>
<td>@context.DeletedBy</td> <th>@Localizer["DeletedOn"]</th>
<td>@context.DeletedOn</td> </Header>
</Row> <Row>
</Pager> <td><button type="button" @onclick="@(() => RestorePage(context))" class="btn btn-success" title="Restore">Restore</button></td>
@if (_pages.Any()) <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>
<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" /> <td>@context.DeletedBy</td>
} <td>@context.DeletedOn</td>
} </Row>
</TabPanel> </Pager>
<TabPanel Name="Modules" ResourceKey="Modules"> <br />
@if (_modules == null) <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" />
{ }
<br /> </TabPanel>
<p>@Localizer["NoModule.Deleted"]</p> <TabPanel Name="Modules" ResourceKey="Modules">
} @if (!_modules.Where(item => item.IsDeleted).Any())
else {
{ <br />
<Pager Items="@_modules"> <p>@Localizer["NoModule.Deleted"]</p>
<Header> }
<th style="width: 1px;">&nbsp;</th> else
<th style="width: 1px;">&nbsp;</th> {
<th>@Localizer["Page"]</th> <Pager Items="@_modules.Where(item => item.IsDeleted)">
<th>@Localizer["Module"]</th> <Header>
<th>@Localizer["DeletedBy"]</th> <th style="width: 1px;">&nbsp;</th>
<th>@Localizer["DeletedOn"]</th> <th style="width: 1px;">&nbsp;</th>
</Header> <th>@Localizer["Page"]</th>
<Row> <th>@Localizer["Module"]</th>
<td><button type="button" @onclick="@(() => RestoreModule(context))" class="btn btn-success" title="Restore">@Localizer["Restore"]</button></td> <th>@Localizer["DeletedBy"]</th>
<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> <th>@Localizer["DeletedOn"]</th>
<td>@_pages.Find(item => item.PageId == context.PageId).Name</td> </Header>
<td>@context.Title</td> <Row>
<td>@context.DeletedBy</td> <td><button type="button" @onclick="@(() => RestoreModule(context))" class="btn btn-success" title="Restore">@Localizer["Restore"]</button></td>
<td>@context.DeletedOn</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>
</Row> <td>@_pages.Find(item => item.PageId == context.PageId).Name</td>
</Pager> <td>@context.Title</td>
@if (_modules.Any()) <td>@context.DeletedBy</td>
{ <td>@context.DeletedOn</td>
<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" /> </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> </TabPanel>
</TabStrip>
}
@code { @code {
private List<Page> _pages; private List<Page> _pages;
private List<Module> _modules; private List<Module> _modules;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
@ -95,10 +97,7 @@
private async Task Load() private async Task Load()
{ {
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId); _pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
_pages = _pages.Where(item => item.IsDeleted).ToList();
_modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId); _modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
_modules = _modules.Where(item => item.IsDeleted).ToList();
} }
private async Task RestorePage(Page page) private async Task RestorePage(Page page)
@ -141,7 +140,7 @@
try try
{ {
ModuleInstance.ShowProgressIndicator(); ModuleInstance.ShowProgressIndicator();
foreach (Page page in _pages) foreach (Page page in _pages.Where(item => item.IsDeleted))
{ {
await PageService.DeletePageAsync(page.PageId); await PageService.DeletePageAsync(page.PageId);
await logger.LogInformation("Page Permanently Deleted {Page}", page); await logger.LogInformation("Page Permanently Deleted {Page}", page);
@ -184,9 +183,8 @@
try try
{ {
await PageModuleService.DeletePageModuleAsync(module.PageModuleId); 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)) if (!_modules.Exists(item => item.ModuleId == module.ModuleId))
{ {
await ModuleService.DeleteModuleAsync(module.ModuleId); await ModuleService.DeleteModuleAsync(module.ModuleId);
@ -208,12 +206,11 @@
try try
{ {
ModuleInstance.ShowProgressIndicator(); ModuleInstance.ShowProgressIndicator();
foreach (Module module in _modules) foreach (Module module in _modules.Where(item => item.IsDeleted))
{ {
await PageModuleService.DeletePageModuleAsync(module.PageModuleId); 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)) if (!_modules.Exists(item => item.ModuleId == module.ModuleId))
{ {
await ModuleService.DeleteModuleAsync(module.ModuleId); await ModuleService.DeleteModuleAsync(module.ModuleId);

View File

@ -29,7 +29,7 @@ else
<Row> <Row>
<td><ActionLink Action="View" Parameters="@($"name=" + WebUtility.UrlEncode(context.ThemeName))" ResourceKey="ViewTheme" /></td> <td><ActionLink Action="View" Parameters="@($"name=" + WebUtility.UrlEncode(context.ThemeName))" ResourceKey="ViewTheme" /></td>
<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" /> <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" />
} }

View File

@ -286,6 +286,15 @@ else
<input id="emailclaimtype" class="form-control" @bind="@_emailclaimtype" /> <input id="emailclaimtype" class="form-control" @bind="@_emailclaimtype" />
</div> </div>
</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"> <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> <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"> <div class="col-sm-9">
@ -385,6 +394,7 @@ else
private string _redirecturl; private string _redirecturl;
private string _identifierclaimtype; private string _identifierclaimtype;
private string _emailclaimtype; private string _emailclaimtype;
private string _roleclaimtype;
private string _domainfilter; private string _domainfilter;
private string _createusers; private string _createusers;
@ -436,8 +446,9 @@ else
_parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", ""); _parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", "");
_pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false"); _pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false");
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype; _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"); _identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "sub");
_emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"); _emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "email");
_roleclaimtype = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimType", "");
_domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", ""); _domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", "");
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true"); _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:PKCE", _pkce, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:IdentifierClaimType", _identifierclaimtype, true); settings = SettingService.SetSetting(settings, "ExternalLogin:IdentifierClaimType", _identifierclaimtype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:EmailClaimType", _emailclaimtype, 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:DomainFilter", _domainfilter, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true); settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true);
@ -590,14 +602,10 @@ else
if (_providertype == AuthenticationProviderTypes.OpenIDConnect) if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
{ {
_scopes = "openid,profile,email"; _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 else
{ {
_scopes = ""; _scopes = "";
_identifierclaimtype = "sub";
_emailclaimtype = "email";
} }
} }
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype; _redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;

View File

@ -70,6 +70,9 @@
} }
} }
</tbody> </tbody>
<tfoot>
<tr class="@RowClass">@Footer</tr>
</tfoot>
</table> </table>
</div> </div>
} }
@ -185,6 +188,9 @@
[Parameter] [Parameter]
public RenderFragment<TableItem> Row { get; set; } = null; // required public RenderFragment<TableItem> Row { get; set; } = null; // required
[Parameter]
public RenderFragment Footer { get; set; } = null; // only applicable to Table layouts
[Parameter] [Parameter]
public RenderFragment<TableItem> Detail { get; set; } = null; // only applicable to Table layouts public RenderFragment<TableItem> Detail { get; set; } = null; // only applicable to Table layouts
@ -293,6 +299,7 @@
{ {
_page = 1; _page = 1;
} }
if (_page < 1) _page = 1;
_startPage = 0; _startPage = 0;
_endPage = 0; _endPage = 0;

View File

@ -262,7 +262,7 @@
{ {
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
int pos = await interop.GetCaretPosition("rawhtmleditor"); 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); _rawhtml = _rawhtml.Substring(0, pos) + image + _rawhtml.Substring(pos);
_rawfilemanager = false; _rawfilemanager = false;
} }

View File

@ -75,7 +75,7 @@ namespace Oqtane.Modules
var scripts = new List<object>(); var scripts = new List<object>();
foreach (Resource resource in Resources.Where(item => item.ResourceType == ResourceType.Script)) 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 }); scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module });
} }
if (scripts.Any()) if (scripts.Any())
@ -91,7 +91,7 @@ namespace Oqtane.Modules
public string ModulePath() public string ModulePath()
{ {
return "Modules/" + GetType().Namespace + "/"; return PageState?.Alias.BaseUrl + "/Modules/" + GetType().Namespace + "/";
} }
// url methods // url methods
@ -145,14 +145,23 @@ namespace Oqtane.Modules
return Utilities.EditUrl(PageState.Alias.Path, path, moduleid, action, parameters); 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) 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); 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);
}
} }
} }

View File

@ -5,7 +5,7 @@
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<RazorLangVersion>3.0</RazorLangVersion> <RazorLangVersion>3.0</RazorLangVersion>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Version>3.2.0</Version> <Version>3.2.1</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -13,7 +13,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <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> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace> <RootNamespace>Oqtane</RootNamespace>

View File

@ -63,7 +63,7 @@ namespace Oqtane.Client
{ {
var dlls = new Dictionary<string, byte[]>(); var dlls = new Dictionary<string, byte[]>();
var pdbs = 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 jsRuntime = serviceProvider.GetRequiredService<IJSRuntime>();
var interop = new Interop(jsRuntime); var interop = new Interop(jsRuntime);
@ -81,14 +81,14 @@ namespace Oqtane.Client
var file = files.FirstOrDefault(item => item.Contains(assembly)); var file = files.FirstOrDefault(item => item.Contains(assembly));
if (file == null) if (file == null)
{ {
filter.Add(assembly); list.Add(assembly);
} }
else else
{ {
// check if newer version available // check if newer version available
if (GetFileDate(assembly) > GetFileDate(file)) if (GetFileDate(assembly) > GetFileDate(file))
{ {
filter.Add(assembly); list.Add(assembly);
} }
} }
} }
@ -96,7 +96,7 @@ namespace Oqtane.Client
// get assemblies already downloaded // get assemblies already downloaded
foreach (var file in files) foreach (var file in files)
{ {
if (assemblies.Contains(file) && !filter.Contains(file)) if (assemblies.Contains(file) && !list.Contains(file))
{ {
try try
{ {
@ -117,6 +117,7 @@ namespace Oqtane.Client
try try
{ {
await interop.RemoveIndexedDBItem(file); await interop.RemoveIndexedDBItem(file);
await interop.RemoveIndexedDBItem(file.Replace(".dll", ".pdb"));
} }
catch catch
{ {
@ -127,13 +128,13 @@ namespace Oqtane.Client
} }
else else
{ {
filter.Add("*"); list.Add("*");
} }
if (filter.Count != 0) if (list.Count != 0)
{ {
// get assemblies from server and load into client app domain // 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 // asemblies and debug symbols are packaged in a zip file
using (ZipArchive archive = new ZipArchive(new MemoryStream(zip))) using (ZipArchive archive = new ZipArchive(new MemoryStream(zip)))

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!-- <!--
Microsoft ResX Schema Microsoft ResX Schema
@ -166,16 +166,22 @@
<value>Error Permanently Deleting Modules</value> <value>Error Permanently Deleting Modules</value>
</data> </data>
<data name="DeleteAllPages.Header" xml:space="preserve"> <data name="DeleteAllPages.Header" xml:space="preserve">
<value>Delete All Pages</value> <value>Remove All Deleted Pages</value>
</data> </data>
<data name="DeleteAllPages.Message" xml:space="preserve"> <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>
<data name="DeleteAllModules.Header" xml:space="preserve"> <data name="DeleteAllModules.Header" xml:space="preserve">
<value>Delete All Modules</value> <value>Remove All Deleted Modules</value>
</data> </data>
<data name="DeleteAllModules.Message" xml:space="preserve"> <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>
<data name="Pages.Heading" xml:space="preserve"> <data name="Pages.Heading" xml:space="preserve">
<value>Pages</value> <value>Pages</value>

View File

@ -211,25 +211,25 @@
<value>Allow Login?</value> <value>Allow Login?</value>
</data> </data>
<data name="Authority.HelpText" xml:space="preserve"> <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>
<data name="Authority.Text" xml:space="preserve"> <data name="Authority.Text" xml:space="preserve">
<value>Authority:</value> <value>Authority:</value>
</data> </data>
<data name="AuthorizationUrl.HelpText" xml:space="preserve"> <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>
<data name="AuthorizationUrl.Text" xml:space="preserve"> <data name="AuthorizationUrl.Text" xml:space="preserve">
<value>Authorization Url:</value> <value>Authorization Url:</value>
</data> </data>
<data name="ClientID.HelpText" xml:space="preserve"> <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>
<data name="ClientID.Text" xml:space="preserve"> <data name="ClientID.Text" xml:space="preserve">
<value>Client ID:</value> <value>Client ID:</value>
</data> </data>
<data name="ClientSecret.HelpText" xml:space="preserve"> <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>
<data name="ClientSecret.Text" xml:space="preserve"> <data name="ClientSecret.Text" xml:space="preserve">
<value>Client Secret:</value> <value>Client Secret:</value>
@ -247,7 +247,7 @@
<value>Domain Filter:</value> <value>Domain Filter:</value>
</data> </data>
<data name="EmailClaimType.HelpText" xml:space="preserve"> <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>
<data name="EmailClaimType.Text" xml:space="preserve"> <data name="EmailClaimType.Text" xml:space="preserve">
<value>Email Claim:</value> <value>Email Claim:</value>
@ -259,7 +259,7 @@
<value>Lockout Settings</value> <value>Lockout Settings</value>
</data> </data>
<data name="MetadataUrl.HelpText" xml:space="preserve"> <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>
<data name="MetadataUrl.Text" xml:space="preserve"> <data name="MetadataUrl.Text" xml:space="preserve">
<value>Metadata Url:</value> <value>Metadata Url:</value>
@ -268,7 +268,7 @@
<value>Password Settings</value> <value>Password Settings</value>
</data> </data>
<data name="PKCE.HelpText" xml:space="preserve"> <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>
<data name="PKCE.Text" xml:space="preserve"> <data name="PKCE.Text" xml:space="preserve">
<value>Use PKCE?</value> <value>Use PKCE?</value>
@ -286,25 +286,25 @@
<value>Provider Type:</value> <value>Provider Type:</value>
</data> </data>
<data name="RedirectUrl.HelpText" xml:space="preserve"> <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>
<data name="RedirectUrl.Text" xml:space="preserve"> <data name="RedirectUrl.Text" xml:space="preserve">
<value>Redirect Url:</value> <value>Redirect Url:</value>
</data> </data>
<data name="Scopes.HelpText" xml:space="preserve"> <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>
<data name="Scopes.Text" xml:space="preserve"> <data name="Scopes.Text" xml:space="preserve">
<value>Scopes:</value> <value>Scopes:</value>
</data> </data>
<data name="TokenUrl.HelpText" xml:space="preserve"> <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>
<data name="TokenUrl.Text" xml:space="preserve"> <data name="TokenUrl.Text" xml:space="preserve">
<value>Token Url:</value> <value>Token Url:</value>
</data> </data>
<data name="UserInfoUrl.HelpText" xml:space="preserve"> <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>
<data name="UserInfoUrl.Text" xml:space="preserve"> <data name="UserInfoUrl.Text" xml:space="preserve">
<value>User Info Url:</value> <value>User Info Url:</value>
@ -373,15 +373,21 @@
<value>Last Login</value> <value>Last Login</value>
</data> </data>
<data name="IdentifierClaimType.HelpText" xml:space="preserve"> <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>
<data name="IdentifierClaimType.Text" xml:space="preserve"> <data name="IdentifierClaimType.Text" xml:space="preserve">
<value>Identifier Claim:</value> <value>Identifier Claim:</value>
</data> </data>
<data name="Parameters.HelpText" xml:space="preserve"> <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>
<data name="Parameters.Text" xml:space="preserve"> <data name="Parameters.Text" xml:space="preserve">
<value>Parameters:</value> <value>Parameters:</value>
</data> </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> </root>

View File

@ -3,6 +3,7 @@ using Microsoft.JSInterop;
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Shared; using Oqtane.Shared;
using Oqtane.UI; using Oqtane.UI;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -34,7 +35,8 @@ namespace Oqtane.Themes
var scripts = new List<object>(); var scripts = new List<object>();
foreach (Resource resource in Resources.Where(item => item.ResourceType == ResourceType.Script)) 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()) if (scripts.Any())
{ {
@ -49,7 +51,7 @@ namespace Oqtane.Themes
public string ThemePath() public string ThemePath()
{ {
return "Themes/" + GetType().Namespace + "/"; return PageState?.Alias.BaseUrl + "/Themes/" + GetType().Namespace + "/";
} }
// url methods // url methods
@ -94,14 +96,23 @@ namespace Oqtane.Themes
return Utilities.EditUrl(PageState.Alias.Path, path, moduleid, action, parameters); 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) 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); 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);
}
} }
} }

View File

@ -1,9 +0,0 @@
namespace Oqtane.UI
{
public enum Refresh
{
None,
Site,
Application
}
}

View File

@ -79,7 +79,7 @@
Page page; Page page;
User user = null; User user = null;
var editmode = false; var editmode = false;
var refresh = UI.Refresh.None; var refresh = false;
var lastsyncdate = DateTime.UtcNow.AddHours(-1); var lastsyncdate = DateTime.UtcNow.AddHours(-1);
var runtime = (Shared.Runtime)Enum.Parse(typeof(Shared.Runtime), Runtime); var runtime = (Shared.Runtime)Enum.Parse(typeof(Shared.Runtime), Runtime);
_error = ""; _error = "";
@ -116,7 +116,7 @@
// the refresh parameter is used to refresh the client-side PageState // the refresh parameter is used to refresh the client-side PageState
if (querystring.ContainsKey("refresh")) if (querystring.ContainsKey("refresh"))
{ {
refresh = UI.Refresh.Site; refresh = true;
} }
if (PageState != null) if (PageState != null)
@ -126,7 +126,7 @@
} }
// get user // 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(); var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
if (authState.User.Identity.IsAuthenticated) if (authState.User.Identity.IsAuthenticated)
@ -149,27 +149,27 @@
if (sync.SyncEvents.Any()) if (sync.SyncEvents.Any())
{ {
// reload client application if server was restarted or site runtime/rendermode was modified // 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); NavigationManager.NavigateTo(_absoluteUri, true);
return; 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)) 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)) 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); site = await SiteService.GetSiteAsync(SiteState.Alias.SiteId);
refresh = UI.Refresh.Site; refresh = true;
} }
else else
{ {
@ -178,7 +178,7 @@
if (site != null) 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)); page = site.Pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase));
editmode = false; editmode = false;

View File

@ -36,7 +36,7 @@
foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet)) foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet))
{ {
var prefix = "app-stylesheet-" + resource.Level.ToString().ToLower(); 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 }); 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()) if (links.Any())

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Version>3.2.0</Version> <Version>3.2.1</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <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> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Database.MySQL</id> <id>Oqtane.Database.MySQL</id>
<version>3.2.0</version> <version>3.2.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane MySQL Provider</title> <title>Oqtane MySQL Provider</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <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> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Version>3.2.0</Version> <Version>3.2.1</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <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> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Database.PostgreSQL</id> <id>Oqtane.Database.PostgreSQL</id>
<version>3.2.0</version> <version>3.2.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane PostgreSQL Provider</title> <title>Oqtane PostgreSQL Provider</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <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> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Version>3.2.0</Version> <Version>3.2.1</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <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> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Database.SqlServer</id> <id>Oqtane.Database.SqlServer</id>
<version>3.2.0</version> <version>3.2.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane SQL Server Provider</title> <title>Oqtane SQL Server Provider</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <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> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Version>3.2.0</Version> <Version>3.2.1</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <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> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Database.Sqlite</id> <id>Oqtane.Database.Sqlite</id>
<version>3.2.0</version> <version>3.2.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane SQLite Provider</title> <title>Oqtane SQLite Provider</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <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> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>

View File

@ -12,7 +12,8 @@ namespace Oqtane.Maui;
public static class MauiProgram public static class MauiProgram
{ {
// the API service url // 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() public static MauiApp CreateMauiApp()
{ {
@ -72,7 +73,7 @@ public static class MauiProgram
var dlls = new Dictionary<string, byte[]>(); var dlls = new Dictionary<string, byte[]>();
var pdbs = 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>(); var files = new List<string>();
foreach (var file in Directory.EnumerateFiles(folder, "*.dll", SearchOption.AllDirectories)) 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)); var file = files.FirstOrDefault(item => item.Contains(assembly));
if (file == null) if (file == null)
{ {
filter.Add(assembly); list.Add(assembly);
} }
else else
{ {
// check if newer version available // check if newer version available
if (GetFileDate(assembly) > GetFileDate(file)) if (GetFileDate(assembly) > GetFileDate(file))
{ {
filter.Add(assembly); list.Add(assembly);
} }
} }
} }
@ -107,7 +108,7 @@ public static class MauiProgram
// get assemblies already downloaded // get assemblies already downloaded
foreach (var file in files) foreach (var file in files)
{ {
if (assemblies.Contains(file) && !filter.Contains(file)) if (assemblies.Contains(file) && !list.Contains(file))
{ {
try try
{ {
@ -127,7 +128,10 @@ public static class MauiProgram
{ {
try try
{ {
File.Delete(Path.Combine(folder, file)); foreach (var path in Directory.EnumerateFiles(folder, Path.GetFileNameWithoutExtension(file) + ".*"))
{
File.Delete(path);
}
} }
catch catch
{ {
@ -138,13 +142,13 @@ public static class MauiProgram
} }
else else
{ {
filter.Add("*"); list.Add("*");
} }
if (filter.Count != 0) if (list.Count != 0)
{ {
// get assemblies from server // 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 // asemblies and debug symbols are packaged in a zip file
using (ZipArchive archive = new ZipArchive(new MemoryStream(zip))) using (ZipArchive archive = new ZipArchive(new MemoryStream(zip)))
@ -159,11 +163,6 @@ public static class MauiProgram
// save assembly to local folder // save assembly to local folder
try 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)); using var stream = File.Create(Path.Combine(folder, entry.FullName));
stream.Write(file, 0, file.Length); stream.Write(file, 0, file.Length);
} }

View File

@ -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 --> <!-- 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> --> <!-- <TargetFrameworks>$(TargetFrameworks);net6.0-tizen</TargetFrameworks> -->
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<Version>3.2.0</Version> <Version>3.2.1</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -14,7 +14,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <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> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane.Maui</RootNamespace> <RootNamespace>Oqtane.Maui</RootNamespace>
@ -31,7 +31,7 @@
<ApplicationIdGuid>0E29FC31-1B83-48ED-B6E0-9F3C67B775D4</ApplicationIdGuid> <ApplicationIdGuid>0E29FC31-1B83-48ED-B6E0-9F3C67B775D4</ApplicationIdGuid>
<!-- Versions --> <!-- Versions -->
<ApplicationDisplayVersion>3.2.0</ApplicationDisplayVersion> <ApplicationDisplayVersion>3.2.1</ApplicationDisplayVersion>
<ApplicationVersion>1</ApplicationVersion> <ApplicationVersion>1</ApplicationVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion> <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Client</id> <id>Oqtane.Client</id>
<version>3.2.0</version> <version>3.2.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <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> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Framework</id> <id>Oqtane.Framework</id>
<version>3.2.0</version> <version>3.2.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>
@ -11,8 +11,8 @@
<copyright>.NET Foundation</copyright> <copyright>.NET Foundation</copyright>
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <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> <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.0</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane framework</tags> <tags>oqtane framework</tags>
</metadata> </metadata>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Server</id> <id>Oqtane.Server</id>
<version>3.2.0</version> <version>3.2.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <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> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Shared</id> <id>Oqtane.Shared</id>
<version>3.2.0</version> <version>3.2.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <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> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Updater</id> <id>Oqtane.Updater</id>
<version>3.2.0</version> <version>3.2.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <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> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>

View File

@ -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

View File

@ -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

View File

@ -17,13 +17,15 @@ namespace Oqtane.Controllers
{ {
private readonly IAliasRepository _aliases; private readonly IAliasRepository _aliases;
private readonly ITenantRepository _tenants; private readonly ITenantRepository _tenants;
private readonly ISyncManager _syncManager;
private readonly ILogManager _logger; private readonly ILogManager _logger;
private readonly Alias _alias; 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; _aliases = aliases;
_tenants = tenants; _tenants = tenants;
_syncManager = syncManager;
_logger = logger; _logger = logger;
_alias = tenantManager.GetAlias(); _alias = tenantManager.GetAlias();
} }
@ -57,6 +59,7 @@ namespace Oqtane.Controllers
if (ModelState.IsValid) if (ModelState.IsValid)
{ {
alias = _aliases.AddAlias(alias); 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); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Alias Added {Alias}", alias);
} }
else else
@ -76,6 +79,7 @@ namespace Oqtane.Controllers
if (ModelState.IsValid && _aliases.GetAlias(alias.AliasId, false) != null) if (ModelState.IsValid && _aliases.GetAlias(alias.AliasId, false) != null)
{ {
alias = _aliases.UpdateAlias(alias); 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); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Alias Updated {Alias}", alias);
} }
else else
@ -96,6 +100,7 @@ namespace Oqtane.Controllers
if (alias != null) if (alias != null)
{ {
_aliases.DeleteAlias(id); _aliases.DeleteAlias(id);
_syncManager.AddSyncEvent(alias.TenantId, EntityNames.Alias, alias.AliasId, SyncEventActions.Delete);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Alias Deleted {AliasId}", id); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Alias Deleted {AliasId}", id);
var aliases = _aliases.GetAliases(); var aliases = _aliases.GetAliases();

View File

@ -32,15 +32,17 @@ namespace Oqtane.Controllers
private readonly IFileRepository _files; private readonly IFileRepository _files;
private readonly IFolderRepository _folders; private readonly IFolderRepository _folders;
private readonly IUserPermissions _userPermissions; private readonly IUserPermissions _userPermissions;
private readonly ISyncManager _syncManager;
private readonly ILogManager _logger; private readonly ILogManager _logger;
private readonly Alias _alias; 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; _environment = environment;
_files = files; _files = files;
_folders = folders; _folders = folders;
_userPermissions = userPermissions; _userPermissions = userPermissions;
_syncManager = syncManager;
_logger = logger; _logger = logger;
_alias = tenantManager.GetAlias(); _alias = tenantManager.GetAlias();
} }
@ -148,6 +150,7 @@ namespace Oqtane.Controllers
file.Extension = Path.GetExtension(file.Name).ToLower().Replace(".", ""); file.Extension = Path.GetExtension(file.Name).ToLower().Replace(".", "");
file = _files.UpdateFile(file); 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); _logger.Log(LogLevel.Information, this, LogFunction.Update, "File Updated {File}", file);
} }
else else
@ -168,8 +171,6 @@ namespace Oqtane.Controllers
Models.File file = _files.GetFile(id); Models.File file = _files.GetFile(id);
if (file != null && file.Folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Folder, file.Folder.FolderId, PermissionNames.Edit)) 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); string filepath = _files.GetFilePath(file);
if (System.IO.File.Exists(filepath)) 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); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "File Deleted {File}", file);
} }
else else
@ -251,6 +254,7 @@ namespace Oqtane.Controllers
if (file != null) if (file != null)
{ {
file = _files.AddFile(file); file = _files.AddFile(file);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.File, file.FileId, SyncEventActions.Create);
} }
} }
catch (Exception ex) catch (Exception ex)
@ -324,7 +328,8 @@ namespace Oqtane.Controllers
var file = CreateFile(upload, FolderId, Path.Combine(folderPath, upload)); var file = CreateFile(upload, FolderId, Path.Combine(folderPath, upload));
if (file != null) 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); var filepath = _files.GetFilePath(file);
if (System.IO.File.Exists(filepath)) if (System.IO.File.Exists(filepath))
{ {
var result = asAttachment if (asAttachment)
? PhysicalFile(filepath, file.GetMimeType(), file.Name) {
: PhysicalFile(filepath, file.GetMimeType()); _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.File, file.FileId, "Download");
return result; return PhysicalFile(filepath, file.GetMimeType(), file.Name);
}
else
{
return PhysicalFile(filepath, file.GetMimeType());
}
} }
else else
{ {

View File

@ -20,13 +20,15 @@ namespace Oqtane.Controllers
{ {
private readonly IFolderRepository _folders; private readonly IFolderRepository _folders;
private readonly IUserPermissions _userPermissions; private readonly IUserPermissions _userPermissions;
private readonly ISyncManager _syncManager;
private readonly ILogManager _logger; private readonly ILogManager _logger;
private readonly Alias _alias; 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; _folders = folders;
_userPermissions = userPermissions; _userPermissions = userPermissions;
_syncManager = syncManager;
_logger = logger; _logger = logger;
_alias = tenantManager.GetAlias(); _alias = tenantManager.GetAlias();
} }
@ -124,6 +126,7 @@ namespace Oqtane.Controllers
} }
folder.Path = folder.Path + "/"; folder.Path = folder.Path + "/";
folder = _folders.AddFolder(folder); 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); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Folder Added {Folder}", folder);
} }
else else
@ -172,6 +175,7 @@ namespace Oqtane.Controllers
} }
folder = _folders.UpdateFolder(folder); 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); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Folder Updated {Folder}", folder);
} }
else else
@ -205,6 +209,7 @@ namespace Oqtane.Controllers
{ {
folder.Order = order; folder.Order = order;
_folders.UpdateFolder(folder); _folders.UpdateFolder(folder);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Folder, folder.FolderId, SyncEventActions.Update);
} }
order += 2; order += 2;
} }
@ -230,6 +235,7 @@ namespace Oqtane.Controllers
Directory.Delete(_folders.GetFolderPath(folder)); Directory.Delete(_folders.GetFolderPath(folder));
} }
_folders.DeleteFolder(id); _folders.DeleteFolder(id);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Folder, folder.FolderId, SyncEventActions.Delete);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Folder Deleted {FolderId}", id); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Folder Deleted {FolderId}", id);
} }
else else

View File

@ -103,10 +103,7 @@ namespace Oqtane.Controllers
[HttpGet("list")] [HttpGet("list")]
public List<string> List() public List<string> List()
{ {
return _cache.GetOrCreate("assemblieslist", entry => return GetAssemblyList().Select(item => item.HashedName).ToList();
{
return GetAssemblyList();
});
} }
// GET api/<controller>/load?list=x,y // 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"); 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 return _cache.GetOrCreate("assemblieslist", entry =>
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++)
{ {
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 // get list of assemblies which should be downloaded to client
foreach (var culture in _localizationManager.GetInstalledCultures()) var assemblies = AppDomain.CurrentDomain.GetOqtaneClientAssemblies();
{ var list = assemblies.Select(a => a.GetName().Name).ToList();
var assembliesFolderPath = Path.Combine(binFolder, culture);
if (culture == Constants.DefaultCulture) // 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 // insert module and theme dependencies at beginning of list
foreach (var assembly in assemblies) foreach (var assembly in assemblies)
{
foreach (var type in assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IModule))))
{ {
var instance = Activator.CreateInstance(type) as IModule; foreach (var type in assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IModule))))
foreach (string name in instance.ModuleDefinition.Dependencies.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{ {
var path = Path.Combine(binFolder, name + ".dll"); var instance = Activator.CreateInstance(type) as IModule;
if (System.IO.File.Exists(path)) foreach (string name in instance.ModuleDefinition.Dependencies.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Reverse())
{ {
path = Path.GetFileName(AddFileDate(path)); var filepath = Path.Combine(binFolder, name + ".dll");
if (!list.Contains(path)) list.Insert(0, path); 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) private byte[] GetAssemblies(string list)
@ -213,14 +219,11 @@ namespace Oqtane.Controllers
var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
// get list of assemblies which should be downloaded to client // get list of assemblies which should be downloaded to client
List<string> assemblies; List<ClientAssembly> assemblies = GetAssemblyList();
if (list == "*") if (list != "*")
{ {
assemblies = GetAssemblyList(); var filter = list.Split(',').ToList();
} assemblies.RemoveAll(item => !filter.Contains(item.HashedName));
else
{
assemblies = list.Split(',').ToList();
} }
// create zip file containing assemblies and debug symbols // create zip file containing assemblies and debug symbols
@ -228,22 +231,21 @@ namespace Oqtane.Controllers
{ {
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true)) 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(assembly.FilePath))
if (System.IO.File.Exists(Path.Combine(binFolder, filename)))
{ {
using (var filestream = new FileStream(Path.Combine(binFolder, filename), FileMode.Open, FileAccess.Read)) using (var filestream = new FileStream(assembly.FilePath, FileMode.Open, FileAccess.Read))
using (var entrystream = archive.CreateEntry(file).Open()) using (var entrystream = archive.CreateEntry(assembly.HashedName).Open())
{ {
filestream.CopyTo(entrystream); filestream.CopyTo(entrystream);
} }
} }
filename = filename.Replace(".dll", ".pdb"); var pdb = assembly.FilePath.Replace(".dll", ".pdb");
if (System.IO.File.Exists(Path.Combine(binFolder, filename))) if (System.IO.File.Exists(pdb))
{ {
using (var filestream = new FileStream(Path.Combine(binFolder, filename), FileMode.Open, FileAccess.Read)) using (var filestream = new FileStream(pdb, FileMode.Open, FileAccess.Read))
using (var entrystream = archive.CreateEntry(file.Replace(".dll", ".pdb")).Open()) using (var entrystream = archive.CreateEntry(assembly.HashedName.Replace(".dll", ".pdb")).Open())
{ {
filestream.CopyTo(entrystream); 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) private async Task RegisterContact(string email)
{ {
try try
@ -292,5 +282,38 @@ namespace Oqtane.Controllers
{ {
await RegisterContact(email); 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);
}
}
} }
} }

View File

@ -42,10 +42,13 @@ namespace Oqtane.Controllers
{ {
if (!string.IsNullOrEmpty(packagename)) 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)); 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(); languages = _languages.GetLanguages(SiteId).ToList();
if (!string.IsNullOrEmpty(packagename)) 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)); var code = Path.GetFileName(Path.GetDirectoryName(file));
if (languages.Any(item => item.Code == code)) if (languages.Any(item => item.Code == code))
@ -99,7 +102,8 @@ namespace Oqtane.Controllers
if (ModelState.IsValid && language.SiteId == _alias.SiteId) if (ModelState.IsValid && language.SiteId == _alias.SiteId)
{ {
language = _languages.AddLanguage(language); 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); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Language Added {Language}", language);
} }
else else
@ -119,7 +123,8 @@ namespace Oqtane.Controllers
if (language != null && language.SiteId == _alias.SiteId) if (language != null && language.SiteId == _alias.SiteId)
{ {
_languages.DeleteLanguage(id); _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); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Language Deleted {LanguageId}", id);
} }
else else

View File

@ -124,7 +124,8 @@ namespace Oqtane.Controllers
if (ModelState.IsValid && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Page, module.PageId, PermissionNames.Edit)) if (ModelState.IsValid && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Page, module.PageId, PermissionNames.Edit))
{ {
module = _modules.AddModule(module); 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); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Module Added {Module}", module);
} }
else 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); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Module Updated {Module}", module);
} }
else else
@ -195,7 +197,8 @@ namespace Oqtane.Controllers
if (module != null && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Module, module.ModuleId, PermissionNames.Edit)) if (module != null && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Module, module.ModuleId, PermissionNames.Edit))
{ {
_modules.DeleteModule(id); _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); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Deleted {ModuleId}", id);
} }
else else

View File

@ -29,10 +29,11 @@ namespace Oqtane.Controllers
private readonly IWebHostEnvironment _environment; private readonly IWebHostEnvironment _environment;
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly ITenantManager _tenantManager; private readonly ITenantManager _tenantManager;
private readonly ISyncManager _syncManager;
private readonly ILogManager _logger; private readonly ILogManager _logger;
private readonly Alias _alias; 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; _moduleDefinitions = moduleDefinitions;
_tenants = tenants; _tenants = tenants;
@ -42,6 +43,7 @@ namespace Oqtane.Controllers
_environment = environment; _environment = environment;
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
_tenantManager = tenantManager; _tenantManager = tenantManager;
_syncManager = syncManager;
_logger = logger; _logger = logger;
_alias = tenantManager.GetAlias(); _alias = tenantManager.GetAlias();
} }
@ -145,6 +147,7 @@ namespace Oqtane.Controllers
if (ModelState.IsValid && moduleDefinition.SiteId == _alias.SiteId && _moduleDefinitions.GetModuleDefinition(moduleDefinition.ModuleDefinitionId, moduleDefinition.SiteId) != null) if (ModelState.IsValid && moduleDefinition.SiteId == _alias.SiteId && _moduleDefinitions.GetModuleDefinition(moduleDefinition.ModuleDefinitionId, moduleDefinition.SiteId) != null)
{ {
_moduleDefinitions.UpdateModuleDefinition(moduleDefinition); _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); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Module Definition Updated {ModuleDefinition}", moduleDefinition);
} }
else else
@ -227,6 +230,7 @@ namespace Oqtane.Controllers
// remove module definition // remove module definition
_moduleDefinitions.DeleteModuleDefinition(id); _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); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Definition {ModuleDefinitionName} Deleted", moduledefinition.Name);
} }
else else

View File

@ -8,6 +8,7 @@ using Oqtane.Infrastructure;
using Oqtane.Repository; using Oqtane.Repository;
using Oqtane.Security; using Oqtane.Security;
using System.Net; using System.Net;
using System.Reflection.Metadata;
namespace Oqtane.Controllers namespace Oqtane.Controllers
{ {
@ -16,13 +17,15 @@ namespace Oqtane.Controllers
{ {
private readonly INotificationRepository _notifications; private readonly INotificationRepository _notifications;
private readonly IUserPermissions _userPermissions; private readonly IUserPermissions _userPermissions;
private readonly ISyncManager _syncManager;
private readonly ILogManager _logger; private readonly ILogManager _logger;
private readonly Alias _alias; 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; _notifications = notifications;
_userPermissions = userPermissions; _userPermissions = userPermissions;
_syncManager = syncManager;
_logger = logger; _logger = logger;
_alias = tenantManager.GetAlias(); _alias = tenantManager.GetAlias();
} }
@ -83,6 +86,7 @@ namespace Oqtane.Controllers
if (ModelState.IsValid && notification.SiteId == _alias.SiteId && IsAuthorized(notification.FromUserId)) if (ModelState.IsValid && notification.SiteId == _alias.SiteId && IsAuthorized(notification.FromUserId))
{ {
notification = _notifications.AddNotification(notification); 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); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Notification Added {NotificationId}", notification.NotificationId);
} }
else else
@ -102,6 +106,7 @@ namespace Oqtane.Controllers
if (ModelState.IsValid && notification.SiteId == _alias.SiteId && _notifications.GetNotification(notification.NotificationId, false) != null && IsAuthorized(notification.FromUserId)) if (ModelState.IsValid && notification.SiteId == _alias.SiteId && _notifications.GetNotification(notification.NotificationId, false) != null && IsAuthorized(notification.FromUserId))
{ {
notification = _notifications.UpdateNotification(notification); 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); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Notification Updated {NotificationId}", notification.NotificationId);
} }
else else
@ -122,6 +127,7 @@ namespace Oqtane.Controllers
if (notification != null && notification.SiteId == _alias.SiteId && (IsAuthorized(notification.FromUserId) || IsAuthorized(notification.ToUserId))) if (notification != null && notification.SiteId == _alias.SiteId && (IsAuthorized(notification.FromUserId) || IsAuthorized(notification.ToUserId)))
{ {
_notifications.DeleteNotification(id); _notifications.DeleteNotification(id);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Notification, notification.NotificationId, SyncEventActions.Delete);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Notification Deleted {NotificationId}", id); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Notification Deleted {NotificationId}", id);
} }
else else

View File

@ -1,7 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Oqtane.Models; using Oqtane.Models;
using Newtonsoft.Json;
using System; using System;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -12,6 +11,7 @@ using Oqtane.Shared;
using Oqtane.Infrastructure; using Oqtane.Infrastructure;
using Oqtane.Enums; using Oqtane.Enums;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Text.Json;
// ReSharper disable PartialTypeWithSinglePart // ReSharper disable PartialTypeWithSinglePart
namespace Oqtane.Controllers namespace Oqtane.Controllers
@ -106,11 +106,7 @@ namespace Oqtane.Controllers
var stream = await response.Content.ReadAsStreamAsync(); var stream = await response.Content.ReadAsStreamAsync();
using (var streamReader = new StreamReader(stream)) using (var streamReader = new StreamReader(stream))
{ {
using (var jsonTextReader = new JsonTextReader(streamReader)) return await JsonSerializer.DeserializeAsync<T>(stream, new JsonSerializerOptions(JsonSerializerDefaults.Web));
{
var serializer = new JsonSerializer();
return serializer.Deserialize<T>(jsonTextReader);
}
} }
} }
return default(T); return default(T);

View File

@ -143,7 +143,8 @@ namespace Oqtane.Controllers
if (_userPermissions.IsAuthorized(User,PermissionNames.Edit, permissions)) if (_userPermissions.IsAuthorized(User,PermissionNames.Edit, permissions))
{ {
page = _pages.AddPage(page); 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); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Page Added {Page}", page);
if (!page.Path.StartsWith("admin/")) if (!page.Path.StartsWith("admin/"))
@ -200,7 +201,8 @@ namespace Oqtane.Controllers
page.IsPersonalizable = false; page.IsPersonalizable = false;
page.UserId = int.Parse(userid); page.UserId = int.Parse(userid);
page = _pages.AddPage(page); 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 // copy modules
List<PageModule> pagemodules = _pageModules.GetPageModules(page.SiteId).ToList(); 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); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Page Updated {Page}", page);
} }
else else
@ -353,10 +356,12 @@ namespace Oqtane.Controllers
{ {
page.Order = order; page.Order = order;
_pages.UpdatePage(page); _pages.UpdatePage(page);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Page, page.PageId, SyncEventActions.Update);
} }
order += 2; 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); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Page Order Updated {SiteId} {PageId} {ParentId}", siteid, pageid, parentid);
} }
else else
@ -375,7 +380,8 @@ namespace Oqtane.Controllers
if (page != null && page.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Page, page.PageId, PermissionNames.Edit)) if (page != null && page.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Page, page.PageId, PermissionNames.Edit))
{ {
_pages.DeletePage(page.PageId); _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); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Page Deleted {PageId}", page.PageId);
} }
else else

View File

@ -9,6 +9,7 @@ using Oqtane.Infrastructure;
using Oqtane.Repository; using Oqtane.Repository;
using Oqtane.Security; using Oqtane.Security;
using System.Net; using System.Net;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Oqtane.Controllers 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)) if (ModelState.IsValid && page != null && page.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Page, pageModule.PageId, PermissionNames.Edit))
{ {
pageModule = _pageModules.AddPageModule(pageModule); 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); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Page Module Added {PageModule}", pageModule);
} }
else 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)) 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); 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); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Page Module Updated {PageModule}", pageModule);
} }
else else
@ -124,10 +127,11 @@ namespace Oqtane.Controllers
{ {
pagemodule.Order = order; pagemodule.Order = order;
_pageModules.UpdatePageModule(pagemodule); _pageModules.UpdatePageModule(pagemodule);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.PageModule, pagemodule.PageModuleId, SyncEventActions.Update);
} }
order += 2; 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); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Page Module Order Updated {PageId} {Pane}", pageid, pane);
} }
else 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)) if (pagemodule != null && pagemodule.Module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Page, pagemodule.PageId, PermissionNames.Edit))
{ {
_pageModules.DeletePageModule(id); _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); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Page Module Deleted {PageModuleId}", id);
} }
else else

View File

@ -14,12 +14,14 @@ namespace Oqtane.Controllers
public class ProfileController : Controller public class ProfileController : Controller
{ {
private readonly IProfileRepository _profiles; private readonly IProfileRepository _profiles;
private readonly ISyncManager _syncManager;
private readonly ILogManager _logger; private readonly ILogManager _logger;
private readonly Alias _alias; private readonly Alias _alias;
public ProfileController(IProfileRepository profiles, ILogManager logger, ITenantManager tenantManager) public ProfileController(IProfileRepository profiles, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager)
{ {
_profiles = profiles; _profiles = profiles;
_syncManager = syncManager;
_logger = logger; _logger = logger;
_alias = tenantManager.GetAlias(); _alias = tenantManager.GetAlias();
} }
@ -66,6 +68,7 @@ namespace Oqtane.Controllers
if (ModelState.IsValid && profile.SiteId == _alias.SiteId) if (ModelState.IsValid && profile.SiteId == _alias.SiteId)
{ {
profile = _profiles.AddProfile(profile); 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); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Profile Added {Profile}", profile);
} }
else else
@ -85,6 +88,7 @@ namespace Oqtane.Controllers
if (ModelState.IsValid && profile.SiteId == _alias.SiteId && _profiles.GetProfile(profile.ProfileId, false) != null) if (ModelState.IsValid && profile.SiteId == _alias.SiteId && _profiles.GetProfile(profile.ProfileId, false) != null)
{ {
profile = _profiles.UpdateProfile(profile); 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); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Profile Updated {Profile}", profile);
} }
else else
@ -105,6 +109,7 @@ namespace Oqtane.Controllers
if (profile != null && profile.SiteId == _alias.SiteId) if (profile != null && profile.SiteId == _alias.SiteId)
{ {
_profiles.DeleteProfile(id); _profiles.DeleteProfile(id);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Profile, profile.ProfileId, SyncEventActions.Delete);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Profile Deleted {ProfileId}", id); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Profile Deleted {ProfileId}", id);
} }
else else

View File

@ -14,12 +14,14 @@ namespace Oqtane.Controllers
public class RoleController : Controller public class RoleController : Controller
{ {
private readonly IRoleRepository _roles; private readonly IRoleRepository _roles;
private readonly ISyncManager _syncManager;
private readonly ILogManager _logger; private readonly ILogManager _logger;
private readonly Alias _alias; private readonly Alias _alias;
public RoleController(IRoleRepository roles, ILogManager logger, ITenantManager tenantManager) public RoleController(IRoleRepository roles, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager)
{ {
_roles = roles; _roles = roles;
_syncManager = syncManager;
_logger = logger; _logger = logger;
_alias = tenantManager.GetAlias(); _alias = tenantManager.GetAlias();
} }
@ -72,6 +74,7 @@ namespace Oqtane.Controllers
if (ModelState.IsValid && role.SiteId == _alias.SiteId) if (ModelState.IsValid && role.SiteId == _alias.SiteId)
{ {
role = _roles.AddRole(role); 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); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Role Added {Role}", role);
} }
else else
@ -91,6 +94,7 @@ namespace Oqtane.Controllers
if (ModelState.IsValid && role.SiteId == _alias.SiteId && _roles.GetRole(role.RoleId, false) != null) if (ModelState.IsValid && role.SiteId == _alias.SiteId && _roles.GetRole(role.RoleId, false) != null)
{ {
role = _roles.UpdateRole(role); 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); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Role Updated {Role}", role);
} }
else else
@ -111,6 +115,7 @@ namespace Oqtane.Controllers
if (role != null && !role.IsSystem && role.SiteId == _alias.SiteId) if (role != null && !role.IsSystem && role.SiteId == _alias.SiteId)
{ {
_roles.DeleteRole(id); _roles.DeleteRole(id);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Role, role.RoleId, SyncEventActions.Delete);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Role Deleted {RoleId}", id); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Role Deleted {RoleId}", id);
} }
else else

View File

@ -98,7 +98,7 @@ namespace Oqtane.Controllers
if (ModelState.IsValid && IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.Edit)) if (ModelState.IsValid && IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.Edit))
{ {
setting = _settings.AddSetting(setting); 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); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Setting Added {Setting}", setting);
} }
else else
@ -117,7 +117,7 @@ namespace Oqtane.Controllers
if (ModelState.IsValid && IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.Edit)) if (ModelState.IsValid && IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.Edit))
{ {
setting = _settings.UpdateSetting(setting); 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); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Setting Updated {Setting}", setting);
} }
else else
@ -137,7 +137,7 @@ namespace Oqtane.Controllers
if (IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.Edit)) if (IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.Edit))
{ {
_settings.DeleteSetting(setting.EntityName, id); _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); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Setting Deleted {Setting}", setting);
} }
else else
@ -277,14 +277,16 @@ namespace Oqtane.Controllers
return filter; 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) switch (EntityName)
{ {
case EntityNames.Module: case EntityNames.Module:
case EntityNames.Page: case EntityNames.Page:
case EntityNames.Site: case EntityNames.Site:
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId); _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh);
break; break;
} }
} }

View File

@ -160,6 +160,7 @@ namespace Oqtane.Controllers
if (ModelState.IsValid) if (ModelState.IsValid)
{ {
site = _sites.AddSite(site); 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); _logger.Log(site.SiteId, LogLevel.Information, this, LogFunction.Create, "Site Added {Site}", site);
} }
else else
@ -180,12 +181,13 @@ namespace Oqtane.Controllers
if (ModelState.IsValid && site.SiteId == _alias.SiteId && site.TenantId == _alias.TenantId && current != null) if (ModelState.IsValid && site.SiteId == _alias.SiteId && site.TenantId == _alias.TenantId && current != null)
{ {
site = _sites.UpdateSite(site); 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) 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); _logger.Log(site.SiteId, LogLevel.Information, this, LogFunction.Update, "Site Updated {Site}", site);
} }
else else
@ -206,6 +208,7 @@ namespace Oqtane.Controllers
if (site != null && site.SiteId == _alias.SiteId) if (site != null && site.SiteId == _alias.SiteId)
{ {
_sites.DeleteSite(id); _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); _logger.Log(id, LogLevel.Information, this, LogFunction.Delete, "Site Deleted {SiteId}", id);
} }
else else

View File

@ -2,9 +2,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Oqtane.Models; using Oqtane.Models;
using System.Collections.Generic; using System.Collections.Generic;
using Oqtane.Enums;
using Oqtane.Shared; using Oqtane.Shared;
using Oqtane.Infrastructure;
using Oqtane.Repository; using Oqtane.Repository;
namespace Oqtane.Controllers namespace Oqtane.Controllers
@ -13,12 +11,10 @@ namespace Oqtane.Controllers
public class TenantController : Controller public class TenantController : Controller
{ {
private readonly ITenantRepository _tenants; private readonly ITenantRepository _tenants;
private readonly ILogManager _logger;
public TenantController(ITenantRepository tenants, ILogManager logger) public TenantController(ITenantRepository tenants)
{ {
_tenants = tenants; _tenants = tenants;
_logger = logger;
} }
// GET: api/<controller> // GET: api/<controller>

View File

@ -56,7 +56,7 @@ namespace Oqtane.Controllers
{ {
List<Theme> themes = _themes.GetThemes().ToList(); List<Theme> themes = _themes.GetThemes().ToList();
Theme theme = themes.Where(item => item.ThemeName == themename).FirstOrDefault(); Theme theme = themes.Where(item => item.ThemeName == themename).FirstOrDefault();
if (theme != null && Utilities.GetAssemblyName(theme.ThemeName) != "Oqtane.Client") if (theme != null && Utilities.GetAssemblyName(theme.ThemeName) != Constants.ClientId)
{ {
// remove theme assets // remove theme assets
if (_installationManager.UninstallPackage(theme.PackageName)) if (_installationManager.UninstallPackage(theme.PackageName))

View File

@ -14,12 +14,14 @@ namespace Oqtane.Controllers
public class UrlMappingController : Controller public class UrlMappingController : Controller
{ {
private readonly IUrlMappingRepository _urlMappings; private readonly IUrlMappingRepository _urlMappings;
private readonly ISyncManager _syncManager;
private readonly ILogManager _logger; private readonly ILogManager _logger;
private readonly Alias _alias; private readonly Alias _alias;
public UrlMappingController(IUrlMappingRepository urlMappings, ILogManager logger, ITenantManager tenantManager) public UrlMappingController(IUrlMappingRepository urlMappings, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager)
{ {
_urlMappings = urlMappings; _urlMappings = urlMappings;
_syncManager = syncManager;
_logger = logger; _logger = logger;
_alias = tenantManager.GetAlias(); _alias = tenantManager.GetAlias();
} }
@ -85,6 +87,7 @@ namespace Oqtane.Controllers
if (ModelState.IsValid && urlMapping.SiteId == _alias.SiteId) if (ModelState.IsValid && urlMapping.SiteId == _alias.SiteId)
{ {
urlMapping = _urlMappings.AddUrlMapping(urlMapping); 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); _logger.Log(LogLevel.Information, this, LogFunction.Create, "UrlMapping Added {UrlMapping}", urlMapping);
} }
else else
@ -104,6 +107,7 @@ namespace Oqtane.Controllers
if (ModelState.IsValid && urlMapping.SiteId == _alias.SiteId && _urlMappings.GetUrlMapping(urlMapping.UrlMappingId, false) != null) if (ModelState.IsValid && urlMapping.SiteId == _alias.SiteId && _urlMappings.GetUrlMapping(urlMapping.UrlMappingId, false) != null)
{ {
urlMapping = _urlMappings.UpdateUrlMapping(urlMapping); 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); _logger.Log(LogLevel.Information, this, LogFunction.Update, "UrlMapping Updated {UrlMapping}", urlMapping);
} }
else else
@ -124,6 +128,7 @@ namespace Oqtane.Controllers
if (urlMapping != null && urlMapping.SiteId == _alias.SiteId) if (urlMapping != null && urlMapping.SiteId == _alias.SiteId)
{ {
_urlMappings.DeleteUrlMapping(id); _urlMappings.DeleteUrlMapping(id);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.UrlMapping, urlMapping.UrlMappingId, SyncEventActions.Delete);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "UrlMapping Deleted {UrlMappingId}", id); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "UrlMapping Deleted {UrlMappingId}", id);
} }
else else

View File

@ -23,7 +23,6 @@ namespace Oqtane.Controllers
public class UserController : Controller public class UserController : Controller
{ {
private readonly IUserRepository _users; private readonly IUserRepository _users;
private readonly IRoleRepository _roles;
private readonly IUserRoleRepository _userRoles; private readonly IUserRoleRepository _userRoles;
private readonly UserManager<IdentityUser> _identityUserManager; private readonly UserManager<IdentityUser> _identityUserManager;
private readonly SignInManager<IdentityUser> _identitySignInManager; private readonly SignInManager<IdentityUser> _identitySignInManager;
@ -35,10 +34,9 @@ namespace Oqtane.Controllers
private readonly IJwtManager _jwtManager; private readonly IJwtManager _jwtManager;
private readonly ILogManager _logger; 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; _users = users;
_roles = roles;
_userRoles = userRoles; _userRoles = userRoles;
_identityUserManager = identityUserManager; _identityUserManager = identityUserManager;
_identitySignInManager = identitySignInManager; _identitySignInManager = identitySignInManager;
@ -165,6 +163,7 @@ namespace Oqtane.Controllers
if (allowregistration) if (allowregistration)
{ {
bool succeeded; bool succeeded;
string errors = "";
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username); IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username);
if (identityuser == null) if (identityuser == null)
{ {
@ -174,12 +173,20 @@ namespace Oqtane.Controllers
identityuser.EmailConfirmed = verified; identityuser.EmailConfirmed = verified;
var result = await _identityUserManager.CreateAsync(identityuser, user.Password); var result = await _identityUserManager.CreateAsync(identityuser, user.Password);
succeeded = result.Succeeded; succeeded = result.Succeeded;
if (!succeeded)
{
errors = string.Join(", ", result.Errors.Select(e => e.Description));
}
} }
else else
{ {
var result = await _identitySignInManager.CheckPasswordSignInAsync(identityuser, user.Password, false); var result = await _identitySignInManager.CheckPasswordSignInAsync(identityuser, user.Password, false);
succeeded = result.Succeeded; succeeded = result.Succeeded;
verified = true; if (!succeeded)
{
errors = "Password Not Valid For User";
}
verified = succeeded;
} }
if (succeeded) if (succeeded)
@ -187,6 +194,11 @@ namespace Oqtane.Controllers
user.LastLoginOn = null; user.LastLoginOn = null;
user.LastIPAddress = ""; user.LastIPAddress = "";
newUser = _users.AddUser(user); 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) if (newUser != null)
@ -242,7 +254,8 @@ namespace Oqtane.Controllers
await _identityUserManager.UpdateAsync(identityuser); await _identityUserManager.UpdateAsync(identityuser);
} }
user = _users.UpdateUser(user); 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 user.Password = ""; // remove sensitive information
_logger.Log(LogLevel.Information, this, LogFunction.Update, "User Updated {User}", user); _logger.Log(LogLevel.Information, this, LogFunction.Update, "User Updated {User}", user);
} }
@ -297,6 +310,7 @@ namespace Oqtane.Controllers
{ {
// delete user // delete user
_users.DeleteUser(user.UserId); _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); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "User Deleted {UserId}", user.UserId);
} }
else else

View File

@ -122,9 +122,10 @@ namespace Oqtane.Controllers
if (ModelState.IsValid && role != null && SiteValid(role.SiteId) && RoleValid(role.Name)) if (ModelState.IsValid && role != null && SiteValid(role.SiteId) && RoleValid(role.Name))
{ {
userRole = _userRoles.AddUserRole(userRole); 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); _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 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) if (ModelState.IsValid && role != null && SiteValid(role.SiteId) && RoleValid(role.Name) && _userRoles.GetUserRole(userRole.UserRoleId, false) != null)
{ {
userRole = _userRoles.UpdateUserRole(userRole); 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); _logger.Log(LogLevel.Information, this, LogFunction.Update, "User Role Updated {UserRole}", userRole);
} }
else else
@ -165,6 +167,7 @@ namespace Oqtane.Controllers
if (userrole != null && SiteValid(userrole.Role.SiteId) && RoleValid(userrole.Role.Name)) if (userrole != null && SiteValid(userrole.Role.SiteId) && RoleValid(userrole.Role.Name))
{ {
_userRoles.DeleteUserRole(id); _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); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "User Role Deleted {UserRole}", userrole);
if (userrole.Role.Name == RoleNames.Host) 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); _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 else
{ {

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IdentityModel.Tokens.Jwt;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
@ -157,6 +158,9 @@ namespace Microsoft.Extensions.DependencyInjection
public static IServiceCollection ConfigureOqtaneAuthenticationOptions(this IServiceCollection services, IConfigurationRoot Configuration) public static IServiceCollection ConfigureOqtaneAuthenticationOptions(this IServiceCollection services, IConfigurationRoot Configuration)
{ {
// prevent remapping of claims
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
// settings defined in appsettings // settings defined in appsettings
services.Configure<OAuthOptions>(Configuration); services.Configure<OAuthOptions>(Configuration);
services.Configure<OpenIdConnectOptions>(Configuration); services.Configure<OpenIdConnectOptions>(Configuration);
@ -276,7 +280,7 @@ namespace Microsoft.Extensions.DependencyInjection
var serviceTypes = assembly.GetTypes(hostedServiceType); var serviceTypes = assembly.GetTypes(hostedServiceType);
foreach (var serviceType in serviceTypes) foreach (var serviceType in serviceTypes)
{ {
if (serviceType.IsSubclassOf(typeof(HostedServiceBase))) if (!services.Any(item => item.ServiceType == serviceType))
{ {
services.AddSingleton(hostedServiceType, serviceType); services.AddSingleton(hostedServiceType, serviceType);
} }

View File

@ -56,6 +56,10 @@ namespace Oqtane.Extensions
options.ClientId = sitesettings.GetValue("ExternalLogin:ClientId", ""); options.ClientId = sitesettings.GetValue("ExternalLogin:ClientId", "");
options.ClientSecret = sitesettings.GetValue("ExternalLogin:ClientSecret", ""); options.ClientSecret = sitesettings.GetValue("ExternalLogin:ClientSecret", "");
options.UsePkce = bool.Parse(sitesettings.GetValue("ExternalLogin:PKCE", "false")); 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(); options.Scope.Clear();
foreach (var scope in sitesettings.GetValue("ExternalLogin:Scopes", "openid,profile,email").Split(',', StringSplitOptions.RemoveEmptyEntries)) 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); var identity = await ValidateUser(email, id, claims, context.HttpContext);
if (identity.Label == ExternalLoginStatus.Success) 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)); identity.AddClaim(new Claim("access_token", context.SecurityToken.RawData));
context.Principal = new ClaimsPrincipal(identity); context.Principal = new ClaimsPrincipal(identity);
} }

View File

@ -1,9 +1,9 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oqtane.Shared; using Oqtane.Shared;
namespace Oqtane.Infrastructure namespace Oqtane.Infrastructure
@ -52,9 +52,9 @@ namespace Oqtane.Infrastructure
try try
{ {
var path = Path.Combine(Directory.GetCurrentDirectory(), file); var path = Path.Combine(Directory.GetCurrentDirectory(), file);
dynamic jsonObj = JsonConvert.DeserializeObject(File.ReadAllText(path)); JsonNode node = JsonNode.Parse(File.ReadAllText(path));
SetValueRecursively(key, jsonObj, value, "set"); SetValueRecursively(node, key, value);
File.WriteAllText(path, JsonConvert.SerializeObject(jsonObj, Formatting.Indented)); File.WriteAllText(path, JsonSerializer.Serialize(node, new JsonSerializerOptions() { WriteIndented = true }));
if (reload) Reload(); if (reload) Reload();
} }
catch (Exception ex) catch (Exception ex)
@ -73,9 +73,9 @@ namespace Oqtane.Infrastructure
try try
{ {
var path = Path.Combine(Directory.GetCurrentDirectory(), file); var path = Path.Combine(Directory.GetCurrentDirectory(), file);
dynamic jsonObj = JsonConvert.DeserializeObject(File.ReadAllText(path)); JsonNode node = JsonNode.Parse(File.ReadAllText(path));
SetValueRecursively(key, jsonObj, "", "remove"); RemovePropertyRecursively(node, key);
File.WriteAllText(path, JsonConvert.SerializeObject(jsonObj, Formatting.Indented)); File.WriteAllText(path, JsonSerializer.Serialize(node, new JsonSerializerOptions() { WriteIndented = true }));
if (reload) Reload(); if (reload) Reload();
} }
catch (Exception ex) 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]; var currentSection = remainingSections[0];
if (remainingSections.Length > 1) if (remainingSections.Length > 1)
{
var nextSection = remainingSections[1];
jsonObj[currentSection] ??= new JObject();
SetValueRecursively(nextSection, jsonObj[currentSection], value, action);
}
else
{
switch (action)
{ {
case "set": var nextSection = remainingSections[1];
jsonObj[currentSection] = JToken.FromObject(value); SetValueRecursively(json[currentSection] ??= new JsonObject(), nextSection, value);
break; }
case "remove": else
if (jsonObj.Property(currentSection) != null) {
{ if (value.GetType() == typeof(string) && (value.ToString()!.StartsWith("[") || value.ToString()!.StartsWith("{")))
jsonObj.Property(currentSection).Remove(); {
} json[currentSection] = JsonNode.Parse(value.ToString()!);
break; }
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);
}
} }
} }
} }

View File

@ -16,7 +16,6 @@ using Oqtane.Models;
using Oqtane.Repository; using Oqtane.Repository;
using Oqtane.Shared; using Oqtane.Shared;
using Oqtane.Enums; using Oqtane.Enums;
using Newtonsoft.Json;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
// ReSharper disable MemberCanBePrivate.Global // ReSharper disable MemberCanBePrivate.Global
@ -51,7 +50,6 @@ namespace Oqtane.Infrastructure
if (!string.IsNullOrEmpty(_config.GetConnectionString(SettingKeys.ConnectionStringKey))) if (!string.IsNullOrEmpty(_config.GetConnectionString(SettingKeys.ConnectionStringKey)))
{ {
result.Success = true;
using (var db = GetInstallationContext()) using (var db = GetInstallationContext())
{ {
if (db.Database.CanConnect()) if (db.Database.CanConnect())
@ -60,6 +58,7 @@ namespace Oqtane.Infrastructure
{ {
// verify master database contains a Tenant table ( ie. validate schema is properly provisioned ) // verify master database contains a Tenant table ( ie. validate schema is properly provisioned )
var provisioned = db.Tenant.Any(); var provisioned = db.Tenant.Any();
result.Success = true;
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -715,7 +714,7 @@ namespace Oqtane.Infrastructure
foreach (var upgrade in siteupgrades) foreach (var upgrade in siteupgrades)
{ {
var aliasname = upgrade.Key.Split(' ').First(); 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)) if (string.Equals(alias.Name, aliasname, StringComparison.OrdinalIgnoreCase))
{ {
tenantManager.SetTenant(alias.TenantId); 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\": \"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 += "{ \"Name\": \"PostgreSQL\", \"ControlType\": \"Oqtane.Installer.Controls.PostgreSQLConfig, Oqtane.Client\", \"DBTYpe\": \"Oqtane.Database.PostgreSQL.PostgreSQLDatabase, Oqtane.Database.PostgreSQL\" }";
databases += "]"; databases += "]";
_configManager.AddOrUpdateSetting(SettingKeys.AvailableDatabasesSection, JsonConvert.DeserializeObject<dynamic>(databases), true); _configManager.AddOrUpdateSetting(SettingKeys.AvailableDatabasesSection, databases, true);
} }
} }
} }

View File

@ -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;
}
}
}

View File

@ -6,8 +6,8 @@ namespace Oqtane.Infrastructure
{ {
public interface ISyncManager public interface ISyncManager
{ {
event EventHandler<SyncEvent> EntityChanged;
List<SyncEvent> GetSyncEvents(int tenantId, DateTime lastSyncDate); List<SyncEvent> GetSyncEvents(int tenantId, DateTime lastSyncDate);
void AddSyncEvent(int tenantId, string entityName, int entityId); void AddSyncEvent(int tenantId, string entityName, int entityId, string action);
void AddSyncEvent(int tenantId, string entityName, int entityId, bool reload);
} }
} }

View File

@ -40,7 +40,7 @@ namespace Oqtane.Infrastructure
public string[] GetInstalledCultures() public string[] GetInstalledCultures()
{ {
var cultures = new List<string>(); 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))); cultures.Add(Path.GetFileName(Path.GetDirectoryName(file)));
} }

View File

@ -1,21 +1,19 @@
using Microsoft.Extensions.Caching.Memory;
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Shared; using Oqtane.Shared;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
namespace Oqtane.Infrastructure namespace Oqtane.Infrastructure
{ {
public class SyncManager : ISyncManager public class SyncManager : ISyncManager
{ {
private readonly IMemoryCache _cache;
private List<SyncEvent> SyncEvents { get; set; } private List<SyncEvent> SyncEvents { get; set; }
public SyncManager(IMemoryCache cache) public event EventHandler<SyncEvent> EntityChanged;
public SyncManager()
{ {
_cache = cache;
SyncEvents = new List<SyncEvent>(); 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(); 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) // client actions for PageState management
{ if (action == SyncEventActions.Refresh || action == SyncEventActions.Reload)
SyncEvents.Add(new SyncEvent { TenantId = tenantId, EntityName = entityName, EntityId = entityId, Reload = reload, ModifiedOn = DateTime.UtcNow }); {
if (entityName == EntityNames.Site) // trim sync events
{ SyncEvents.RemoveAll(item => item.ModifiedOn < DateTime.UtcNow.AddHours(-1));
_cache.Remove($"site:{tenantId}:{entityId}");
// add sync event
SyncEvents.Add(syncevent);
} }
// trim sync events
SyncEvents.RemoveAll(item => item.ModifiedOn < DateTime.UtcNow.AddHours(-1)); // raise event
EntityChanged?.Invoke(this, syncevent);
} }
} }
} }

View File

@ -57,6 +57,9 @@ namespace Oqtane.Infrastructure
case "3.2.0": case "3.2.0":
Upgrade_3_2_0(tenant, scope); Upgrade_3_2_0(tenant, scope);
break; 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}");
}
}
} }
} }

View File

@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Version>3.2.0</Version> <Version>3.2.1</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -11,7 +11,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <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> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace> <RootNamespace>Oqtane</RootNamespace>
@ -34,7 +34,6 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="6.0.3" /> <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.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.Identity.EntityFrameworkCore" Version="6.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.ViewFeatures" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.ViewFeatures" Version="2.2.0" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="4.1.0" /> <PackageReference Include="Microsoft.Data.SqlClient" Version="4.1.0" />

View File

@ -23,15 +23,17 @@ namespace Oqtane.Pages
private readonly IFileRepository _files; private readonly IFileRepository _files;
private readonly IUserPermissions _userPermissions; private readonly IUserPermissions _userPermissions;
private readonly IUrlMappingRepository _urlMappings; private readonly IUrlMappingRepository _urlMappings;
private readonly ISyncManager _syncManager;
private readonly ILogManager _logger; private readonly ILogManager _logger;
private readonly Alias _alias; 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; _environment = environment;
_files = files; _files = files;
_userPermissions = userPermissions; _userPermissions = userPermissions;
_urlMappings = urlMappings; _urlMappings = urlMappings;
_syncManager = syncManager;
_logger = logger; _logger = logger;
_alias = tenantManager.GetAlias(); _alias = tenantManager.GetAlias();
} }
@ -42,6 +44,12 @@ namespace Oqtane.Pages
var folderpath = ""; var folderpath = "";
var filename = ""; var filename = "";
bool download = false;
if (Request.Query.ContainsKey("download"))
{
download = true;
}
var segments = path.Split('/'); var segments = path.Split('/');
if (segments.Length > 0) 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 (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); var filepath = _files.GetFilePath(file);
if (System.IO.File.Exists(filepath)) 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 else
{ {
@ -91,7 +116,7 @@ namespace Oqtane.Pages
} }
// broken link // 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)); return PhysicalFile(errorPath, MimeUtilities.GetMimeType(errorPath));
} }
} }

View File

@ -50,6 +50,10 @@
{ {
<script src="_framework/blazor.server.js"></script> <script src="_framework/blazor.server.js"></script>
} }
@if (!string.IsNullOrEmpty(Model.ReconnectScript))
{
@Html.Raw(Model.ReconnectScript)
}
@if (!string.IsNullOrEmpty(Model.PWAScript)) @if (!string.IsNullOrEmpty(Model.PWAScript))
{ {
@Html.Raw(Model.PWAScript) @Html.Raw(Model.PWAScript)

View File

@ -20,6 +20,7 @@ using Oqtane.Enums;
using Oqtane.Security; using Oqtane.Security;
using Oqtane.Extensions; using Oqtane.Extensions;
using Oqtane.Themes; using Oqtane.Themes;
using Oqtane.UI;
namespace Oqtane.Pages namespace Oqtane.Pages
{ {
@ -69,6 +70,7 @@ namespace Oqtane.Pages
public string Meta = ""; public string Meta = "";
public string FavIcon = "favicon.ico"; public string FavIcon = "favicon.ico";
public string PWAScript = ""; public string PWAScript = "";
public string ReconnectScript = "";
public string Message = ""; public string Message = "";
public IActionResult OnGet() public IActionResult OnGet()
@ -126,7 +128,11 @@ namespace Oqtane.Pages
} }
if (site.FaviconFileId != null) 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) if (site.PwaIsEnabled && site.PwaAppIconFileId != null && site.PwaSplashIconFileId != null)
{ {
@ -174,7 +180,7 @@ namespace Oqtane.Pages
{ {
ThemeType = page.ThemeType; ThemeType = page.ThemeType;
} }
ProcessThemeResources(ThemeType); ProcessThemeResources(ThemeType, alias);
} }
else // page not found else // page not found
{ {
@ -198,7 +204,7 @@ namespace Oqtane.Pages
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies(); var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
foreach (Assembly assembly in assemblies) foreach (Assembly assembly in assemblies)
{ {
ProcessHostResources(assembly); ProcessHostResources(assembly, alias);
} }
// set culture if not specified // set culture if not specified
@ -374,44 +380,64 @@ namespace Oqtane.Pages
private string CreatePWAScript(Alias alias, Site site, Route route) private string CreatePWAScript(Alias alias, Site site, Route route)
{ {
return return
"<script id=\"app-pwa\">" + "<script>" + Environment.NewLine +
"setTimeout(() => { " + " // PWA Manifest" + Environment.NewLine +
"var manifest = { " + " setTimeout(() => {" + Environment.NewLine +
"\"name\": \"" + site.Name + "\", " + " var manifest = {" + Environment.NewLine +
"\"short_name\": \"" + site.Name + "\", " + " \"name\": \"" + site.Name + "\"," + Environment.NewLine +
"\"start_url\": \"" + route.SiteUrl + "/\", " + " \"short_name\": \"" + site.Name + "\"," + Environment.NewLine +
"\"display\": \"standalone\", " + " \"start_url\": \"" + route.SiteUrl + "/\"," + Environment.NewLine +
"\"background_color\": \"#fff\", " + " \"display\": \"standalone\"," + Environment.NewLine +
"\"description\": \"" + site.Name + "\", " + " \"background_color\": \"#fff\"," + Environment.NewLine +
"\"icons\": [{ " + " \"description\": \"" + site.Name + "\"," + Environment.NewLine +
"\"src\": \"" + route.RootUrl + Utilities.ContentUrl(alias, site.PwaAppIconFileId.Value) + "\", " + " \"icons\": [{" + Environment.NewLine +
"\"sizes\": \"192x192\", " + " \"src\": \"" + route.RootUrl + Utilities.FileUrl(alias, site.PwaAppIconFileId.Value) + "\"," + Environment.NewLine +
"\"type\": \"image/png\" " + " \"sizes\": \"192x192\"," + Environment.NewLine +
"}, { " + " \"type\": \"image/png\"" + Environment.NewLine +
"\"src\": \"" + route.RootUrl + Utilities.ContentUrl(alias, site.PwaSplashIconFileId.Value) + "\", " + " }, {" + Environment.NewLine +
"\"sizes\": \"512x512\", " + " \"src\": \"" + route.RootUrl + Utilities.FileUrl(alias, site.PwaSplashIconFileId.Value) + "\"," + Environment.NewLine +
"\"type\": \"image/png\" " + " \"sizes\": \"512x512\"," + Environment.NewLine +
"}] " + " \"type\": \"image/png\"" + Environment.NewLine +
"}; " + " }]" + Environment.NewLine +
"const serialized = JSON.stringify(manifest); " + " };" + Environment.NewLine +
"const blob = new Blob([serialized], {type: 'application/javascript'}); " + " const serialized = JSON.stringify(manifest);" + Environment.NewLine +
"const url = URL.createObjectURL(blob); " + " const blob = new Blob([serialized], {type: 'application/javascript'});" + Environment.NewLine +
"document.getElementById('app-manifest').setAttribute('href', url); " + " const url = URL.createObjectURL(blob);" + Environment.NewLine +
"} " + " document.getElementById('app-manifest').setAttribute('href', url);" + Environment.NewLine +
", 1000);" + " }, 1000);" + Environment.NewLine +
"</script>" + Environment.NewLine + "</script>" + Environment.NewLine +
"<script id=\"app-serviceworker\">" + "<script>" + Environment.NewLine +
"if ('serviceWorker' in navigator) { " + " // PWA Service Worker" + Environment.NewLine +
"navigator.serviceWorker.register('/service-worker.js').then(function(registration) { " + " if ('serviceWorker' in navigator) {" + Environment.NewLine +
"console.log('ServiceWorker Registration Successful'); " + " navigator.serviceWorker.register('/service-worker.js').then(function(registration) {" + Environment.NewLine +
"}).catch (function(err) { " + " console.log('ServiceWorker Registration Successful');" + Environment.NewLine +
"console.log('ServiceWorker Registration Failed ', err); " + " }).catch (function(err) {" + Environment.NewLine +
"}); " + " console.log('ServiceWorker Registration Failed ', err);" + Environment.NewLine +
"};" + " });" + Environment.NewLine +
" };" + Environment.NewLine +
"</script>"; "</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))); var types = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IHostResources)));
foreach (var type in types) foreach (var type in types)
@ -420,12 +446,12 @@ namespace Oqtane.Pages
foreach (var resource in obj.Resources) foreach (var resource in obj.Resources)
{ {
resource.Level = ResourceLevel.App; 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); var type = Type.GetType(ThemeType);
if (type != null) if (type != null)
@ -437,40 +463,41 @@ namespace Oqtane.Pages
foreach (var resource in obj.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet)) foreach (var resource in obj.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet))
{ {
resource.Level = ResourceLevel.Page; 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) switch (resource.ResourceType)
{ {
case ResourceType.Stylesheet: case ResourceType.Stylesheet:
if (!HeadResources.Contains(resource.Url, StringComparison.OrdinalIgnoreCase)) if (!HeadResources.Contains(url, StringComparison.OrdinalIgnoreCase))
{ {
string id = ""; string id = "";
if (resource.Level == ResourceLevel.Page) if (resource.Level == ResourceLevel.Page)
{ {
id = "id=\"app-stylesheet-" + resource.Level.ToString().ToLower() + "-" + DateTime.UtcNow.ToString("yyyyMMddHHmmssfff") + "-" + count.ToString("00") + "\" "; 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; break;
case ResourceType.Script: case ResourceType.Script:
if (resource.Location == Shared.ResourceLocation.Body) 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 else
{ {
if (!HeadResources.Contains(resource.Url, StringComparison.OrdinalIgnoreCase)) 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; break;

View File

@ -133,7 +133,6 @@ namespace Oqtane
{ {
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()); options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
}) })
.AddNewtonsoftJson()
.AddOqtaneApplicationParts() // register any Controllers from custom modules .AddOqtaneApplicationParts() // register any Controllers from custom modules
.ConfigureOqtaneMvc(); // any additional configuration from IStartup classes .ConfigureOqtaneMvc(); // any additional configuration from IStartup classes
@ -191,7 +190,7 @@ namespace Oqtane
}); });
// create a global sync event to identify server application startup // create a global sync event to identify server application startup
sync.AddSyncEvent(-1, "Application", -1, true); sync.AddSyncEvent(-1, EntityNames.Host, -1, SyncEventActions.Reload);
} }
} }
} }

View File

@ -356,10 +356,15 @@ Oqtane.Interop = {
} }
} }
}, },
refreshBrowser: function (reload, wait) { refreshBrowser: function (verify, wait) {
setInterval(function () { async function attemptReload (verify) {
window.location.reload(reload); if (verify) {
}, wait * 1000); await fetch('');
}
window.location.reload();
}
attemptReload(verify);
setInterval(attemptReload, wait * 1000);
}, },
redirectBrowser: function (url, wait) { redirectBrowser: function (url, wait) {
setInterval(function () { setInterval(function () {

View File

@ -79,7 +79,7 @@ namespace System.Reflection
{ {
return appDomain.GetOqtaneAssemblies() return appDomain.GetOqtaneAssemblies()
.Where(a => a.GetTypes<IModuleControl>().Any() || a.GetTypes<IThemeControl>().Any() || a.GetTypes<IClientStartup>().Any()) .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> /// <summary>

View File

@ -8,6 +8,8 @@ namespace Oqtane.Models
/// </summary> /// </summary>
public class Resource public class Resource
{ {
private string _url;
/// <summary> /// <summary>
/// A <see cref="ResourceType"/> so the Interop can properly create `script` or `link` tags /// A <see cref="ResourceType"/> so the Interop can properly create `script` or `link` tags
/// </summary> /// </summary>
@ -16,7 +18,14 @@ namespace Oqtane.Models
/// <summary> /// <summary>
/// Path to the resources. /// Path to the resources.
/// </summary> /// </summary>
public string Url { get; set; } public string Url
{
get => _url;
set
{
_url = (value.Contains("://")) ? value : (!value.StartsWith("/") ? "/" : "") + value;
}
}
/// <summary> /// <summary>
/// Integrity checks to increase the security of resources accessed. Especially common in CDN resources. /// Integrity checks to increase the security of resources accessed. Especially common in CDN resources.

View File

@ -9,12 +9,12 @@ namespace Oqtane.Models
public List<SyncEvent> SyncEvents { get; set; } public List<SyncEvent> SyncEvents { get; set; }
} }
public class SyncEvent public class SyncEvent : EventArgs
{ {
public int TenantId { get; set; } public int TenantId { get; set; }
public string EntityName { get; set; } public string EntityName { get; set; }
public int EntityId { get; set; } public int EntityId { get; set; }
public bool Reload { get; set; } public string Action { get; set; }
public DateTime ModifiedOn { get; set; } public DateTime ModifiedOn { get; set; }
} }
} }

View File

@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Version>3.2.0</Version> <Version>3.2.1</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -11,7 +11,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <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> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace> <RootNamespace>Oqtane</RootNamespace>

View File

@ -4,9 +4,10 @@ namespace Oqtane.Shared
{ {
public class Constants public class Constants
{ {
public static readonly string Version = "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"; 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 PackageId = "Oqtane.Framework";
public const string ClientId = "Oqtane.Client";
public const string UpdaterPackageId = "Oqtane.Updater"; public const string UpdaterPackageId = "Oqtane.Updater";
public const string PackageRegistryUrl = "https://www.oqtane.net"; 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 ContainerComponent = "Oqtane.UI.ContainerBuilder, Oqtane.Client";
public const string DefaultTheme = "Oqtane.Themes.OqtaneTheme.Default, 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 DefaultContainer = "Oqtane.Themes.OqtaneTheme.Container, Oqtane.Client";
public const string DefaultAdminContainer = "Oqtane.Themes.AdminContainer, Oqtane.Client"; public const string DefaultAdminContainer = "Oqtane.Themes.AdminContainer, Oqtane.Client";
public const string ActionToken = "{Action}"; public const string ActionToken = "{Action}";
public const string DefaultAction = "Index"; public const string DefaultAction = "Index";
[Obsolete("Use PaneNames.Admin")]
public const string AdminPane = PaneNames.Admin;
public static readonly string[] ReservedRoutes = { "api", "pages", "files" }; public static readonly string[] ReservedRoutes = { "api", "pages", "files" };
public const string ModuleDelimiter = "*"; public const string ModuleDelimiter = "*";
public const string UrlParametersDelimiter = "!"; public const string UrlParametersDelimiter = "!";
@ -42,29 +38,13 @@ namespace Oqtane.Shared
public const string DefaultSiteTemplate = "Oqtane.SiteTemplates.DefaultSiteTemplate, Oqtane.Server"; 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 string ImageUrl = "/api/file/image/";
public const int UserFolderCapacity = 20; // megabytes public const int UserFolderCapacity = 20; // megabytes
public const string PackagesFolder = "Packages"; 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"; 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 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 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$"; 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 HttpContextSiteSettingsKey = "SiteSettings";
public static readonly string MauiUserAgent = "MAUI"; 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/";
} }
} }

View File

@ -2,15 +2,25 @@ namespace Oqtane.Shared
{ {
public class EntityNames 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 Module = "Module";
public const string ModuleDefinition = "ModuleDefinition"; public const string ModuleDefinition = "ModuleDefinition";
public const string PageModule = "PageModule"; public const string Notification = "Notification";
public const string Tenant = "Tenant";
public const string Site = "Site";
public const string Page = "Page"; 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 User = "User";
public const string UserRole = "UserRole";
public const string Visitor = "Visitor"; public const string Visitor = "Visitor";
public const string Host = "Host"; public const string Host = "Host"; // a conceptual entity
} }
} }

View 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";
}
}

View File

@ -98,23 +98,28 @@ namespace Oqtane.Shared
return NavigateUrl(alias, path, parameters); 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) 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 : ""; 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) 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; mode = string.IsNullOrEmpty(mode) ? "crop" : mode;
position = string.IsNullOrEmpty(position) ? "center" : position; position = string.IsNullOrEmpty(position) ? "center" : position;
background = string.IsNullOrEmpty(background) ? "000000" : background; 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) public static string TenantUrl(Alias alias, string url)
{ {
url = (!url.StartsWith("/")) ? "/" + url : url; url = (!url.StartsWith("/")) ? "/" + url : url;
url = (alias != null && !string.IsNullOrEmpty(alias.Path)) ? "/" + alias.Path + 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) public static string FormatContent(string content, Alias alias, string operation)
{ {
var aliasUrl = (alias != null && !string.IsNullOrEmpty(alias.Path)) ? "/" + alias.Path : "";
switch (operation) switch (operation)
{ {
case "save": 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(UrlCombine("Content", "Tenants", alias.TenantId.ToString(), "Sites", alias.SiteId.ToString()), "[siteroot]");
content = content.Replace(alias.Path + Constants.ContentUrl, Constants.ContentUrl); content = content.Replace(alias.Path + Constants.ContentUrl, Constants.ContentUrl);
break; break;
case "render": 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("[siteroot]", UrlCombine("Content", "Tenants", alias.TenantId.ToString(), "Sites", alias.SiteId.ToString()));
content = content.Replace(Constants.ContentUrl, alias.Path + Constants.ContentUrl); content = content.Replace(Constants.ContentUrl, alias.Path + Constants.ContentUrl);
break; break;
@ -491,5 +501,19 @@ namespace Oqtane.Shared
return (localDateTime?.Date, localTime); 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}";
}
} }
} }

View File

@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Version>3.2.0</Version> <Version>3.2.1</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -11,7 +11,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <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> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace> <RootNamespace>Oqtane</RootNamespace>

View File

@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<Version>3.2.0</Version> <Version>3.2.1</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -11,7 +11,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <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> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace> <RootNamespace>Oqtane</RootNamespace>

View File

@ -41,15 +41,15 @@ There is a separate [Documentation repository](https://github.com/oqtane/oqtane.
# Roadmap # Roadmap
This project is open source, and therefore is a work in progress... 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 - [ ] Migration to .NET 7
- [ ] Folder Providers - [ ] 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] MAUI / Blazor Hybrid support
- [x] Upgrade to Bootstrap 5.2 - [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 - [x] Stabilization improvements
[3.1.2](https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.2) ( May 14, 2022 ) [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 ) [3.1.1](https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.1) ( May 3, 2022 )
- [x] Stabilization improvements - [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] User account lockout support
- [x] Two factor authentication support - [x] Two factor authentication support
- [x] Per-site configuration of password complexity, lockout criteria - [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 # 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.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) [Oqtane 3.0](https://www.oqtane.org/Resources/Blog/PostId/551/announcing-oqtane-30-for-net-6)