Merge pull request #2317 from oqtane/dev

3.1.4 release
This commit is contained in:
Shaun Walker 2022-07-27 16:15:43 -04:00 committed by GitHub
commit 2909aa1656
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
85 changed files with 875 additions and 387 deletions

View File

@ -4,7 +4,7 @@
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="server" HelpText="Enter the database server" 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 (ie. servername.database.windows.net,1433) " ResourceKey="Server">Server:</Label>
<div class="col-sm-9">
<input id="server" type="text" class="form-control" @bind="@_server" />
</div>
@ -51,7 +51,7 @@
@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" ResourceKey="TrustServerCertificate">Certificate:</Label>
<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>

View File

@ -26,22 +26,42 @@
<div class="col-sm-9">
@if (_databases != null)
{
<select id="databasetype" class="form-select custom-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))">
@foreach (var database in _databases)
{
<option value="@database.Name">@Localizer[@database.Name]</option>
}
</select>
<div class="input-group">
<select id="databaseType" class="form-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))" required>
@foreach (var database in _databases)
{
<option value="@database.Name">@Localizer[@database.Name]</option>
}
</select>
@if (!_showConnectionString)
{
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionString"]</button>
}
else
{
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionParameters"]</button>
}
</div>
}
</div>
</div>
@{
if (_databaseConfigType != null)
{
@DatabaseConfigComponent;
}
}
</div>
@if (!_showConnectionString)
{
if (_databaseConfigType != null)
{
@DatabaseConfigComponent
}
}
else
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="connectionstring" HelpText="Enter a complete connection string including all parameters and delimiters" ResourceKey="ConnectionString">String:</Label>
<div class="col-sm-9">
<textarea id="connectionstring" class="form-control" @bind="@_connectionString" rows="3"></textarea>
</div>
</div>
}
</div>
</div>
<div class="col text-center">
<h2>@Localizer["ApplicationAdmin"]</h2><br />
@ -100,13 +120,15 @@
private Type _databaseConfigType;
private object _databaseConfig;
private RenderFragment DatabaseConfigComponent { get; set; }
private bool _showConnectionString = false;
private string _connectionString = string.Empty;
private string _hostUsername = string.Empty;
private string _hostPassword = string.Empty;
private string _passwordType = "password";
private string _confirmPasswordType = "password";
private string _confirmPasswordType = "password";
private string _togglePassword = string.Empty;
private string _toggleConfirmPassword = string.Empty;
private string _toggleConfirmPassword = string.Empty;
private string _confirmPassword = string.Empty;
private string _hostEmail = string.Empty;
private bool _register = true;
@ -116,7 +138,7 @@
protected override async Task OnInitializedAsync()
{
_togglePassword = SharedLocalizer["ShowPassword"];
_toggleConfirmPassword = SharedLocalizer["ShowPassword"];
_toggleConfirmPassword = SharedLocalizer["ShowPassword"];
_databases = await DatabaseService.GetDatabasesAsync();
if (_databases.Exists(item => item.IsDefault))
@ -135,7 +157,7 @@
try
{
_databaseName = (string)eventArgs.Value;
_showConnectionString = false;
LoadDatabaseConfigComponent();
}
catch
@ -172,9 +194,16 @@
private async Task Install()
{
var connectionString = String.Empty;
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
if (_showConnectionString)
{
connectionString = databaseConfigControl.GetConnectionString();
connectionString = _connectionString;
}
else
{
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
{
connectionString = databaseConfigControl.GetConnectionString();
}
}
if (connectionString != "" && !string.IsNullOrEmpty(_hostUsername) && !string.IsNullOrEmpty(_hostPassword) && _hostPassword == _confirmPassword && !string.IsNullOrEmpty(_hostEmail) && _hostEmail.Contains("@"))
@ -218,12 +247,12 @@
{
_message = Localizer["Message.Password.Invalid"];
}
}
else
{
_message = Localizer["Message.Require.DbInfo"];
}
}
}
else
{
_message = Localizer["Message.Require.DbInfo"];
}
}
private void TogglePassword()
{
@ -239,7 +268,7 @@
}
}
private void ToggleConfirmPassword()
private void ToggleConfirmPassword()
{
if (_confirmPasswordType == "password")
{
@ -252,4 +281,14 @@
_toggleConfirmPassword = SharedLocalizer["ShowPassword"];
}
}
private void ToggleConnectionString()
{
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
{
_connectionString = databaseConfigControl.GetConnectionString();
}
_showConnectionString = !_showConnectionString;
}
}

View File

@ -11,8 +11,7 @@
Module module = await ModuleService.GetModuleAsync(ModuleState.ModuleId);
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
string message = string.Format(Localizer["Error.Module.Load"], module.ModuleDefinitionName);
AddModuleMessage(message, MessageType.Error);
AddModuleMessage(string.Format(Localizer["Error.Module.Load"], module.ModuleDefinitionName), MessageType.Error);
}
await logger.LogCritical("Error Loading Module {Module}", module);

View File

@ -56,6 +56,10 @@ else
protected override async Task OnParametersSetAsync()
{
_jobs = await JobService.GetJobsAsync();
if (_jobs.Count == 0)
{
AddModuleMessage(string.Format(Localizer["Message.NoJobs"], NavigateUrl("admin/system")), MessageType.Warning);
}
}
private string DisplayStatus(bool isEnabled, bool isExecuting)

View File

@ -32,7 +32,7 @@ else
<select id="_code" class="form-select" @bind="@_code" required>
@foreach (var culture in _availableCultures)
{
<option value="@culture.Name">@culture.DisplayName</option>
<option value="@culture.Name">@(culture.DisplayName + " (" + culture.Name + ")")</option>
}
</select>
</div>
@ -52,7 +52,7 @@ else
</form>
}
</TabPanel>
<TabPanel Name="Download" ResourceKey="Download" Security="SecurityAccessLevel.Host">
<TabPanel Name="Translations" Heading="Translations" ResourceKey="Download" Security="SecurityAccessLevel.Host">
<div class="row justify-content-center mb-3">
<div class="col-sm-6">
<div class="input-group">
@ -115,7 +115,7 @@ else
<TabPanel Name="Upload" ResourceKey="Upload" Security="SecurityAccessLevel.Host">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" HelpText="Upload one or more translations. Once they are uploaded click Install to complete the installation." ResourceKey="LanguageUpload">Language: </Label>
<Label Class="col-sm-3" HelpText="Upload one or more translations. Once they are uploaded click Install to complete the installation." ResourceKey="LanguageUpload">Translation: </Label>
<div class="col-sm-9">
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" />
</div>
@ -161,33 +161,32 @@ else
}
@code {
private ElementReference form;
private bool validated = false;
private ElementReference form;
private bool validated = false;
private string _code = string.Empty;
private string _isDefault = "False";
private string _message;
private IEnumerable<Culture> _supportedCultures;
private IEnumerable<Culture> _availableCultures;
private List<Package> _packages;
private string _price = "free";
private string _search = "";
private string _productname = "";
private string _license = "";
private string _packageid = "";
private string _version = "";
private string _code = string.Empty;
private string _isDefault = "False";
private string _message;
private IEnumerable<Culture> _supportedCultures;
private IEnumerable<Culture> _availableCultures;
private List<Package> _packages;
private string _price = "free";
private string _search = "";
private string _productname = "";
private string _license = "";
private string _packageid = "";
private string _version = "";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnParametersSetAsync()
{
var languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
var languagesCodes = languages.Select(l => l.Code).ToList();
protected override async Task OnParametersSetAsync()
{
var languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
var languagesCodes = languages.Select(l => l.Code).ToList();
_supportedCultures = await LocalizationService.GetCulturesAsync();
_availableCultures = _supportedCultures
.Where(c => !c.Name.Equals(Constants.DefaultCulture) && !languagesCodes.Contains(c.Name));
await LoadTranslations();
_supportedCultures = await LocalizationService.GetCulturesAsync();
_availableCultures = _supportedCultures.Where(c => !c.Name.Equals(Constants.DefaultCulture) && !languagesCodes.Contains(c.Name));
await LoadTranslations();
if (_supportedCultures.Count() == 1)
{

View File

@ -19,15 +19,17 @@ else
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th>
<th>@Localizer["Code"]</th>
<th>@Localizer["Translation"]</th>
<th>@Localizer["Default"]</th>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
</Header>
<Row>
<td><ActionDialog Header="Delete Language" Message="@string.Format(Localizer["Confirm.Language.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteLanguage(context))" Disabled="@((context.IsDefault && _languages.Count > 2) || context.Code == Constants.DefaultCulture)" ResourceKey="DeleteLanguage" /></td>
<td>@context.Name</td>
<td>@context.Code</td>
<td><TriStateCheckBox Value="@(context.IsDefault)" Disabled="true"></TriStateCheckBox></td>
<td>
<td>@context.Version</td>
<td><TriStateCheckBox Value="@(context.IsDefault)" Disabled="true"></TriStateCheckBox></td>
<td>
@if (UpgradeAvailable(context.Code))
{
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadLanguage(context.Code))>@SharedLocalizer["Upgrade"]</button>
@ -50,9 +52,6 @@ else
var cultures = await LocalizationService.GetCulturesAsync();
var culture = cultures.First(c => c.Name.Equals(Constants.DefaultCulture));
// Adds English as default language
_languages.Insert(0, new Language { Name = culture.DisplayName, Code = culture.Name, IsDefault = !_languages.Any(l => l.IsDefault) });
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_packages = await PackageService.GetPackagesAsync("translation");
@ -81,7 +80,7 @@ else
var upgradeavailable = false;
if (_packages != null)
{
var package = _packages.Where(item => item.PackageId == (Constants.PackageId + ".Client." + code)).FirstOrDefault();
var package = _packages.Where(item => item.PackageId == ("Oqtane.Client." + code)).FirstOrDefault();
if (package != null)
{
upgradeavailable = (Version.Parse(package.Version).CompareTo(Version.Parse(Constants.Version)) == 0);

View File

@ -85,7 +85,7 @@ else
private List<Template> _templates;
private string _template = "-";
private string[] _versions;
private string _reference = Constants.Version;
private string _reference = "local";
private string _minversion = "2.0.0";
private string _location = string.Empty;

View File

@ -91,9 +91,9 @@
<div class="modal-body">
<p style="height: 200px; overflow-y: scroll;">
<h3>@_productname</h3>
@if (!string.IsNullOrEmpty(_license))
@if (!string.IsNullOrEmpty(_packagelicense))
{
@((MarkupString)_license)
@((MarkupString)_packagelicense)
}
else
{
@ -119,9 +119,9 @@
private string _price = "free";
private string _search = "";
private string _productname = "";
private string _license = "";
private string _packageid = "";
private string _version = "";
private string _packagelicense = "";
private string _packageversion = "";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
@ -198,7 +198,7 @@
private void HideModal()
{
_productname = "";
_license = "";
_packagelicense = "";
StateHasChanged();
}
@ -210,12 +210,12 @@
if (package != null)
{
_productname = package.Name;
_packageid = package.PackageId;
if (!string.IsNullOrEmpty(package.License))
{
_license = package.License.Replace("\n", "<br />");
_packagelicense = package.License.Replace("\n", "<br />");
}
_packageid = package.PackageId;
_version = package.Version;
_packageversion = package.Version;
}
StateHasChanged();
}
@ -230,16 +230,16 @@
{
try
{
await PackageService.DownloadPackageAsync(_packageid, _version, Constants.PackagesFolder);
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _packageid, _version);
await PackageService.DownloadPackageAsync(_packageid, _packageversion, Constants.PackagesFolder);
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _packageid, _packageversion);
AddModuleMessage(Localizer["Success.Module.Download"], MessageType.Success);
_productname = "";
_license = "";
_packagelicense = "";
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packageid, _version);
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packageid, _packageversion);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
}
}
@ -253,7 +253,7 @@
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Installing Module");
await logger.LogError(ex, "Error Installing Modules");
}
}
}

View File

@ -151,7 +151,7 @@
var template = _templates.FirstOrDefault(item => item.Name == _template);
_minversion = template.Version;
}
GetLocation();
GetLocation();
}
private void GetLocation()

View File

@ -1,6 +1,7 @@
@namespace Oqtane.Modules.Admin.ModuleDefinitions
@inherits ModuleBase
@inject IModuleDefinitionService ModuleDefinitionService
@inject IPackageService PackageService
@inject NavigationManager NavigationManager
@inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@ -43,7 +44,13 @@
<input id="version" class="form-control" @bind="@_version" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this module was installed" ResourceKey="PackageName">Package Name: </Label>
<div class="col-sm-9">
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="owner" HelpText="The owner or creator of the module" ResourceKey="Owner">Owner: </Label>
<div class="col-sm-9">
<input id="owner" class="form-control" @bind="@_owner" disabled />
@ -74,7 +81,13 @@
</div>
</div>
</div>
</Section>
</Section>
<br />
<button type="button" class="btn btn-success" @onclick="SaveModuleDefinition">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<br />
<br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
</TabPanel>
<TabPanel Name="Permissions" ResourceKey="Permissions">
<div class="container">
@ -82,24 +95,100 @@
<PermissionGrid EntityName="@EntityNames.ModuleDefinition" PermissionNames="@PermissionNames.Utilize" Permissions="@_permissions" @ref="_permissionGrid" />
</div>
</div>
<button type="button" class="btn btn-success" @onclick="SaveModuleDefinition">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</TabPanel>
<TabPanel Name="Translations" ResourceKey="Translations">
@if (_packages != null)
{
if (_packages.Count > 0)
{
<Pager Items="@_packages">
<Row>
<td>
<h3 style="display: inline;"><a href="@context.ProductUrl" target="_new">@context.Name</a></h3>&nbsp;&nbsp;by:&nbsp;&nbsp;<strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
<strong>@(String.Format("{0:n0}", context.Downloads))</strong> @SharedLocalizer["Search.Downloads"]&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Released"]: <strong>@context.ReleaseDate.ToString("MMM dd, yyyy")</strong>&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Version"]: <strong>@context.Version</strong>
@((MarkupString)(!string.IsNullOrEmpty(context.PackageUrl) ? "&nbsp;&nbsp;|&nbsp;&nbsp;" + SharedLocalizer["Search.Source"] + ": <strong>" + new Uri(context.PackageUrl).Host + "</strong>" : ""))
@((MarkupString)(context.TrialPeriod > 0 ? "&nbsp;&nbsp;|&nbsp;&nbsp;<strong>" + context.TrialPeriod + " " + @SharedLocalizer["Trial"] + "</strong>" : ""))
</td>
<td style="width: 1px; vertical-align: middle;">
@if (context.Price != null && !string.IsNullOrEmpty(context.PackageUrl))
{
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
}
</td>
<td style="width: 1px; vertical-align: middle;">
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
{
<a class="btn btn-primary" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@context.Price.Value.ToString("$#,##0.00")</a>
}
else
{
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
}
</td>
</Row>
</Pager>
<button type="button" class="btn btn-success" @onclick="InstallTranslations">@SharedLocalizer["Install"]</button>
}
else
{
<br />
<div class="mx-auto text-center">
@Localizer["Search.NoResults"]
</div>
}
}
</TabPanel>
</TabStrip>
<button type="button" class="btn btn-success" @onclick="SaveModuleDefinition">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<br />
<br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
@if (_productname != "")
{
<div class="app-actiondialog">
<div class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@SharedLocalizer["Review License Terms"]</h5>
<button type="button" class="btn-close" aria-label="Close" @onclick="HideModal"></button>
</div>
<div class="modal-body">
<p style="height: 200px; overflow-y: scroll;">
<h3>@_productname</h3>
@if (!string.IsNullOrEmpty(_packagelicense))
{
@((MarkupString)_packagelicense)
}
else
{
@SharedLocalizer["License Not Specified"]
}
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success" @onclick="DownloadTranslation">@SharedLocalizer["Accept"]</button>
<button type="button" class="btn btn-secondary" @onclick="HideModal">@SharedLocalizer["Cancel"]</button>
</div>
</div>
</div>
</div>
</div>
}
@code {
private ElementReference form;
private bool validated = false;
private int _moduleDefinitionId;
private string _name;
private string _version;
private string _categories;
private string _description = "";
private string _categories;
private string _moduledefinitionname = "";
private string _description = "";
private string _owner = "";
private string _version;
private string _packagename = "";
private string _owner = "";
private string _url = "";
private string _contact = "";
private string _license = "";
@ -114,6 +203,12 @@
private PermissionGrid _permissionGrid;
#pragma warning restore 649
private List<Package> _packages;
private string _productname = "";
private string _packageid = "";
private string _packagelicense = "";
private string _packageversion = "";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnInitializedAsync()
@ -125,11 +220,12 @@
if (moduleDefinition != null)
{
_name = moduleDefinition.Name;
_version = moduleDefinition.Version;
_description = moduleDefinition.Description;
_categories = moduleDefinition.Categories;
_moduledefinitionname = moduleDefinition.ModuleDefinitionName;
_description = moduleDefinition.Description;
_owner = moduleDefinition.Owner;
_version = moduleDefinition.Version;
_packagename = moduleDefinition.PackageName;
_owner = moduleDefinition.Owner;
_url = moduleDefinition.Url;
_contact = moduleDefinition.Contact;
_license = moduleDefinition.License;
@ -139,7 +235,9 @@
_createdon = moduleDefinition.CreatedOn;
_modifiedby = moduleDefinition.ModifiedBy;
_modifiedon = moduleDefinition.ModifiedOn;
}
_packages = await PackageService.GetPackagesAsync("translation", "", "", moduleDefinition.PackageName);
}
}
catch (Exception ex)
{
@ -185,4 +283,66 @@
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
private void HideModal()
{
_productname = "";
_packagelicense = "";
StateHasChanged();
}
private async Task GetPackage(string packageid, string version)
{
try
{
var package = await PackageService.GetPackageAsync(packageid, version);
if (package != null)
{
_productname = package.Name;
_packageid = package.PackageId;
if (!string.IsNullOrEmpty(package.License))
{
_packagelicense = package.License.Replace("\n", "<br />");
}
_packageversion = package.Version;
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", packageid, version);
AddModuleMessage(Localizer["Error.Translation.Download"], MessageType.Error);
}
}
private async Task DownloadTranslation()
{
try
{
await PackageService.DownloadPackageAsync(_packageid, _version, Constants.PackagesFolder);
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _packageid, _version);
AddModuleMessage(Localizer["Success.Translation.Download"], MessageType.Success);
_productname = "";
_packagelicense = "";
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packageid, _version);
AddModuleMessage(Localizer["Error.Translation.Download"], MessageType.Error);
}
}
private async Task InstallTranslations()
{
try
{
await PackageService.InstallPackagesAsync();
AddModuleMessage(string.Format(Localizer["Success.Translation.Install"], NavigateUrl("admin/system")), MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Installing Translations");
}
}
}

View File

@ -124,37 +124,45 @@
_containerType = ModuleState.ContainerType;
_allPages = ModuleState.AllPages.ToString();
_permissions = ModuleState.Permissions;
_permissionNames = ModuleState.ModuleDefinition.PermissionNames;
_pageId = ModuleState.PageId.ToString();
createdby = ModuleState.CreatedBy;
createdon = ModuleState.CreatedOn;
modifiedby = ModuleState.ModifiedBy;
modifiedon = ModuleState.ModifiedOn;
if (!string.IsNullOrEmpty(ModuleState.ModuleDefinition.SettingsType))
if (ModuleState.ModuleDefinition != null)
{
// module settings type explicitly declared in IModule interface
_moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.SettingsType);
_permissionNames = ModuleState.ModuleDefinition?.PermissionNames;
if (!string.IsNullOrEmpty(ModuleState.ModuleDefinition.SettingsType))
{
// module settings type explicitly declared in IModule interface
_moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.SettingsType);
}
else
{
// legacy support - module settings type determined by convention ( ie. existence of a "Settings.razor" component in module )
_moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, PageState.Action), false, true);
}
if (_moduleSettingsType != null)
{
var moduleobject = Activator.CreateInstance(_moduleSettingsType) as IModuleControl;
if (!string.IsNullOrEmpty(moduleobject.Title))
{
_moduleSettingsTitle = moduleobject.Title;
}
ModuleSettingsComponent = builder =>
{
builder.OpenComponent(0, _moduleSettingsType);
builder.AddComponentReferenceCapture(1, inst => { _moduleSettings = Convert.ChangeType(inst, _moduleSettingsType); });
builder.CloseComponent();
};
}
}
else
{
// legacy support - module settings type determined by convention ( ie. existence of a "Settings.razor" component in module )
_moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, PageState.Action), false, true);
}
if (_moduleSettingsType != null)
{
var moduleobject = Activator.CreateInstance(_moduleSettingsType) as IModuleControl;
if (!string.IsNullOrEmpty(moduleobject.Title))
{
_moduleSettingsTitle = moduleobject.Title;
}
ModuleSettingsComponent = builder =>
{
builder.OpenComponent(0, _moduleSettingsType);
builder.AddComponentReferenceCapture(1, inst => { _moduleSettings = Convert.ChangeType(inst, _moduleSettingsType); });
builder.CloseComponent();
};
AddModuleMessage(string.Format(Localizer["Error.Module.Load"], ModuleState.ModuleDefinitionName), MessageType.Error);
}
var theme = _themes.FirstOrDefault(item => item.Containers.Any(themecontrol => themecontrol.TypeName.Equals(_containerType)));

View File

@ -557,8 +557,8 @@
await AliasService.DeleteAliasAsync(alias.AliasId);
}
aliases = await AliasService.GetAliasesAsync();
NavigationManager.NavigateTo(PageState.Uri.Scheme + "://" + aliases.First().Name, true);
var redirect = aliases.First(item => item.SiteId != PageState.Site.SiteId || item.TenantId != PageState.Site.TenantId);
NavigationManager.NavigateTo(PageState.Uri.Scheme + "://" + redirect.Name, true);
}
else
{

View File

@ -29,7 +29,7 @@ else
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="alias" HelpText="Enter the aliases for the site. An alias can be a domain name (www.site.com) or a virtual folder (ie. www.site.com/folder). If a site has multiple aliases they can be separated by commas." ResourceKey="Aliases">Aliases: </Label>
<Label Class="col-sm-3" For="alias" HelpText="Enter the aliases for the site. An alias can be a domain name (www.site.com) or a virtual folder (ie. www.site.com/folder)." ResourceKey="Aliases">Aliases: </Label>
<div class="col-sm-9">
<textarea id="alias" class="form-control" @bind="@_urls" rows="3" required></textarea>
</div>
@ -128,18 +128,43 @@ else
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="databaseType" HelpText="Select the database type for the tenant" ResourceKey="DatabaseType">Database Type: </Label>
<div class="col-sm-9">
<select id="databaseType" class="form-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))" required>
@foreach (var database in _databases)
{
<option value="@database.Name">@Localizer[@database.Name]</option>
}
</select>
</div>
@if (_databases != null)
{
<div class="input-group">
<select id="databaseType" class="form-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))" required>
@foreach (var database in _databases)
{
<option value="@database.Name">@Localizer[@database.Name]</option>
}
</select>
@if (!_showConnectionString)
{
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionString"]</button>
}
else
{
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionParameters"]</button>
}
</div>
}
</div>
</div>
if (_databaseConfigType != null)
{
@DatabaseConfigComponent;
}
@if (!_showConnectionString)
{
if (_databaseConfigType != null)
{
@DatabaseConfigComponent
}
}
else
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="connectionstring" HelpText="Enter a complete connection string including all parameters and delimiters" ResourceKey="ConnectionString">String:</Label>
<div class="col-sm-9">
<textarea id="connectionstring" class="form-control" @bind="@_connectionString" rows="3"></textarea>
</div>
</div>
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="hostUsername" HelpText="Enter the username of an existing host user" ResourceKey="HostUsername">Host Username:</Label>
<div class="col-sm-9">
@ -169,6 +194,8 @@ else
private Type _databaseConfigType;
private object _databaseConfig;
private RenderFragment DatabaseConfigComponent { get; set; }
private bool _showConnectionString = false;
private string _connectionString = string.Empty;
private List<Theme> _themeList;
private List<ThemeControl> _themes = new List<ThemeControl>();
@ -218,7 +245,7 @@ else
try
{
_databaseName = (string)eventArgs.Value;
_showConnectionString = false;
LoadDatabaseConfigComponent();
}
catch
@ -312,12 +339,19 @@ else
user = await UserService.LoginUserAsync(user);
if (user.IsAuthenticated)
{
var connectionString = String.Empty;
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
{
connectionString = databaseConfigControl.GetConnectionString();
}
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
var connectionString = String.Empty;
if (_showConnectionString)
{
connectionString = _connectionString;
}
else
{
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
{
connectionString = databaseConfigControl.GetConnectionString();
}
}
if (connectionString != "")
{
@ -398,4 +432,13 @@ else
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
private void ToggleConnectionString()
{
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
{
_connectionString = databaseConfigControl.GetConnectionString();
}
_showConnectionString = !_showConnectionString;
}
}

View File

@ -38,7 +38,13 @@
<input id="ipaddress" class="form-control" @bind="@_ipaddress" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="environment" HelpText="Environment name" ResourceKey="Environment">Environment: </Label>
<div class="col-sm-9">
<input id="environment" class="form-control" @bind="@_environment" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="contentrootpath" HelpText="Root Path" ResourceKey="ContentRootPath">Root Path: </Label>
<div class="col-sm-9">
<input id="contentrootpath" class="form-control" @bind="@_contentrootpath" readonly />
@ -152,6 +158,7 @@
private string _osversion = string.Empty;
private string _machinename = string.Empty;
private string _ipaddress = string.Empty;
private string _environment = string.Empty;
private string _contentrootpath = string.Empty;
private string _webrootpath = string.Empty;
private string _servertime = string.Empty;
@ -176,6 +183,7 @@
_osversion = systeminfo["OSVersion"].ToString();
_machinename = systeminfo["MachineName"].ToString();
_ipaddress = systeminfo["IPAddress"].ToString();
_environment = systeminfo["Environment"].ToString();
_contentrootpath = systeminfo["ContentRootPath"].ToString();
_webrootpath = systeminfo["WebRootPath"].ToString();
_servertime = systeminfo["ServerTime"].ToString() + " UTC";

View File

@ -25,7 +25,13 @@
<input id="version" class="form-control" @bind="@_version" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this module was installed" ResourceKey="PackageName">Package Name: </Label>
<div class="col-sm-9">
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="owner" HelpText="The owner or creator of the theme" ResourceKey="Owner">Owner: </Label>
<div class="col-sm-9">
<input id="owner" class="form-control" @bind="@_owner" disabled />
@ -53,28 +59,30 @@
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@code {
private string _themeName = "";
private string _name;
private string _version;
private string _owner = "";
private string _url = "";
private string _contact = "";
private string _license = "";
private string _themeName = "";
private string _name;
private string _version;
private string _packagename;
private string _owner = "";
private string _url = "";
private string _contact = "";
private string _license = "";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync()
{
try
{
_themeName = WebUtility.UrlDecode(PageState.QueryString["name"]);
var themes = await ThemeService.GetThemesAsync();
var theme = themes.FirstOrDefault(item => item.ThemeName == _themeName);
if (theme != null)
{
_name = theme.Name;
_version = theme.Version;
_owner = theme.Owner;
protected override async Task OnInitializedAsync()
{
try
{
_themeName = WebUtility.UrlDecode(PageState.QueryString["name"]);
var themes = await ThemeService.GetThemesAsync();
var theme = themes.FirstOrDefault(item => item.ThemeName == _themeName);
if (theme != null)
{
_name = theme.Name;
_version = theme.Version;
_packagename = theme.PackageName;
_owner = theme.Owner;
_url = theme.Url;
_contact = theme.Contact;
_license = theme.License;

View File

@ -5,7 +5,7 @@
<OutputType>Exe</OutputType>
<RazorLangVersion>3.0</RazorLangVersion>
<Configurations>Debug;Release</Configurations>
<Version>3.1.3</Version>
<Version>3.1.4</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -13,7 +13,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.3</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace>

View File

@ -114,6 +114,7 @@ namespace Oqtane.Client
private static void RegisterModuleServices(Assembly assembly, IServiceCollection services)
{
// dynamically register module scoped services
var implementationTypes = assembly.GetInterfaces<IService>();
foreach (var implementationType in implementationTypes)
{

View File

@ -121,7 +121,7 @@
<value>Server:</value>
</data>
<data name="Server.HelpText" xml:space="preserve">
<value>Enter the database server</value>
<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>
</data>
<data name="Database.Text" xml:space="preserve">
<value>Database:</value>
@ -133,7 +133,7 @@
<value>Integrated Security:</value>
</data>
<data name="IntegratedSecurity.HelpText" xml:space="preserve">
<value>Select if you want integrated security or not</value>
<value>Select if you are using integrated security</value>
</data>
<data name="Uid.Text" xml:space="preserve">
<value>User Id:</value>
@ -163,7 +163,7 @@
<value>Self Signed</value>
</data>
<data name="TrustServerCertificate.HelpText" xml:space="preserve">
<value>Specify the type of certificate you are using for encryption</value>
<value>Specify the type of certificate you are using for encryption. Verifiable is equivalent to False. Self Signed is equivalent to True.</value>
</data>
<data name="TrustServerCertificate.Text" xml:space="preserve">
<value>Trust Server Certificate:</value>

View File

@ -135,10 +135,10 @@
<data name="Message.Require.DbInfo" xml:space="preserve">
<value>Please Enter All Required Fields. Ensure Passwords Match And Email Address Provided Is Valid.</value>
</data>
<data name="Message.Password.Invalid" xml:space="preserve">
<data name="Message.Password.Invalid" xml:space="preserve">
<value>The Password Provided Does Not Meet The Password Policy. Please Verify The Minimum Password Length And Complexity Requirements.</value>
</data>
<data name="Register" xml:space="preserve">
<data name="Register" xml:space="preserve">
<value>Please Register Me For Major Product Updates And Security Bulletins</value>
</data>
<data name="Confirm.HelpText" xml:space="preserve">
@ -168,4 +168,16 @@
<data name="Username.Text" xml:space="preserve">
<value>Username:</value>
</data>
<data name="ConnectionString.HelpText" xml:space="preserve">
<value>Enter a complete connection string including all parameters and delimiters</value>
</data>
<data name="ConnectionString.Text" xml:space="preserve">
<value>String:</value>
</data>
<data name="EnterConnectionParameters" xml:space="preserve">
<value>Enter Connection Parameters</value>
</data>
<data name="EnterConnectionString" xml:space="preserve">
<value>Enter Connection String</value>
</data>
</root>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@ -118,6 +118,6 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Error.Module.Load" xml:space="preserve">
<value>A Problem Was Encountered Loading Module {0}</value>
<value>A Problem Was Encountered Loading Module {0}. The Module Is Either Invalid Or Does Not Exist.</value>
</data>
</root>

View File

@ -192,4 +192,7 @@
<data name="Once" xml:space="preserve">
<value>Execute Once</value>
</data>
<data name="Message.NoJobs" xml:space="preserve">
<value>Please Note That After An Initial Installation You Must &amp;lt;a href={0}&amp;gt;Restart&amp;lt;/a&amp;gt; The Application In Order To Activate The Default Scheduled Jobs.</value>
</data>
</root>

View File

@ -154,13 +154,13 @@
<value>No Translations Match The Criteria Provided Or Package Service Is Disabled</value>
</data>
<data name="Download.Heading" xml:space="preserve">
<value>Download</value>
<value>Translations</value>
</data>
<data name="LanguageUpload.HelpText" xml:space="preserve">
<value>Upload one or more translations. Once they are uploaded click Install to complete the installation.</value>
<value>Upload one or more translation packages. Once they are uploaded click Install to complete the installation.</value>
</data>
<data name="LanguageUpload.Text" xml:space="preserve">
<value>Upload Language</value>
<value>Translation</value>
</data>
<data name="Manage.Heading" xml:space="preserve">
<value>Manage</value>

View File

@ -144,4 +144,7 @@
<data name="DeleteLanguage.Text" xml:space="preserve">
<value>Delete</value>
</data>
<data name="Translation" xml:space="preserve">
<value>Translation</value>
</data>
</root>

View File

@ -195,4 +195,25 @@
<data name="Information.Text" xml:space="preserve">
<value>Information</value>
</data>
<data name="PackageName.HelpText" xml:space="preserve">
<value>The unique name of the package from which this module was installed</value>
</data>
<data name="PackageName.Text" xml:space="preserve">
<value>Package Name:</value>
</data>
<data name="Error.Translation.Download" xml:space="preserve">
<value>Error Downloading Translation</value>
</data>
<data name="Search.NoResults" xml:space="preserve">
<value>No Translations Exist For This Module Or Package Service Is Disabled</value>
</data>
<data name="Success.Translation.Download" xml:space="preserve">
<value>Translation Downloaded Successfully. Click Install To Complete Installation.</value>
</data>
<data name="Success.Translation.Install" xml:space="preserve">
<value>Translation Installed Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Apply These Changes.</value>
</data>
<data name="Translations.Heading" xml:space="preserve">
<value>Translations</value>
</data>
</root>

View File

@ -147,4 +147,7 @@
<data name="Message.Required.Title" xml:space="preserve">
<value>You Must Provide A Title For The Module</value>
</data>
<data name="Error.Module.Load" xml:space="preserve">
<value>A Problem Was Encountered Loading Module {0}. The Module Is Either Invalid Or Does Not Exist.</value>
</data>
</root>

View File

@ -166,7 +166,7 @@
<value>Enter the tenant for the site</value>
</data>
<data name="Aliases.HelpText" xml:space="preserve">
<value>The aliases for the site. An alias can be a domain name (www.site.com) or a virtual folder (ie. www.site.com/folder). If a site has multiple aliases they should be separated by commas.</value>
<value>The aliases for the site. An alias can be a domain name (www.site.com) or a virtual folder (ie. www.site.com/folder).</value>
</data>
<data name="IsDeleted.HelpText" xml:space="preserve">
<value>Is this site deleted?</value>

View File

@ -270,4 +270,16 @@
<data name="Runtime.Text" xml:space="preserve">
<value>Runtime: </value>
</data>
<data name="ConnectionString.HelpText" xml:space="preserve">
<value>Enter a complete connection string including all parameters and delimiters</value>
</data>
<data name="ConnectionString.Text" xml:space="preserve">
<value>String:</value>
</data>
<data name="EnterConnectionParameters" xml:space="preserve">
<value>Enter Connection Parameters</value>
</data>
<data name="EnterConnectionString" xml:space="preserve">
<value>Enter Connection String</value>
</data>
</root>

View File

@ -270,4 +270,10 @@
<data name="WorkingSet.Text" xml:space="preserve">
<value>Memory Allocation:</value>
</data>
<data name="Environment.HelpText" xml:space="preserve">
<value>Environment Name</value>
</data>
<data name="Environment.Text" xml:space="preserve">
<value>Environment:</value>
</data>
</root>

View File

@ -162,4 +162,10 @@
<data name="License.HelpText" xml:space="preserve">
<value>The license of the theme</value>
</data>
<data name="PackageName.HelpText" xml:space="preserve">
<value>The unique name of the package from which this module was installed</value>
</data>
<data name="PackageName.Text" xml:space="preserve">
<value>Package Name:</value>
</data>
</root>

View File

@ -138,6 +138,12 @@
<data name="Register.Text" xml:space="preserve">
<value>Show Register?</value>
</data>
<data name="Scope.HelpText" xml:space="preserve">
<value>Specify if the settings are applicable to this page or the entire site.</value>
</data>
<data name="Scope.Text" xml:space="preserve">
<value>Setting Scope:</value>
</data>
<data name="Site" xml:space="preserve">
<value>Site</value>
</data>

View File

@ -47,7 +47,7 @@ namespace Oqtane.Services
var path = WebUtility.UrlEncode(folderPath);
List<File> files = await GetJsonAsync<List<File>>($"{Apiurl}/{siteId}/{path}");
return files.OrderBy(item => item.Name).ToList();
return files?.OrderBy(item => item.Name).ToList();
}
public async Task<File> GetFileAsync(int fileId)

View File

@ -17,6 +17,14 @@ namespace Oqtane.Services
/// <returns></returns>
Task<List<Language>> GetLanguagesAsync(int siteId);
/// <summary>
/// Returns a list of all available languages for the given <see cref="Site" /> and package
/// </summary>
/// <param name="siteId"></param>
/// <param name="packageName"></param>
/// <returns></returns>
Task<List<Language>> GetLanguagesAsync(int siteId, string packageName);
/// <summary>
/// Returns the given language
/// </summary>

View File

@ -17,18 +17,27 @@ namespace Oqtane.Services
public async Task<List<Language>> GetLanguagesAsync(int siteId)
{
var languages = await GetJsonAsync<List<Language>>($"{Apiurl}?siteid={siteId}");
return await GetLanguagesAsync(siteId, "");
}
return languages?.OrderBy(l => l.Name).ToList() ?? Enumerable.Empty<Language>().ToList();
public async Task<List<Language>> GetLanguagesAsync(int siteId, string packageName)
{
return await GetJsonAsync<List<Language>>($"{Apiurl}?siteid={siteId}&packagename={packageName}");
}
public async Task<Language> GetLanguageAsync(int languageId)
=> await GetJsonAsync<Language>($"{Apiurl}/{languageId}");
{
return await GetJsonAsync<Language>($"{Apiurl}/{languageId}");
}
public async Task<Language> AddLanguageAsync(Language language)
=> await PostJsonAsync<Language>(Apiurl, language);
{
return await PostJsonAsync<Language>(Apiurl, language);
}
public async Task DeleteLanguageAsync(int languageId)
=> await DeleteAsync($"{Apiurl}/{languageId}");
{
await DeleteAsync($"{Apiurl}/{languageId}");
}
}
}

View File

@ -17,7 +17,7 @@
<LanguageSwitcher />
}
@if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions) || (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered)))
@if (_showEditMode || (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered)))
{
if (PageState.EditMode)
{
@ -218,6 +218,7 @@
}
@code{
private bool _showEditMode = false;
private bool _deleteConfirmation = false;
private List<string> _categories = new List<string>();
private List<ModuleDefinition> _allModuleDefinitions;
@ -285,8 +286,10 @@
protected override async Task OnParametersSetAsync()
{
_showEditMode = false;
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions))
{
_showEditMode = true;
_pages?.Clear();
foreach (Page p in PageState.Pages)
@ -305,6 +308,17 @@
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(Category)).ToList();
_categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',')).Distinct().ToList();
}
else
{
foreach (var module in PageState.Modules.Where(item => item.PageId == PageState.Page.PageId))
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, module.Permissions))
{
_showEditMode = true;
break;
}
}
}
}
private void CategoryChanged(ChangeEventArgs e)
@ -405,21 +419,21 @@
Message = $"<div class=\"alert alert-success mt-2 text-center\" role=\"alert\">{Localizer["Success.Page.ModuleAdd"]}</div>";
Title = "";
NavigationManager.NavigateTo(NavigateUrl());
}
else
{
Message = $"<div class=\"alert alert-warning mt-2 text-center\" role=\"alert\">{Localizer["Message.Require.ModuleSelect"]}</div>";
}
}
else
{
Message = $"<div class=\"alert alert-error mt-2 text-center\" role=\"alert\">{Localizer["Error.Authorize.No"]}</div>";
}
}
}
else
{
Message = $"<div class=\"alert alert-warning mt-2 text-center\" role=\"alert\">{Localizer["Message.Require.ModuleSelect"]}</div>";
}
}
else
{
Message = $"<div class=\"alert alert-error mt-2 text-center\" role=\"alert\">{Localizer["Error.Authorize.No"]}</div>";
}
}
private async Task ToggleEditMode(bool EditMode)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions))
if (_showEditMode)
{
if (EditMode)
{
@ -445,7 +459,6 @@
private void Navigate(string location)
{
//HideControlPanel();
Module module;
switch (location)
{

View File

@ -24,13 +24,9 @@
@code{
private IEnumerable<Culture> _supportedCultures;
protected override async Task OnParametersSetAsync()
protected override void OnParametersSet()
{
var languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
var defaultCulture = CultureInfo.GetCultureInfo(Constants.DefaultCulture);
languages.Add(new Language { Code = defaultCulture.Name, Name = defaultCulture.DisplayName });
var languages = PageState.Languages;
_supportedCultures = languages.Select(l => new Culture { Name = l.Code, DisplayName = l.Name });
}

View File

@ -8,6 +8,7 @@ namespace Oqtane.UI
{
public Alias Alias { get; set; }
public Site Site { get; set; }
public List<Language> Languages { get; set; }
public List<Page> Pages { get; set; }
public Page Page { get; set; }
public User User { get; set; }

View File

@ -6,6 +6,7 @@
@inject INavigationInterception NavigationInterception
@inject ISyncService SyncService
@inject ISiteService SiteService
@inject ILanguageService LanguageService
@inject IPageService PageService
@inject IUserService UserService
@inject IModuleService ModuleService
@ -70,6 +71,7 @@
private async Task Refresh()
{
Site site;
List<Language> languages;
List<Page> pages;
Page page;
User user = null;
@ -102,7 +104,7 @@
return;
}
}
// the refresh parameter is used to refresh the client-side PageState
if (querystring.ContainsKey("refresh"))
{
@ -173,11 +175,13 @@
if (PageState == null || refresh == UI.Refresh.Site)
{
languages = await LanguageService.GetLanguagesAsync(site.SiteId);
pages = await PageService.GetPagesAsync(site.SiteId);
pages = pages.Where(item => !item.IsDeleted).ToList();
}
else
{
languages = PageState.Languages;
pages = PageState.Pages;
}
@ -230,6 +234,7 @@
{
Alias = SiteState.Alias,
Site = site,
Languages = languages,
Pages = pages,
Page = page,
User = user,

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Version>3.1.3</Version>
<Version>3.1.4</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.3</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Database.MySQL</id>
<version>3.1.3</version>
<version>3.1.4</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane MySQL Provider</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.3</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Version>3.1.3</Version>
<Version>3.1.4</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.3</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Database.PostgreSQL</id>
<version>3.1.3</version>
<version>3.1.4</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane PostgreSQL Provider</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.3</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Version>3.1.3</Version>
<Version>3.1.4</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.3</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Database.SqlServer</id>
<version>3.1.3</version>
<version>3.1.4</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane SQL Server Provider</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.3</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Version>3.1.3</Version>
<Version>3.1.4</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.3</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Database.Sqlite</id>
<version>3.1.3</version>
<version>3.1.4</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane SQLite Provider</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.3</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Client</id>
<version>3.1.3</version>
<version>3.1.4</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.3</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Framework</id>
<version>3.1.3</version>
<version>3.1.4</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -11,8 +11,8 @@
<copyright>.NET Foundation</copyright>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v3.1.3/Oqtane.Framework.3.1.2.Upgrade.zip</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.3</releaseNotes>
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v3.1.4/Oqtane.Framework.3.1.2.Upgrade.zip</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane framework</tags>
</metadata>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Server</id>
<version>3.1.3</version>
<version>3.1.4</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.3</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Shared</id>
<version>3.1.3</version>
<version>3.1.4</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.3</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Oqtane.Updater</id>
<version>3.1.3</version>
<version>3.1.4</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.3</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</releaseNotes>
<icon>icon.png</icon>
<tags>oqtane</tags>
</metadata>

View File

@ -1 +1 @@
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.1.3.Install.zip" -Force
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.1.4.Install.zip" -Force

View File

@ -1 +1 @@
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.1.3.Upgrade.zip" -Force
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.1.4.Upgrade.zip" -Force

View File

@ -16,12 +16,14 @@ namespace Oqtane.Controllers
public class AliasController : Controller
{
private readonly IAliasRepository _aliases;
private readonly ITenantRepository _tenants;
private readonly ILogManager _logger;
private readonly Alias _alias;
public AliasController(IAliasRepository aliases, ILogManager logger, ITenantManager tenantManager)
public AliasController(IAliasRepository aliases, ITenantRepository tenants, ILogManager logger, ITenantManager tenantManager)
{
_aliases = aliases;
_tenants = tenants;
_logger = logger;
_alias = tenantManager.GetAlias();
}
@ -95,6 +97,13 @@ namespace Oqtane.Controllers
{
_aliases.DeleteAlias(id);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Alias Deleted {AliasId}", id);
var aliases = _aliases.GetAliases();
if (!aliases.Any(item => item.TenantId == alias.TenantId))
{
_tenants.DeleteTenant(alias.TenantId);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Tenant Deleted {TenantId}", alias.TenantId);
}
}
else
{

View File

@ -1,5 +1,7 @@
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Reflection;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Oqtane.Enums;
@ -7,6 +9,10 @@ using Oqtane.Infrastructure;
using Oqtane.Models;
using Oqtane.Repository;
using Oqtane.Shared;
using System.Linq;
using System.Diagnostics;
using System.Globalization;
using System;
namespace Oqtane.Controllers
{
@ -14,23 +20,40 @@ namespace Oqtane.Controllers
public class LanguageController : Controller
{
private readonly ILanguageRepository _languages;
private readonly ISyncManager _syncManager;
private readonly ILogManager _logger;
private readonly Alias _alias;
public LanguageController(ILanguageRepository language, ILogManager logger, ITenantManager tenantManager)
public LanguageController(ILanguageRepository language, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager)
{
_languages = language;
_syncManager = syncManager;
_logger = logger;
_alias = tenantManager.GetAlias();
}
[HttpGet]
public IEnumerable<Language> Get(string siteid)
public IEnumerable<Language> Get(string siteid, string packagename)
{
int SiteId;
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId)
{
return _languages.GetLanguages(SiteId);
if (string.IsNullOrEmpty(packagename))
{
packagename = "Oqtane";
}
var languages = _languages.GetLanguages(SiteId).ToList();
foreach (var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"{packagename}.*{Constants.SatelliteAssemblyExtension}", SearchOption.AllDirectories))
{
var code = Path.GetFileName(Path.GetDirectoryName(file));
if (languages.Any(item => item.Code == code))
{
languages.Single(item => item.Code == code).Version = FileVersionInfo.GetVersionInfo(file).FileVersion;
}
}
var defaultCulture = CultureInfo.GetCultureInfo(Constants.DefaultCulture);
languages.Add(new Language { Code = defaultCulture.Name, Name = defaultCulture.DisplayName, Version = Constants.Version, IsDefault = !languages.Any(l => l.IsDefault) });
return languages.OrderBy(item => item.Name);
}
else
{
@ -63,6 +86,7 @@ namespace Oqtane.Controllers
if (ModelState.IsValid && language.SiteId == _alias.SiteId)
{
language = _languages.AddLanguage(language);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Language Added {Language}", language);
}
else
@ -82,6 +106,7 @@ namespace Oqtane.Controllers
if (language != null && language.SiteId == _alias.SiteId)
{
_languages.DeleteLanguage(id);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Language Deleted {LanguageId}", id);
}
else
@ -89,7 +114,6 @@ namespace Oqtane.Controllers
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Language Delete Attempt {LanguageId}", id);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
}
}

View File

@ -22,11 +22,15 @@ namespace Oqtane.Controllers
// GET: api/localization
[HttpGet()]
public IEnumerable<Culture> Get()
=> _localizationManager.GetSupportedCultures().Select(c => new Culture {
Name = CultureInfo.GetCultureInfo(c).Name,
DisplayName = CultureInfo.GetCultureInfo(c).DisplayName,
IsDefault = _localizationManager.GetDefaultCulture()
{
var cultures = _localizationManager.GetSupportedCultures().Select(c => new Culture
{
Name = CultureInfo.GetCultureInfo(c).Name,
DisplayName = CultureInfo.GetCultureInfo(c).DisplayName,
IsDefault = _localizationManager.GetDefaultCulture()
.Equals(CultureInfo.GetCultureInfo(c).Name, StringComparison.OrdinalIgnoreCase)
});
});
return cultures.OrderBy(item => item.DisplayName);
}
}
}

View File

@ -307,9 +307,9 @@ namespace Oqtane.Controllers
if (moduleDefinition.Version == "local")
{
text = text.Replace("[FrameworkVersion]", Constants.Version);
text = text.Replace("[ClientReference]", "<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\oqtane.framework\\Oqtane.Server\\bin\\Debug\\net6.0\\Oqtane.Client.dll</HintPath></Reference>");
text = text.Replace("[ServerReference]", "<Reference Include=\"Oqtane.Server\"><HintPath>..\\..\\oqtane.framework\\Oqtane.Server\\bin\\Debug\\net6.0\\Oqtane.Server.dll</HintPath></Reference>");
text = text.Replace("[SharedReference]", "<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\oqtane.framework\\Oqtane.Server\\bin\\Debug\\net6.0\\Oqtane.Shared.dll</HintPath></Reference>");
text = text.Replace("[ClientReference]", $"<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net6.0\\Oqtane.Client.dll</HintPath></Reference>");
text = text.Replace("[ServerReference]", $"<Reference Include=\"Oqtane.Server\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net6.0\\Oqtane.Server.dll</HintPath></Reference>");
text = text.Replace("[SharedReference]", $"<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net6.0\\Oqtane.Shared.dll</HintPath></Reference>");
}
else
{

View File

@ -38,6 +38,7 @@ namespace Oqtane.Controllers
systeminfo.Add("TickCount", Environment.TickCount64.ToString());
systeminfo.Add("ContentRootPath", _environment.ContentRootPath);
systeminfo.Add("WebRootPath", _environment.WebRootPath);
systeminfo.Add("Environment", _environment.EnvironmentName);
systeminfo.Add("ServerTime", DateTime.UtcNow.ToString());
var feature = HttpContext.Features.Get<IHttpConnectionFeature>();
systeminfo.Add("IPAddress", feature?.LocalIpAddress?.ToString());

View File

@ -188,8 +188,8 @@ namespace Oqtane.Controllers
if (theme.Version == "local")
{
text = text.Replace("[FrameworkVersion]", Constants.Version);
text = text.Replace("[ClientReference]", "<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\oqtane.framework\\Oqtane.Server\\bin\\Debug\\net6.0\\Oqtane.Client.dll</HintPath></Reference>");
text = text.Replace("[SharedReference]", "<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\oqtane.framework\\Oqtane.Server\\bin\\Debug\\net6.0\\Oqtane.Shared.dll</HintPath></Reference>");
text = text.Replace("[ClientReference]", $"<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net6.0\\Oqtane.Client.dll</HintPath></Reference>");
text = text.Replace("[SharedReference]", $"<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net6.0\\Oqtane.Shared.dll</HintPath></Reference>");
}
else
{

View File

@ -5,6 +5,7 @@ using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Reflection.Metadata;
using System.Runtime.Loader;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.OAuth;
@ -17,6 +18,7 @@ using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
using Oqtane.Infrastructure;
using Oqtane.Models;
using Oqtane.Modules;
using Oqtane.Repository;
using Oqtane.Security;
@ -248,7 +250,7 @@ namespace Microsoft.Extensions.DependencyInjection
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
foreach (var assembly in assemblies)
{
// dynamically register module services, contexts, and repository classes
// dynamically register module scoped services (ie. client service classes)
var implementationTypes = assembly.GetInterfaces<IService>();
foreach (var implementationType in implementationTypes)
{
@ -259,6 +261,17 @@ namespace Microsoft.Extensions.DependencyInjection
}
}
// dynamically register module transient services (ie. server DBContext, repository classes)
implementationTypes = assembly.GetInterfaces<ITransientService>();
foreach (var implementationType in implementationTypes)
{
if (implementationType.AssemblyQualifiedName != null)
{
var serviceType = Type.GetType(implementationType.AssemblyQualifiedName.Replace(implementationType.Name, $"I{implementationType.Name}"));
services.AddTransient(serviceType ?? implementationType, implementationType);
}
}
// dynamically register hosted services
var serviceTypes = assembly.GetTypes(hostedServiceType);
foreach (var serviceType in serviceTypes)
@ -315,52 +328,26 @@ namespace Microsoft.Extensions.DependencyInjection
private static void LoadSatelliteAssemblies(string[] supportedCultures)
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
if (assemblyPath == null)
{
return;
}
AssemblyLoadContext.Default.Resolving += ResolveDependencies;
foreach (var culture in supportedCultures)
foreach (var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"*{Constants.SatelliteAssemblyExtension}", SearchOption.AllDirectories))
{
if (culture == Constants.DefaultCulture)
var code = Path.GetFileName(Path.GetDirectoryName(file));
if (supportedCultures.Contains(code))
{
continue;
}
var assembliesFolder = new DirectoryInfo(Path.Combine(assemblyPath, culture));
if (assembliesFolder.Exists)
{
foreach (var assemblyFile in assembliesFolder.EnumerateFiles($"*{Constants.SatelliteAssemblyExtension}"))
try
{
AssemblyName assemblyName;
try
{
assemblyName = AssemblyName.GetAssemblyName(assemblyFile.FullName);
}
catch
{
Debug.WriteLine($"Oqtane Error: Cannot Get Satellite Assembly Name For {assemblyFile.Name}");
continue;
}
try
{
Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(System.IO.File.ReadAllBytes(assemblyFile.FullName)));
Debug.WriteLine($"Oqtane Info: Loaded Assembly {assemblyName}");
}
catch (Exception ex)
{
Debug.WriteLine($"Oqtane Error: Unable To Load Assembly {assemblyName} - {ex}");
}
Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(System.IO.File.ReadAllBytes(file)));
Debug.WriteLine($"Oqtane Info: Loaded Satellite Assembly {file}");
}
catch (Exception ex)
{
Debug.WriteLine($"Oqtane Error: Unable To Load Satellite Assembly {file} - {ex}");
}
}
else
{
Debug.WriteLine($"Oqtane Error: The Satellite Assembly Folder For {culture} Does Not Exist");
Debug.WriteLine($"Oqtane Error: Culture Not Supported For Satellite Assembly {file}");
}
}
}

View File

@ -48,26 +48,35 @@ namespace Oqtane.Infrastructure
}
// move packages to secure /Packages folder
foreach (var folder in "Modules,Themes,Packages".Split(","))
foreach (var folderName in "Modules,Themes,Packages".Split(","))
{
foreach(var file in Directory.GetFiles(Path.Combine(webRootPath, folder), "*.nupkg*"))
string folder = Path.Combine(webRootPath, folderName);
if (Directory.Exists(folder))
{
var destinationFile = Path.Combine(sourceFolder, Path.GetFileName(file));
if (File.Exists(destinationFile))
foreach (var file in Directory.GetFiles(folder, "*.nupkg*"))
{
File.Delete(destinationFile);
}
if (destinationFile.ToLower().EndsWith(".nupkg.bak"))
{
// leave a copy in the current folder as it is distributed with the core framework
File.Copy(file, destinationFile);
}
else
{
// move to destination
File.Move(file, destinationFile);
var destinationFile = Path.Combine(sourceFolder, Path.GetFileName(file));
if (File.Exists(destinationFile))
{
File.Delete(destinationFile);
}
if (destinationFile.ToLower().EndsWith(".nupkg.bak"))
{
// leave a copy in the current folder as it is distributed with the core framework
File.Copy(file, destinationFile);
}
else
{
// move to destination
File.Move(file, destinationFile);
}
}
}
else
{
Directory.CreateDirectory(folder);
}
}
// iterate through Nuget packages in source folder
@ -200,7 +209,7 @@ namespace Oqtane.Infrastructure
string[] packages = Directory.GetFiles(Path.Combine(_environment.ContentRootPath, Constants.PackagesFolder), PackageName + "*.log");
if (packages.Length > 0)
{
packagename = packages[packages.Length - 1]; // use highest version
packagename = packages[packages.Length - 1]; // use highest version
}
if (!string.IsNullOrEmpty(packagename))
@ -245,7 +254,7 @@ namespace Oqtane.Infrastructure
string[] packages = Directory.GetFiles(folder, Constants.PackageId + ".*.nupkg");
if (packages.Length > 0)
{
packagename = packages[packages.Length - 1]; // use highest version
packagename = packages[packages.Length - 1]; // use highest version
}
if (packagename != "")

View File

@ -1,8 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Globalization;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.Options;
using Oqtane.Shared;
@ -11,7 +8,6 @@ namespace Oqtane.Infrastructure
public class LocalizationManager : ILocalizationManager
{
private static readonly string DefaultCulture = Constants.DefaultCulture;
private static readonly string[] DefaultSupportedCultures = new[] { DefaultCulture };
private readonly LocalizationOptions _localizationOptions;
@ -21,19 +17,20 @@ namespace Oqtane.Infrastructure
}
public string GetDefaultCulture()
=> String.IsNullOrEmpty(_localizationOptions.DefaultCulture)
? DefaultCulture
: _localizationOptions.DefaultCulture;
{
if (string.IsNullOrEmpty(_localizationOptions.DefaultCulture))
{
return DefaultCulture;
}
else
{
return _localizationOptions.DefaultCulture;
}
}
public string[] GetSupportedCultures()
{
var cultures = new List<string>(DefaultSupportedCultures);
foreach(var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "Oqtane.Client.resources.dll", SearchOption.AllDirectories))
{
cultures.Add(Path.GetFileName(Path.GetDirectoryName(file)));
}
return cultures.OrderBy(c => c).ToArray();
return CultureInfo.GetCultures(CultureTypes.AllCultures).Select(item => item.Name).OrderBy(c => c).ToArray();
}
}
}

View File

@ -62,7 +62,7 @@ namespace Oqtane.SiteTemplates
"<p><a href=\"https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor\" target=\"_new\">Blazor</a> is an open source and cross-platform web UI framework for building single-page apps using .NET and C# instead of JavaScript. Blazor WebAssembly relies on Wasm, an open web standard that does not require plugins or code transpilation in order to run natively in a web browser. Blazor Server uses SignalR to host your application on a web server and provide a responsive and robust development experience. Blazor applications work in all modern web browsers, including mobile browsers.</p>" +
"<p>Blazor is a feature of <a href=\"https://dotnet.microsoft.com/apps/aspnet\" target=\"_new\">.NET Core</a>, the popular cross platform web development framework from Microsoft that extends the <a href=\"https://dotnet.microsoft.com/learn/dotnet/what-is-dotnet\" target=\"_new\" >.NET developer platform</a> with tools and libraries for building web apps.</p>"
},
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "MIT License", Pane = "Content",
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "MIT License", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission> {
new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.View, RoleNames.Admin, true),

View File

@ -1,7 +1,5 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Oqtane.Extensions;
using Oqtane.Models;
using Oqtane.Repository;
@ -53,6 +51,9 @@ namespace Oqtane.Infrastructure
case "3.1.3":
Upgrade_3_1_3(tenant, scope);
break;
case "3.1.4":
Upgrade_3_1_4(tenant, scope);
break;
}
}
}
@ -195,5 +196,47 @@ namespace Oqtane.Infrastructure
}
}
private void Upgrade_3_1_4(Tenant tenant, IServiceScope scope)
{
var pageTemplates = new List<PageTemplate>();
pageTemplates.Add(new PageTemplate
{
Name = "Not Found",
Parent = "",
Path = "404",
Icon = Icons.X,
IsNavigation = false,
IsPersonalizable = false,
PagePermissions = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
}.EncodePermissions(),
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "Not Found", Pane = PaneNames.Admin,
ModulePermissions = new List<Permission> {
new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
}.EncodePermissions(),
Content = "<p>The page you requested does not exist.</p>"
}
}
});
var pages = scope.ServiceProvider.GetRequiredService<IPageRepository>();
var sites = scope.ServiceProvider.GetRequiredService<ISiteRepository>();
foreach (Site site in sites.GetSites().ToList())
{
if (!pages.GetPages(site.SiteId).ToList().Where(item => item.Path == "404").Any())
{
sites.CreatePages(site, pageTemplates);
}
}
}
}
}

View File

@ -11,7 +11,7 @@ using Oqtane.Repository.Databases.Interfaces;
namespace Oqtane.Modules.HtmlText.Repository
{
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
public class HtmlTextContext : DBContextBase, IService, IMultiDatabase
public class HtmlTextContext : DBContextBase, ITransientService, IMultiDatabase
{
public HtmlTextContext(ITenantManager tenantManager, IHttpContextAccessor httpContextAccessor) : base(tenantManager, httpContextAccessor) { }

View File

@ -1,13 +1,11 @@
using Microsoft.EntityFrameworkCore;
using System.Linq;
using Oqtane.Modules.HtmlText.Models;
using Oqtane.Documentation;
using System.Collections.Generic;
namespace Oqtane.Modules.HtmlText.Repository
{
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
public class HtmlTextRepository : IHtmlTextRepository, IService
public class HtmlTextRepository : IHtmlTextRepository, ITransientService
{
private readonly HtmlTextContext _db;

View File

@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Configurations>Debug;Release</Configurations>
<Version>3.1.3</Version>
<Version>3.1.4</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -11,7 +11,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.3</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace>

View File

@ -1,11 +1,12 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Security.Policy;
using Oqtane.Models;
// ReSharper disable once CheckNamespace
namespace Oqtane.Repository
{
public interface IPermissionRepository
{
{
IEnumerable<Permission> GetPermissions(int siteId, string entityName);
IEnumerable<Permission> GetPermissions(string entityName, int entityId);
IEnumerable<Permission> GetPermissions(string entityName, int entityId, string permissionName);

View File

@ -13,13 +13,16 @@ namespace Oqtane.Repository
_db = context;
}
public IEnumerable<Language> GetLanguages(int siteId) => _db.Language.Where(l => l.SiteId == siteId);
public IEnumerable<Language> GetLanguages(int siteId)
{
return _db.Language.Where(l => l.SiteId == siteId);
}
public Language AddLanguage(Language language)
{
if (language.IsDefault)
{
// Ensure all other languages are not set to current
// Ensure all other languages are not set to default
_db.Language
.Where(l => l.SiteId == language.SiteId)
.ToList()
@ -32,7 +35,10 @@ namespace Oqtane.Repository
return language;
}
public Language GetLanguage(int languageId) => _db.Language.Find(languageId);
public Language GetLanguage(int languageId)
{
return _db.Language.Find(languageId);
}
public void DeleteLanguage(int languageId)
{

View File

@ -3,6 +3,7 @@ using System.Linq;
using Microsoft.EntityFrameworkCore;
using Oqtane.Extensions;
using Oqtane.Models;
using Oqtane.Shared;
namespace Oqtane.Repository
{
@ -24,7 +25,7 @@ namespace Oqtane.Repository
.Where(item => item.Module.SiteId == siteId);
if (pagemodules.Any())
{
IEnumerable<Permission> permissions = _permissions.GetPermissions(pagemodules.FirstOrDefault().Module.SiteId, "Module").ToList();
IEnumerable<Permission> permissions = _permissions.GetPermissions(siteId, EntityNames.Module).ToList();
foreach (PageModule pagemodule in pagemodules)
{
pagemodule.Module.Permissions = permissions.Where(item => item.EntityId == pagemodule.ModuleId).EncodePermissions();
@ -44,7 +45,8 @@ namespace Oqtane.Repository
}
if (pagemodules.Any())
{
IEnumerable<Permission> permissions = _permissions.GetPermissions(pagemodules.FirstOrDefault().Module.SiteId, "Module").ToList();
var siteId = pagemodules.FirstOrDefault().Module.SiteId;
IEnumerable<Permission> permissions = _permissions.GetPermissions(siteId, EntityNames.Module).ToList();
foreach (PageModule pagemodule in pagemodules)
{
pagemodule.Module.Permissions = permissions.Where(item => item.EntityId == pagemodule.ModuleId).EncodePermissions();
@ -87,7 +89,7 @@ namespace Oqtane.Repository
}
if (pagemodule != null)
{
pagemodule.Module.Permissions = _permissions.GetPermissionString("Module", pagemodule.ModuleId);
pagemodule.Module.Permissions = _permissions.GetPermissionString(EntityNames.Module, pagemodule.ModuleId);
}
return pagemodule;
}
@ -98,7 +100,7 @@ namespace Oqtane.Repository
.SingleOrDefault(item => item.PageId == pageId && item.ModuleId == moduleId);
if (pagemodule != null)
{
pagemodule.Module.Permissions = _permissions.GetPermissionString("Module", pagemodule.ModuleId);
pagemodule.Module.Permissions = _permissions.GetPermissionString(EntityNames.Module, pagemodule.ModuleId);
}
return pagemodule;
}

View File

@ -1,11 +1,14 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Oqtane.Extensions;
using Oqtane.Models;
using Microsoft.Extensions.Caching.Memory;
using Oqtane.Infrastructure;
namespace Oqtane.Repository
{
@ -13,33 +16,49 @@ namespace Oqtane.Repository
{
private TenantDBContext _db;
private readonly IRoleRepository _roles;
private readonly IMemoryCache _cache;
private readonly SiteState _siteState;
public PermissionRepository(TenantDBContext context, IRoleRepository roles)
public PermissionRepository(TenantDBContext context, IRoleRepository roles, IMemoryCache cache, SiteState siteState)
{
_db = context;
_roles = roles;
}
_cache = cache;
_siteState = siteState;
}
public IEnumerable<Permission> GetPermissions(int siteId, string entityName)
{
return _db.Permission.Where(item => item.SiteId == siteId)
.Where(item => item.EntityName == entityName)
.Include(item => item.Role); // eager load roles
var alias = _siteState?.Alias;
if (alias != null && alias.SiteId != -1)
{
return _cache.GetOrCreate($"permissions:{alias.SiteKey}:{entityName}", entry =>
{
entry.SlidingExpiration = TimeSpan.FromMinutes(30);
return _db.Permission.Where(item => item.SiteId == alias.SiteId)
.Where(item => item.EntityName == entityName)
.Include(item => item.Role).ToList(); // eager load roles
});
}
else
{
return _db.Permission.Where(item => item.SiteId == siteId || siteId == -1)
.Where(item => item.EntityName == entityName)
.Include(item => item.Role).ToList(); // eager load roles
}
}
public IEnumerable<Permission> GetPermissions(string entityName, int entityId)
{
return _db.Permission.Where(item => item.EntityName == entityName)
.Where(item => item.EntityId == entityId)
.Include(item => item.Role); // eager load roles
var permissions = GetPermissions(-1, entityName);
return permissions.Where(item => item.EntityId == entityId);
}
public IEnumerable<Permission> GetPermissions(string entityName, int entityId, string permissionName)
{
return _db.Permission.Where(item => item.EntityName == entityName)
.Where(item => item.EntityId == entityId)
.Where(item => item.PermissionName == permissionName)
.Include(item => item.Role); // eager load roles
var permissions = GetPermissions(-1, entityName);
return permissions.Where(item => item.EntityId == entityId)
.Where(item => item.PermissionName == permissionName);
}
public string GetPermissionString(int siteId, string entityName)
@ -62,6 +81,7 @@ namespace Oqtane.Repository
{
_db.Permission.Add(permission);
_db.SaveChanges();
ClearCache(permission.EntityName);
return permission;
}
@ -69,6 +89,7 @@ namespace Oqtane.Repository
{
_db.Entry(permission).State = EntityState.Modified;
_db.SaveChanges();
ClearCache(permission.EntityName);
return permission;
}
@ -90,6 +111,7 @@ namespace Oqtane.Repository
_db.Permission.Add(permission);
}
_db.SaveChanges();
ClearCache(entityName);
}
public Permission GetPermission(int permissionId)
@ -102,6 +124,7 @@ namespace Oqtane.Repository
Permission permission = _db.Permission.Find(permissionId);
_db.Permission.Remove(permission);
_db.SaveChanges();
ClearCache(permission.EntityName);
}
public void DeletePermissions(int siteId, string entityName, int entityId)
@ -115,6 +138,16 @@ namespace Oqtane.Repository
_db.Permission.Remove(permission);
}
_db.SaveChanges();
ClearCache(entityName);
}
private void ClearCache(string entityName)
{
var alias = _siteState?.Alias;
if (alias != null && alias.SiteId != -1)
{
_cache.Remove($"permissions:{alias.SiteKey}:{entityName}");
}
}
// permissions are stored in the format "{permissionname:!rolename1;![userid1];rolename2;rolename3;[userid2];[userid3]}" where "!" designates Deny permissions

View File

@ -31,7 +31,8 @@ namespace Oqtane
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", true, true);
Configuration = builder.Build();
_supportedCultures = localizationManager.GetSupportedCultures();

View File

@ -7,7 +7,7 @@ using Oqtane.Repository.Databases.Interfaces;
namespace [Owner].[Module].Repository
{
public class [Module]Context : DBContextBase, IService, IMultiDatabase
public class [Module]Context : DBContextBase, ITransientService, IMultiDatabase
{
public virtual DbSet<Models.[Module]> [Module] { get; set; }

View File

@ -6,7 +6,7 @@ using [Owner].[Module].Models;
namespace [Owner].[Module].Repository
{
public class [Module]Repository : I[Module]Repository, IService
public class [Module]Repository : I[Module]Repository, ITransientService
{
private readonly [Module]Context _db;

View File

@ -1,5 +1,5 @@
{
"Title": "Default Module Template",
"Type": "External",
"Version": "3.0.0"
"Version": "3.1.4"
}

View File

@ -1,7 +1,7 @@
namespace Oqtane.Modules
{
/// <summary>
/// Empty interface used to decorate module services for auto registration
/// Empty interface used to decorate module services for auto registration as scoped
/// </summary>
public interface IService
{

View File

@ -0,0 +1,9 @@
namespace Oqtane.Modules
{
/// <summary>
/// Empty interface used to decorate module services for auto registration as a transient
/// </summary>
public interface ITransientService
{
}
}

View File

@ -1,4 +1,5 @@
using System;
using System.ComponentModel.DataAnnotations.Schema;
namespace Oqtane.Models
{
@ -34,6 +35,12 @@ namespace Oqtane.Models
/// </summary>
public bool IsDefault { get; set; }
[NotMapped]
/// <summary>
/// Version of the satellite assembly
/// </summary>
public string Version { get; set; }
#region IAuditable Properties
/// <inheritdoc/>

View File

@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Configurations>Debug;Release</Configurations>
<Version>3.1.3</Version>
<Version>3.1.4</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -11,7 +11,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.3</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace>

View File

@ -4,8 +4,8 @@ namespace Oqtane.Shared
{
public class Constants
{
public static readonly string Version = "3.1.3";
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";
public static readonly string Version = "3.1.4";
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";
public const string PackageId = "Oqtane.Framework";
public const string UpdaterPackageId = "Oqtane.Updater";
public const string PackageRegistryUrl = "https://www.oqtane.net";

View File

@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Configurations>Debug;Release</Configurations>
<Version>3.1.3</Version>
<Version>3.1.4</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -11,7 +11,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.3</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.4</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace>

View File

@ -20,22 +20,6 @@ Please note that this project is owned by the .NET Foundation and is governed by
- clone the Oqtane dev branch source code to your local system. Open the **Oqtane.sln** solution file and Build the solution. Make sure you specify Oqtane.Server as the Startup Project and then Run the application.
**Using Version 2:**
- Install **[.NET 5 SDK](https://dotnet.microsoft.com/download/dotnet/5.0)**.
- Install the latest edition (v16.8 or higher) of [Visual Studio 2019](https://visualstudio.microsoft.com/vs/) 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 **.NET desktop development workload**.
- Download a release or Clone the Oqtane source code from a v2.x Tag to your local system. Open the **Oqtane.sln** solution file and Build the solution. Make sure you specify Oqtane.Server as the Startup Project and then Run the application.
**Using Version 1:**
- Install **[.NET Core 3.1 SDK](https://dotnet.microsoft.com/download/dotnet-core/thank-you/sdk-3.1.300-windows-x64-installer)**.
- Install [Visual Studio 2019](https://visualstudio.microsoft.com/vs) with the **ASP.NET and web development** workload enabled. Oqtane works with ALL editions of Visual Studio from Community to Enterprise. If you do not have a SQL Server installation available already and you wish to use LocalDB for development, you must also install the **.NET desktop development workload**.
- Download a release or Clone the Oqtane source code from a v1.x Tag to your local system. Open the **Oqtane.sln** solution file and Build the solution. Make sure you specify Oqtane.Server as the Startup Project and then Run the application.
**Installing an official release:**
- A detailed set of instructions for installing Oqtane on IIS is located here: [Installing Oqtane on IIS](https://www.oqtane.org/Resources/Blog/PostId/542/installing-oqtane-on-iis)
@ -60,16 +44,16 @@ This project is open source, and therefore is a work in progress...
V.4.0.0 ( Q4 2022 )
- [ ] MAUI / Blazor Hybrid support
V.3.1.3 ( June 2022 )
- [ ] Stabilization improvements
V.3.1.2 ( May 14, 2022 )
[3.1.3](https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.3) ( June 27, 2022 )
- [x] Stabilization improvements
V.3.1.1 ( May 3, 2022 )
[3.1.2](https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.2) ( May 14, 2022 )
- [x] Stabilization improvements
V.3.1.0 ( April 5, 2022 )
[3.1.1](https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.1) ( May 3, 2022 )
- [x] Stabilization improvements
[3.1.0](https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.0) ( April 5, 2022 )
- [x] User account lockout support
- [x] Two factor authentication support
- [x] Per-site configuration of password complexity, lockout criteria
@ -84,43 +68,42 @@ V.3.1.0 ( April 5, 2022 )
- [x] Property change component notifications
- [x] Support for ES6 JavaScript modules
V.3.0.3 ( Feb 15, 2022 )
[3.0.3](https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.3) ( Feb 15, 2022 )
- [x] Url fragment and anchor navigation support
- [x] Meta tag support in page head
- [x] Html/Text content versioning support
V.3.0.2 ( Jan 16, 2022 )
[3.0.2](https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.2) ( Jan 16, 2022 )
- [x] Default alias specification, auto alias registration, redirect logic
- [x] Improvements to visitor tracking and url mapping
- [x] Scheduler enhancements for stop/start, weekly and one-time jobs
- [x] Purge job for daily housekeeping of event log and visitors
- [x] Granular security filtering for Settings
V.3.0.1 ( Dec 12, 2021 )
[3.0.1](https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.1) ( Dec 12, 2021 )
- [x] Url mapping for broken links, content migration
- [x] Visitor tracking for usage insights, personalization
- [x] User experience improvements in Page and Module management
V.3.0.0 ( Nov 11, 2021 )
[3.0.0](https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.0) ( Nov 11, 2021 )
- [x] Migration to .NET 6
- [x] Blazor hosting model flexibility per site
- [x] Blazor WebAssembly prerendering support
V.2.3.1 ( Sep 27, 2021 )
[2.3.1](https://github.com/oqtane/oqtane.framework/releases/tag/v2.3.1) ( Sep 27, 2021 )
- [x] Complete UI migration to Bootstrap 5 and HTML5 form validation
- [x] Improve module/theme installation and add support for commercial extensions
- [x] Replace System.Drawing with ImageSharp
- [x] Image resizing service
V.2.2.0 ( Jul 6, 2021 )
[2.2.0](https://github.com/oqtane/oqtane.framework/releases/tag/v2.2.0) ( Jul 6, 2021 )
- [x] Bootstrap 5 Upgrade
- [x] Package Service integration
- [x] Default and Shared Resource File inclusion
- [x] Startup Error logging
- [x] API Controller Validation and Logging
V.2.1.0 ( Jun 4, 2021 )
[2.1.0](https://github.com/oqtane/oqtane.framework/releases/tag/v2.1.0) ( Jun 4, 2021 )
- [x] Cross Platform Database Support ( ie. LocalDB, SQL Server, SQLite, MySQL, PostgreSQL ) - see [#964](https://github.com/oqtane/oqtane.framework/discussions/964)
- [x] Utilize EF Core Migrations - see [#964](https://github.com/oqtane/oqtane.framework/discussions/964)
- [x] Public Content Folder support
@ -130,20 +113,20 @@ V.2.1.0 ( Jun 4, 2021 )
- [x] Blazor Server Pre-rendering
- [x] Translation Package installation support
V.2.0.2 ( Apr 19, 2021 )
[2.0.2](https://github.com/oqtane/oqtane.framework/releases/tag/v2.0.2) ( Apr 19, 2021 )
- [x] Assorted fixes and user experience improvements
V.2.0.1 ( Feb 27, 2021 )
[2.0.1](https://github.com/oqtane/oqtane.framework/releases/tag/v2.0.1) ( Feb 27, 2021 )
- [x] Complete Static Localization of Admin UI
V.2.0.0 ( Nov 11, 2020 - released in conjunction with .NET 5 )
[2.0.0](https://github.com/oqtane/oqtane.framework/releases/tag/v2.0.0) ( Nov 11, 2020 - released in conjunction with .NET 5 )
- [x] Migration to .NET 5
- [x] Static Localization ( ie. labels, help text, etc.. )
- [x] Improved JavaScript Reference Support
- [x] Performance Optimizations
- [x] Developer Productivity Enhancements
V.1.0.0 ( May 19, 2020 - released in conjunction with .NET Core 3.2 )
[1.0.0](https://github.com/oqtane/oqtane.framework/releases/tag/v1.0.0) ( May 19, 2020 - released in conjunction with .NET Core 3.2 )
- [x] Multi-Tenant ( Shared Database & Isolated Database )
- [x] Modular Architecture
- [x] Headless API with Swagger Support
@ -168,6 +151,8 @@ Oqtane was created by [Shaun Walker](https://www.linkedin.com/in/shaunbrucewalke
# Release Announcements
[Oqtane 3.1](https://www.oqtane.org/blog/!/41/oqtane-3-1-released)
[Oqtane 3.0](https://www.oqtane.org/Resources/Blog/PostId/551/announcing-oqtane-30-for-net-6)
[Oqtane 2.2](https://www.oqtane.org/Resources/Blog/PostId/549/oqtane-22-upgrades-to-bootstrap-5)