Merge pull request #5237 from oqtane/dev

6.1.2 Release
This commit is contained in:
Shaun Walker 2025-04-10 11:48:36 -04:00 committed by GitHub
commit 31ccd80894
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
80 changed files with 801 additions and 419 deletions

View File

@ -1,2 +0,0 @@
[config]
project = Oqtane.Server/Oqtane.Server.csproj

View File

@ -0,0 +1,119 @@
@namespace Oqtane.Installer.Controls
@implements Oqtane.Interfaces.IDatabaseConfigControl
@inject IStringLocalizer<SqlServerConfig> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="server" HelpText="Enter the database server name. This might include a port number as well if you are using a cloud service." ResourceKey="Server">Server:</Label>
<div class="col-sm-9">
<input id="server" type="text" class="form-control" @bind="@_server" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="database" HelpText="Enter the name of the database" ResourceKey="Database">Database:</Label>
<div class="col-sm-9">
<input id="database" type="text" class="form-control" @bind="@_database" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="security" HelpText="Select your security method" ResourceKey="Security">Security:</Label>
<div class="col-sm-9">
<select id="security" class="form-select custom-select" @bind="@_security">
<option value="integrated" selected>@Localizer["Integrated"]</option>
<option value="custom">@Localizer["Custom"]</option>
</select>
</div>
</div>
@if (_security == "custom")
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="uid" HelpText="Enter the username to use for the database" ResourceKey="Uid">User Id:</Label>
<div class="col-sm-9">
<input id="uid" type="text" class="form-control" @bind="@_uid" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="pwd" HelpText="Enter the password to use for the database" ResourceKey="Pwd">Password:</Label>
<div class="col-sm-9">
<div class="input-group">
<input id="pwd" type="@_passwordType" class="form-control" @bind="@_pwd" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglePassword</button>
</div>
</div>
</div>
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="encryption" HelpText="Specify if you are using an encrypted database connection. It is highly recommended to use encryption in a production environment." ResourceKey="Encryption">Encryption:</Label>
<div class="col-sm-9">
<select id="encryption" class="form-select custom-select" @bind="@_encryption">
<option value="true">@SharedLocalizer["True"]</option>
<option value="false">@SharedLocalizer["False"]</option>
</select>
</div>
</div>
@if (_encryption == "true")
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="trustservercertificate" HelpText="Specify the type of certificate you are using for encryption. Verifiable is equivalent to False. Self Signed is equivalent to True." ResourceKey="TrustServerCertificate">Certificate:</Label>
<div class="col-sm-9">
<select id="encryption" class="form-select custom-select" @bind="@_trustservercertificate">
<option value="true">@Localizer["Self Signed"]</option>
<option value="false">@Localizer["Verifiable"]</option>
</select>
</div>
</div>
}
@code {
private string _server = "tcp:{SQL Server Name}.database.windows.net,1433";
private string _database = "{SQL Database Name}";
private string _security = "custom";
private string _uid = "{SQL Administrator Login}";
private string _pwd = String.Empty;
private string _passwordType = "password";
private string _togglePassword = string.Empty;
private string _encryption = "true";
private string _trustservercertificate = "false";
protected override void OnInitialized()
{
_togglePassword = SharedLocalizer["ShowPassword"];
}
public string GetConnectionString()
{
var connectionString = String.Empty;
if (!String.IsNullOrEmpty(_server) && !String.IsNullOrEmpty(_database))
{
connectionString = $"Data Source={_server};Initial Catalog={_database};";
}
if (_security == "integrated")
{
connectionString += "Integrated Security=SSPI;";
}
else
{
connectionString += $"User ID={_uid};Password={_pwd};";
}
connectionString += $"Encrypt={_encryption};";
connectionString += $"TrustServerCertificate={_trustservercertificate};";
return connectionString;
}
private void TogglePassword()
{
if (_passwordType == "password")
{
_passwordType = "text";
_togglePassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordType = "password";
_togglePassword = SharedLocalizer["ShowPassword"];
}
}
}

View File

@ -4,7 +4,7 @@
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="server" HelpText="Enter the database server name. This might include a port number as well if you are using a cloud service (ie. servername.database.windows.net,1433) " ResourceKey="Server">Server:</Label> <Label Class="col-sm-3" For="server" HelpText="Enter the database server name. This might include a port number as well if you are using a cloud service." ResourceKey="Server">Server:</Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="server" type="text" class="form-control" @bind="@_server" /> <input id="server" type="text" class="form-control" @bind="@_server" />
</div> </div>

View File

@ -15,7 +15,7 @@
<div class="row"> <div class="row">
<div class="mx-auto text-center"> <div class="mx-auto text-center">
<img src="oqtane-black.png" /> <img src="oqtane-black.png" />
<div style="font-weight: bold">@SharedLocalizer["Version"] @Constants.Version (.NET 9)</div> <div style="font-weight: bold">@SharedLocalizer["Version"] @Constants.Version (.NET @Environment.Version.Major)</div>
</div> </div>
</div> </div>
<hr class="app-rule" /> <hr class="app-rule" />

View File

@ -45,7 +45,7 @@
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="retention" HelpText="Number of log entries to retain for this job" ResourceKey="RetentionLog">Retention Log (Items): </Label> <Label Class="col-sm-3" For="retention" HelpText="Number of log entries to retain for this job" ResourceKey="RetentionLog">Retention Log (Items): </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="retention" type="number" min="0" step ="1" class="form-control" @bind="@_retentionHistory" maxlength="4" required /> <input id="retention" type="number" min="0" step ="1" class="form-control" @bind="@_retentionHistory" maxlength="3" required />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">

View File

@ -44,14 +44,12 @@ else
private async Task GetJobLogs() private async Task GetJobLogs()
{ {
_jobLogs = await JobLogService.GetJobLogsAsync(); var jobId = -1;
if (PageState.QueryString.ContainsKey("id")) if (PageState.QueryString.ContainsKey("id"))
{ {
_jobLogs = _jobLogs.Where(item => item.JobId == Int32.Parse(PageState.QueryString["id"])).ToList(); jobId = int.Parse(PageState.QueryString["id"]);
} }
_jobLogs = await JobLogService.GetJobLogsAsync(jobId);
_jobLogs = _jobLogs.OrderByDescending(item => item.JobLogId).ToList();
} }
private string DisplayStatus(bool isExecuting, bool? succeeded) private string DisplayStatus(bool isExecuting, bool? succeeded)

View File

@ -73,7 +73,7 @@
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="url" HelpText="The reference url of the module" ResourceKey="ReferenceUrl">Reference Url: </Label> <Label Class="col-sm-3" For="url" HelpText="The url of the module" ResourceKey="Url">Url: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="url" class="form-control" @bind="@_url" disabled /> <input id="url" class="form-control" @bind="@_url" disabled />
</div> </div>

View File

@ -13,32 +13,32 @@
} }
else else
{ {
<div class="container"> <div class="container">
<div class="row mb-3 align-items-center"> <div class="row mb-3 align-items-center">
<div class="col-sm-6"> <div class="col-sm-6">
<ActionLink Action="Add" Text="Install Module" ResourceKey="InstallModule" /> <ActionLink Action="Add" Text="Install Module" ResourceKey="InstallModule" />
@((MarkupString)"&nbsp;") <ActionLink Action="Create" Text="Create Module" ResourceKey="CreateModule" Class="btn btn-secondary ps-2" />
<ActionLink Action="Create" Text="Create Module" ResourceKey="CreateModule" Class="btn btn-secondary" /> <button type="button" class="btn btn-secondary pw-2" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
<select class="form-select" @onchange="(e => CategoryChanged(e))"> <select class="form-select" @onchange="(e => CategoryChanged(e))">
@foreach (var category in _categories) @foreach (var category in _categories)
{ {
if (category == _category) if (category == _category)
{ {
<option value="@category" selected>@category @Localizer["Modules"]</option> <option value="@category" selected>@category @Localizer["Modules"]</option>
} }
else else
{ {
<option value="@category">@category @Localizer["Modules"]</option> <option value="@category">@category @Localizer["Modules"]</option>
} }
} }
</select> </select>
</div> </div>
</div> </div>
</div> </div>
<Pager Items="@_moduleDefinitions"> <Pager Items="@_moduleDefinitions">
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
@ -61,17 +61,17 @@ else
<td>@context.Name</td> <td>@context.Name</td>
<td>@context.Version</td> <td>@context.Version</td>
<td> <td>
@if (context.IsEnabled) @if (context.IsEnabled)
{ {
<span>@SharedLocalizer["Yes"]</span> <span>@SharedLocalizer["Yes"]</span>
} }
else else
{ {
<span>@SharedLocalizer["No"]</span> <span>@SharedLocalizer["No"]</span>
} }
</td> </td>
<td> <td>
@if (context.AssemblyName == Constants.ClientId || _modules.Where(m => m.ModuleDefinition?.ModuleDefinitionId == context.ModuleDefinitionId).FirstOrDefault() != null) @if (context.AssemblyName == Constants.ClientId || _modules.Where(m => m.ModuleDefinition?.ModuleDefinitionId == context.ModuleDefinitionId).FirstOrDefault() != null)
{ {
<span>@SharedLocalizer["Yes"]</span> <span>@SharedLocalizer["Yes"]</span>
} }
@ -87,9 +87,9 @@ else
@((MarkupString)PurchaseLink(context.PackageName)) @((MarkupString)PurchaseLink(context.PackageName))
</td> </td>
<td> <td>
@{ @{
var version = UpgradeAvailable(context.PackageName, context.Version); var version = UpgradeAvailable(context.PackageName, context.Version);
} }
@if (version != context.Version) @if (version != context.Version)
{ {
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadModule(context.PackageName, version))>@SharedLocalizer["Upgrade"]</button> <button type="button" class="btn btn-success" @onclick=@(async () => await DownloadModule(context.PackageName, version))>@SharedLocalizer["Upgrade"]</button>
@ -153,10 +153,10 @@ else
link = "<a class=\"btn btn-primary\" style=\"text-decoration: none !important\" href=\"" + package.PaymentUrl + "\" target=\"_new\">" + package.ExpiryDate.Value.Date.ToString("MMM dd, yyyy") + "</a>"; link = "<a class=\"btn btn-primary\" style=\"text-decoration: none !important\" href=\"" + package.PaymentUrl + "\" target=\"_new\">" + package.ExpiryDate.Value.Date.ToString("MMM dd, yyyy") + "</a>";
} }
} }
} }
} }
return link; return link;
} }
private string SupportLink(string packagename, string version) private string SupportLink(string packagename, string version)
{ {
@ -172,52 +172,75 @@ else
return link; return link;
} }
private string UpgradeAvailable(string packagename, string version) private string UpgradeAvailable(string packagename, string version)
{ {
if (!string.IsNullOrEmpty(packagename) && _packages != null) if (!string.IsNullOrEmpty(packagename) && _packages != null)
{ {
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault(); var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
if (package != null && Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0) if (package != null && Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0)
{ {
return package.Version; return package.Version;
} }
} }
return version; return version;
} }
private async Task DownloadModule(string packagename, string version) private async Task DownloadModule(string packagename, string version)
{ {
try try
{ {
await PackageService.DownloadPackageAsync(packagename, version); await PackageService.DownloadPackageAsync(packagename, version);
await logger.LogInformation("Module Downloaded {ModuleDefinitionName} {Version}", packagename, version); await logger.LogInformation("Module Downloaded {ModuleDefinitionName} {Version}", packagename, version);
AddModuleMessage(string.Format(Localizer["Success.Module.Install"], NavigateUrl("admin/system")), MessageType.Success); AddModuleMessage(string.Format(Localizer["Success.Module.Install"], NavigateUrl("admin/system")), MessageType.Success);
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Downloading Module {ModuleDefinitionName} {Version} {Error}", packagename, version, ex.Message); await logger.LogError(ex, "Error Downloading Module {ModuleDefinitionName} {Version} {Error}", packagename, version, ex.Message);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error); AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
} }
} }
private async Task DeleteModule(ModuleDefinition moduleDefinition) private async Task DeleteModule(ModuleDefinition moduleDefinition)
{ {
try try
{ {
await ModuleDefinitionService.DeleteModuleDefinitionAsync(moduleDefinition.ModuleDefinitionId, moduleDefinition.SiteId); await ModuleDefinitionService.DeleteModuleDefinitionAsync(moduleDefinition.ModuleDefinitionId, moduleDefinition.SiteId);
AddModuleMessage(Localizer["Success.Module.Delete"], MessageType.Success); AddModuleMessage(Localizer["Success.Module.Delete"], MessageType.Success);
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true)); NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Deleting Module {ModuleDefinition} {Error}", moduleDefinition, ex.Message); await logger.LogError(ex, "Error Deleting Module {ModuleDefinition} {Error}", moduleDefinition, ex.Message);
AddModuleMessage(Localizer["Error.Module.Delete"], MessageType.Error); AddModuleMessage(Localizer["Error.Module.Delete"], MessageType.Error);
} }
} }
private async Task CategoryChanged(ChangeEventArgs e) private async Task CategoryChanged(ChangeEventArgs e)
{ {
_category = (string)e.Value; _category = (string)e.Value;
await LoadModuleDefinitions(); await LoadModuleDefinitions();
} }
private async Task Synchronize()
{
try
{
ShowProgressIndicator();
foreach (var moduleDefinition in _moduleDefinitions)
{
if (!string.IsNullOrEmpty(moduleDefinition.PackageName) && !_packages.Any(item => item.PackageId == moduleDefinition.PackageName))
{
var package = await PackageService.GetPackageAsync(moduleDefinition.PackageName, moduleDefinition.Version, false);
}
}
HideProgressIndicator();
AddModuleMessage(Localizer["Success.Module.Synchronize"], MessageType.Success);
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Synchronizing Modules {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Module.Synchronize"], MessageType.Error);
}
}
} }

View File

@ -55,7 +55,7 @@
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="url" HelpText="The reference url of the theme" ResourceKey="ReferenceUrl">Reference Url: </Label> <Label Class="col-sm-3" For="url" HelpText="The url of the theme" ResourceKey="Url">Url: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="url" class="form-control" @bind="@_url" disabled /> <input id="url" class="form-control" @bind="@_url" disabled />
</div> </div>

View File

@ -15,8 +15,8 @@
else else
{ {
<ActionLink Action="Add" Text="Install Theme" ResourceKey="InstallTheme" /> <ActionLink Action="Add" Text="Install Theme" ResourceKey="InstallTheme" />
@((MarkupString)"&nbsp;") <ActionLink Action="Create" Text="Create Theme" ResourceKey="CreateTheme" Class="btn btn-secondary ps-2" />
<ActionLink Action="Create" Text="Create Theme" ResourceKey="CreateTheme" Class="btn btn-secondary" /> <button type="button" class="btn btn-secondary pw-2" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
<Pager Items="@_themes"> <Pager Items="@_themes">
<Header> <Header>
@ -173,4 +173,27 @@ else
AddModuleMessage(Localizer["Error.Theme.Delete"], MessageType.Error); AddModuleMessage(Localizer["Error.Theme.Delete"], MessageType.Error);
} }
} }
private async Task Synchronize()
{
try
{
ShowProgressIndicator();
foreach (var theme in _themes)
{
if (!string.IsNullOrEmpty(theme.PackageName) && !_packages.Any(item => item.PackageId == theme.PackageName))
{
await PackageService.GetPackageAsync(theme.PackageName, theme.Version, false);
}
}
HideProgressIndicator();
AddModuleMessage(Localizer["Success.Theme.Synchronize"], MessageType.Success);
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Synchronizing Themes {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Theme.Synchronize"], MessageType.Error);
}
}
} }

View File

@ -13,9 +13,21 @@
<TabPanel Name="Download" ResourceKey="Download"> <TabPanel Name="Download" ResourceKey="Download">
@if (_package != null && _upgradeavailable) @if (_package != null && _upgradeavailable)
{ {
<ModuleMessage Type="MessageType.Info" Message="Select The Download Button To Download The Framework Upgrade Package And Then Select Upgrade"></ModuleMessage> <div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" HelpText="Specify if you want to backup files during the upgrade process. Disabling this option will result in a better experience in some environments." ResourceKey="Backup">Backup Files? </Label>
<div class="col-sm-9">
<select id="backup" class="form-select" @bind="@_backup">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
<button type="button" class="btn btn-primary" @onclick=@(async () => await Download(Constants.PackageId, @_package.Version))>@SharedLocalizer["Download"] @_package.Version</button> <button type="button" class="btn btn-primary" @onclick=@(async () => await Download(Constants.PackageId, @_package.Version))>@SharedLocalizer["Download"] @_package.Version</button>
<button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button> <button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button>
<br /><br />
<ModuleMessage Type="MessageType.Info" Message="Select The Download Button To Download The Framework Upgrade Package And Then Select Upgrade"></ModuleMessage>
} }
else else
{ {
@ -23,7 +35,6 @@
} }
</TabPanel> </TabPanel>
<TabPanel Name="Upload" ResourceKey="Upload"> <TabPanel Name="Upload" ResourceKey="Upload">
<ModuleMessage Type="MessageType.Info" Message=@Localizer["MessageUpgrade.Text"]></ModuleMessage>
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" HelpText="Upload A Framework Package And Then Select Upgrade" ResourceKey="Framework">Framework: </Label> <Label Class="col-sm-3" HelpText="Upload A Framework Package And Then Select Upgrade" ResourceKey="Framework">Framework: </Label>
@ -31,8 +42,19 @@
<FileManager Folder="@Constants.PackagesFolder" /> <FileManager Folder="@Constants.PackagesFolder" />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" HelpText="Specify if you want to backup files during the upgrade process. Disabling this option will result in a better experience in some environments." ResourceKey="Backup">Backup Files? </Label>
<div class="col-sm-9">
<select id="backup" class="form-select" @bind="@_backup">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div> </div>
<button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button> <button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button>
<br /><br />
<ModuleMessage Type="MessageType.Info" Message=@Localizer["MessageUpgrade.Text"]></ModuleMessage>
</TabPanel> </TabPanel>
</TabStrip> </TabStrip>
} }
@ -41,6 +63,7 @@
private bool _initialized = false; private bool _initialized = false;
private Package _package; private Package _package;
private bool _upgradeavailable = false; private bool _upgradeavailable = false;
private string _backup = "True";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
@ -86,7 +109,7 @@
ShowProgressIndicator(); ShowProgressIndicator();
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
await interop.RedirectBrowser(NavigateUrl(), 10); await interop.RedirectBrowser(NavigateUrl(), 10);
await InstallationService.Upgrade(); await InstallationService.Upgrade(bool.Parse(_backup));
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -421,6 +421,18 @@ else
</select> </select>
</div> </div>
</div> </div>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="allowhostrole" HelpText="Indicate if host roles are supported from the identity provider. Please use caution with this option as it allows the host user to administrate every site within your installation." ResourceKey="AllowHostRole">Allow Host Role?</Label>
<div class="col-sm-9">
<select id="allowhostrole" class="form-select" @bind="@_allowhostrole" required>
<option value="true">@SharedLocalizer["Yes"]</option>
<option value="false">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
}
} }
</Section> </Section>
<Section Name="Token" Heading="Token Settings" ResourceKey="TokenSettings"> <Section Name="Token" Heading="Token Settings" ResourceKey="TokenSettings">
@ -520,6 +532,7 @@ else
private string _domainfilter; private string _domainfilter;
private string _createusers; private string _createusers;
private string _verifyusers; private string _verifyusers;
private string _allowhostrole;
private string _secret; private string _secret;
private string _secrettype = "password"; private string _secrettype = "password";
@ -602,6 +615,7 @@ else
_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");
_verifyusers = SettingService.GetSetting(settings, "ExternalLogin:VerifyUsers", "true"); _verifyusers = SettingService.GetSetting(settings, "ExternalLogin:VerifyUsers", "true");
_allowhostrole = SettingService.GetSetting(settings, "ExternalLogin:AllowHostRole", "false");
} }
private async Task LoadUsersAsync(bool load) private async Task LoadUsersAsync(bool load)
@ -705,6 +719,7 @@ else
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);
settings = SettingService.SetSetting(settings, "ExternalLogin:VerifyUsers", _verifyusers, true); settings = SettingService.SetSetting(settings, "ExternalLogin:VerifyUsers", _verifyusers, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:AllowHostRole", _allowhostrole, true);
settings = SettingService.SetSetting(settings, "JwtOptions:Secret", _secret, true); settings = SettingService.SetSetting(settings, "JwtOptions:Secret", _secret, true);
settings = SettingService.SetSetting(settings, "JwtOptions:Issuer", _issuer, true); settings = SettingService.SetSetting(settings, "JwtOptions:Issuer", _issuer, true);

View File

@ -100,6 +100,18 @@ else
<br /> <br />
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
</TabPanel> </TabPanel>
<TabPanel Name="Robots" Heading="Robots.txt" ResourceKey="Robots">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="robots" HelpText="Specify your robots.txt instructions to provide bots with guidance on which parts of your site should be indexed" ResourceKey="Robots">Instructions: </Label>
<div class="col-sm-9">
<textarea id="robots" class="form-control" @bind="@_robots" rows="3"></textarea>
</div>
</div>
</div>
<br />
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
</TabPanel>
</TabStrip> </TabStrip>
} }
@ -113,6 +125,7 @@ else
private string _filter = ""; private string _filter = "";
private int _retention = 30; private int _retention = 30;
private string _correlation = "true"; private string _correlation = "true";
private string _robots = "";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
@ -139,7 +152,8 @@ else
_filter = SettingService.GetSetting(settings, "VisitorFilter", Constants.DefaultVisitorFilter); _filter = SettingService.GetSetting(settings, "VisitorFilter", Constants.DefaultVisitorFilter);
_retention = int.Parse(SettingService.GetSetting(settings, "VisitorRetention", "30")); _retention = int.Parse(SettingService.GetSetting(settings, "VisitorRetention", "30"));
_correlation = SettingService.GetSetting(settings, "VisitorCorrelation", "true"); _correlation = SettingService.GetSetting(settings, "VisitorCorrelation", "true");
} _robots = SettingService.GetSetting(settings, "Robots", "");
}
private async void TypeChanged(ChangeEventArgs e) private async void TypeChanged(ChangeEventArgs e)
{ {
@ -191,6 +205,7 @@ else
settings = SettingService.SetSetting(settings, "VisitorFilter", _filter, true); settings = SettingService.SetSetting(settings, "VisitorFilter", _filter, true);
settings = SettingService.SetSetting(settings, "VisitorRetention", _retention.ToString(), true); settings = SettingService.SetSetting(settings, "VisitorRetention", _retention.ToString(), true);
settings = SettingService.SetSetting(settings, "VisitorCorrelation", _correlation, true); settings = SettingService.SetSetting(settings, "VisitorCorrelation", _correlation, true);
settings = SettingService.SetSetting(settings, "Robots", _robots, true);
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId); await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success); AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);

View File

@ -163,6 +163,13 @@
[Parameter] [Parameter]
public EventCallback<int> OnUpload { get; set; } // optional - executes a method in the calling component when a file is uploaded public EventCallback<int> OnUpload { get; set; } // optional - executes a method in the calling component when a file is uploaded
[Parameter]
public EventCallback<int> OnSelectFolder { get; set; } // optional - executes a method in the calling component when a folder is selected
[Parameter]
public EventCallback<int> OnSelectFile { get; set; } // optional - executes a method in the calling component when a file is selected
[Obsolete("Use OnSelectFile instead.")]
[Parameter] [Parameter]
public EventCallback<int> OnSelect { get; set; } // optional - executes a method in the calling component when a file is selected public EventCallback<int> OnSelect { get; set; } // optional - executes a method in the calling component when a file is selected
@ -300,6 +307,8 @@
FileId = -1; FileId = -1;
_file = null; _file = null;
_image = string.Empty; _image = string.Empty;
await OnSelectFolder.InvokeAsync(FolderId);
StateHasChanged(); StateHasChanged();
} }
catch (Exception ex) catch (Exception ex)
@ -315,7 +324,10 @@
_message = string.Empty; _message = string.Empty;
FileId = int.Parse((string)e.Value); FileId = int.Parse((string)e.Value);
await SetImage(); await SetImage();
#pragma warning disable CS0618
await OnSelect.InvokeAsync(FileId); await OnSelect.InvokeAsync(FileId);
#pragma warning restore CS0618
await OnSelectFile.InvokeAsync(FileId);
StateHasChanged(); StateHasChanged();
} }
@ -438,7 +450,10 @@
{ {
FileId = file.FileId; FileId = file.FileId;
await SetImage(); await SetImage();
#pragma warning disable CS0618
await OnSelect.InvokeAsync(FileId); await OnSelect.InvokeAsync(FileId);
#pragma warning restore CS0618
await OnSelectFile.InvokeAsync(FileId);
await OnUpload.InvokeAsync(FileId); await OnUpload.InvokeAsync(FileId);
} }
await GetFiles(); await GetFiles();
@ -489,7 +504,10 @@
await GetFiles(); await GetFiles();
FileId = -1; FileId = -1;
await SetImage(); await SetImage();
#pragma warning disable CS0618
await OnSelect.InvokeAsync(FileId); await OnSelect.InvokeAsync(FileId);
#pragma warning restore CS0618
await OnSelectFile.InvokeAsync(FileId);
StateHasChanged(); StateHasChanged();
} }
catch (Exception ex) catch (Exception ex)

View File

@ -60,7 +60,7 @@
@foreach (User user in _users) @foreach (User user in _users)
{ {
<tr> <tr>
<td>@user.DisplayName</td> <td>@user.DisplayName (@user.Username)</td>
@foreach (var permissionname in _permissionnames) @foreach (var permissionname in _permissionnames)
{ {
<td style="text-align: center; width: 1px;"> <td style="text-align: center; width: 1px;">
@ -270,8 +270,8 @@
private async Task<Dictionary<string, string>> GetUsers(string filter) private async Task<Dictionary<string, string>> GetUsers(string filter)
{ {
var users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered); var users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
return users.Where(item => item.User.DisplayName.Contains(filter, StringComparison.OrdinalIgnoreCase)) return users.Where(item => item.User.DisplayName.Contains(filter, StringComparison.OrdinalIgnoreCase) || item.User.Username.Contains(filter, StringComparison.OrdinalIgnoreCase))
.ToDictionary(item => item.UserId.ToString(), item => item.User.DisplayName); .ToDictionary(item => item.UserId.ToString(), item => item.User.DisplayName + " (" + item.User.Username + ")");
} }
private async Task AddUser() private async Task AddUser()

View File

@ -4,7 +4,7 @@
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Version>6.1.1</Version> <Version>6.1.2</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -12,7 +12,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/v6.1.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2</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>
@ -22,16 +22,22 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.3" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.3" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.4" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.3" /> <PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.4" />
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.3" /> <PackageReference Include="Microsoft.Extensions.Http" Version="9.0.4" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Oqtane.Shared\Oqtane.Shared.csproj" /> <ProjectReference Include="..\Oqtane.Shared\Oqtane.Shared.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Content Update="Installer\Controls\AzureSqlConfig.razor">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</Content>
</ItemGroup>
<PropertyGroup> <PropertyGroup>
<PublishTrimmed>false</PublishTrimmed> <PublishTrimmed>false</PublishTrimmed>
<BlazorEnableCompression>false</BlazorEnableCompression> <BlazorEnableCompression>false</BlazorEnableCompression>

View File

@ -121,7 +121,7 @@
<value>Server:</value> <value>Server:</value>
</data> </data>
<data name="Server.HelpText" xml:space="preserve"> <data name="Server.HelpText" xml:space="preserve">
<value>Enter the database server name. This might include a port number as well if you are using a cloud service (ie. servername.database.windows.net,1433) </value> <value>Enter the database server name. This might include a port number as well if you are using a cloud service.</value>
</data> </data>
<data name="Database.Text" xml:space="preserve"> <data name="Database.Text" xml:space="preserve">
<value>Database:</value> <value>Database:</value>

View File

@ -147,8 +147,8 @@
<data name="Owner.HelpText" xml:space="preserve"> <data name="Owner.HelpText" xml:space="preserve">
<value>The owner or creator of the module</value> <value>The owner or creator of the module</value>
</data> </data>
<data name="ReferenceUrl.HelpText" xml:space="preserve"> <data name="Url.HelpText" xml:space="preserve">
<value>The reference url of the module</value> <value>The url of the module</value>
</data> </data>
<data name="Contact.HelpText" xml:space="preserve"> <data name="Contact.HelpText" xml:space="preserve">
<value>The contact for the module</value> <value>The contact for the module</value>
@ -171,8 +171,8 @@
<data name="Owner.Text" xml:space="preserve"> <data name="Owner.Text" xml:space="preserve">
<value>Owner: </value> <value>Owner: </value>
</data> </data>
<data name="ReferenceUrl.Text" xml:space="preserve"> <data name="Url.Text" xml:space="preserve">
<value>Reference Url: </value> <value>Url: </value>
</data> </data>
<data name="Contact.Text" xml:space="preserve"> <data name="Contact.Text" xml:space="preserve">
<value>Contact: </value> <value>Contact: </value>

View File

@ -159,4 +159,13 @@
<data name="Enabled" xml:space="preserve"> <data name="Enabled" xml:space="preserve">
<value>Enabled?</value> <value>Enabled?</value>
</data> </data>
<data name="Synchronize" xml:space="preserve">
<value>Synchronize</value>
</data>
<data name="Success.Module.Synchronize" xml:space="preserve">
<value>Modules Have Been Successfully Synchronized With The Marketplace</value>
</data>
<data name="Error.Module.Synchronize" xml:space="preserve">
<value>Error Synchronizing Modules With The Marketplace</value>
</data>
</root> </root>

View File

@ -132,8 +132,8 @@
<data name="Owner.Text" xml:space="preserve"> <data name="Owner.Text" xml:space="preserve">
<value>Owner: </value> <value>Owner: </value>
</data> </data>
<data name="ReferenceUrl.Text" xml:space="preserve"> <data name="Url.Text" xml:space="preserve">
<value>Reference Url: </value> <value>Url: </value>
</data> </data>
<data name="Contact.Text" xml:space="preserve"> <data name="Contact.Text" xml:space="preserve">
<value>Contact: </value> <value>Contact: </value>
@ -153,8 +153,8 @@
<data name="Owner.HelpText" xml:space="preserve"> <data name="Owner.HelpText" xml:space="preserve">
<value>The owner or creator of the theme</value> <value>The owner or creator of the theme</value>
</data> </data>
<data name="ReferenceUrl.HelpText" xml:space="preserve"> <data name="Url.HelpText" xml:space="preserve">
<value>The reference url of the theme</value> <value>The url of the theme</value>
</data> </data>
<data name="Contact.HelpText" xml:space="preserve"> <data name="Contact.HelpText" xml:space="preserve">
<value>The contact for the theme</value> <value>The contact for the theme</value>

View File

@ -159,4 +159,13 @@
<data name="Assign" xml:space="preserve"> <data name="Assign" xml:space="preserve">
<value>Assign</value> <value>Assign</value>
</data> </data>
<data name="Synchronize" xml:space="preserve">
<value>Synchronize</value>
</data>
<data name="Success.Theme.Synchronize" xml:space="preserve">
<value>Themes Have Been Successfully Synchronized With The Marketplace</value>
</data>
<data name="Error.Theme.Synchronize" xml:space="preserve">
<value>Error Synchronizing Themes With The Marketplace</value>
</data>
</root> </root>

View File

@ -151,6 +151,12 @@
<value>You Cannot Perform A System Update In A Development Environment</value> <value>You Cannot Perform A System Update In A Development Environment</value>
</data> </data>
<data name="Disclaimer.Text" xml:space="preserve"> <data name="Disclaimer.Text" xml:space="preserve">
<value>Please Note That The System Update Capability Is A Simplified Upgrade Process Intended For Small To Medium Sized Installations. For Larger Enterprise Installations You Will Want To Use A Manual Upgrade Process. Also Note That The System Update Capability Is Not Recommended When Using Microsoft Azure Due To Environmental Limitations.</value> <value>Please Note That The System Update Capability Is A Simplified Upgrade Process Intended For Small To Medium Sized Installations. For Larger Enterprise Installations You Will Want To Use A Manual Upgrade Process.</value>
</data>
<data name="Backup.Text" xml:space="preserve">
<value>Backup Files?</value>
</data>
<data name="Backup.HelpText" xml:space="preserve">
<value>Specify if you want to backup files during the upgrade process. Disabling this option will result in a better experience in some environments.</value>
</data> </data>
</root> </root>

View File

@ -513,4 +513,10 @@
<data name="LogoutEverywhere.HelpText" xml:space="preserve"> <data name="LogoutEverywhere.HelpText" xml:space="preserve">
<value>Do you want users to be logged out of every active session on any device, or only their current session?</value> <value>Do you want users to be logged out of every active session on any device, or only their current session?</value>
</data> </data>
<data name="AllowHostRole.Text" xml:space="preserve">
<value>Allow Host Role?</value>
</data>
<data name="AllowHostRole.HelpText" xml:space="preserve">
<value>Indicate if host roles are supported from the identity provider. Please use caution with this option as it allows the host user to administrate every site within your installation.</value>
</data>
</root> </root>

View File

@ -198,4 +198,13 @@
<data name="Duration.Text" xml:space="preserve"> <data name="Duration.Text" xml:space="preserve">
<value>Session Duration:</value> <value>Session Duration:</value>
</data> </data>
<data name="Robots.Heading" xml:space="preserve">
<value>Robots.txt</value>
</data>
<data name="Robots.Text" xml:space="preserve">
<value>Instructions:</value>
</data>
<data name="Robots.HelpText" xml:space="preserve">
<value>Specify your robots.txt instructions to provide bots with guidance on which parts of your site should be indexed</value>
</data>
</root> </root>

View File

@ -47,9 +47,9 @@ namespace Oqtane.Services
return await PostJsonAsync<InstallConfig,Installation>(ApiUrl, config); return await PostJsonAsync<InstallConfig,Installation>(ApiUrl, config);
} }
public async Task<Installation> Upgrade() public async Task<Installation> Upgrade(bool backup)
{ {
return await GetJsonAsync<Installation>($"{ApiUrl}/upgrade"); return await GetJsonAsync<Installation>($"{ApiUrl}/upgrade/?backup={backup}");
} }
public async Task RestartAsync() public async Task RestartAsync()

View File

@ -26,8 +26,9 @@ namespace Oqtane.Services
/// <summary> /// <summary>
/// Starts the upgrade process /// Starts the upgrade process
/// </summary> /// </summary>
/// <param name="backup">indicates if files should be backed up during upgrade</param>
/// <returns>internal status/message object</returns> /// <returns>internal status/message object</returns>
Task<Installation> Upgrade(); Task<Installation> Upgrade(bool backup);
/// <summary> /// <summary>
/// Restarts the installation /// Restarts the installation

View File

@ -10,10 +10,11 @@ namespace Oqtane.Services
public interface IJobLogService public interface IJobLogService
{ {
/// <summary> /// <summary>
/// Return a list of all <see cref="JobLog"/> entries /// Return a list of <see cref="JobLog"/> entries
/// </summary> /// </summary>
/// <param name="jobId"></param>
/// <returns></returns> /// <returns></returns>
Task<List<JobLog>> GetJobLogsAsync(); Task<List<JobLog>> GetJobLogsAsync(int jobId);
/// <summary> /// <summary>
/// Return a <see cref="JobLog"/> entry for the given Id /// Return a <see cref="JobLog"/> entry for the given Id

View File

@ -15,10 +15,9 @@ namespace Oqtane.Services
private string Apiurl => CreateApiUrl("JobLog"); private string Apiurl => CreateApiUrl("JobLog");
public async Task<List<JobLog>> GetJobLogsAsync() public async Task<List<JobLog>> GetJobLogsAsync(int jobId)
{ {
List<JobLog> joblogs = await GetJsonAsync<List<JobLog>>(Apiurl); return await GetJsonAsync<List<JobLog>>($"{Apiurl}?jobid={jobId}");
return joblogs.OrderBy(item => item.StartDate).ToList();
} }
public async Task<JobLog> GetJobLogAsync(int jobLogId) public async Task<JobLog> GetJobLogAsync(int jobLogId)

View File

@ -6,8 +6,7 @@
@if (_enabled && !Hidden) @if (_enabled && !Hidden)
{ {
<div class="gdpr-consent-bar bg-light text-dark @(_showBanner ? "px-0 py-3 pt-5 pt-sm-3 pe-sm-5 ps-sm-3" : "p-0") fixed-bottom">
<div class="gdpr-consent-bar bg-light text-dark @(_showBanner ? "p-3" : "p-0") pe-5 fixed-bottom">
<form method="post" @formname="CookieConsentForm" @onsubmit="async () => await AcceptPolicy()" data-enhance> <form method="post" @formname="CookieConsentForm" @onsubmit="async () => await AcceptPolicy()" data-enhance>
@if (_showBanner) @if (_showBanner)
{ {

View File

@ -2,19 +2,18 @@
@using System.Net @using System.Net
@inherits ThemeControlBase @inherits ThemeControlBase
@inject IStringLocalizer<UserProfile> Localizer @inject IStringLocalizer<UserProfile> Localizer
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
<span class="app-profile"> <span class="app-profile">
@if (PageState.User != null) @if (PageState.User != null)
{ {
<a href="@NavigateUrl("profile", "returnurl=" + _returnurl)" class="@CssClass">@PageState.User.Username</a> <a href="@_profileurl" class="@CssClass">@PageState.User.Username</a>
} }
else else
{ {
@if (ShowRegister && PageState.Site.AllowRegistration) @if (ShowRegister && PageState.Site.AllowRegistration)
{ {
<a href="@NavigateUrl("register", "returnurl=" + _returnurl)" class="@CssClass">@Localizer["Register"]</a> <a href="@_registerurl" class="@CssClass">@Localizer["Register"]</a>
} }
} }
</span> </span>
@ -23,23 +22,50 @@
[Parameter] [Parameter]
public bool ShowRegister { get; set; } public bool ShowRegister { get; set; }
[Parameter] [Parameter]
public string CssClass { get; set; } = "btn btn-primary"; public string CssClass { get; set; } = "btn btn-primary";
[Parameter]
public string RegisterUrl { get; set; } // optional parameter to specify a custom registration url
[Parameter]
public string ProfileUrl { get; set; } // optional parameter to specify a custom user profile url
private string _registerurl = "";
private string _profileurl = "";
private string _returnurl = ""; private string _returnurl = "";
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
if (!PageState.QueryString.ContainsKey("returnurl")) if (!PageState.QueryString.ContainsKey("returnurl"))
{ {
// remember current url // remember current url
_returnurl = WebUtility.UrlEncode(PageState.Route.PathAndQuery); _returnurl = WebUtility.UrlEncode(PageState.Route.PathAndQuery);
} }
else else
{ {
// use existing value // use existing value
_returnurl = PageState.QueryString["returnurl"]; _returnurl = PageState.QueryString["returnurl"];
} }
if (!string.IsNullOrEmpty(RegisterUrl))
{
_registerurl = RegisterUrl + "?returnurl=" + (RegisterUrl.Contains("://") ? WebUtility.UrlEncode(PageState.Route.RootUrl) + _returnurl : _returnurl);
}
else
{
_registerurl = NavigateUrl("register", "returnurl=" + _returnurl);
}
if (!string.IsNullOrEmpty(ProfileUrl))
{
_registerurl = ProfileUrl + "?returnurl=" + (ProfileUrl.Contains("://") ? WebUtility.UrlEncode(PageState.Route.RootUrl) + _returnurl : _returnurl);
}
else
{
_registerurl = NavigateUrl("profile", "returnurl=" + _returnurl);
}
} }
} }

View File

@ -623,7 +623,7 @@
} }
// ensure resource does not exist already // ensure resource does not exist already
if (!pageresources.Exists(item => item.Url.ToLower() == resource.Url.ToLower())) if (!pageresources.Exists(item => Utilities.GetUrlPath(item.Url).ToLower() == Utilities.GetUrlPath(resource.Url).ToLower()))
{ {
pageresources.Add(resource.Clone(level, name, fingerprint)); pageresources.Add(resource.Clone(level, name, fingerprint));
} }

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<Version>6.1.1</Version> <Version>6.1.2</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/v6.1.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2</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>
@ -42,7 +42,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<MySQLFiles Include="$(OutputPath)Oqtane.Database.MySQL.dll;$(OutputPath)Oqtane.Database.MySQL.pdb;$(OutputPath)Pomelo.EntityFrameworkCore.MySql.dll;$(OutputPath)MySql.Data.dll" DestinationPath="..\Oqtane.Server\bin\$(Configuration)\net9.0\%(Filename)%(Extension)" /> <MySQLFiles Include="$(OutputPath)Oqtane.Database.MySQL.dll;$(OutputPath)Oqtane.Database.MySQL.pdb;$(OutputPath)Pomelo.EntityFrameworkCore.MySql.dll;$(OutputPath)MySqlConnector.dll;$(OutputPath)MySql.Data.dll" DestinationPath="..\Oqtane.Server\bin\$(Configuration)\net9.0\%(Filename)%(Extension)" />
</ItemGroup> </ItemGroup>
<Target Name="PublishProvider" AfterTargets="PostBuildEvent" Inputs="@(MySQLFiles)" Outputs="@(MySQLFiles->'%(DestinationPath)')"> <Target Name="PublishProvider" AfterTargets="PostBuildEvent" Inputs="@(MySQLFiles)" Outputs="@(MySQLFiles->'%(DestinationPath)')">

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<Version>6.1.1</Version> <Version>6.1.2</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/v6.1.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2</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>
@ -34,7 +34,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" /> <PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.4" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
</ItemGroup> </ItemGroup>

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<Version>6.1.1</Version> <Version>6.1.2</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/v6.1.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2</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>
@ -33,7 +33,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.4" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<Version>6.1.1</Version> <Version>6.1.2</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/v6.1.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2</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>
@ -33,7 +33,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.4" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -6,7 +6,7 @@
<!-- <TargetFrameworks>net9.0-android;net9.0-ios;net9.0-maccatalyst</TargetFrameworks> --> <!-- <TargetFrameworks>net9.0-android;net9.0-ios;net9.0-maccatalyst</TargetFrameworks> -->
<!-- <TargetFrameworks>$(TargetFrameworks);net9.0-tizen</TargetFrameworks> --> <!-- <TargetFrameworks>$(TargetFrameworks);net9.0-tizen</TargetFrameworks> -->
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<Version>6.1.1</Version> <Version>6.1.2</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/v6.1.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2</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>
@ -30,7 +30,7 @@
<ApplicationId>com.oqtane.maui</ApplicationId> <ApplicationId>com.oqtane.maui</ApplicationId>
<!-- Versions --> <!-- Versions -->
<ApplicationDisplayVersion>6.1.1</ApplicationDisplayVersion> <ApplicationDisplayVersion>6.1.2</ApplicationDisplayVersion>
<ApplicationVersion>1</ApplicationVersion> <ApplicationVersion>1</ApplicationVersion>
<!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged --> <!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged -->
@ -67,14 +67,14 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="9.0.3" /> <PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="9.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.3" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.4" />
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.3" /> <PackageReference Include="Microsoft.Extensions.Http" Version="9.0.4" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.3" /> <PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.4" />
<PackageReference Include="System.Net.Http.Json" Version="9.0.3" /> <PackageReference Include="System.Net.Http.Json" Version="9.0.4" />
<PackageReference Include="Microsoft.Maui.Controls" Version="9.0.40" /> <PackageReference Include="Microsoft.Maui.Controls" Version="9.0.50" />
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="9.0.40" /> <PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="9.0.50" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="9.0.40" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="9.0.50" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -2,7 +2,7 @@
<package> <package>
<metadata> <metadata>
<id>Oqtane.Client</id> <id>Oqtane.Client</id>
<version>6.1.1</version> <version>6.1.2</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/v6.1.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2</releaseNotes>
<readme>readme.md</readme> <readme>readme.md</readme>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>

View File

@ -2,7 +2,7 @@
<package> <package>
<metadata> <metadata>
<id>Oqtane.Framework</id> <id>Oqtane.Framework</id>
<version>6.1.1</version> <version>6.1.2</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/v6.1.1/Oqtane.Framework.6.1.1.Upgrade.zip</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v6.1.2/Oqtane.Framework.6.1.2.Upgrade.zip</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2</releaseNotes>
<readme>readme.md</readme> <readme>readme.md</readme>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane framework</tags> <tags>oqtane framework</tags>

View File

@ -2,7 +2,7 @@
<package> <package>
<metadata> <metadata>
<id>Oqtane.Server</id> <id>Oqtane.Server</id>
<version>6.1.1</version> <version>6.1.2</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/v6.1.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2</releaseNotes>
<readme>readme.md</readme> <readme>readme.md</readme>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>

View File

@ -2,7 +2,7 @@
<package> <package>
<metadata> <metadata>
<id>Oqtane.Shared</id> <id>Oqtane.Shared</id>
<version>6.1.1</version> <version>6.1.2</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/v6.1.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2</releaseNotes>
<readme>readme.md</readme> <readme>readme.md</readme>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>

View File

@ -2,7 +2,7 @@
<package> <package>
<metadata> <metadata>
<id>Oqtane.Updater</id> <id>Oqtane.Updater</id>
<version>6.1.1</version> <version>6.1.2</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/v6.1.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2</releaseNotes>
<readme>readme.md</readme> <readme>readme.md</readme>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>

View File

@ -1 +1 @@
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.1.1.Install.zip" -Force Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.1.2.Install.zip" -Force

View File

@ -1 +1 @@
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.1.1.Upgrade.zip" -Force Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.1.2.Upgrade.zip" -Force

View File

@ -764,7 +764,7 @@
} }
// ensure resource does not exist already // ensure resource does not exist already
if (!pageresources.Exists(item => item.Url.ToLower() == resource.Url.ToLower())) if (!pageresources.Exists(item => Utilities.GetUrlPath(item.Url).ToLower() == Utilities.GetUrlPath(resource.Url).ToLower()))
{ {
pageresources.Add(resource.Clone(level, name, fingerprint)); pageresources.Add(resource.Clone(level, name, fingerprint));
} }

View File

@ -23,6 +23,7 @@ using System.IO.Compression;
using Oqtane.Services; using Oqtane.Services;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.Net.Http.Headers;
// ReSharper disable StringIndexOfIsCultureSpecific.1 // ReSharper disable StringIndexOfIsCultureSpecific.1
@ -735,6 +736,10 @@ namespace Oqtane.Controllers
} }
if (!string.IsNullOrEmpty(imagepath)) if (!string.IsNullOrEmpty(imagepath))
{ {
if (!string.IsNullOrEmpty(file.Folder.CacheControl))
{
HttpContext.Response.Headers.Append(HeaderNames.CacheControl, value: file.Folder.CacheControl);
}
return PhysicalFile(imagepath, file.GetMimeType()); return PhysicalFile(imagepath, file.GetMimeType());
} }
else else

View File

@ -280,9 +280,14 @@ namespace Oqtane.Controllers
var folder = _folders.GetFolder(id, false); var folder = _folders.GetFolder(id, false);
if (folder != null && folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, folder.SiteId, EntityNames.Folder, id, PermissionNames.Edit)) if (folder != null && folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, folder.SiteId, EntityNames.Folder, id, PermissionNames.Edit))
{ {
if (Directory.Exists(_folders.GetFolderPath(folder))) var folderPath = _folders.GetFolderPath(folder);
if (Directory.Exists(folderPath))
{ {
Directory.Delete(_folders.GetFolderPath(folder)); foreach (var filePath in Directory.GetFiles(folderPath))
{
System.IO.File.Delete(filePath);
}
Directory.Delete(folderPath);
} }
_folders.DeleteFolder(id); _folders.DeleteFolder(id);
_syncManager.AddSyncEvent(_alias, EntityNames.Folder, folder.FolderId, SyncEventActions.Delete); _syncManager.AddSyncEvent(_alias, EntityNames.Folder, folder.FolderId, SyncEventActions.Delete);

View File

@ -88,10 +88,10 @@ namespace Oqtane.Controllers
[HttpGet("upgrade")] [HttpGet("upgrade")]
[Authorize(Roles = RoleNames.Host)] [Authorize(Roles = RoleNames.Host)]
public Installation Upgrade() public Installation Upgrade(string backup)
{ {
var installation = new Installation { Success = true, Message = "" }; var installation = new Installation { Success = true, Message = "" };
_installationManager.UpgradeFramework(); _installationManager.UpgradeFramework(bool.Parse(backup));
return installation; return installation;
} }

View File

@ -17,12 +17,12 @@ namespace Oqtane.Controllers
_jobLogs = jobLogs; _jobLogs = jobLogs;
} }
// GET: api/<controller> // GET: api/<controller>?jobid=x
[HttpGet] [HttpGet]
[Authorize(Roles = RoleNames.Host)] [Authorize(Roles = RoleNames.Host)]
public IEnumerable<JobLog> Get() public IEnumerable<JobLog> Get(string jobid)
{ {
return _jobLogs.GetJobLogs(); return _jobLogs.GetJobLogs(int.Parse(jobid));
} }
// GET api/<controller>/5 // GET api/<controller>/5

View File

@ -90,33 +90,26 @@ namespace Oqtane.Controllers
package = await GetJson<Package>(client, url + $"/api/registry/package/?id={_configManager.GetInstallationId()}&package={packageid}&version={version}&download={download}&email={WebUtility.UrlEncode(GetPackageRegistryEmail())}"); package = await GetJson<Package>(client, url + $"/api/registry/package/?id={_configManager.GetInstallationId()}&package={packageid}&version={version}&download={download}&email={WebUtility.UrlEncode(GetPackageRegistryEmail())}");
} }
if (package != null) if (package != null && bool.Parse(install))
{ {
if (bool.Parse(install)) using (var httpClient = new HttpClient())
{ {
using (var httpClient = new HttpClient()) var folder = Path.Combine(_environment.ContentRootPath, Constants.PackagesFolder);
var response = await httpClient.GetAsync(package.PackageUrl).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{ {
var folder = Path.Combine(_environment.ContentRootPath, Constants.PackagesFolder); string filename = packageid + "." + version + ".nupkg";
var response = await httpClient.GetAsync(package.PackageUrl).ConfigureAwait(false); using (var fileStream = new FileStream(Path.Combine(Constants.PackagesFolder, filename), FileMode.Create, FileAccess.Write, FileShare.None))
if (response.IsSuccessStatusCode)
{ {
string filename = packageid + "." + version + ".nupkg"; await response.Content.CopyToAsync(fileStream).ConfigureAwait(false);
using (var fileStream = new FileStream(Path.Combine(Constants.PackagesFolder, filename), FileMode.Create, FileAccess.Write, FileShare.None))
{
await response.Content.CopyToAsync(fileStream).ConfigureAwait(false);
}
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Create, "Could Not Download {PackageUrl}", package.PackageUrl);
} }
} }
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Create, "Could Not Download {PackageUrl}", package.PackageUrl);
}
} }
} }
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Create, "Package {PackageId}.{Version} Is Not Registered In The Marketplace", packageid, version);
}
} }
return package; return package;
} }

View File

@ -533,7 +533,8 @@ namespace Oqtane.Extensions
if (claimsPrincipal.Claims.Any(item => item.Type == httpContext.GetSiteSettings().GetValue("ExternalLogin:RoleClaimType", ""))) if (claimsPrincipal.Claims.Any(item => item.Type == httpContext.GetSiteSettings().GetValue("ExternalLogin:RoleClaimType", "")))
{ {
var _roles = httpContext.RequestServices.GetRequiredService<IRoleRepository>(); var _roles = httpContext.RequestServices.GetRequiredService<IRoleRepository>();
var roles = _roles.GetRoles(user.SiteId).ToList(); // global roles excluded ie. host users cannot be added/deleted var allowhostrole = bool.Parse(httpContext.GetSiteSettings().GetValue("ExternalLogin:AllowHostRole", "false"));
var roles = _roles.GetRoles(user.SiteId, allowhostrole).ToList();
var mappings = httpContext.GetSiteSettings().GetValue("ExternalLogin:RoleClaimMappings", "").Split(','); var mappings = httpContext.GetSiteSettings().GetValue("ExternalLogin:RoleClaimMappings", "").Split(',');
foreach (var claim in claimsPrincipal.Claims.Where(item => item.Type == httpContext.GetSiteSettings().GetValue("ExternalLogin:RoleClaimType", ""))) foreach (var claim in claimsPrincipal.Claims.Where(item => item.Type == httpContext.GetSiteSettings().GetValue("ExternalLogin:RoleClaimType", "")))
@ -583,8 +584,9 @@ namespace Oqtane.Extensions
} }
} }
var userrole = userRoles.FirstOrDefault(item => item.Role.Name == RoleNames.Registered); var host = userRoles.FirstOrDefault(item => item.Role.Name == RoleNames.Host);
if (!user.IsDeleted && userrole != null && Utilities.IsEffectiveAndNotExpired(userrole.EffectiveDate, userrole.ExpiryDate)) var registered = userRoles.FirstOrDefault(item => item.Role.Name == RoleNames.Registered);
if (!user.IsDeleted && (host != null || registered != null && Utilities.IsEffectiveAndNotExpired(registered.EffectiveDate, registered.ExpiryDate)))
{ {
// update user // update user
user.LastLoginOn = DateTime.UtcNow; user.LastLoginOn = DateTime.UtcNow;

View File

@ -731,6 +731,7 @@ namespace Oqtane.Infrastructure
{ {
_configManager.AddOrUpdateSetting($"{SettingKeys.DatabaseSection}:{SettingKeys.DatabaseTypeKey}", Constants.DefaultDBType, true); _configManager.AddOrUpdateSetting($"{SettingKeys.DatabaseSection}:{SettingKeys.DatabaseTypeKey}", Constants.DefaultDBType, true);
} }
if (!_configManager.GetSection(SettingKeys.AvailableDatabasesSection).Exists()) if (!_configManager.GetSection(SettingKeys.AvailableDatabasesSection).Exists())
{ {
string databases = "["; string databases = "[";
@ -742,6 +743,19 @@ namespace Oqtane.Infrastructure
databases += "]"; databases += "]";
_configManager.AddOrUpdateSetting(SettingKeys.AvailableDatabasesSection, databases, true); _configManager.AddOrUpdateSetting(SettingKeys.AvailableDatabasesSection, databases, true);
} }
var availabledatabases = _configManager.GetSection(SettingKeys.AvailableDatabasesSection).GetChildren();
if (!availabledatabases.Any(item => item.GetSection("Name").Value == "Azure SQL"))
{
// Azure SQL added in 6.1.2
string databases = "[";
foreach (var database in availabledatabases)
{
databases += "{ " + $"\"Name\": \"{database["Name"]}\", \"ControlType\": \"{database["ControlType"]}\", \"DBTYpe\": \"{database["DBType"]}\"" + " },";
}
databases += "{ \"Name\": \"Azure SQL\", \"ControlType\": \"Oqtane.Installer.Controls.AzureSqlConfig, Oqtane.Client\", \"DBTYpe\": \"Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Database.SqlServer\" }";
databases += "]";
_configManager.AddOrUpdateSetting(SettingKeys.AvailableDatabasesSection, databases, true);
}
} }
} }
} }

View File

@ -380,7 +380,7 @@ namespace Oqtane.Infrastructure
File.WriteAllText(assemblyLogPath, JsonSerializer.Serialize(assemblies, new JsonSerializerOptions { WriteIndented = true })); File.WriteAllText(assemblyLogPath, JsonSerializer.Serialize(assemblies, new JsonSerializerOptions { WriteIndented = true }));
} }
public async Task UpgradeFramework() public async Task UpgradeFramework(bool backup)
{ {
string folder = Path.Combine(_environment.ContentRootPath, Constants.PackagesFolder); string folder = Path.Combine(_environment.ContentRootPath, Constants.PackagesFolder);
if (Directory.Exists(folder)) if (Directory.Exists(folder))
@ -448,14 +448,14 @@ namespace Oqtane.Infrastructure
// install Oqtane.Upgrade zip package // install Oqtane.Upgrade zip package
if (File.Exists(upgradepackage)) if (File.Exists(upgradepackage))
{ {
FinishUpgrade(); FinishUpgrade(backup);
} }
} }
} }
} }
} }
private void FinishUpgrade() private void FinishUpgrade(bool backup)
{ {
// check if updater application exists // check if updater application exists
string Updater = Constants.UpdaterPackageId + ".dll"; string Updater = Constants.UpdaterPackageId + ".dll";
@ -469,7 +469,7 @@ namespace Oqtane.Infrastructure
{ {
WorkingDirectory = folder, WorkingDirectory = folder,
FileName = "dotnet", FileName = "dotnet",
Arguments = Path.Combine(folder, Updater) + " \"" + _environment.ContentRootPath + "\" \"" + _environment.WebRootPath + "\"", Arguments = Path.Combine(folder, Updater) + " \"" + _environment.ContentRootPath + "\" \"" + _environment.WebRootPath + "\" \"" + backup.ToString() + "\"",
UseShellExecute = false, UseShellExecute = false,
ErrorDialog = false, ErrorDialog = false,
CreateNoWindow = true, CreateNoWindow = true,

View File

@ -7,7 +7,7 @@ namespace Oqtane.Infrastructure
void InstallPackages(); void InstallPackages();
bool UninstallPackage(string PackageName); bool UninstallPackage(string PackageName);
int RegisterAssemblies(); int RegisterAssemblies();
Task UpgradeFramework(); Task UpgradeFramework(bool backup);
void RestartApplication(); void RestartApplication();
} }
} }

View File

@ -46,6 +46,8 @@ namespace Oqtane.Infrastructure
protected async Task ExecuteAsync(CancellationToken stoppingToken) protected async Task ExecuteAsync(CancellationToken stoppingToken)
{ {
await Task.Yield(); // required so that this method does not block startup
while (!stoppingToken.IsCancellationRequested) while (!stoppingToken.IsCancellationRequested)
{ {
using (var scope = _serviceScopeFactory.CreateScope()) using (var scope = _serviceScopeFactory.CreateScope())
@ -170,8 +172,7 @@ namespace Oqtane.Infrastructure
jobs.UpdateJob(job); jobs.UpdateJob(job);
// trim the job log // trim the job log
List<JobLog> logs = jobLogs.GetJobLogs().Where(item => item.JobId == job.JobId) List<JobLog> logs = jobLogs.GetJobLogs(job.JobId).ToList();
.OrderByDescending(item => item.JobLogId).ToList();
for (int i = logs.Count; i > job.RetentionHistory; i--) for (int i = logs.Count; i > job.RetentionHistory; i--)
{ {
jobLogs.DeleteJobLog(logs[i - 1].JobLogId); jobLogs.DeleteJobLog(logs[i - 1].JobLogId);

View File

@ -130,7 +130,12 @@ namespace Oqtane.Infrastructure
mailMessage.Subject = notification.Subject; mailMessage.Subject = notification.Subject;
//body //body
mailMessage.Body = notification.Body.Replace("\n", "<br />"); mailMessage.Body = notification.Body;
if (!mailMessage.Body.Contains("<") || !mailMessage.Body.Contains(">"))
{
// plain text messages should convert line breaks to HTML tags to preserve formatting
mailMessage.Body = mailMessage.Body.Replace("\n", "<br />");
}
// encoding // encoding
mailMessage.SubjectEncoding = System.Text.Encoding.UTF8; mailMessage.SubjectEncoding = System.Text.Encoding.UTF8;

View File

@ -74,8 +74,21 @@ namespace Oqtane.Infrastructure
// handle robots.txt root request (does not support subfolder aliases) // handle robots.txt root request (does not support subfolder aliases)
if (context.Request.Path.StartsWithSegments("/robots.txt") && string.IsNullOrEmpty(alias.Path)) if (context.Request.Path.StartsWithSegments("/robots.txt") && string.IsNullOrEmpty(alias.Path))
{ {
// allow all user agents and specify site map string robots = "";
var robots = $"User-agent: *\n\nSitemap: {context.Request.Scheme}://{alias.Name}/sitemap.xml"; if (sitesettings.ContainsKey("Robots") && !string.IsNullOrEmpty(sitesettings["Robots"]))
{
robots = sitesettings["Robots"];
}
else
{
// allow all user agents by default
robots = $"User-agent: *";
}
if (!robots.ToLower().Contains("Sitemap:"))
{
// add sitemap if not specified
robots += $"\n\nSitemap: {context.Request.Scheme}://{alias.Name}/sitemap.xml";
}
context.Response.ContentType = "text/plain"; context.Response.ContentType = "text/plain";
await context.Response.WriteAsync(robots); await context.Response.WriteAsync(robots);
return; return;

View File

@ -1,7 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;
using Oqtane.Documentation; using Oqtane.Documentation;
using Oqtane.Infrastructure;
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Shared; using Oqtane.Shared;
@ -182,6 +181,7 @@ namespace Oqtane.Infrastructure.SiteTemplates
Name = "Privacy", Name = "Privacy",
Parent = "", Parent = "",
Path = "privacy", Path = "privacy",
Order = seed + 11,
Icon = Icons.Eye, Icon = Icons.Eye,
IsNavigation = false, IsNavigation = false,
IsPersonalizable = false, IsPersonalizable = false,
@ -212,6 +212,7 @@ namespace Oqtane.Infrastructure.SiteTemplates
Name = "Terms", Name = "Terms",
Parent = "", Parent = "",
Path = "terms", Path = "terms",
Order = seed + 13,
Icon = Icons.List, Icon = Icons.List,
IsNavigation = false, IsNavigation = false,
IsPersonalizable = false, IsPersonalizable = false,
@ -242,7 +243,7 @@ namespace Oqtane.Infrastructure.SiteTemplates
Name = "Not Found", Name = "Not Found",
Parent = "", Parent = "",
Path = "404", Path = "404",
Order = seed + 11, Order = seed + 15,
Icon = Icons.X, Icon = Icons.X,
IsNavigation = false, IsNavigation = false,
IsPersonalizable = false, IsPersonalizable = false,

View File

@ -2,7 +2,6 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Oqtane.Documentation; using Oqtane.Documentation;
using Oqtane.Infrastructure;
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Repository; using Oqtane.Repository;
using Oqtane.Shared; using Oqtane.Shared;

View File

@ -1,6 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using Oqtane.Documentation; using Oqtane.Documentation;
using Oqtane.Infrastructure;
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Shared; using Oqtane.Shared;

View File

@ -473,6 +473,7 @@ namespace Oqtane.Infrastructure
Name = "Privacy", Name = "Privacy",
Parent = "", Parent = "",
Path = "privacy", Path = "privacy",
Order = 1011,
Icon = Icons.Eye, Icon = Icons.Eye,
IsNavigation = false, IsNavigation = false,
IsPersonalizable = false, IsPersonalizable = false,
@ -502,6 +503,7 @@ namespace Oqtane.Infrastructure
Name = "Terms", Name = "Terms",
Parent = "", Parent = "",
Path = "terms", Path = "terms",
Order = 1013,
Icon = Icons.List, Icon = Icons.List,
IsNavigation = false, IsNavigation = false,
IsPersonalizable = false, IsPersonalizable = false,

View File

@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Version>6.1.1</Version> <Version>6.1.2</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/v6.1.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2</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,21 +34,21 @@
<EmbeddedResource Include="Scripts\MigrateTenant.sql" /> <EmbeddedResource Include="Scripts\MigrateTenant.sql" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.3" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.3" /> <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.4" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.1" /> <PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.3"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.4">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.3" /> <PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="9.0.3" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="9.0.4" />
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="9.0.3" /> <PackageReference Include="Microsoft.Data.Sqlite.Core" Version="9.0.4" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.11" /> <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.11" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.7" /> <PackageReference Include="SixLabors.ImageSharp" Version="3.1.7" />
<PackageReference Include="HtmlAgilityPack" Version="1.12.0" /> <PackageReference Include="HtmlAgilityPack" Version="1.12.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.3.1" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Oqtane.Client\Oqtane.Client.csproj" /> <ProjectReference Include="..\Oqtane.Client\Oqtane.Client.csproj" />

View File

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using Oqtane.Models; using Oqtane.Models;
namespace Oqtane.Repository namespace Oqtane.Repository
@ -6,6 +6,7 @@ namespace Oqtane.Repository
public interface IJobLogRepository public interface IJobLogRepository
{ {
IEnumerable<JobLog> GetJobLogs(); IEnumerable<JobLog> GetJobLogs();
IEnumerable<JobLog> GetJobLogs(int jobId);
JobLog AddJobLog(JobLog jobLog); JobLog AddJobLog(JobLog jobLog);
JobLog UpdateJobLog(JobLog jobLog); JobLog UpdateJobLog(JobLog jobLog);
JobLog GetJobLog(int jobLogId); JobLog GetJobLog(int jobLogId);

View File

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Oqtane.Models; using Oqtane.Models;
@ -15,9 +15,17 @@ namespace Oqtane.Repository
} }
public IEnumerable<JobLog> GetJobLogs() public IEnumerable<JobLog> GetJobLogs()
{
return GetJobLogs(-1);
}
public IEnumerable<JobLog> GetJobLogs(int jobId)
{ {
return _db.JobLog return _db.JobLog
.AsNoTracking()
.Where(item => item.JobId == jobId || jobId == -1)
.Include(item => item.Job) // eager load jobs .Include(item => item.Job) // eager load jobs
.OrderByDescending(item => item.JobLogId)
.ToList(); .ToList();
} }

View File

@ -61,6 +61,9 @@ namespace Oqtane.Repository
public UserRole AddUserRole(UserRole userRole) public UserRole AddUserRole(UserRole userRole)
{ {
userRole.EffectiveDate = userRole.EffectiveDate.HasValue ? DateTime.SpecifyKind(userRole.EffectiveDate.Value, DateTimeKind.Utc) : userRole.EffectiveDate;
userRole.ExpiryDate = userRole.ExpiryDate.HasValue ? DateTime.SpecifyKind(userRole.ExpiryDate.Value, DateTimeKind.Utc) : userRole.ExpiryDate;
using var db = _dbContextFactory.CreateDbContext(); using var db = _dbContextFactory.CreateDbContext();
db.UserRole.Add(userRole); db.UserRole.Add(userRole);
db.SaveChanges(); db.SaveChanges();
@ -84,6 +87,9 @@ namespace Oqtane.Repository
public UserRole UpdateUserRole(UserRole userRole) public UserRole UpdateUserRole(UserRole userRole)
{ {
userRole.EffectiveDate = userRole.EffectiveDate.HasValue ? DateTime.SpecifyKind(userRole.EffectiveDate.Value, DateTimeKind.Utc) : userRole.EffectiveDate;
userRole.ExpiryDate = userRole.ExpiryDate.HasValue ? DateTime.SpecifyKind(userRole.ExpiryDate.Value, DateTimeKind.Utc) : userRole.ExpiryDate;
using var db = _dbContextFactory.CreateDbContext(); using var db = _dbContextFactory.CreateDbContext();
db.Entry(userRole).State = EntityState.Modified; db.Entry(userRole).State = EntityState.Modified;
db.SaveChanges(); db.SaveChanges();

View File

@ -70,7 +70,6 @@ namespace Oqtane
services.AddLocalization(options => options.ResourcesPath = "Resources"); services.AddLocalization(options => options.ResourcesPath = "Resources");
services.AddOptions<List<Database>>().Bind(Configuration.GetSection(SettingKeys.AvailableDatabasesSection)); services.AddOptions<List<Database>>().Bind(Configuration.GetSection(SettingKeys.AvailableDatabasesSection));
services.Configure<HostOptions>(opts => opts.ShutdownTimeout = TimeSpan.FromSeconds(10)); // increase from default of 5 seconds
// register scoped core services // register scoped core services
services.AddScoped<IAuthorizationHandler, PermissionHandler>() services.AddScoped<IAuthorizationHandler, PermissionHandler>()

View File

@ -13,11 +13,11 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.3" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.3" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.4" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.3" /> <PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.4" />
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.3" /> <PackageReference Include="Microsoft.Extensions.Http" Version="9.0.4" />
<PackageReference Include="System.Net.Http.Json" Version="9.0.3" /> <PackageReference Include="System.Net.Http.Json" Version="9.0.4" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -19,10 +19,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.3" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.3" /> <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.4" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.3" /> <PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.4" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -13,9 +13,9 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.3" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.3" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.4" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.3" /> <PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.4" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -4,4 +4,4 @@ set ProjectName=%2
del "*.nupkg" del "*.nupkg"
"..\..\[RootFolder]\oqtane.package\nuget.exe" pack %ProjectName%.nuspec -Properties targetframework=%TargetFramework%;projectname=%ProjectName% "..\..\[RootFolder]\oqtane.package\nuget.exe" pack %ProjectName%.nuspec -Properties targetframework=%TargetFramework%;projectname=%ProjectName%
XCOPY "*.nupkg" "..\..\[RootFolder]\Oqtane.Server\wwwroot\Packages\" /Y XCOPY "*.nupkg" "..\..\[RootFolder]\Oqtane.Server\Packages\" /Y

View File

@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Version>6.1.1</Version> <Version>6.1.2</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/v6.1.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2</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>
@ -19,11 +19,11 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.4" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.3" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.4" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" /> <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="System.Text.Json" Version="9.0.3" /> <PackageReference Include="System.Text.Json" Version="9.0.4" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -4,8 +4,8 @@ namespace Oqtane.Shared
{ {
public class Constants public class Constants
{ {
public static readonly string Version = "6.1.1"; public static readonly string Version = "6.1.2";
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,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,5.0.0,5.0.1,5.0.2,5.0.3,5.1.0,5.1.1,5.1.2,5.2.0,5.2.1,5.2.2,5.2.3,5.2.4,6.0.0,6.0.1,6.1.0,6.1.1"; public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,5.0.0,5.0.1,5.0.2,5.0.3,5.1.0,5.1.1,5.1.2,5.2.0,5.2.1,5.2.2,5.2.3,5.2.4,6.0.0,6.0.1,6.1.0,6.1.1,6.1.2";
public const string PackageId = "Oqtane.Framework"; public const string PackageId = "Oqtane.Framework";
public const string ClientId = "Oqtane.Client"; public const string ClientId = "Oqtane.Client";
public const string UpdaterPackageId = "Oqtane.Updater"; public const string UpdaterPackageId = "Oqtane.Updater";

View File

@ -121,6 +121,17 @@ namespace Oqtane.Shared
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 ImageUrl(Alias alias, string folderpath, string filename, int width, int height, string mode, string position, string background, int rotate, string format, bool recreate)
{
var aliasUrl = (alias != null && !string.IsNullOrEmpty(alias.Path)) ? "/" + alias.Path : "";
mode = string.IsNullOrEmpty(mode) ? "crop" : mode;
position = string.IsNullOrEmpty(position) ? "center" : position;
background = string.IsNullOrEmpty(background) ? "transparent" : background;
format = string.IsNullOrEmpty(format) ? "png" : format;
var querystring = $"?width={width}&height={height}&mode={mode}&position={position}&background={background}&rotate={rotate}&format={format}&recreate={recreate}";
return $"{alias?.BaseUrl}{aliasUrl}{Constants.FileUrl}{folderpath.Replace("\\", "/")}{filename}{querystring}";
}
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;
@ -480,6 +491,15 @@ namespace Oqtane.Shared
return querystring; return querystring;
} }
public static string GetUrlPath(string url)
{
if (url.Contains("?"))
{
url = url.Substring(0, url.IndexOf("?"));
}
return url;
}
public static string LogMessage(object @class, string message) public static string LogMessage(object @class, string message)
{ {
return $"[{@class.GetType()}] {message}"; return $"[{@class.GetType()}] {message}";

View File

@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<Version>6.1.1</Version> <Version>6.1.2</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/v6.1.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2</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

@ -15,17 +15,19 @@ namespace Oqtane.Updater
/// <param name="args"></param> /// <param name="args"></param>
static void Main(string[] args) static void Main(string[] args)
{ {
// requires 2 arguments - the ContentRootPath and the WebRootPath of the site // requires 3 arguments - the ContentRootPath, the WebRootPath of the site, and a backup flag
// for testing purposes you can uncomment and modify the logic below // for testing purposes you can uncomment and modify the logic below
//Array.Resize(ref args, 2); //Array.Resize(ref args, 3);
//args[0] = @"C:\yourpath\oqtane.framework\Oqtane.Server"; //args[0] = @"C:\yourpath\oqtane.framework\Oqtane.Server";
//args[1] = @"C:\yourpath\oqtane.framework\Oqtane.Server\wwwroot"; //args[1] = @"C:\yourpath\oqtane.framework\Oqtane.Server\wwwroot";
//args[2] = @"true";
if (args.Length == 2) if (args.Length == 3)
{ {
string contentrootfolder = args[0]; string contentrootfolder = args[0];
string webrootfolder = args[1]; string webrootfolder = args[1];
bool backup = bool.Parse(args[2]);
string deployfolder = Path.Combine(contentrootfolder, "Packages"); string deployfolder = Path.Combine(contentrootfolder, "Packages");
string backupfolder = Path.Combine(contentrootfolder, "Backup"); string backupfolder = Path.Combine(contentrootfolder, "Backup");
@ -49,6 +51,7 @@ namespace Oqtane.Updater
WriteLog(logFilePath, "Upgrade Process Started: " + DateTime.UtcNow.ToString() + Environment.NewLine); WriteLog(logFilePath, "Upgrade Process Started: " + DateTime.UtcNow.ToString() + Environment.NewLine);
WriteLog(logFilePath, "ContentRootPath: " + contentrootfolder + Environment.NewLine); WriteLog(logFilePath, "ContentRootPath: " + contentrootfolder + Environment.NewLine);
WriteLog(logFilePath, "WebRootPath: " + webrootfolder + Environment.NewLine); WriteLog(logFilePath, "WebRootPath: " + webrootfolder + Environment.NewLine);
if (packagename != "" && File.Exists(Path.Combine(webrootfolder, "app_offline.bak"))) if (packagename != "" && File.Exists(Path.Combine(webrootfolder, "app_offline.bak")))
{ {
WriteLog(logFilePath, "Located Upgrade Package: " + packagename + Environment.NewLine); WriteLog(logFilePath, "Located Upgrade Package: " + packagename + Environment.NewLine);
@ -74,8 +77,8 @@ namespace Oqtane.Updater
} }
bool success = true; bool success = true;
// ensure files are not locked
if (CanAccessFiles(files)) if (backup)
{ {
UpdateOfflineContent(offlineFilePath, offlineTemplate, 10, "Preparing Backup Folder"); UpdateOfflineContent(offlineFilePath, offlineTemplate, 10, "Preparing Backup Folder");
WriteLog(logFilePath, "Preparing Backup Folder: " + backupfolder + Environment.NewLine); WriteLog(logFilePath, "Preparing Backup Folder: " + backupfolder + Environment.NewLine);
@ -112,8 +115,28 @@ namespace Oqtane.Updater
{ {
Directory.CreateDirectory(Path.GetDirectoryName(filename)); Directory.CreateDirectory(Path.GetDirectoryName(filename));
} }
File.Copy(file, filename);
WriteLog(logFilePath, "Copy File: " + filename + Environment.NewLine); try
{
// try optimistically to backup the file
File.Copy(file, filename);
WriteLog(logFilePath, "Copy File: " + filename + Environment.NewLine);
}
catch
{
// if the file is locked, wait until it is unlocked
if (CanAccessFile(file))
{
File.Copy(file, filename);
WriteLog(logFilePath, "Copy File: " + filename + Environment.NewLine);
}
else
{
// file could not be backed up, upgrade unsuccessful
success = false;
WriteLog(logFilePath, "Error Backing Up Files" + Environment.NewLine);
}
}
} }
} }
catch (Exception ex) catch (Exception ex)
@ -125,17 +148,20 @@ namespace Oqtane.Updater
} }
} }
} }
}
// extract files // extract files
if (success) if (success)
{
UpdateOfflineContent(offlineFilePath, offlineTemplate, 50, "Extracting Files From Upgrade Package");
WriteLog(logFilePath, "Extracting Files From Upgrade Package..." + Environment.NewLine);
try
{ {
UpdateOfflineContent(offlineFilePath, offlineTemplate, 50, "Extracting Files From Upgrade Package"); using (ZipArchive archive = ZipFile.OpenRead(packagename))
WriteLog(logFilePath, "Extracting Files From Upgrade Package..." + Environment.NewLine);
try
{ {
using (ZipArchive archive = ZipFile.OpenRead(packagename)) foreach (ZipArchiveEntry entry in archive.Entries)
{ {
foreach (ZipArchiveEntry entry in archive.Entries) if (success)
{ {
if (!string.IsNullOrEmpty(entry.Name)) if (!string.IsNullOrEmpty(entry.Name))
{ {
@ -144,20 +170,42 @@ namespace Oqtane.Updater
{ {
Directory.CreateDirectory(Path.GetDirectoryName(filename)); Directory.CreateDirectory(Path.GetDirectoryName(filename));
} }
entry.ExtractToFile(filename, true);
WriteLog(logFilePath, "Exact File: " + filename + Environment.NewLine); try
{
// try optimistically to extract the file
entry.ExtractToFile(filename, true);
WriteLog(logFilePath, "Exact File: " + filename + Environment.NewLine);
}
catch
{
// if the file is locked, wait until it is unlocked
if (CanAccessFile(filename))
{
entry.ExtractToFile(filename, true);
WriteLog(logFilePath, "Exact File: " + filename + Environment.NewLine);
}
else
{
// file could not be extracted, upgrade unsuccessful
success = false;
WriteLog(logFilePath, "Error Extracting Files From Upgrade Package" + Environment.NewLine);
}
}
} }
} }
} }
} }
catch (Exception ex) }
{ catch (Exception ex)
success = false; {
UpdateOfflineContent(offlineFilePath, offlineTemplate, 95, "Error Extracting Files From Upgrade Package", "bg-danger"); success = false;
WriteLog(logFilePath, "Error Extracting Files From Upgrade Package: " + ex.Message + Environment.NewLine); WriteLog(logFilePath, "Error Extracting Files From Upgrade Package: " + ex.Message + Environment.NewLine);
} }
if (success) if (success)
{
if (backup)
{ {
UpdateOfflineContent(offlineFilePath, offlineTemplate, 90, "Removing Backup Folder"); UpdateOfflineContent(offlineFilePath, offlineTemplate, 90, "Removing Backup Folder");
WriteLog(logFilePath, "Removing Backup Folder..." + Environment.NewLine); WriteLog(logFilePath, "Removing Backup Folder..." + Environment.NewLine);
@ -174,7 +222,12 @@ namespace Oqtane.Updater
WriteLog(logFilePath, "Error Removing Backup Folder: " + ex.Message + Environment.NewLine); WriteLog(logFilePath, "Error Removing Backup Folder: " + ex.Message + Environment.NewLine);
} }
} }
else }
else
{
UpdateOfflineContent(offlineFilePath, offlineTemplate, 95, "Error Extracting Files From Upgrade Package", "bg-danger");
if (backup)
{ {
UpdateOfflineContent(offlineFilePath, offlineTemplate, 50, "Upgrade Failed, Restoring Files From Backup Folder", "bg-warning"); UpdateOfflineContent(offlineFilePath, offlineTemplate, 50, "Upgrade Failed, Restoring Files From Backup Folder", "bg-warning");
WriteLog(logFilePath, "Restoring Files From Backup Folder..." + Environment.NewLine); WriteLog(logFilePath, "Restoring Files From Backup Folder..." + Environment.NewLine);
@ -201,18 +254,14 @@ namespace Oqtane.Updater
} }
} }
} }
else
{
UpdateOfflineContent(offlineFilePath, offlineTemplate, 95, "Upgrade Failed: Could Not Backup Files", "bg-danger");
WriteLog(logFilePath, "Upgrade Failed: Could Not Backup Files" + Environment.NewLine);
}
} }
else else
{ {
UpdateOfflineContent(offlineFilePath, offlineTemplate, 95, "Upgrade Failed: Some Files Are Locked By The Hosting Environment", "bg-danger"); UpdateOfflineContent(offlineFilePath, offlineTemplate, 95, "Upgrade Failed: Could Not Backup Files", "bg-danger");
WriteLog(logFilePath, "Upgrade Failed: Some Files Are Locked By The Hosting Environment" + Environment.NewLine); WriteLog(logFilePath, "Upgrade Failed: Could Not Backup Files" + Environment.NewLine);
} }
UpdateOfflineContent(offlineFilePath, offlineTemplate, 100, "Upgrade Process Finished, Reloading", success ? "" : "bg-danger"); UpdateOfflineContent(offlineFilePath, offlineTemplate, 100, "Upgrade Process Finished, Reloading", success ? "" : "bg-danger");
Thread.Sleep(3000); //wait for 3 seconds to complete the upgrade process. Thread.Sleep(3000); //wait for 3 seconds to complete the upgrade process.
// bring the app back online // bring the app back online
@ -240,55 +289,45 @@ namespace Oqtane.Updater
} }
} }
private static bool CanAccessFiles(List<string> files) private static bool CanAccessFile(string filepath)
{ {
// ensure files are not locked by another process // ensure file is not locked by another process
// the IIS ShutdownTimeLimit defines the duration for app shutdown (default is 90 seconds)
// websockets can delay application shutdown (ie. Blazor Server)
int retries = 60; int retries = 60;
int sleep = 2; int sleep = 2;
int attempts = 0;
bool canAccess = true;
FileStream stream = null; FileStream stream = null;
int i = 0;
while (i < (files.Count - 1) && canAccess)
{
string filepath = files[i];
int attempts = 0;
bool locked = true;
while (attempts < retries && locked) bool locked = true;
while (attempts < retries && locked)
{
try
{ {
try if (File.Exists(filepath))
{ {
if (File.Exists(filepath)) stream = File.Open(filepath, FileMode.Open, FileAccess.Read, FileShare.None);
{ locked = false;
stream = File.Open(filepath, FileMode.Open, FileAccess.Read, FileShare.None);
locked = false;
}
else
{
locked = false;
}
} }
catch // file is locked by another process else
{ {
Thread.Sleep(sleep * 1000); // wait locked = false;
} }
finally
{
stream?.Close();
}
attempts += 1;
} }
if (locked) catch // file is locked by another process
{ {
canAccess = false; Thread.Sleep(sleep * 1000); // wait
Console.WriteLine("File Locked: " + filepath);
} }
i += 1; finally
{
stream?.Close();
}
attempts += 1;
} }
return canAccess; if (locked)
{
Console.WriteLine("File Locked: " + filepath);
}
return !locked;
} }
private static void UpdateOfflineContent(string filePath, string contentTemplate, int progress, string status, string progressClass = "") private static void UpdateOfflineContent(string filePath, string contentTemplate, int progress, string status, string progressClass = "")

View File

@ -12,7 +12,7 @@ Oqtane is being developed based on some fundamental principles which are outline
# Latest Release # Latest Release
[6.1.0](https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.0) was released on February 11, 2025 and is a minor release including 95 pull requests by 9 different contributors, pushing the total number of project commits all-time to over 6300. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. [6.1.1](https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.1) was released on March 12, 2025 and is a maintenance release including 46 pull requests by 4 different contributors, pushing the total number of project commits all-time to over 6400. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers.
# Try It Now! # Try It Now!
@ -22,11 +22,11 @@ Microsoft's Public Cloud (requires an Azure account)
A free ASP.NET hosting account. No hidden fees. No credit card required. A free ASP.NET hosting account. No hidden fees. No credit card required.
[![Deploy to MonsterASP.NET](https://www.oqtane.org/files/Public/MonsterASPNET.png)](https://www.monsterasp.net/) [![Deploy to MonsterASP.NET](https://www.oqtane.org/files/Public/MonsterASPNET.png)](https://www.monsterasp.net/)
# Getting Started (Version 6.x) # Getting Started (Version 6.1.1)
**Installing using source code from the Dev/Master branch:** **Installing using source code from the Dev/Master branch:**
- Install **[.NET 9.0.2 SDK](https://dotnet.microsoft.com/download/dotnet/9.0)**. - Install **[.NET 9.0.3 SDK](https://dotnet.microsoft.com/download/dotnet/9.0)**.
- Install the latest edition (v17.12 or higher) of [Visual Studio 2022](https://visualstudio.microsoft.com/downloads) with the **ASP.NET and web development** workload enabled. Oqtane works with ALL editions of Visual Studio from Community to Enterprise. If you wish to use LocalDB for development ( not a requirement as Oqtane supports SQLite, mySQL, and PostgreSQL ) you must also install the **Data storage and processing**. - Install the latest edition (v17.12 or higher) of [Visual Studio 2022](https://visualstudio.microsoft.com/downloads) with the **ASP.NET and web development** workload enabled. Oqtane works with ALL editions of Visual Studio from Community to Enterprise. If you wish to use LocalDB for development ( not a requirement as Oqtane supports SQLite, mySQL, and PostgreSQL ) you must also install the **Data storage and processing**.
@ -92,6 +92,10 @@ Connect with other developers, get support, and share ideas by joining the Oqtan
# 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...
[6.1.1](https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.1) (Mar 12, 2025)
- [x] Stabilization improvements
- [x] Cookie Consent Banner & Privacy/Terms
[6.1.0](https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.0) (Feb 11, 2025) [6.1.0](https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.0) (Feb 11, 2025)
- [x] Static Asset / Folder Asset Caching - [x] Static Asset / Folder Asset Caching
- [x] JavaScript improvements in Blazor Static Server Rendering (SSR) - [x] JavaScript improvements in Blazor Static Server Rendering (SSR)

View File

@ -2,6 +2,24 @@
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.2", "contentVersion": "1.0.0.2",
"parameters": { "parameters": {
"sqlServerName": {
"type": "string",
"metadata": {
"description": "The SQL Server name (must be unique). If you have an existing SQL Server in your resource group, you can specify its name and include its login credentials below."
}
},
"sqlAdministratorLogin": {
"type": "string",
"metadata": {
"description": "The Administrator login username for the SQL Server specified above"
}
},
"sqlAdministratorLoginPassword": {
"type": "securestring",
"metadata": {
"description": "The Administrator login password for the SQL Server specified above"
}
},
"sqlDatabaseEditionTierDtuCapacity": { "sqlDatabaseEditionTierDtuCapacity": {
"type": "string", "type": "string",
"defaultValue": "Basic-Basic-5-2", "defaultValue": "Basic-Basic-5-2",
@ -26,37 +44,19 @@
"GeneralPurpose-GP_S_Gen5_2-2-250" "GeneralPurpose-GP_S_Gen5_2-2-250"
], ],
"metadata": { "metadata": {
"description": "Describes the database Edition, Tier, Dtu, Gigabytes (Edition-Tier-Dtu-Gigabytes)" "description": "The SQL Database Configuration (Edition-Tier-DTU-Capacity)"
}
},
"sqlServerName": {
"type": "string",
"metadata": {
"description": "The name of the sql server. It has to be unique."
} }
}, },
"sqlDatabaseName": { "sqlDatabaseName": {
"type": "string", "type": "string",
"metadata": { "metadata": {
"description": "The name of the sql database. It has to be unique." "description": "The SQL Database name (must be unique)"
}
},
"sqlAdministratorLogin": {
"type": "string",
"metadata": {
"description": "The admin user of the SQL Server."
}
},
"sqlAdministratorLoginPassword": {
"type": "securestring",
"metadata": {
"description": "The password of the admin user of the SQL Server."
} }
}, },
"BlazorWebsiteName": { "BlazorWebsiteName": {
"type": "string", "type": "string",
"metadata": { "metadata": {
"description": "The name of the website. It has to be unique." "description": "The App Service name for the Blazor Website (must be unique). If you use the same name as the SQL Database specified above, it will make it easier to associate the resources later."
} }
}, },
"BlazorSKU": { "BlazorSKU": {
@ -77,28 +77,12 @@
], ],
"defaultValue": "B1", "defaultValue": "B1",
"metadata": { "metadata": {
"description": "The SKU for the App Service Plan" "description": "The App Service SKU for the Blazor Website"
}
},
"BlazorSKUCapacity": {
"type": "int",
"defaultValue": 1,
"maxValue": 3,
"minValue": 1,
"metadata": {
"description": "Describes plan's instance count"
}
},
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"metadata": {
"description": "Location for all resources."
} }
} }
}, },
"variables": { "variables": {
"hostingPlanName": "[concat('Oqtane-hostingplan-', uniqueString(resourceGroup().id))]", "hostingPlanName": "[concat('Oqtane-HostingPlan-', uniqueString(resourceGroup().id))]",
"databaseCollation": "SQL_Latin1_General_CP1_CI_AS", "databaseCollation": "SQL_Latin1_General_CP1_CI_AS",
"databaseEditionTierDtuCapacity": "[split(parameters('sqlDatabaseEditionTierDtuCapacity'),'-')]", "databaseEditionTierDtuCapacity": "[split(parameters('sqlDatabaseEditionTierDtuCapacity'),'-')]",
"databaseEdition": "[variables('databaseEditionTierDtuCapacity')[0]]", "databaseEdition": "[variables('databaseEditionTierDtuCapacity')[0]]",
@ -115,9 +99,9 @@
// ------------------------------------------------------ // ------------------------------------------------------
{ {
"type": "Microsoft.Sql/servers", "type": "Microsoft.Sql/servers",
"apiVersion": "2022-05-01-preview", // Updated API version "apiVersion": "2022-05-01-preview",
"name": "[parameters('sqlServerName')]", "name": "[parameters('sqlServerName')]",
"location": "[parameters('location')]", "location": "[resourceGroup().location]",
"tags": { "tags": {
"displayName": "SQL Server" "displayName": "SQL Server"
}, },
@ -132,9 +116,9 @@
// ------------------------------------------------------ // ------------------------------------------------------
{ {
"type": "Microsoft.Sql/servers/databases", "type": "Microsoft.Sql/servers/databases",
"apiVersion": "2022-05-01-preview", // Updated API version "apiVersion": "2022-05-01-preview",
"name": "[format('{0}/{1}', parameters('sqlServerName'), parameters('sqlDatabaseName'))]", "name": "[format('{0}/{1}', parameters('sqlServerName'), parameters('sqlDatabaseName'))]",
"location": "[parameters('location')]", "location": "[resourceGroup().location]",
"tags": { "tags": {
"displayName": "Database" "displayName": "Database"
}, },
@ -169,11 +153,11 @@
] ]
}, },
// ------------------------------------------------------ // ------------------------------------------------------
// Firewall Rule (renamed to 'AllowAllMicrosoftAzureIps') // Firewall Rule
// ------------------------------------------------------ // ------------------------------------------------------
{ {
"type": "Microsoft.Sql/servers/firewallRules", "type": "Microsoft.Sql/servers/firewallRules",
"apiVersion": "2022-05-01-preview", // Updated API version "apiVersion": "2022-05-01-preview",
"name": "[format('{0}/{1}', parameters('sqlServerName'), 'AllowAllMicrosoftAzureIps')]", "name": "[format('{0}/{1}', parameters('sqlServerName'), 'AllowAllMicrosoftAzureIps')]",
"properties": { "properties": {
"endIpAddress": "0.0.0.0", "endIpAddress": "0.0.0.0",
@ -188,16 +172,15 @@
// ------------------------------------------------------ // ------------------------------------------------------
{ {
"type": "Microsoft.Web/serverfarms", "type": "Microsoft.Web/serverfarms",
"apiVersion": "2022-03-01", // Updated API version "apiVersion": "2022-03-01",
"name": "[variables('hostingPlanName')]", "name": "[variables('hostingPlanName')]",
"location": "[parameters('location')]", "location": "[resourceGroup().location]",
"tags": { "tags": {
"displayName": "Blazor" "displayName": "Blazor"
}, },
"sku": { "sku": {
"name": "[parameters('BlazorSKU')]", "name": "[parameters('BlazorSKU')]",
// If you want to auto-map to certain "tier" strings, you can do so. Here we just set the capacity: "capacity": "1"
"capacity": "[parameters('BlazorSKUCapacity')]"
}, },
"properties": { "properties": {
"name": "[variables('hostingPlanName')]", "name": "[variables('hostingPlanName')]",
@ -209,13 +192,11 @@
// Web App // Web App
// ------------------------------------------------------ // ------------------------------------------------------
{ {
"apiVersion": "2022-03-01", // Updated API version
"name": "[parameters('BlazorWebsiteName')]",
"type": "Microsoft.Web/sites", "type": "Microsoft.Web/sites",
"location": "[parameters('location')]", "apiVersion": "2022-03-01",
"dependsOn": [ "name": "[parameters('BlazorWebsiteName')]",
"[variables('hostingPlanName')]" "kind": "app",
], "location": "[resourceGroup().location]",
"tags": { "tags": {
"[concat('hidden-related:', resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName')))]": "empty", "[concat('hidden-related:', resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName')))]": "empty",
"displayName": "Website" "displayName": "Website"
@ -224,44 +205,26 @@
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]", "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
"siteConfig": { "siteConfig": {
"webSocketsEnabled": true, "webSocketsEnabled": true,
// Updated .NET version "v9.0" from second snippet
"netFrameworkVersion": "v9.0" "netFrameworkVersion": "v9.0"
} }
}, },
"dependsOn": [
"[variables('hostingPlanName')]"
],
"resources": [ "resources": [
// -------------------------------------------------- // --------------------------------------------------
// Source Control for your Web App // ZIP Deploy
// -------------------------------------------------- // --------------------------------------------------
{ {
"type": "sourcecontrols", "type": "Microsoft.Web/sites/extensions",
"apiVersion": "2022-03-01", "apiVersion": "2024-04-01",
"name": "web", "name": "[concat(parameters('BlazorWebsiteName'), '/ZipDeploy')]",
"location": "[parameters('location')]", "properties": {
"packageUri": "https://github.com/oqtane/oqtane.framework/releases/download/v6.1.1/Oqtane.Framework.6.1.1.Install.zip"
},
"dependsOn": [ "dependsOn": [
"[resourceId('Microsoft.Web/sites', parameters('BlazorWebsiteName'))]" "[resourceId('Microsoft.Web/sites', parameters('BlazorWebsiteName'))]"
], ]
"properties": {
"repoUrl": "https://github.com/oqtane/oqtane.framework.git",
"branch": "master",
"isManualIntegration": true
}
},
// --------------------------------------------------
// Connection Strings (to use FQDN)
// --------------------------------------------------
{
"type": "config",
"apiVersion": "2022-03-01",
"name": "connectionstrings",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', parameters('BlazorWebsiteName'))]"
],
"properties": {
"DefaultConnection": {
"value": "[concat('Data Source=tcp:', reference(resourceId('Microsoft.Sql/servers', parameters('sqlServerName'))).fullyQualifiedDomainName, ',1433;Initial Catalog=', parameters('sqlDatabaseName'), ';User Id=', parameters('sqlAdministratorLogin'), '@', reference(resourceId('Microsoft.Sql/servers', parameters('sqlServerName'))).fullyQualifiedDomainName, ';Password=', parameters('sqlAdministratorLoginPassword'), ';')]",
"type": "SQLAzure"
}
}
} }
] ]
} }