commit
b2f65903ae
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2018-2024 .NET Foundation
|
Copyright (c) 2018-2025 .NET Foundation
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
|
@ -71,14 +71,14 @@
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="username" HelpText="Provide a username for the primary user account" ResourceKey="Username">Username:</Label>
|
<Label Class="col-sm-3" For="username" HelpText="Provide a username for the primary user account" ResourceKey="Username">Username:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="username" type="text" class="form-control" @bind="@_hostUsername" />
|
<input id="username" type="text" class="form-control" maxlength="256" @bind="@_hostUsername" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="password" HelpText="Provide a password for the primary user account" ResourceKey="Password">Password:</Label>
|
<Label Class="col-sm-3" For="password" HelpText="Provide a password for the primary user account" ResourceKey="Password">Password:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="password" type="@_passwordType" class="form-control" @bind="@_hostPassword" autocomplete="new-password" />
|
<input id="password" type="@_passwordType" class="form-control" maxlength="256" @bind="@_hostPassword" autocomplete="new-password" />
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglePassword</button>
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglePassword</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -87,7 +87,7 @@
|
||||||
<Label Class="col-sm-3" For="confirm" HelpText="Please confirm the password entered above by entering it again" ResourceKey="Confirm">Confirm:</Label>
|
<Label Class="col-sm-3" For="confirm" HelpText="Please confirm the password entered above by entering it again" ResourceKey="Confirm">Confirm:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="confirm" type="@_confirmPasswordType" class="form-control" @bind="@_confirmPassword" autocomplete="new-password" />
|
<input id="confirm" type="@_confirmPasswordType" class="form-control" maxlength="256" @bind="@_confirmPassword" autocomplete="new-password" />
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@ToggleConfirmPassword" tabindex="-1">@_toggleConfirmPassword</button>
|
<button type="button" class="btn btn-secondary" @onclick="@ToggleConfirmPassword" tabindex="-1">@_toggleConfirmPassword</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -95,7 +95,13 @@
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="email" HelpText="Provide the email address for the host user account" ResourceKey="Email">Email:</Label>
|
<Label Class="col-sm-3" For="email" HelpText="Provide the email address for the host user account" ResourceKey="Email">Email:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input type="text" class="form-control" @bind="@_hostEmail" />
|
<input type="text" class="form-control" maxlength="256" @bind="@_hostEmail" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="name" HelpText="Provide the full name of the host user" ResourceKey="Name">Full Name:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" class="form-control" maxlength="50" @bind="@_hostName" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
|
@ -153,6 +159,7 @@
|
||||||
private string _toggleConfirmPassword = string.Empty;
|
private string _toggleConfirmPassword = string.Empty;
|
||||||
private string _confirmPassword = string.Empty;
|
private string _confirmPassword = string.Empty;
|
||||||
private string _hostEmail = string.Empty;
|
private string _hostEmail = string.Empty;
|
||||||
|
private string _hostName = string.Empty;
|
||||||
private List<SiteTemplate> _templates;
|
private List<SiteTemplate> _templates;
|
||||||
private string _template = Constants.DefaultSiteTemplate;
|
private string _template = Constants.DefaultSiteTemplate;
|
||||||
private bool _register = true;
|
private bool _register = true;
|
||||||
|
@ -236,7 +243,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connectionString != "" && !string.IsNullOrEmpty(_hostUsername) && !string.IsNullOrEmpty(_hostPassword) && _hostPassword == _confirmPassword && !string.IsNullOrEmpty(_hostEmail) && _hostEmail.Contains("@"))
|
if (connectionString != "" && !string.IsNullOrEmpty(_hostUsername) && !string.IsNullOrEmpty(_hostPassword) && _hostPassword == _confirmPassword && !string.IsNullOrEmpty(_hostEmail) && _hostEmail.Contains("@") && !string.IsNullOrEmpty(_hostName))
|
||||||
{
|
{
|
||||||
var result = await UserService.ValidateUserAsync(_hostUsername, _hostEmail, _hostPassword);
|
var result = await UserService.ValidateUserAsync(_hostUsername, _hostEmail, _hostPassword);
|
||||||
if (result.Succeeded)
|
if (result.Succeeded)
|
||||||
|
@ -256,7 +263,7 @@
|
||||||
HostUsername = _hostUsername,
|
HostUsername = _hostUsername,
|
||||||
HostPassword = _hostPassword,
|
HostPassword = _hostPassword,
|
||||||
HostEmail = _hostEmail,
|
HostEmail = _hostEmail,
|
||||||
HostName = _hostUsername,
|
HostName = _hostName,
|
||||||
TenantName = TenantNames.Master,
|
TenantName = TenantNames.Master,
|
||||||
IsNewTenant = true,
|
IsNewTenant = true,
|
||||||
SiteName = Constants.DefaultSite,
|
SiteName = Constants.DefaultSite,
|
||||||
|
|
|
@ -49,18 +49,24 @@
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="imagesizes" HelpText="Enter a list of image sizes which can be generated dynamically from uploaded images (ie. 200x200,400x400). Use * to indicate the folder supports all image sizes." ResourceKey="ImageSizes">Image Sizes: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input id="imagesizes" class="form-control" @bind="@_imagesizes" maxlength="512" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="capacity" HelpText="Enter the maximum folder capacity (in megabytes). Specify zero if the capacity is unlimited." ResourceKey="Capacity">Capacity: </Label>
|
<Label Class="col-sm-3" For="capacity" HelpText="Enter the maximum folder capacity (in megabytes). Specify zero if the capacity is unlimited." ResourceKey="Capacity">Capacity: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="capacity" class="form-control" @bind="@_capacity" required />
|
<input id="capacity" class="form-control" @bind="@_capacity" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="cachecontrol" HelpText="Optionally provide a Cache-Control directive for this folder. For example 'public, max-age=60' indicates that files in this folder should be cached for 60 seconds. Please note that when caching is enabled, changes to files will not be immediately reflected in the UI." ResourceKey="CacheControl">Caching: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="cachecontrol" class="form-control" @bind="@_cachecontrol" maxlength="50" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="imagesizes" HelpText="Optionally enter a list of image sizes which can be generated dynamically from uploaded images (ie. 200x200,400x400). Use * to indicate the folder supports all image sizes (not recommended)." ResourceKey="ImageSizes">Image Sizes: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="imagesizes" class="form-control" @bind="@_imagesizes" maxlength="512" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (PageState.QueryString.ContainsKey("id"))
|
@if (PageState.QueryString.ContainsKey("id"))
|
||||||
{
|
{
|
||||||
|
@ -100,8 +106,9 @@
|
||||||
private int _parentId = -1;
|
private int _parentId = -1;
|
||||||
private string _name;
|
private string _name;
|
||||||
private string _type = FolderTypes.Private;
|
private string _type = FolderTypes.Private;
|
||||||
private string _imagesizes = string.Empty;
|
|
||||||
private string _capacity = "0";
|
private string _capacity = "0";
|
||||||
|
private string _cachecontrol = string.Empty;
|
||||||
|
private string _imagesizes = string.Empty;
|
||||||
private bool _isSystem;
|
private bool _isSystem;
|
||||||
private List<Permission> _permissions = null;
|
private List<Permission> _permissions = null;
|
||||||
private string _createdBy;
|
private string _createdBy;
|
||||||
|
@ -132,8 +139,9 @@
|
||||||
_parentId = folder.ParentId ?? -1;
|
_parentId = folder.ParentId ?? -1;
|
||||||
_name = folder.Name;
|
_name = folder.Name;
|
||||||
_type = folder.Type;
|
_type = folder.Type;
|
||||||
_imagesizes = folder.ImageSizes;
|
|
||||||
_capacity = folder.Capacity.ToString();
|
_capacity = folder.Capacity.ToString();
|
||||||
|
_cachecontrol = folder.CacheControl;
|
||||||
|
_imagesizes = folder.ImageSizes;
|
||||||
_isSystem = folder.IsSystem;
|
_isSystem = folder.IsSystem;
|
||||||
_permissions = folder.PermissionList;
|
_permissions = folder.PermissionList;
|
||||||
_createdBy = folder.CreatedBy;
|
_createdBy = folder.CreatedBy;
|
||||||
|
@ -193,7 +201,7 @@
|
||||||
{
|
{
|
||||||
folder.ParentId = _parentId;
|
folder.ParentId = _parentId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for duplicate folder names
|
// check for duplicate folder names
|
||||||
if (_folders.Any(item => item.ParentId == folder.ParentId && item.Name == _name && item.FolderId != _folderId))
|
if (_folders.Any(item => item.ParentId == folder.ParentId && item.Name == _name && item.FolderId != _folderId))
|
||||||
{
|
{
|
||||||
|
@ -204,8 +212,9 @@
|
||||||
folder.SiteId = PageState.Site.SiteId;
|
folder.SiteId = PageState.Site.SiteId;
|
||||||
folder.Name = _name;
|
folder.Name = _name;
|
||||||
folder.Type = _type;
|
folder.Type = _type;
|
||||||
folder.ImageSizes = _imagesizes;
|
|
||||||
folder.Capacity = int.Parse(_capacity);
|
folder.Capacity = int.Parse(_capacity);
|
||||||
|
folder.CacheControl = _cachecontrol;
|
||||||
|
folder.ImageSizes = _imagesizes;
|
||||||
folder.IsSystem = _isSystem;
|
folder.IsSystem = _isSystem;
|
||||||
folder.PermissionList = _permissionGrid.GetPermissionList();
|
folder.PermissionList = _permissionGrid.GetPermissionList();
|
||||||
|
|
||||||
|
|
|
@ -3,54 +3,92 @@
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IFolderService FolderService
|
@inject IFolderService FolderService
|
||||||
@inject IFileService FileService
|
@inject IFileService FileService
|
||||||
|
@inject ISettingService SettingService
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@if (_files != null)
|
@if (_files == null)
|
||||||
{
|
{
|
||||||
<div class="row">
|
<p>
|
||||||
<div class="col-md mb-1">
|
<em>@SharedLocalizer["Loading"]</em>
|
||||||
<ActionLink Action="Edit" Text="Add Folder" Class="btn btn-secondary" ResourceKey="AddFolder" />
|
</p>
|
||||||
</div>
|
}
|
||||||
<div class="col-md-8 mb-1">
|
else
|
||||||
<div class="input-group">
|
{
|
||||||
<span class="input-group-text">@Localizer["Folder"]:</span>
|
<TabStrip>
|
||||||
<select class="form-select" @onchange="(e => FolderChanged(e))">
|
<TabPanel Name="Files" Heading="Files" ResourceKey="Files">
|
||||||
@foreach (Folder folder in _folders)
|
<div class="row">
|
||||||
{
|
<div class="col-md mb-1">
|
||||||
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
|
<ActionLink Action="Edit" Text="Add Folder" Class="btn btn-secondary" ResourceKey="AddFolder" />
|
||||||
}
|
</div>
|
||||||
</select>
|
<div class="col-md-8 mb-1">
|
||||||
<ActionLink Action="Edit" Text="Edit Folder" Class="btn btn-secondary" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="EditFolder" />
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">@Localizer["Folder"]:</span>
|
||||||
|
<select class="form-select" @onchange="(e => FolderChanged(e))">
|
||||||
|
@foreach (Folder folder in _folders)
|
||||||
|
{
|
||||||
|
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
<ActionLink Action="Edit" Text="Edit Folder" Class="btn btn-secondary" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="EditFolder" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md mb-1 text-end">
|
||||||
|
<ActionLink Action="Add" Text="Upload Files" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="UploadFiles" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="col-md mb-1 text-end">
|
|
||||||
<ActionLink Action="Add" Text="Upload Files" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="UploadFiles" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Pager Items="@_files" SearchProperties="Name">
|
@if (_files.Count != 0)
|
||||||
<Header>
|
{
|
||||||
<th style="width: 1px;"> </th>
|
<Pager Items="@_files" SearchProperties="Name">
|
||||||
<th style="width: 1px;"> </th>
|
<Header>
|
||||||
<th>@SharedLocalizer["Name"]</th>
|
<th style="width: 1px;"> </th>
|
||||||
<th>@Localizer["Modified"]</th>
|
<th style="width: 1px;"> </th>
|
||||||
<th>@Localizer["Type"]</th>
|
<th>@SharedLocalizer["Name"]</th>
|
||||||
<th>@Localizer["Size"]</th>
|
<th>@Localizer["Modified"]</th>
|
||||||
</Header>
|
<th>@Localizer["Type"]</th>
|
||||||
<Row>
|
<th>@Localizer["Size"]</th>
|
||||||
<td><ActionLink Action="Details" Text="Edit" Parameters="@($"id=" + context.FileId.ToString())" ResourceKey="Details" /></td>
|
</Header>
|
||||||
<td><ActionDialog Header="Delete File" Message="@string.Format(Localizer["Confirm.File.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteFile(context))" ResourceKey="DeleteFile" /></td>
|
<Row>
|
||||||
<td><a href="@context.Url" target="_new">@context.Name</a></td>
|
<td><ActionLink Action="Details" Text="Edit" Parameters="@($"id=" + context.FileId.ToString())" ResourceKey="Details" /></td>
|
||||||
<td>@context.ModifiedOn</td>
|
<td><ActionDialog Header="Delete File" Message="@string.Format(Localizer["Confirm.File.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteFile(context))" ResourceKey="DeleteFile" /></td>
|
||||||
<td>@context.Extension.ToUpper() @SharedLocalizer["File"]</td>
|
<td><a href="@context.Url" target="_new">@context.Name</a></td>
|
||||||
<td>@string.Format("{0:0.00}", ((decimal)context.Size / 1000)) KB</td>
|
<td>@context.ModifiedOn</td>
|
||||||
</Row>
|
<td>@context.Extension.ToUpper() @SharedLocalizer["File"]</td>
|
||||||
</Pager>
|
<td>@string.Format("{0:0.00}", ((decimal)context.Size / 1000)) KB</td>
|
||||||
@if (_files.Count == 0)
|
</Row>
|
||||||
{
|
</Pager>
|
||||||
<div class="text-center">@Localizer["NoFiles"]</div>
|
}
|
||||||
}
|
else
|
||||||
|
{
|
||||||
|
<div class="text-center">@Localizer["NoFiles"]</div>
|
||||||
|
}
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings" Security="SecurityAccessLevel.Admin">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="imageExt" HelpText="Enter a comma separated list of image file extensions" ResourceKey="ImageExtensions">Image Extensions: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="imageExt" spellcheck="false" class="form-control" @bind="@_imageFiles" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="uploadableFileExt" HelpText="Enter a comma separated list of uploadable file extensions" ResourceKey="UploadableFileExtensions">Uploadable File Extensions: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="uploadableFileExt" spellcheck="false" class="form-control" @bind="@_uploadableFiles" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="maxChunkSize" HelpText="Files are split into chunks to streamline the upload process. Specify the maximum chunk size in MB (note that higher chunk sizes should only be used on faster networks)." ResourceKey="MaxChunkSize">Max Upload Chunk Size (MB): </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="maxChunkSize" type="number" min="1" max="10" step="1" class="form-control" @bind="@_maxChunkSize" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||||
|
</TabPanel>
|
||||||
|
</TabStrip>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
@ -58,6 +96,10 @@
|
||||||
private int _folderId = -1;
|
private int _folderId = -1;
|
||||||
private List<File> _files;
|
private List<File> _files;
|
||||||
|
|
||||||
|
private string _imageFiles = string.Empty;
|
||||||
|
private string _uploadableFiles = string.Empty;
|
||||||
|
private int _maxChunkSize = 1;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
|
@ -71,6 +113,13 @@
|
||||||
_folderId = _folders[0].FolderId;
|
_folderId = _folders[0].FolderId;
|
||||||
await GetFiles();
|
await GetFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
_imageFiles = SettingService.GetSetting(settings, "ImageFiles", Constants.ImageFiles);
|
||||||
|
_imageFiles = (string.IsNullOrEmpty(_imageFiles)) ? Constants.ImageFiles : _imageFiles;
|
||||||
|
_uploadableFiles = SettingService.GetSetting(settings, "UploadableFiles", Constants.UploadableFiles);
|
||||||
|
_uploadableFiles = (string.IsNullOrEmpty(_uploadableFiles)) ? Constants.UploadableFiles : _uploadableFiles;
|
||||||
|
_maxChunkSize = int.Parse(SettingService.GetSetting(settings, "MaxChunkSize", "1"));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -115,4 +164,23 @@
|
||||||
AddModuleMessage(string.Format(Localizer["Error.File.Delete"], file.Name), MessageType.Error);
|
AddModuleMessage(string.Format(Localizer["Error.File.Delete"], file.Name), MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task SaveSiteSettings()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
settings = SettingService.SetSetting(settings, "ImageFiles", (_imageFiles != Constants.ImageFiles) ? _imageFiles.Replace(" ", "") : "", false);
|
||||||
|
settings = SettingService.SetSetting(settings, "UploadableFiles", (_uploadableFiles != Constants.UploadableFiles) ? _uploadableFiles.Replace(" ", "") : "", false);
|
||||||
|
settings = SettingService.SetSetting(settings, "MaxChunkSize", _maxChunkSize.ToString(), false);
|
||||||
|
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||||
|
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,6 +111,8 @@
|
||||||
private async Task CreateModule()
|
private async Task CreateModule()
|
||||||
{
|
{
|
||||||
validated = true;
|
validated = true;
|
||||||
|
_owner = _owner.Trim();
|
||||||
|
_module = _module.Trim();
|
||||||
var interop = new Interop(JSRuntime);
|
var interop = new Interop(JSRuntime);
|
||||||
if (await interop.FormValid(form))
|
if (await interop.FormValid(form))
|
||||||
{
|
{
|
||||||
|
|
|
@ -116,7 +116,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="path" HelpText="Optionally enter a url path for this page (ie. home ). If you do not provide a url path, the page name will be used. If the page is intended to be the root path specify '/'." ResourceKey="UrlPath">Url Path: </Label>
|
<Label Class="col-sm-3" For="path" HelpText="Optionally enter a url path for this page (ie. home ). If you do not provide a url path, the page name will be used. Please note that spaces and punctuation will be replaced by a dash. If the page is intended to be the root path specify '/'." ResourceKey="UrlPath">Url Path: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="path" class="form-control" @bind="@_path" maxlength="256" />
|
<input id="path" class="form-control" @bind="@_path" maxlength="256" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -263,6 +263,12 @@
|
||||||
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="path" HelpText="Provide a url path for your personalized page. Please note that spaces and punctuation will be replaced by a dash." ResourceKey="PersonalizedUrlPath">Url Path: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="path" class="form-control" @bind="@_path" maxlength="256" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
|
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
|
|
|
@ -13,71 +13,71 @@
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<TabStrip>
|
<TabStrip>
|
||||||
<TabPanel Name="Pages" ResourceKey="Pages" Heading="Pages">
|
<TabPanel Name="Pages" ResourceKey="Pages" Heading="Pages">
|
||||||
@if (!_pages.Where(item => item.IsDeleted).Any())
|
@if (!_pages.Where(item => item.IsDeleted).Any())
|
||||||
{
|
{
|
||||||
<br />
|
<br />
|
||||||
<p>@Localizer["NoPage.Deleted"]</p>
|
<p>@Localizer["NoPage.Deleted"]</p>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<Pager Items="@_pages.Where(item => item.IsDeleted).OrderByDescending(item => item.DeletedOn)" CurrentPage="@_pagePage.ToString()" OnPageChange="OnPageChangePage">
|
<Pager Items="@_pages.Where(item => item.IsDeleted).OrderByDescending(item => item.DeletedOn)" CurrentPage="@_pagePage.ToString()" OnPageChange="OnPageChangePage">
|
||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th>@SharedLocalizer["Name"]</th>
|
<th>@SharedLocalizer["Path"]</th>
|
||||||
<th>@Localizer["DeletedBy"]</th>
|
<th>@Localizer["DeletedBy"]</th>
|
||||||
<th>@Localizer["DeletedOn"]</th>
|
<th>@Localizer["DeletedOn"]</th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><button type="button" @onclick="@(() => RestorePage(context))" class="btn btn-success" title="Restore">@Localizer["Restore"]</button></td>
|
<td><button type="button" @onclick="@(() => RestorePage(context))" class="btn btn-success" title="Restore">@Localizer["Restore"]</button></td>
|
||||||
<td><ActionDialog Header="Delete Page" Message="@string.Format(Localizer["Confirm.Page.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
|
<td><ActionDialog Header="Delete Page" Message="@string.Format(Localizer["Confirm.Page.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
|
||||||
<td>@context.Name</td>
|
<td>@context.Path</td>
|
||||||
<td>@context.DeletedBy</td>
|
<td>@context.DeletedBy</td>
|
||||||
<td>@context.DeletedOn</td>
|
<td>@context.DeletedOn</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
<br />
|
<br />
|
||||||
<ActionDialog Header="Remove All Deleted Pages" Message="Are You Sure You Wish To Permanently Remove All Deleted Pages?" Action="Remove All Deleted Pages" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllPages())" ResourceKey="DeleteAllPages" />
|
<ActionDialog Header="Remove All Deleted Pages" Message="Are You Sure You Wish To Permanently Remove All Deleted Pages?" Action="Remove All Deleted Pages" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllPages())" ResourceKey="DeleteAllPages" />
|
||||||
}
|
}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Modules" ResourceKey="Modules" Heading="Modules">
|
<TabPanel Name="Modules" ResourceKey="Modules" Heading="Modules">
|
||||||
@if (!_modules.Where(item => item.IsDeleted).Any())
|
@if (!_modules.Where(item => item.IsDeleted).Any())
|
||||||
{
|
{
|
||||||
<br />
|
<br />
|
||||||
<p>@Localizer["NoModule.Deleted"]</p>
|
<p>@Localizer["NoModule.Deleted"]</p>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<Pager Items="@_modules.Where(item => item.IsDeleted).OrderByDescending(item => item.DeletedOn)" CurrentPage="@_pageModule.ToString()" OnPageChange="OnPageChangeModule">
|
<Pager Items="@_modules.Where(item => item.IsDeleted).OrderByDescending(item => item.DeletedOn)" CurrentPage="@_pageModule.ToString()" OnPageChange="OnPageChangeModule">
|
||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th>@Localizer["Page"]</th>
|
<th>@Localizer["Page"]</th>
|
||||||
<th>@Localizer["Module"]</th>
|
<th>@Localizer["Module"]</th>
|
||||||
<th>@Localizer["DeletedBy"]</th>
|
<th>@Localizer["DeletedBy"]</th>
|
||||||
<th>@Localizer["DeletedOn"]</th>
|
<th>@Localizer["DeletedOn"]</th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><button type="button" @onclick="@(() => RestoreModule(context))" class="btn btn-success" title="Restore">@Localizer["Restore"]</button></td>
|
<td><button type="button" @onclick="@(() => RestoreModule(context))" class="btn btn-success" title="Restore">@Localizer["Restore"]</button></td>
|
||||||
<td><ActionDialog Header="Delete Module" Message="@string.Format(Localizer["Confirm.Module.Delete"], context.Title)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" /></td>
|
<td><ActionDialog Header="Delete Module" Message="@string.Format(Localizer["Confirm.Module.Delete"], context.Title)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" /></td>
|
||||||
<td>@_pages.Find(item => item.PageId == context.PageId).Name</td>
|
<td>@_pages.Find(item => item.PageId == context.PageId).Name</td>
|
||||||
<td>@context.Title</td>
|
<td>@context.Title</td>
|
||||||
<td>@context.DeletedBy</td>
|
<td>@context.DeletedBy</td>
|
||||||
<td>@context.DeletedOn</td>
|
<td>@context.DeletedOn</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
<br />
|
<br />
|
||||||
<ActionDialog Header="Remove All Deleted Modules" Message="Are You Sure You Wish To Permanently Remove All Deleted Modules?" Action="Remove All Deleted Modules" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllModules())" ResourceKey="DeleteAllModules" />
|
<ActionDialog Header="Remove All Deleted Modules" Message="Are You Sure You Wish To Permanently Remove All Deleted Modules?" Action="Remove All Deleted Modules" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllModules())" ResourceKey="DeleteAllModules" />
|
||||||
}
|
}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabStrip>
|
</TabStrip>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private List<Page> _pages;
|
private List<Page> _pages;
|
||||||
private List<Module> _modules;
|
private List<Module> _modules;
|
||||||
private int _pagePage = 1;
|
private int _pagePage = 1;
|
||||||
private int _pageModule = 1;
|
private int _pageModule = 1;
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
@ -105,12 +105,25 @@ else
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
page.IsDeleted = false;
|
var validated = true;
|
||||||
await PageService.UpdatePageAsync(page);
|
if (page.ParentId != null)
|
||||||
await logger.LogInformation("Page Restored {Page}", page);
|
{
|
||||||
await Load();
|
var parent = _pages.Find(item => item.PageId == page.ParentId);
|
||||||
StateHasChanged();
|
validated = !parent.IsDeleted;
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
}
|
||||||
|
if (validated)
|
||||||
|
{
|
||||||
|
page.IsDeleted = false;
|
||||||
|
await PageService.UpdatePageAsync(page);
|
||||||
|
await logger.LogInformation("Page Restored {Page}", page);
|
||||||
|
AddModuleMessage(Localizer["Success.Page.Restore"], MessageType.Success);
|
||||||
|
await Load();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.Page.Restore"], MessageType.Warning);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -125,9 +138,9 @@ else
|
||||||
{
|
{
|
||||||
await PageService.DeletePageAsync(page.PageId);
|
await PageService.DeletePageAsync(page.PageId);
|
||||||
await logger.LogInformation("Page Permanently Deleted {Page}", page);
|
await logger.LogInformation("Page Permanently Deleted {Page}", page);
|
||||||
|
AddModuleMessage(Localizer["Success.Page.Delete"], MessageType.Success);
|
||||||
await Load();
|
await Load();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -148,10 +161,10 @@ else
|
||||||
}
|
}
|
||||||
|
|
||||||
await logger.LogInformation("Pages Permanently Deleted");
|
await logger.LogInformation("Pages Permanently Deleted");
|
||||||
|
AddModuleMessage(Localizer["Success.Pages.Delete"], MessageType.Success);
|
||||||
await Load();
|
await Load();
|
||||||
HideProgressIndicator();
|
HideProgressIndicator();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -169,6 +182,7 @@ else
|
||||||
pagemodule.IsDeleted = false;
|
pagemodule.IsDeleted = false;
|
||||||
await PageModuleService.UpdatePageModuleAsync(pagemodule);
|
await PageModuleService.UpdatePageModuleAsync(pagemodule);
|
||||||
await logger.LogInformation("Module Restored {Module}", module);
|
await logger.LogInformation("Module Restored {Module}", module);
|
||||||
|
AddModuleMessage(Localizer["Success.Module.Restore"], MessageType.Success);
|
||||||
await Load();
|
await Load();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
@ -185,6 +199,7 @@ else
|
||||||
{
|
{
|
||||||
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
|
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
|
||||||
await logger.LogInformation("Module Permanently Deleted {Module}", module);
|
await logger.LogInformation("Module Permanently Deleted {Module}", module);
|
||||||
|
AddModuleMessage(Localizer["Success.Module.Delete"], MessageType.Success);
|
||||||
await Load();
|
await Load();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
@ -205,6 +220,7 @@ else
|
||||||
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
|
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
|
||||||
}
|
}
|
||||||
await logger.LogInformation("Modules Permanently Deleted");
|
await logger.LogInformation("Modules Permanently Deleted");
|
||||||
|
AddModuleMessage(Localizer["Success.Modules.Delete"], MessageType.Success);
|
||||||
await Load();
|
await Load();
|
||||||
HideProgressIndicator();
|
HideProgressIndicator();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
|
|
|
@ -58,7 +58,7 @@ else
|
||||||
<td>@context.EffectiveDate</td>
|
<td>@context.EffectiveDate</td>
|
||||||
<td>@context.ExpiryDate</td>
|
<td>@context.ExpiryDate</td>
|
||||||
<td>
|
<td>
|
||||||
<ActionDialog Header="Remove User" Message="@string.Format(Localizer["Confirm.User.DeleteRole"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.IsAutoAssigned || context.User.Username == UserNames.Host || context.User.UserId == PageState.User.UserId)" ResourceKey="DeleteUserRole" />
|
<ActionDialog Header="Remove User" Message="@string.Format(Localizer["Confirm.User.DeleteRole"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.User.Username == UserNames.Host || context.User.UserId == PageState.User.UserId)" ResourceKey="DeleteUserRole" />
|
||||||
</td>
|
</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
|
@ -180,27 +180,28 @@ else
|
||||||
|
|
||||||
private async Task DeleteUserRole(int UserRoleId)
|
private async Task DeleteUserRole(int UserRoleId)
|
||||||
{
|
{
|
||||||
validated = true;
|
try
|
||||||
var interop = new Interop(JSRuntime);
|
|
||||||
if (await interop.FormValid(form))
|
|
||||||
{
|
{
|
||||||
try
|
var userrole = await UserRoleService.GetUserRoleAsync(UserRoleId);
|
||||||
|
if (userrole.Role.Name == RoleNames.Registered)
|
||||||
|
{
|
||||||
|
userrole.ExpiryDate = DateTime.UtcNow;
|
||||||
|
await UserRoleService.UpdateUserRoleAsync(userrole);
|
||||||
|
await logger.LogInformation("User {Username} Expired From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
await UserRoleService.DeleteUserRoleAsync(UserRoleId);
|
await UserRoleService.DeleteUserRoleAsync(UserRoleId);
|
||||||
await logger.LogInformation("User Removed From Role {UserRoleId}", UserRoleId);
|
await logger.LogInformation("User {Username} Removed From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
||||||
AddModuleMessage(Localizer["Confirm.User.RoleRemoved"], MessageType.Success);
|
|
||||||
await GetUserRoles();
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error Removing User From Role {UserRoleId} {Error}", UserRoleId, ex.Message);
|
|
||||||
AddModuleMessage(Localizer["Error.User.RemoveRole"], MessageType.Error);
|
|
||||||
}
|
}
|
||||||
|
AddModuleMessage(Localizer["Confirm.User.RoleRemoved"], MessageType.Success);
|
||||||
|
await GetUserRoles();
|
||||||
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
else
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
await logger.LogError(ex, "Error Removing User From Role {UserRoleId} {Error}", UserRoleId, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.User.RemoveRole"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,18 +144,6 @@
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="imageExt" HelpText="Enter a comma separated list of image file extensions" ResourceKey="ImageExtensions">Image Extensions: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input id="imageExt" spellcheck="false" class="form-control" @bind="@_imageFiles" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="uploadableFileExt" HelpText="Enter a comma separated list of uploadable file extensions" ResourceKey="UploadableFileExtensions">Uploadable File Extensions: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input id="uploadableFileExt" spellcheck="false" class="form-control" @bind="@_uploadableFiles" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
<Section Name="PageContent" Heading="Page Content" ResourceKey="PageContent">
|
<Section Name="PageContent" Heading="Page Content" ResourceKey="PageContent">
|
||||||
|
@ -431,7 +419,6 @@
|
||||||
private Dictionary<string, string> _textEditors = new Dictionary<string, string>();
|
private Dictionary<string, string> _textEditors = new Dictionary<string, string>();
|
||||||
private string _textEditor = "";
|
private string _textEditor = "";
|
||||||
private string _imageFiles = string.Empty;
|
private string _imageFiles = string.Empty;
|
||||||
private string _uploadableFiles = string.Empty;
|
|
||||||
|
|
||||||
private string _headcontent = string.Empty;
|
private string _headcontent = string.Empty;
|
||||||
private string _bodycontent = string.Empty;
|
private string _bodycontent = string.Empty;
|
||||||
|
@ -528,8 +515,6 @@
|
||||||
_textEditor = SettingService.GetSetting(settings, "TextEditor", Constants.DefaultTextEditor);
|
_textEditor = SettingService.GetSetting(settings, "TextEditor", Constants.DefaultTextEditor);
|
||||||
_imageFiles = SettingService.GetSetting(settings, "ImageFiles", Constants.ImageFiles);
|
_imageFiles = SettingService.GetSetting(settings, "ImageFiles", Constants.ImageFiles);
|
||||||
_imageFiles = (string.IsNullOrEmpty(_imageFiles)) ? Constants.ImageFiles : _imageFiles;
|
_imageFiles = (string.IsNullOrEmpty(_imageFiles)) ? Constants.ImageFiles : _imageFiles;
|
||||||
_uploadableFiles = SettingService.GetSetting(settings, "UploadableFiles", Constants.UploadableFiles);
|
|
||||||
_uploadableFiles = (string.IsNullOrEmpty(_uploadableFiles)) ? Constants.UploadableFiles : _uploadableFiles;
|
|
||||||
|
|
||||||
// page content
|
// page content
|
||||||
_headcontent = site.HeadContent;
|
_headcontent = site.HeadContent;
|
||||||
|
@ -734,8 +719,6 @@
|
||||||
|
|
||||||
// functionality
|
// functionality
|
||||||
settings = SettingService.SetSetting(settings, "TextEditor", _textEditor);
|
settings = SettingService.SetSetting(settings, "TextEditor", _textEditor);
|
||||||
settings = SettingService.SetSetting(settings, "ImageFiles", (_imageFiles != Constants.ImageFiles) ? _imageFiles.Replace(" ", "") : "", false);
|
|
||||||
settings = SettingService.SetSetting(settings, "UploadableFiles", (_uploadableFiles != Constants.UploadableFiles) ? _uploadableFiles.Replace(" ", "") : "", false);
|
|
||||||
|
|
||||||
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
|
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
|
||||||
|
|
||||||
|
|
|
@ -133,15 +133,27 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="packageregistryurl" HelpText="Specify The Package Manager Service For Installing Modules, Themes, And Translations. If This Field Is Blank It Means The Package Manager Service Is Disabled For This Installation." ResourceKey="PackageManager">Package Manager: </Label>
|
<Label Class="col-sm-3" For="cachecontrol" HelpText="Provide a Cache-Control directive for static assets. For example 'public, max-age=60' indicates that static assets should be cached for 60 seconds. A blank value indicates caching is not enabled." ResourceKey="CacheControl">Static Asset Caching: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="cachecontrol" class="form-control" @bind="@_cachecontrol" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="packageregistryurl" HelpText="Specify The Url Of The Package Manager Service For Installing Modules, Themes, And Translations. If This Field Is Blank It Means The Package Manager Service Is Disabled For This Installation." ResourceKey="PackageManager">Package Manager Url: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="packageregistryurl" class="form-control" @bind="@_packageregistryurl" />
|
<input id="packageregistryurl" class="form-control" @bind="@_packageregistryurl" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="packageregistryemail" HelpText="Specify The Email Address Of The User Account Used For Interacting With The Package Manager Service. This Account Is Used For Managing Packages Across Multiple Installations." ResourceKey="PackageManagerEmail">Package Manager Email: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="packageregistryemail" class="form-control" @bind="@_packageregistryemail" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br /><br />
|
<br /><br />
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveConfig">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveConfig">@SharedLocalizer["Save"]</button>
|
||||||
<a class="btn btn-primary" href="swagger/index.html" target="_new">@Localizer["Access.ApiFramework"]</a>
|
<a class="btn btn-primary" href="swagger/index.html" target="_new">@Localizer["Swagger"]</a>
|
||||||
<ActionDialog Header="Restart Application" Message="Are You Sure You Wish To Restart The Application?" Action="Restart Application" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await RestartApplication())" ResourceKey="RestartApplication" />
|
<ActionDialog Header="Restart Application" Message="Are You Sure You Wish To Restart The Application?" Action="Restart Application" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await RestartApplication())" ResourceKey="RestartApplication" />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Log" Heading="Log" ResourceKey="Log">
|
<TabPanel Name="Log" Heading="Log" ResourceKey="Log">
|
||||||
|
@ -179,9 +191,11 @@
|
||||||
private string _logginglevel = string.Empty;
|
private string _logginglevel = string.Empty;
|
||||||
private string _notificationlevel = string.Empty;
|
private string _notificationlevel = string.Empty;
|
||||||
private string _swagger = string.Empty;
|
private string _swagger = string.Empty;
|
||||||
|
private string _cachecontrol = string.Empty;
|
||||||
private string _packageregistryurl = string.Empty;
|
private string _packageregistryurl = string.Empty;
|
||||||
|
private string _packageregistryemail = string.Empty;
|
||||||
|
|
||||||
private string _log = string.Empty;
|
private string _log = string.Empty;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
|
@ -209,9 +223,11 @@
|
||||||
_detailederrors = systeminfo["DetailedErrors"].ToString();
|
_detailederrors = systeminfo["DetailedErrors"].ToString();
|
||||||
_logginglevel = systeminfo["Logging:LogLevel:Default"].ToString();
|
_logginglevel = systeminfo["Logging:LogLevel:Default"].ToString();
|
||||||
_notificationlevel = systeminfo["Logging:LogLevel:Notify"].ToString();
|
_notificationlevel = systeminfo["Logging:LogLevel:Notify"].ToString();
|
||||||
_swagger = systeminfo["UseSwagger"].ToString();
|
_swagger = systeminfo["UseSwagger"].ToString();
|
||||||
|
_cachecontrol = systeminfo["CacheControl"].ToString();
|
||||||
_packageregistryurl = systeminfo["PackageRegistryUrl"].ToString();
|
_packageregistryurl = systeminfo["PackageRegistryUrl"].ToString();
|
||||||
}
|
_packageregistryemail = systeminfo["PackageRegistryEmail"].ToString();
|
||||||
|
}
|
||||||
|
|
||||||
systeminfo = await SystemService.GetSystemInfoAsync("log");
|
systeminfo = await SystemService.GetSystemInfoAsync("log");
|
||||||
if (systeminfo != null)
|
if (systeminfo != null)
|
||||||
|
@ -229,8 +245,10 @@
|
||||||
settings.Add("Logging:LogLevel:Default", _logginglevel);
|
settings.Add("Logging:LogLevel:Default", _logginglevel);
|
||||||
settings.Add("Logging:LogLevel:Notify", _notificationlevel);
|
settings.Add("Logging:LogLevel:Notify", _notificationlevel);
|
||||||
settings.Add("UseSwagger", _swagger);
|
settings.Add("UseSwagger", _swagger);
|
||||||
settings.Add("PackageRegistryUrl", _packageregistryurl);
|
settings.Add("CacheControl", _cachecontrol);
|
||||||
await SystemService.UpdateSystemInfoAsync(settings);
|
settings.Add("PackageRegistryUrl", _packageregistryurl);
|
||||||
|
settings.Add("PackageRegistryEmail", _packageregistryemail);
|
||||||
|
await SystemService.UpdateSystemInfoAsync(settings);
|
||||||
AddModuleMessage(Localizer["Success.UpdateConfig.Restart"], MessageType.Success);
|
AddModuleMessage(Localizer["Success.UpdateConfig.Restart"], MessageType.Success);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IUrlMappingService UrlMappingService
|
@inject IUrlMappingService UrlMappingService
|
||||||
@inject ISiteService SiteService
|
@inject ISiteService SiteService
|
||||||
|
@inject ISettingService SettingService
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
@ -62,7 +63,13 @@ else
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="retention" HelpText="Number of days of broken urls to retain" ResourceKey="Retention">Retention (Days): </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="retention" class="form-control" type="number" min="0" step="1" @bind="@_retention" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
@ -73,6 +80,7 @@ else
|
||||||
private bool _mapped = true;
|
private bool _mapped = true;
|
||||||
private List<UrlMapping> _urlMappings;
|
private List<UrlMapping> _urlMappings;
|
||||||
private string _capturebrokenurls;
|
private string _capturebrokenurls;
|
||||||
|
private int _retention = 30;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
|
||||||
|
@ -80,7 +88,10 @@ else
|
||||||
{
|
{
|
||||||
await GetUrlMappings();
|
await GetUrlMappings();
|
||||||
_capturebrokenurls = PageState.Site.CaptureBrokenUrls.ToString();
|
_capturebrokenurls = PageState.Site.CaptureBrokenUrls.ToString();
|
||||||
}
|
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
_retention = int.Parse(SettingService.GetSetting(settings, "UrlMappingRetention", "30"));
|
||||||
|
}
|
||||||
|
|
||||||
private async void MappedChanged(ChangeEventArgs e)
|
private async void MappedChanged(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
|
@ -124,7 +135,12 @@ else
|
||||||
var site = PageState.Site;
|
var site = PageState.Site;
|
||||||
site.CaptureBrokenUrls = bool.Parse(_capturebrokenurls);
|
site.CaptureBrokenUrls = bool.Parse(_capturebrokenurls);
|
||||||
await SiteService.UpdateSiteAsync(site);
|
await SiteService.UpdateSiteAsync(site);
|
||||||
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
||||||
|
settings = SettingService.SetSetting(settings, "UrlMappingRetention", _retention.ToString(), true);
|
||||||
|
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
|
||||||
|
|
||||||
|
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
@inject IProfileService ProfileService
|
@inject IProfileService ProfileService
|
||||||
@inject ISettingService SettingService
|
@inject ISettingService SettingService
|
||||||
@inject IFileService FileService
|
@inject IFileService FileService
|
||||||
|
@inject IServiceProvider ServiceProvider
|
||||||
@inject IStringLocalizer<Edit> Localizer
|
@inject IStringLocalizer<Edit> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
@ -51,15 +52,18 @@
|
||||||
<input id="displayname" class="form-control" @bind="@displayname" />
|
<input id="displayname" class="form-control" @bind="@displayname" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
<Label Class="col-sm-3" For="isdeleted" HelpText="Indicate if the user is active" ResourceKey="IsDeleted"></Label>
|
{
|
||||||
<div class="col-sm-9">
|
<div class="row mb-1 align-items-center">
|
||||||
<select id="isdeleted" class="form-select" @bind="@isdeleted">
|
<Label Class="col-sm-3" For="isdeleted" HelpText="Indicate if the user is active" ResourceKey="IsDeleted"></Label>
|
||||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
<div class="col-sm-9">
|
||||||
<option value="False">@SharedLocalizer["No"]</option>
|
<select id="isdeleted" class="form-select" @bind="@isdeleted">
|
||||||
</select>
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
}
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="lastlogin" HelpText="The date and time when the user last signed in" ResourceKey="LastLogin"></Label>
|
<Label Class="col-sm-3" For="lastlogin" HelpText="The date and time when the user last signed in" ResourceKey="LastLogin"></Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
|
@ -127,8 +131,15 @@
|
||||||
|
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
<br />
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) && PageState.Runtime != Shared.Runtime.Hybrid && !ishost)
|
||||||
<br />
|
{
|
||||||
|
<button type="button" class="btn btn-primary ms-1" @onclick="ImpersonateUser">@Localizer["Impersonate"]</button>
|
||||||
|
}
|
||||||
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && isdeleted == "True")
|
||||||
|
{
|
||||||
|
<ActionDialog Header="Delete User" Message="Are You Sure You Wish To Permanently Delete This User?" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteUser())" ResourceKey="DeleteUser" />
|
||||||
|
}
|
||||||
|
<br /><br />
|
||||||
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon" DeletedBy="@deletedby" DeletedOn="@deletedon"></AuditInfo>
|
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon" DeletedBy="@deletedby" DeletedOn="@deletedon"></AuditInfo>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,6 +157,7 @@
|
||||||
private string isdeleted;
|
private string isdeleted;
|
||||||
private string lastlogin;
|
private string lastlogin;
|
||||||
private string lastipaddress;
|
private string lastipaddress;
|
||||||
|
private bool ishost = false;
|
||||||
|
|
||||||
private List<Profile> profiles;
|
private List<Profile> profiles;
|
||||||
private Dictionary<string, string> userSettings;
|
private Dictionary<string, string> userSettings;
|
||||||
|
@ -180,6 +192,7 @@
|
||||||
isdeleted = user.IsDeleted.ToString();
|
isdeleted = user.IsDeleted.ToString();
|
||||||
lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn);
|
lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn);
|
||||||
lastipaddress = user.LastIPAddress;
|
lastipaddress = user.LastIPAddress;
|
||||||
|
ishost = UserSecurity.ContainsRole(user.Roles, RoleNames.Host);
|
||||||
|
|
||||||
userSettings = user.Settings;
|
userSettings = user.Settings;
|
||||||
createdby = user.CreatedBy;
|
createdby = user.CreatedBy;
|
||||||
|
@ -226,8 +239,10 @@
|
||||||
user.Password = _password;
|
user.Password = _password;
|
||||||
user.Email = email;
|
user.Email = email;
|
||||||
user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname;
|
user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname;
|
||||||
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
user.IsDeleted = (isdeleted == null ? true : Boolean.Parse(isdeleted));
|
{
|
||||||
|
user.IsDeleted = (isdeleted == null ? true : Boolean.Parse(isdeleted));
|
||||||
|
}
|
||||||
|
|
||||||
user = await UserService.UpdateUserAsync(user);
|
user = await UserService.UpdateUserAsync(user);
|
||||||
if (user != null)
|
if (user != null)
|
||||||
|
@ -259,6 +274,44 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ImpersonateUser()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await logger.LogInformation(LogFunction.Security, "User {Username} Impersonated By Administrator {Administrator}", username, PageState.User.Username);
|
||||||
|
|
||||||
|
// post back to the server so that the cookies are set correctly
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = username, returnurl = PageState.Alias.Path };
|
||||||
|
string url = Utilities.TenantUrl(PageState.Alias, "/pages/impersonate/");
|
||||||
|
await interop.SubmitForm(url, fields);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Impersonating User {Username} {Error}", username, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.User.Impersonate"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DeleteUser()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && userid != PageState.User.UserId)
|
||||||
|
{
|
||||||
|
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
|
||||||
|
await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId);
|
||||||
|
await logger.LogInformation("User Permanently Deleted {User}", user);
|
||||||
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Permanently Deleting User {UserId} {Error}", userid, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.DeleteUser"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private bool ValidateProfiles()
|
private bool ValidateProfiles()
|
||||||
{
|
{
|
||||||
foreach (Profile profile in profiles)
|
foreach (Profile profile in profiles)
|
||||||
|
|
|
@ -35,7 +35,7 @@ else
|
||||||
<ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="EditUser" />
|
<ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="EditUser" />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<ActionDialog Header="Delete User" Message="@string.Format(Localizer["Confirm.User.Delete"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUser(context))" Disabled="@(context.UserId == PageState.User.UserId)" ResourceKey="DeleteUser" />
|
<ActionDialog Header="Delete User" Message="@string.Format(Localizer["Confirm.User.Delete"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUser(context))" Disabled="@(context.UserId == PageState.User.UserId || context.User.IsDeleted)" ResourceKey="DeleteUser" />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<ActionLink Action="Roles" Text="Roles" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="Roles" />
|
<ActionLink Action="Roles" Text="Roles" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="Roles" />
|
||||||
|
@ -379,7 +379,16 @@ else
|
||||||
<input id="profileclaimtypes" class="form-control" @bind="@_profileclaimtypes" />
|
<input id="profileclaimtypes" class="form-control" @bind="@_profileclaimtypes" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="savetokens" HelpText="Specify whether access and refresh tokens should be saved after a successful login. The default is false to reduce the size of the authentication cookie." ResourceKey="SaveTokens">Save Tokens?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="savetokens" class="form-select" @bind="@_savetokens" required>
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="domainfilter" HelpText="Provide any email domain filter criteria (separated by commas). Domains to exclude should be prefixed with an exclamation point (!). For example 'microsoft.com,!hotmail.com' would include microsoft.com email addresses but not hotmail.com email addresses." ResourceKey="DomainFilter">Domain Filter:</Label>
|
<Label Class="col-sm-3" For="domainfilter" HelpText="Provide any email domain filter criteria (separated by commas). Domains to exclude should be prefixed with an exclamation point (!). For example 'microsoft.com,!hotmail.com' would include microsoft.com email addresses but not hotmail.com email addresses." ResourceKey="DomainFilter">Domain Filter:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="domainfilter" class="form-control" @bind="@_domainfilter" />
|
<input id="domainfilter" class="form-control" @bind="@_domainfilter" />
|
||||||
|
@ -497,6 +506,7 @@ else
|
||||||
private string _roleclaimmappings;
|
private string _roleclaimmappings;
|
||||||
private string _synchronizeroles;
|
private string _synchronizeroles;
|
||||||
private string _profileclaimtypes;
|
private string _profileclaimtypes;
|
||||||
|
private string _savetokens;
|
||||||
private string _domainfilter;
|
private string _domainfilter;
|
||||||
private string _createusers;
|
private string _createusers;
|
||||||
private string _verifyusers;
|
private string _verifyusers;
|
||||||
|
@ -577,6 +587,7 @@ else
|
||||||
_roleclaimmappings = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimMappings", "");
|
_roleclaimmappings = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimMappings", "");
|
||||||
_synchronizeroles = SettingService.GetSetting(settings, "ExternalLogin:SynchronizeRoles", "false");
|
_synchronizeroles = SettingService.GetSetting(settings, "ExternalLogin:SynchronizeRoles", "false");
|
||||||
_profileclaimtypes = SettingService.GetSetting(settings, "ExternalLogin:ProfileClaimTypes", "");
|
_profileclaimtypes = SettingService.GetSetting(settings, "ExternalLogin:ProfileClaimTypes", "");
|
||||||
|
_savetokens = SettingService.GetSetting(settings, "ExternalLogin:SaveTokens", "false");
|
||||||
_domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", "");
|
_domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", "");
|
||||||
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
|
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
|
||||||
_verifyusers = SettingService.GetSetting(settings, "ExternalLogin:VerifyUsers", "true");
|
_verifyusers = SettingService.GetSetting(settings, "ExternalLogin:VerifyUsers", "true");
|
||||||
|
@ -600,19 +611,31 @@ else
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var user = await UserService.GetUserAsync(UserRole.UserId, PageState.Site.SiteId);
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
if (user != null)
|
|
||||||
{
|
{
|
||||||
await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId);
|
var user = await UserService.GetUserAsync(UserRole.UserId, PageState.Site.SiteId);
|
||||||
await logger.LogInformation("User Deleted {User}", UserRole.User);
|
if (user != null)
|
||||||
await LoadUsersAsync(true);
|
{
|
||||||
StateHasChanged();
|
user.IsDeleted = true;
|
||||||
|
await UserService.UpdateUserAsync(user);
|
||||||
|
await logger.LogInformation("User Soft Deleted {User}", user);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var userrole = await UserRoleService.GetUserRoleAsync(UserRole.UserRoleId);
|
||||||
|
userrole.ExpiryDate = DateTime.UtcNow;
|
||||||
|
await UserRoleService.UpdateUserRoleAsync(userrole);
|
||||||
|
await logger.LogInformation("User {Username} Expired From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
||||||
|
}
|
||||||
|
AddModuleMessage(Localizer["Success.DeleteUser"], MessageType.Success);
|
||||||
|
await LoadUsersAsync(true);
|
||||||
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Deleting User {User} {Error}", UserRole.User, ex.Message);
|
await logger.LogError(ex, "Error Deleting User {User} {Error}", UserRole.User, ex.Message);
|
||||||
AddModuleMessage(ex.Message, MessageType.Error);
|
AddModuleMessage(Localizer["Error.DeleteUser"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -666,6 +689,7 @@ else
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimMappings", _roleclaimmappings, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimMappings", _roleclaimmappings, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:SynchronizeRoles", _synchronizeroles, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:SynchronizeRoles", _synchronizeroles, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ProfileClaimTypes", _profileclaimtypes, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:ProfileClaimTypes", _profileclaimtypes, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:SaveTokens", _savetokens, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:VerifyUsers", _verifyusers, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:VerifyUsers", _verifyusers, true);
|
||||||
|
|
|
@ -53,17 +53,17 @@ else
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<Pager Items="@userroles">
|
<Pager Items="@userroles">
|
||||||
<Header>
|
<Header>
|
||||||
<th>@Localizer["Roles"]</th>
|
<th>@Localizer["Roles"]</th>
|
||||||
<th>@Localizer["Effective"]</th>
|
<th>@Localizer["Effective"]</th>
|
||||||
<th>@Localizer["Expiry"]</th>
|
<th>@Localizer["Expiry"]</th>
|
||||||
<th> </th>
|
<th> </th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td>@context.Role.Name</td>
|
<td>@context.Role.Name</td>
|
||||||
<td>@Utilities.UtcAsLocalDate(context.EffectiveDate)</td>
|
<td>@Utilities.UtcAsLocalDate(context.EffectiveDate)</td>
|
||||||
<td>@Utilities.UtcAsLocalDate(context.ExpiryDate)</td>
|
<td>@Utilities.UtcAsLocalDate(context.ExpiryDate)</td>
|
||||||
<td>
|
<td>
|
||||||
<ActionDialog Header="Remove Role" Message="@string.Format(Localizer["Confirm.User.RemoveRole"], context.Role.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.IsAutoAssigned || (context.Role.Name == RoleNames.Host && userid == PageState.User.UserId))" ResourceKey="DeleteUserRole" />
|
<ActionDialog Header="Remove Role" Message="@string.Format(Localizer["Confirm.User.RemoveRole"], context.Role.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.Name == RoleNames.Host && userid == PageState.User.UserId)" ResourceKey="DeleteUserRole" />
|
||||||
</td>
|
</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
|
@ -171,8 +171,18 @@ else
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await UserRoleService.DeleteUserRoleAsync(UserRoleId);
|
var userrole = await UserRoleService.GetUserRoleAsync(UserRoleId);
|
||||||
await logger.LogInformation("User Removed From Role {UserRoleId}", UserRoleId);
|
if (userrole.Role.Name == RoleNames.Registered)
|
||||||
|
{
|
||||||
|
userrole.ExpiryDate = DateTime.UtcNow;
|
||||||
|
await UserRoleService.UpdateUserRoleAsync(userrole);
|
||||||
|
await logger.LogInformation("User {Username} Expired From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await UserRoleService.DeleteUserRoleAsync(UserRoleId);
|
||||||
|
await logger.LogInformation("User {Username} Removed From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
||||||
|
}
|
||||||
AddModuleMessage(Localizer["Success.User.Remove"], MessageType.Success);
|
AddModuleMessage(Localizer["Success.User.Remove"], MessageType.Success);
|
||||||
await GetUserRoles();
|
await GetUserRoles();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
|
|
|
@ -22,9 +22,9 @@
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
@if (!string.IsNullOrEmpty(Action))
|
@if (!string.IsNullOrEmpty(Action))
|
||||||
{
|
{
|
||||||
<button type="button" class="@Class" @onclick="Confirm">@((MarkupString)_iconSpan) @Text</button>
|
<button type="button" class="@ConfirmClass" @onclick="Confirm">@((MarkupString)_iconSpan) @Text</button>
|
||||||
}
|
}
|
||||||
<button type="button" class="btn btn-secondary" @onclick="DisplayModal">@SharedLocalizer["Cancel"]</button>
|
<button type="button" class="@CancelClass" @onclick="DisplayModal">@SharedLocalizer["Cancel"]</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -66,12 +66,12 @@ else
|
||||||
{
|
{
|
||||||
<form method="post" @formname="@($"ActionDialogConfirmForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="Confirm" data-enhance>
|
<form method="post" @formname="@($"ActionDialogConfirmForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="Confirm" data-enhance>
|
||||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||||
<button type="submit" class="@Class">@((MarkupString)_iconSpan) @Text</button>
|
<button type="submit" class="@ConfirmClass">@((MarkupString)_iconSpan) @Text</button>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
<form method="post" @formname="@($"ActionDialogCancelForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="DisplayModal" data-enhance>
|
<form method="post" @formname="@($"ActionDialogCancelForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="DisplayModal" data-enhance>
|
||||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||||
<button type="submit" class="btn btn-secondary">@SharedLocalizer["Cancel"]</button>
|
<button type="submit" class="@CancelClass">@SharedLocalizer["Cancel"]</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -128,6 +128,12 @@ else
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Class { get; set; } // optional
|
public string Class { get; set; } // optional
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string ConfirmClass { get; set; } // optional - for Confirm modal button
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string CancelClass { get; set; } // optional - for Cancel modal button
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public bool Disabled { get; set; } // optional
|
public bool Disabled { get; set; } // optional
|
||||||
|
|
||||||
|
@ -168,6 +174,16 @@ else
|
||||||
Class = "btn btn-success";
|
Class = "btn btn-success";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(ConfirmClass))
|
||||||
|
{
|
||||||
|
ConfirmClass = Class;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(CancelClass))
|
||||||
|
{
|
||||||
|
CancelClass = "btn btn-secondary";
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(EditMode))
|
if (!string.IsNullOrEmpty(EditMode))
|
||||||
{
|
{
|
||||||
_editmode = bool.Parse(EditMode);
|
_editmode = bool.Parse(EditMode);
|
||||||
|
@ -196,7 +212,7 @@ else
|
||||||
_openIconSpan = $"<span class=\"{IconName}\"></span>{(IconOnly ? "" : " ")}";
|
_openIconSpan = $"<span class=\"{IconName}\"></span>{(IconOnly ? "" : " ")}";
|
||||||
_iconSpan = $"<span class=\"{IconName}\"></span> ";
|
_iconSpan = $"<span class=\"{IconName}\"></span> ";
|
||||||
}
|
}
|
||||||
|
|
||||||
_permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList;
|
_permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList;
|
||||||
_authorized = IsAuthorized();
|
_authorized = IsAuthorized();
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
@inherits ModuleControlBase
|
@inherits ModuleControlBase
|
||||||
@inject IFolderService FolderService
|
@inject IFolderService FolderService
|
||||||
@inject IFileService FileService
|
@inject IFileService FileService
|
||||||
@inject ISettingService SettingService
|
|
||||||
@inject IUserService UserService
|
@inject IUserService UserService
|
||||||
|
@inject ISettingService SettingService
|
||||||
@inject IStringLocalizer<FileManager> Localizer
|
@inject IStringLocalizer<FileManager> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
@ -157,6 +157,9 @@
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false
|
public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public int ChunkSize { get; set; } = 1; // optional - size of file chunks to upload in MB
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public EventCallback<int> OnUpload { get; set; } // optional - executes a method in the calling component when a file is uploaded
|
public EventCallback<int> OnUpload { get; set; } // optional - executes a method in the calling component when a file is uploaded
|
||||||
|
|
||||||
|
@ -359,6 +362,8 @@
|
||||||
}
|
}
|
||||||
if (restricted == "")
|
if (restricted == "")
|
||||||
{
|
{
|
||||||
|
CancellationTokenSource tokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// upload the files
|
// upload the files
|
||||||
|
@ -377,57 +382,21 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var chunksize = ChunkSize;
|
||||||
|
if (chunksize == 1)
|
||||||
|
{
|
||||||
|
// if ChunkSize parameter is not overridden use the site setting
|
||||||
|
chunksize = int.Parse(SettingService.GetSetting(PageState.Site.Settings, "MaxChunkSize", "1"));
|
||||||
|
}
|
||||||
|
|
||||||
if (!ShowProgress)
|
if (!ShowProgress)
|
||||||
{
|
{
|
||||||
_uploading = true;
|
_uploading = true;
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken, jwt);
|
// upload files
|
||||||
|
var success = await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken, jwt, chunksize, tokenSource.Token);
|
||||||
// uploading is asynchronous so we need to poll to determine if uploads are completed
|
|
||||||
var success = true;
|
|
||||||
int upload = 0;
|
|
||||||
while (upload < uploads.Length && success)
|
|
||||||
{
|
|
||||||
success = false;
|
|
||||||
var filename = uploads[upload].Split(':')[0];
|
|
||||||
|
|
||||||
var size = Int64.Parse(uploads[upload].Split(':')[1]); // bytes
|
|
||||||
var megabits = (size / 1048576.0) * 8; // binary conversion
|
|
||||||
var uploadspeed = (PageState.Alias.Name.Contains("localhost")) ? 100 : 3; // 3 Mbps is FCC minimum for broadband upload
|
|
||||||
var uploadtime = (megabits / uploadspeed); // seconds
|
|
||||||
var maxattempts = 5; // polling (minimum timeout duration will be 5 seconds)
|
|
||||||
var sleep = (int)Math.Ceiling(uploadtime / maxattempts) * 1000; // milliseconds
|
|
||||||
|
|
||||||
int attempts = 0;
|
|
||||||
while (attempts < maxattempts && !success)
|
|
||||||
{
|
|
||||||
attempts += 1;
|
|
||||||
Thread.Sleep(sleep);
|
|
||||||
|
|
||||||
if (Folder == Constants.PackagesFolder)
|
|
||||||
{
|
|
||||||
var files = await FileService.GetFilesAsync(folder);
|
|
||||||
if (files != null && files.Any(item => item.Name == filename))
|
|
||||||
{
|
|
||||||
success = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var file = await FileService.GetFileAsync(int.Parse(folder), filename);
|
|
||||||
if (file != null)
|
|
||||||
{
|
|
||||||
success = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
upload++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset progress indicators
|
// reset progress indicators
|
||||||
if (ShowProgress)
|
if (ShowProgress)
|
||||||
|
@ -452,7 +421,7 @@
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await logger.LogInformation("File Upload Failed Or Is Still In Progress {Files}", uploads);
|
await logger.LogError("File Upload Failed {Files}", uploads);
|
||||||
_message = Localizer["Error.File.Upload"];
|
_message = Localizer["Error.File.Upload"];
|
||||||
_messagetype = MessageType.Error;
|
_messagetype = MessageType.Error;
|
||||||
}
|
}
|
||||||
|
@ -482,6 +451,10 @@
|
||||||
_message = Localizer["Error.File.Upload"];
|
_message = Localizer["Error.File.Upload"];
|
||||||
_messagetype = MessageType.Error;
|
_messagetype = MessageType.Error;
|
||||||
_uploading = false;
|
_uploading = false;
|
||||||
|
await tokenSource.CancelAsync();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
tokenSource.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ namespace Oqtane.Modules
|
||||||
private Logger _logger;
|
private Logger _logger;
|
||||||
private string _urlparametersstate;
|
private string _urlparametersstate;
|
||||||
private Dictionary<string, string> _urlparameters;
|
private Dictionary<string, string> _urlparameters;
|
||||||
|
private bool _scriptsloaded = false;
|
||||||
|
|
||||||
protected Logger logger => _logger ?? (_logger = new Logger(this));
|
protected Logger logger => _logger ?? (_logger = new Logger(this));
|
||||||
|
|
||||||
|
@ -98,7 +99,7 @@ namespace Oqtane.Modules
|
||||||
var inline = 0;
|
var inline = 0;
|
||||||
foreach (Resource resource in resources)
|
foreach (Resource resource in resources)
|
||||||
{
|
{
|
||||||
if ((string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Interactive) && !resource.Reload)
|
if (string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Interactive)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(resource.Url))
|
if (!string.IsNullOrEmpty(resource.Url))
|
||||||
{
|
{
|
||||||
|
@ -117,6 +118,7 @@ namespace Oqtane.Modules
|
||||||
await interop.IncludeScripts(scripts.ToArray());
|
await interop.IncludeScripts(scripts.ToArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_scriptsloaded = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,6 +127,14 @@ namespace Oqtane.Modules
|
||||||
return PageState?.RenderId == ModuleState?.RenderId;
|
return PageState?.RenderId == ModuleState?.RenderId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool ScriptsLoaded
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _scriptsloaded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// path method
|
// path method
|
||||||
|
|
||||||
public string ModulePath()
|
public string ModulePath()
|
||||||
|
@ -132,6 +142,15 @@ namespace Oqtane.Modules
|
||||||
return PageState?.Alias.BaseUrl + "/Modules/" + GetType().Namespace + "/";
|
return PageState?.Alias.BaseUrl + "/Modules/" + GetType().Namespace + "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fingerprint hash code for static assets
|
||||||
|
public string Fingerprint
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return ModuleState.ModuleDefinition.Fingerprint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// url methods
|
// url methods
|
||||||
|
|
||||||
// navigate url
|
// navigate url
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<Configurations>Debug;Release</Configurations>
|
<Configurations>Debug;Release</Configurations>
|
||||||
<Version>6.0.1</Version>
|
<Version>6.1.0</Version>
|
||||||
<Product>Oqtane</Product>
|
<Product>Oqtane</Product>
|
||||||
<Authors>Shaun Walker</Authors>
|
<Authors>Shaun Walker</Authors>
|
||||||
<Company>.NET Foundation</Company>
|
<Company>.NET Foundation</Company>
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
<Copyright>.NET Foundation</Copyright>
|
<Copyright>.NET Foundation</Copyright>
|
||||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</PackageReleaseNotes>
|
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.0</PackageReleaseNotes>
|
||||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||||
<RepositoryType>Git</RepositoryType>
|
<RepositoryType>Git</RepositoryType>
|
||||||
<RootNamespace>Oqtane</RootNamespace>
|
<RootNamespace>Oqtane</RootNamespace>
|
||||||
|
@ -22,10 +22,10 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -8,20 +8,20 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"profiles": {
|
"profiles": {
|
||||||
"IIS Express": {
|
"Oqtane": {
|
||||||
"commandName": "IISExpress",
|
|
||||||
"launchBrowser": true,
|
|
||||||
"environmentVariables": {
|
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Oqtane.Client": {
|
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
},
|
},
|
||||||
"applicationUrl": "http://localhost:44358/"
|
"applicationUrl": "http://localhost:44358/"
|
||||||
|
},
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -186,4 +186,10 @@
|
||||||
<data name="Message.Username.Invalid" xml:space="preserve">
|
<data name="Message.Username.Invalid" xml:space="preserve">
|
||||||
<value>The Username Provided Does Not Meet The System Requirement, It Can Only Contains Letters Or Digits.</value>
|
<value>The Username Provided Does Not Meet The System Requirement, It Can Only Contains Letters Or Digits.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Name.Text" xml:space="preserve">
|
||||||
|
<value>Full Name:</value>
|
||||||
|
</data>
|
||||||
|
<data name="Name.HelpText" xml:space="preserve">
|
||||||
|
<value>Provide the full name of the host user</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
|
@ -175,7 +175,7 @@
|
||||||
<value>Capacity:</value>
|
<value>Capacity:</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ImageSizes.HelpText" xml:space="preserve">
|
<data name="ImageSizes.HelpText" xml:space="preserve">
|
||||||
<value>Enter a list of image sizes which can be generated dynamically from uploaded images (ie. 200x200,400x400). Use * to indicate the folder supports all image sizes.</value>
|
<value>Optionally enter a list of image sizes which can be generated dynamically from uploaded images (ie. 200x200,400x400). Use * to indicate the folder supports all image sizes (not recommended).</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ImageSizes.Text" xml:space="preserve">
|
<data name="ImageSizes.Text" xml:space="preserve">
|
||||||
<value>Image Sizes:</value>
|
<value>Image Sizes:</value>
|
||||||
|
@ -198,4 +198,10 @@
|
||||||
<data name="Settings.Heading" xml:space="preserve">
|
<data name="Settings.Heading" xml:space="preserve">
|
||||||
<value>Settings</value>
|
<value>Settings</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="CacheControl.Text" xml:space="preserve">
|
||||||
|
<value>Caching:</value>
|
||||||
|
</data>
|
||||||
|
<data name="CacheControl.HelpText" xml:space="preserve">
|
||||||
|
<value>Optionally provide a Cache-Control directive for this folder. For example 'public, max-age=60' indicates that files in this folder should be cached for 60 seconds. Please note that when caching is enabled, changes to files will not be immediately reflected in the UI.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
|
@ -165,4 +165,31 @@
|
||||||
<data name="UploadFiles.Text" xml:space="preserve">
|
<data name="UploadFiles.Text" xml:space="preserve">
|
||||||
<value>Upload Files</value>
|
<value>Upload Files</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Files.Heading" xml:space="preserve">
|
||||||
|
<value>Files</value>
|
||||||
|
</data>
|
||||||
|
<data name="ImageExtensions.Text" xml:space="preserve">
|
||||||
|
<value>Image Extensions:</value>
|
||||||
|
</data>
|
||||||
|
<data name="ImageExtensions.HelpText" xml:space="preserve">
|
||||||
|
<value>Enter a comma separated list of image file extensions</value>
|
||||||
|
</data>
|
||||||
|
<data name="UploadableFileExtensions.Text" xml:space="preserve">
|
||||||
|
<value>Uploadable File Extensions:</value>
|
||||||
|
</data>
|
||||||
|
<data name="UploadableFileExtensions.HelpText" xml:space="preserve">
|
||||||
|
<value>Enter a comma separated list of uploadable file extensions</value>
|
||||||
|
</data>
|
||||||
|
<data name="MaxChunkSize.Text" xml:space="preserve">
|
||||||
|
<value>Max Upload Chunk Size (MB):</value>
|
||||||
|
</data>
|
||||||
|
<data name="MaxChunkSize.HelpText" xml:space="preserve">
|
||||||
|
<value>Files are split into chunks to streamline the upload process. Specify the maximum chunk size in MB (note that higher chunk sizes should only be used on faster networks).</value>
|
||||||
|
</data>
|
||||||
|
<data name="Success.SaveSiteSettings" xml:space="preserve">
|
||||||
|
<value>Settings Saved Successfully</value>
|
||||||
|
</data>
|
||||||
|
<data name="Error.SaveSiteSettings" xml:space="preserve">
|
||||||
|
<value>Error Saving Settings</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
|
@ -169,7 +169,7 @@
|
||||||
<value>Select whether the page is part of the site navigation or hidden</value>
|
<value>Select whether the page is part of the site navigation or hidden</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UrlPath.HelpText" xml:space="preserve">
|
<data name="UrlPath.HelpText" xml:space="preserve">
|
||||||
<value>Optionally enter a url path for this page (ie. home ). If you do not provide a url path, the page name will be used. If the page is intended to be the root path specify '/'.</value>
|
<value>Optionally enter a url path for this page (ie. home ). If you do not provide a url path, the page name will be used. Please note that spaces and punctuation will be replaced by a dash. If the page is intended to be the root path specify '/'.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Redirect.HelpText" xml:space="preserve">
|
<data name="Redirect.HelpText" xml:space="preserve">
|
||||||
<value>Optionally enter a url which this page should redirect to when a user navigates to it</value>
|
<value>Optionally enter a url which this page should redirect to when a user navigates to it</value>
|
||||||
|
@ -297,4 +297,10 @@
|
||||||
<data name="ExpiryDate.Text" xml:space="preserve">
|
<data name="ExpiryDate.Text" xml:space="preserve">
|
||||||
<value>Expiry Date: </value>
|
<value>Expiry Date: </value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
<data name="PersonalizedUrlPath.Text" xml:space="preserve">
|
||||||
|
<value>Url Path:</value>
|
||||||
|
</data>
|
||||||
|
<data name="PersonalizedUrlPath.HelpText" xml:space="preserve">
|
||||||
|
<value>Provide a url path for your personalized page. Please note that spaces and punctuation will be replaced by a dash.</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
|
@ -195,4 +195,25 @@
|
||||||
<data name="Modules.Heading" xml:space="preserve">
|
<data name="Modules.Heading" xml:space="preserve">
|
||||||
<value>Modules</value>
|
<value>Modules</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Message.Page.Restore" xml:space="preserve">
|
||||||
|
<value>You Cannot Restore A Page If Its Parent Is Deleted</value>
|
||||||
|
</data>
|
||||||
|
<data name="Success.Page.Restore" xml:space="preserve">
|
||||||
|
<value>Page Restored Successfully</value>
|
||||||
|
</data>
|
||||||
|
<data name="Success.Page.Delete" xml:space="preserve">
|
||||||
|
<value>Page Deleted Successfully</value>
|
||||||
|
</data>
|
||||||
|
<data name="Success.Pages.Deleted" xml:space="preserve">
|
||||||
|
<value>All Pages Deleted Successfully</value>
|
||||||
|
</data>
|
||||||
|
<data name="Success.Module.Restore" xml:space="preserve">
|
||||||
|
<value>Module Restored Successfully</value>
|
||||||
|
</data>
|
||||||
|
<data name="Success.Module.Delete" xml:space="preserve">
|
||||||
|
<value>Module Deleted Successfully</value>
|
||||||
|
</data>
|
||||||
|
<data name="Success.Modules.Delete" xml:space="preserve">
|
||||||
|
<value>All Modules Deleted Successfully</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
|
@ -402,18 +402,6 @@
|
||||||
<data name="Retention.Text" xml:space="preserve">
|
<data name="Retention.Text" xml:space="preserve">
|
||||||
<value>Retention (Days):</value>
|
<value>Retention (Days):</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ImageExtensions.HelpText" xml:space="preserve">
|
|
||||||
<value>Enter a comma separated list of image file extensions</value>
|
|
||||||
</data>
|
|
||||||
<data name="ImageExtensions.Text" xml:space="preserve">
|
|
||||||
<value>Image Extensions:</value>
|
|
||||||
</data>
|
|
||||||
<data name="UploadableFileExtensions.HelpText" xml:space="preserve">
|
|
||||||
<value>Enter a comma separated list of uploadable file extensions</value>
|
|
||||||
</data>
|
|
||||||
<data name="UploadableFileExtensions.Text" xml:space="preserve">
|
|
||||||
<value>Uploadable File Extensions:</value>
|
|
||||||
</data>
|
|
||||||
<data name="HybridEnabled.HelpText" xml:space="preserve">
|
<data name="HybridEnabled.HelpText" xml:space="preserve">
|
||||||
<value>Specifies if the site can be integrated with an external .NET MAUI hybrid application</value>
|
<value>Specifies if the site can be integrated with an external .NET MAUI hybrid application</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
@ -117,8 +117,8 @@
|
||||||
<resheader name="writer">
|
<resheader name="writer">
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
<data name="Access.ApiFramework" xml:space="preserve">
|
<data name="Swagger" xml:space="preserve">
|
||||||
<value>Access Swagger API</value>
|
<value>Access Swagger UI</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FrameworkVersion.HelpText" xml:space="preserve">
|
<data name="FrameworkVersion.HelpText" xml:space="preserve">
|
||||||
<value>Framework Version</value>
|
<value>Framework Version</value>
|
||||||
|
@ -220,10 +220,10 @@
|
||||||
<value>You Have Been Successfully Registered For Updates</value>
|
<value>You Have Been Successfully Registered For Updates</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PackageManager.HelpText" xml:space="preserve">
|
<data name="PackageManager.HelpText" xml:space="preserve">
|
||||||
<value>Specify The Package Manager Service For Installing Modules, Themes, And Translations. If This Field Is Blank It Means The Package Manager Service Is Disabled For This Installation.</value>
|
<value>Specify The Url Of The Package Manager Service For Installing Modules, Themes, And Translations. If This Field Is Blank It Means The Package Manager Service Is Disabled For This Installation.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PackageManager.Text" xml:space="preserve">
|
<data name="PackageManager.Text" xml:space="preserve">
|
||||||
<value>Package Manager:</value>
|
<value>Package Manager Url:</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Swagger.HelpText" xml:space="preserve">
|
<data name="Swagger.HelpText" xml:space="preserve">
|
||||||
<value>Specify If Swagger Is Enabled For Your Server API</value>
|
<value>Specify If Swagger Is Enabled For Your Server API</value>
|
||||||
|
@ -294,4 +294,16 @@
|
||||||
<data name="Process.Text" xml:space="preserve">
|
<data name="Process.Text" xml:space="preserve">
|
||||||
<value>Process: </value>
|
<value>Process: </value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="PackageManagerEmail.Text" xml:space="preserve">
|
||||||
|
<value>Package Manager Email:</value>
|
||||||
|
</data>
|
||||||
|
<data name="PackageManagerEmail.HelpText" xml:space="preserve">
|
||||||
|
<value>Specify The Email Address Of The User Account Used For Interacting With The Package Manager Service. This Account Is Used For Managing Packages Across Multiple Installations.</value>
|
||||||
|
</data>
|
||||||
|
<data name="CacheControl.Text" xml:space="preserve">
|
||||||
|
<value>Static Asset Caching:</value>
|
||||||
|
</data>
|
||||||
|
<data name="CacheControl.HelpText" xml:space="preserve">
|
||||||
|
<value>Provide a Cache-Control directive for static assets. For example 'public, max-age=60' indicates that static assets should be cached for 60 seconds. A blank value indicates caching is not enabled.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
|
@ -162,4 +162,10 @@
|
||||||
<data name="Edit.Text" xml:space="preserve">
|
<data name="Edit.Text" xml:space="preserve">
|
||||||
<value>Edit</value>
|
<value>Edit</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Retention.Text" xml:space="preserve">
|
||||||
|
<value>Retention (Days):</value>
|
||||||
|
</data>
|
||||||
|
<data name="Retention.HelpText" xml:space="preserve">
|
||||||
|
<value>Number of days of broken urls to retain</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
|
@ -195,4 +195,19 @@
|
||||||
<data name="LastLogin.Text" xml:space="preserve">
|
<data name="LastLogin.Text" xml:space="preserve">
|
||||||
<value>Last Login:</value>
|
<value>Last Login:</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="DeleteUser.Header" xml:space="preserve">
|
||||||
|
<value>Delete User</value>
|
||||||
|
</data>
|
||||||
|
<data name="DeleteUser.Text" xml:space="preserve">
|
||||||
|
<value>Delete</value>
|
||||||
|
</data>
|
||||||
|
<data name="DeleteUser.Message" xml:space="preserve">
|
||||||
|
<value>Are You Sure You Wish To Permanently Delete This User?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Impersonate" xml:space="preserve">
|
||||||
|
<value>Impersonate</value>
|
||||||
|
</data>
|
||||||
|
<data name="Error.User.Impersonate" xml:space="preserve">
|
||||||
|
<value>Unable To Impersonate User</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
|
@ -495,4 +495,16 @@
|
||||||
<data name="OIDC" xml:space="preserve">
|
<data name="OIDC" xml:space="preserve">
|
||||||
<value>OpenID Connect (OIDC)</value>
|
<value>OpenID Connect (OIDC)</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SaveTokens.Text" xml:space="preserve">
|
||||||
|
<value>Save Tokens?</value>
|
||||||
|
</data>
|
||||||
|
<data name="SaveTokens.HelpText" xml:space="preserve">
|
||||||
|
<value>Specify whether access and refresh tokens should be saved after a successful login. The default is false to reduce the size of the authentication cookie.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Success.DeleteUser" xml:space="preserve">
|
||||||
|
<value>User Deleted Successfully</value>
|
||||||
|
</data>
|
||||||
|
<data name="Error.DeleteUser" xml:space="preserve">
|
||||||
|
<value>Error Deleting User</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
|
@ -127,7 +127,7 @@
|
||||||
<value>Error Loading Files</value>
|
<value>Error Loading Files</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Error.File.Upload" xml:space="preserve">
|
<data name="Error.File.Upload" xml:space="preserve">
|
||||||
<value>File Upload Failed Or Is Still In Progress</value>
|
<value>File Upload Failed</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Message.File.NotSelected" xml:space="preserve">
|
<data name="Message.File.NotSelected" xml:space="preserve">
|
||||||
<value>You Have Not Selected A File To Upload</value>
|
<value>You Have Not Selected A File To Upload</value>
|
||||||
|
|
|
@ -427,7 +427,7 @@
|
||||||
<value>At Least One Uppercase Letter</value>
|
<value>At Least One Uppercase Letter</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Password.ValidationCriteria" xml:space="preserve">
|
<data name="Password.ValidationCriteria" xml:space="preserve">
|
||||||
<value>Passwords Must Have A Minimum Length Of {0} Characters, Including At Least {1} Unique Character(s), {2}{3}{4}{5} To Satisfy Password Compexity Requirements For This Site.</value>
|
<value>Passwords Must Have A Minimum Length Of {0} Characters, Including At Least {1} Unique Character(s), {2}{3}{4}{5} To Satisfy Password Complexity Requirements For This Site.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ProfileInvalid" xml:space="preserve">
|
<data name="ProfileInvalid" xml:space="preserve">
|
||||||
<value>{0} Is Not Valid</value>
|
<value>{0} Is Not Valid</value>
|
||||||
|
@ -474,4 +474,7 @@
|
||||||
<data name="User" xml:space="preserve">
|
<data name="User" xml:space="preserve">
|
||||||
<value>User</value>
|
<value>User</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Path" xml:space="preserve">
|
||||||
|
<value>Path</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
|
@ -56,10 +56,5 @@ namespace Oqtane.Services
|
||||||
{
|
{
|
||||||
await PostAsync($"{ApiUrl}/restart");
|
await PostAsync($"{ApiUrl}/restart");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RegisterAsync(string email)
|
|
||||||
{
|
|
||||||
await PostJsonAsync($"{ApiUrl}/register?email={WebUtility.UrlEncode(email)}", true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,13 +34,5 @@ namespace Oqtane.Services
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>internal status/message object</returns>
|
/// <returns>internal status/message object</returns>
|
||||||
Task RestartAsync();
|
Task RestartAsync();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Registers a new <see cref="User"/>
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="email">Email of the user to be registered</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
Task RegisterAsync(string email);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,6 +131,7 @@
|
||||||
if (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered))
|
if (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered))
|
||||||
{
|
{
|
||||||
page = await PageService.AddPageAsync(PageState.Page.PageId, PageState.User.UserId);
|
page = await PageService.AddPageAsync(PageState.Page.PageId, PageState.User.UserId);
|
||||||
|
PageState.EditMode = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_showEditMode)
|
if (_showEditMode)
|
||||||
|
@ -153,7 +154,7 @@
|
||||||
{
|
{
|
||||||
if (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered))
|
if (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered))
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(NavigateUrl(page.Path, "edit=" + PageState.EditMode.ToString()));
|
NavigationManager.NavigateTo(NavigateUrl(page.Path, "edit=" + PageState.EditMode.ToString().ToLower()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,14 +8,14 @@
|
||||||
{
|
{
|
||||||
@if (PageState.Runtime == Runtime.Hybrid)
|
@if (PageState.Runtime == Runtime.Hybrid)
|
||||||
{
|
{
|
||||||
<button type="button" class="btn btn-primary" @onclick="LogoutUser">@Localizer["Logout"]</button>
|
<button type="button" class="@CssClass" @onclick="LogoutUser">@Localizer["Logout"]</button>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<form method="post" class="app-form-inline" action="@logouturl" @formname="LogoutForm">
|
<form method="post" class="app-form-inline" action="@logouturl" @formname="LogoutForm">
|
||||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||||
<input type="hidden" name="returnurl" value="@returnurl" />
|
<input type="hidden" name="returnurl" value="@returnurl" />
|
||||||
<button type="submit" class="btn btn-primary">@Localizer["Logout"]</button>
|
<button type="submit" class="@CssClass">@Localizer["Logout"]</button>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
{
|
{
|
||||||
@if (ShowLogin)
|
@if (ShowLogin)
|
||||||
{
|
{
|
||||||
<a href="@loginurl" class="btn btn-primary">@SharedLocalizer["Login"]</a>
|
<a href="@loginurl" class="@CssClass">@SharedLocalizer["Login"]</a>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
|
@ -32,4 +32,6 @@
|
||||||
{
|
{
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public bool ShowLogin { get; set; } = true;
|
public bool ShowLogin { get; set; } = true;
|
||||||
|
[Parameter]
|
||||||
|
public string CssClass { get; set; } = "btn btn-primary";
|
||||||
}
|
}
|
|
@ -8,13 +8,13 @@
|
||||||
<span class="app-profile">
|
<span class="app-profile">
|
||||||
@if (PageState.User != null)
|
@if (PageState.User != null)
|
||||||
{
|
{
|
||||||
<a href="@NavigateUrl("profile", "returnurl=" + _returnurl)" class="btn btn-primary">@PageState.User.Username</a>
|
<a href="@NavigateUrl("profile", "returnurl=" + _returnurl)" class="@CssClass">@PageState.User.Username</a>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@if (ShowRegister && PageState.Site.AllowRegistration)
|
@if (ShowRegister && PageState.Site.AllowRegistration)
|
||||||
{
|
{
|
||||||
<a href="@NavigateUrl("register", "returnurl=" + _returnurl)" class="btn btn-primary">@Localizer["Register"]</a>
|
<a href="@NavigateUrl("register", "returnurl=" + _returnurl)" class="@CssClass">@Localizer["Register"]</a>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
|
@ -23,6 +23,8 @@
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public bool ShowRegister { get; set; }
|
public bool ShowRegister { get; set; }
|
||||||
|
[Parameter]
|
||||||
|
public string CssClass { get; set; } = "btn btn-primary";
|
||||||
|
|
||||||
private string _returnurl = "";
|
private string _returnurl = "";
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,8 @@ namespace Oqtane.Themes
|
||||||
{
|
{
|
||||||
public abstract class ThemeBase : ComponentBase, IThemeControl
|
public abstract class ThemeBase : ComponentBase, IThemeControl
|
||||||
{
|
{
|
||||||
|
private bool _scriptsloaded = false;
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
protected ILogService LoggingService { get; set; }
|
protected ILogService LoggingService { get; set; }
|
||||||
|
|
||||||
|
@ -62,7 +64,7 @@ namespace Oqtane.Themes
|
||||||
var inline = 0;
|
var inline = 0;
|
||||||
foreach (Resource resource in resources)
|
foreach (Resource resource in resources)
|
||||||
{
|
{
|
||||||
if ((string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Interactive) && !resource.Reload)
|
if (string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Interactive)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(resource.Url))
|
if (!string.IsNullOrEmpty(resource.Url))
|
||||||
{
|
{
|
||||||
|
@ -82,6 +84,25 @@ namespace Oqtane.Themes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_scriptsloaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ScriptsLoaded
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _scriptsloaded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// property for obtaining theme information about this theme component
|
||||||
|
public Theme ThemeState
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var type = GetType().Namespace + ", " + GetType().Assembly.GetName().Name;
|
||||||
|
return PageState?.Site.Themes.FirstOrDefault(item => item.ThemeName == type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// path method
|
// path method
|
||||||
|
@ -91,6 +112,15 @@ namespace Oqtane.Themes
|
||||||
return PageState?.Alias.BaseUrl + "/Themes/" + GetType().Namespace + "/";
|
return PageState?.Alias.BaseUrl + "/Themes/" + GetType().Namespace + "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fingerprint hash code for static assets
|
||||||
|
public string Fingerprint
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return ThemeState.Fingerprint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// url methods
|
// url methods
|
||||||
|
|
||||||
// navigate url
|
// navigate url
|
||||||
|
|
|
@ -70,7 +70,7 @@
|
||||||
if (!script.Contains("><") && !script.Contains("data-reload"))
|
if (!script.Contains("><") && !script.Contains("data-reload"))
|
||||||
{
|
{
|
||||||
// add data-reload attribute to inline script
|
// add data-reload attribute to inline script
|
||||||
headcontent = headcontent.Replace(script, script.Replace("<script", "<script data-reload=\"true\""));
|
headcontent = headcontent.Replace(script, script.Replace("<script", "<script data-reload=\"always\""));
|
||||||
}
|
}
|
||||||
index = headcontent.IndexOf("<script", index + 1);
|
index = headcontent.IndexOf("<script", index + 1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ using System.Threading.Tasks;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace Oqtane.UI
|
namespace Oqtane.UI
|
||||||
{
|
{
|
||||||
|
@ -209,17 +210,22 @@ namespace Oqtane.UI
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task UploadFiles(string posturl, string folder, string id, string antiforgerytoken, string jwt)
|
public Task UploadFiles(string posturl, string folder, string id, string antiforgerytoken, string jwt)
|
||||||
|
{
|
||||||
|
UploadFiles(posturl, folder, id, antiforgerytoken, jwt, 1);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueTask<bool> UploadFiles(string posturl, string folder, string id, string antiforgerytoken, string jwt, int chunksize, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_jsRuntime.InvokeVoidAsync(
|
return _jsRuntime.InvokeAsync<bool>(
|
||||||
"Oqtane.Interop.uploadFiles",
|
"Oqtane.Interop.uploadFiles", cancellationToken,
|
||||||
posturl, folder, id, antiforgerytoken, jwt);
|
posturl, folder, id, antiforgerytoken, jwt, chunksize);
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
return Task.CompletedTask;
|
return new ValueTask<bool>(Task.FromResult(false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
@if (!string.IsNullOrEmpty(_error))
|
@if (!string.IsNullOrEmpty(_error))
|
||||||
{
|
{
|
||||||
<ModuleMessage Message="@_error" Type="@MessageType.Warning" />
|
<ModuleMessage Message="@_error" Type="@MessageType.Warning" />
|
||||||
}
|
}
|
||||||
|
|
||||||
@DynamicComponent
|
@DynamicComponent
|
||||||
|
@ -244,7 +244,9 @@
|
||||||
// look for personalized page
|
// look for personalized page
|
||||||
if (user != null && page.IsPersonalizable && !UserSecurity.IsAuthorized(user, PermissionNames.Edit, page.PermissionList))
|
if (user != null && page.IsPersonalizable && !UserSecurity.IsAuthorized(user, PermissionNames.Edit, page.PermissionList))
|
||||||
{
|
{
|
||||||
var personalized = await PageService.GetPageAsync(route.PagePath + "/" + user.Username, site.SiteId);
|
var settingName = $"PersonalizedPagePath:{page.SiteId}:{page.PageId}";
|
||||||
|
var path = (user.Settings.ContainsKey(settingName)) ? user.Settings[settingName] : Utilities.GetFriendlyUrl(user.Username);
|
||||||
|
var personalized = await PageService.GetPageAsync(route.PagePath + "/" + path, site.SiteId);
|
||||||
if (personalized != null)
|
if (personalized != null)
|
||||||
{
|
{
|
||||||
// redirect to the personalized page
|
// redirect to the personalized page
|
||||||
|
@ -389,7 +391,7 @@
|
||||||
if (themetype != null)
|
if (themetype != null)
|
||||||
{
|
{
|
||||||
// get resources for theme (ITheme)
|
// get resources for theme (ITheme)
|
||||||
page.Resources = ManagePageResources(page.Resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName));
|
page.Resources = ManagePageResources(page.Resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName), theme.Fingerprint);
|
||||||
|
|
||||||
var themeobject = Activator.CreateInstance(themetype) as IThemeControl;
|
var themeobject = Activator.CreateInstance(themetype) as IThemeControl;
|
||||||
if (themeobject != null)
|
if (themeobject != null)
|
||||||
|
@ -399,7 +401,7 @@
|
||||||
panes = themeobject.Panes;
|
panes = themeobject.Panes;
|
||||||
}
|
}
|
||||||
// get resources for theme control
|
// get resources for theme control
|
||||||
page.Resources = ManagePageResources(page.Resources, themeobject.Resources, ResourceLevel.Page, alias, "Themes", themetype.Namespace);
|
page.Resources = ManagePageResources(page.Resources, themeobject.Resources, ResourceLevel.Page, alias, "Themes", themetype.Namespace, theme.Fingerprint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// theme settings components are dynamically loaded within the framework Page Management module
|
// theme settings components are dynamically loaded within the framework Page Management module
|
||||||
|
@ -409,7 +411,7 @@
|
||||||
if (settingsType != null)
|
if (settingsType != null)
|
||||||
{
|
{
|
||||||
var objSettings = Activator.CreateInstance(settingsType) as IModuleControl;
|
var objSettings = Activator.CreateInstance(settingsType) as IModuleControl;
|
||||||
page.Resources = ManagePageResources(page.Resources, objSettings.Resources, ResourceLevel.Module, alias, "Modules", settingsType.Namespace);
|
page.Resources = ManagePageResources(page.Resources, objSettings.Resources, ResourceLevel.Module, alias, "Modules", settingsType.Namespace, theme.Fingerprint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -453,7 +455,7 @@
|
||||||
|
|
||||||
if (module.ModuleDefinition != null && (module.ModuleDefinition.Runtimes == "" || module.ModuleDefinition.Runtimes.Contains(Runtime)))
|
if (module.ModuleDefinition != null && (module.ModuleDefinition.Runtimes == "" || module.ModuleDefinition.Runtimes.Contains(Runtime)))
|
||||||
{
|
{
|
||||||
page.Resources = ManagePageResources(page.Resources, module.ModuleDefinition.Resources, ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName));
|
page.Resources = ManagePageResources(page.Resources, module.ModuleDefinition.Resources, ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName), module.ModuleDefinition.Fingerprint);
|
||||||
|
|
||||||
// handle default action
|
// handle default action
|
||||||
if (action == Constants.DefaultAction && !string.IsNullOrEmpty(module.ModuleDefinition.DefaultAction))
|
if (action == Constants.DefaultAction && !string.IsNullOrEmpty(module.ModuleDefinition.DefaultAction))
|
||||||
|
@ -502,7 +504,7 @@
|
||||||
module.RenderMode = moduleobject.RenderMode;
|
module.RenderMode = moduleobject.RenderMode;
|
||||||
module.Prerender = moduleobject.Prerender;
|
module.Prerender = moduleobject.Prerender;
|
||||||
|
|
||||||
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
|
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, module.ModuleDefinition?.Fingerprint);
|
||||||
|
|
||||||
// settings components are dynamically loaded within the framework Settings module
|
// settings components are dynamically loaded within the framework Settings module
|
||||||
if (action.ToLower() == "settings" && module.ModuleDefinition != null)
|
if (action.ToLower() == "settings" && module.ModuleDefinition != null)
|
||||||
|
@ -523,7 +525,7 @@
|
||||||
if (moduletype != null)
|
if (moduletype != null)
|
||||||
{
|
{
|
||||||
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
|
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
|
||||||
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
|
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, module.ModuleDefinition?.Fingerprint);
|
||||||
}
|
}
|
||||||
|
|
||||||
// container settings component
|
// container settings component
|
||||||
|
@ -534,7 +536,7 @@
|
||||||
if (moduletype != null)
|
if (moduletype != null)
|
||||||
{
|
{
|
||||||
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
|
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
|
||||||
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
|
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, theme.Fingerprint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -593,7 +595,7 @@
|
||||||
return (page, modules);
|
return (page, modules);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Resource> ManagePageResources(List<Resource> pageresources, List<Resource> resources, ResourceLevel level, Alias alias, string type, string name)
|
private List<Resource> ManagePageResources(List<Resource> pageresources, List<Resource> resources, ResourceLevel level, Alias alias, string type, string name, string fingerprint)
|
||||||
{
|
{
|
||||||
if (resources != null)
|
if (resources != null)
|
||||||
{
|
{
|
||||||
|
@ -613,7 +615,7 @@
|
||||||
// ensure resource does not exist already
|
// ensure resource does not exist already
|
||||||
if (!pageresources.Exists(item => item.Url.ToLower() == resource.Url.ToLower()))
|
if (!pageresources.Exists(item => item.Url.ToLower() == resource.Url.ToLower()))
|
||||||
{
|
{
|
||||||
pageresources.Add(resource.Clone(level, name));
|
pageresources.Add(resource.Clone(level, name, fingerprint));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,6 @@
|
||||||
|
|
||||||
RenderFragment DynamicComponent { get; set; }
|
RenderFragment DynamicComponent { get; set; }
|
||||||
|
|
||||||
private string lastPagePath = "";
|
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
// handle page redirection
|
// handle page redirection
|
||||||
|
@ -92,8 +90,9 @@
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
{
|
{
|
||||||
if (!firstRender && PageState.Page.Path != lastPagePath)
|
if (!firstRender)
|
||||||
{
|
{
|
||||||
|
// site content
|
||||||
if (!string.IsNullOrEmpty(PageState.Site.HeadContent) && PageState.Site.HeadContent.Contains("<script"))
|
if (!string.IsNullOrEmpty(PageState.Site.HeadContent) && PageState.Site.HeadContent.Contains("<script"))
|
||||||
{
|
{
|
||||||
await InjectScripts(PageState.Site.HeadContent, ResourceLocation.Head);
|
await InjectScripts(PageState.Site.HeadContent, ResourceLocation.Head);
|
||||||
|
@ -102,6 +101,7 @@
|
||||||
{
|
{
|
||||||
await InjectScripts(PageState.Site.BodyContent, ResourceLocation.Body);
|
await InjectScripts(PageState.Site.BodyContent, ResourceLocation.Body);
|
||||||
}
|
}
|
||||||
|
// page content
|
||||||
if (!string.IsNullOrEmpty(PageState.Page.HeadContent) && PageState.Page.HeadContent.Contains("<script"))
|
if (!string.IsNullOrEmpty(PageState.Page.HeadContent) && PageState.Page.HeadContent.Contains("<script"))
|
||||||
{
|
{
|
||||||
await InjectScripts(PageState.Page.HeadContent, ResourceLocation.Head);
|
await InjectScripts(PageState.Page.HeadContent, ResourceLocation.Head);
|
||||||
|
@ -110,7 +110,6 @@
|
||||||
{
|
{
|
||||||
await InjectScripts(PageState.Page.BodyContent, ResourceLocation.Body);
|
await InjectScripts(PageState.Page.BodyContent, ResourceLocation.Body);
|
||||||
}
|
}
|
||||||
lastPagePath = PageState.Page.Path;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// style sheets
|
// style sheets
|
||||||
|
@ -191,16 +190,13 @@
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (dataAttributes == null || !dataAttributes.ContainsKey("data-reload") || dataAttributes["data-reload"] != "false")
|
if (id == "")
|
||||||
{
|
{
|
||||||
if (id == "")
|
count += 1;
|
||||||
{
|
id = $"page{PageState.Page.PageId}-script{count}";
|
||||||
count += 1;
|
|
||||||
id = $"page{PageState.Page.PageId}-script{count}";
|
|
||||||
}
|
|
||||||
var pos = script.IndexOf(">") + 1;
|
|
||||||
await interop.IncludeScript(id, "", "", "", type, script.Substring(pos, script.IndexOf("</script>") - pos), location.ToString().ToLower(), dataAttributes);
|
|
||||||
}
|
}
|
||||||
|
var pos = script.IndexOf(">") + 1;
|
||||||
|
await interop.IncludeScript(id, "", "", "", type, script.Substring(pos, script.IndexOf("</script>") - pos), location.ToString().ToLower(), dataAttributes);
|
||||||
}
|
}
|
||||||
index = content.IndexOf("<script", index + 1);
|
index = content.IndexOf("<script", index + 1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations.Operations;
|
using Microsoft.EntityFrameworkCore.Migrations.Operations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders;
|
using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders;
|
||||||
using MySql.Data.MySqlClient;
|
using MySql.Data.MySqlClient;
|
||||||
using MySql.EntityFrameworkCore.Metadata;
|
|
||||||
using Oqtane.Databases;
|
using Oqtane.Databases;
|
||||||
|
|
||||||
namespace Oqtane.Database.MySQL
|
namespace Oqtane.Database.MySQL
|
||||||
|
@ -21,11 +21,11 @@ namespace Oqtane.Database.MySQL
|
||||||
|
|
||||||
public MySQLDatabase() :base(_name, _friendlyName) { }
|
public MySQLDatabase() :base(_name, _friendlyName) { }
|
||||||
|
|
||||||
public override string Provider => "MySql.EntityFrameworkCore";
|
public override string Provider => "Pomelo.EntityFrameworkCore.MySql";
|
||||||
|
|
||||||
public override OperationBuilder<AddColumnOperation> AddAutoIncrementColumn(ColumnsBuilder table, string name)
|
public override OperationBuilder<AddColumnOperation> AddAutoIncrementColumn(ColumnsBuilder table, string name)
|
||||||
{
|
{
|
||||||
return table.Column<int>(name: name, nullable: false).Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn);
|
return table.Column<int>(name: name, nullable: false).Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ConcatenateSql(params string[] values)
|
public override string ConcatenateSql(params string[] values)
|
||||||
|
@ -86,7 +86,7 @@ namespace Oqtane.Database.MySQL
|
||||||
|
|
||||||
public override DbContextOptionsBuilder UseDatabase(DbContextOptionsBuilder optionsBuilder, string connectionString)
|
public override DbContextOptionsBuilder UseDatabase(DbContextOptionsBuilder optionsBuilder, string connectionString)
|
||||||
{
|
{
|
||||||
return optionsBuilder.UseMySQL(connectionString);
|
return optionsBuilder.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PrepareCommand(MySqlConnection conn, MySqlCommand cmd, string query)
|
private void PrepareCommand(MySqlConnection conn, MySqlCommand cmd, string query)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<Version>6.0.1</Version>
|
<Version>6.1.0</Version>
|
||||||
<Product>Oqtane</Product>
|
<Product>Oqtane</Product>
|
||||||
<Authors>Shaun Walker</Authors>
|
<Authors>Shaun Walker</Authors>
|
||||||
<Company>.NET Foundation</Company>
|
<Company>.NET Foundation</Company>
|
||||||
|
@ -10,7 +10,7 @@
|
||||||
<Copyright>.NET Foundation</Copyright>
|
<Copyright>.NET Foundation</Copyright>
|
||||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</PackageReleaseNotes>
|
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.0</PackageReleaseNotes>
|
||||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||||
<RepositoryType>Git</RepositoryType>
|
<RepositoryType>Git</RepositoryType>
|
||||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||||
|
@ -33,8 +33,8 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="MySql.EntityFrameworkCore" Version="9.0.0-preview" />
|
<PackageReference Include="MySql.Data" Version="9.2.0" />
|
||||||
<PackageReference Include="MySql.Data" Version="9.1.0" />
|
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0-preview.2.efcore.9.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -42,7 +42,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<MySQLFiles Include="$(OutputPath)Oqtane.Database.MySQL.dll;$(OutputPath)Oqtane.Database.MySQL.pdb;$(OutputPath)MySql.EntityFrameworkCore.dll;$(OutputPath)MySql.Data.dll" DestinationPath="..\Oqtane.Server\bin\$(Configuration)\net9.0\%(Filename)%(Extension)" />
|
<MySQLFiles Include="$(OutputPath)Oqtane.Database.MySQL.dll;$(OutputPath)Oqtane.Database.MySQL.pdb;$(OutputPath)Pomelo.EntityFrameworkCore.MySql.dll;$(OutputPath)MySql.Data.dll" DestinationPath="..\Oqtane.Server\bin\$(Configuration)\net9.0\%(Filename)%(Extension)" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PublishProvider" AfterTargets="PostBuildEvent" Inputs="@(MySQLFiles)" Outputs="@(MySQLFiles->'%(DestinationPath)')">
|
<Target Name="PublishProvider" AfterTargets="PostBuildEvent" Inputs="@(MySQLFiles)" Outputs="@(MySQLFiles->'%(DestinationPath)')">
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<Version>6.0.1</Version>
|
<Version>6.1.0</Version>
|
||||||
<Product>Oqtane</Product>
|
<Product>Oqtane</Product>
|
||||||
<Authors>Shaun Walker</Authors>
|
<Authors>Shaun Walker</Authors>
|
||||||
<Company>.NET Foundation</Company>
|
<Company>.NET Foundation</Company>
|
||||||
|
@ -10,7 +10,7 @@
|
||||||
<Copyright>.NET Foundation</Copyright>
|
<Copyright>.NET Foundation</Copyright>
|
||||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</PackageReleaseNotes>
|
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.0</PackageReleaseNotes>
|
||||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||||
<RepositoryType>Git</RepositoryType>
|
<RepositoryType>Git</RepositoryType>
|
||||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||||
|
@ -34,8 +34,8 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
|
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.1" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.1" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<Version>6.0.1</Version>
|
<Version>6.1.0</Version>
|
||||||
<Product>Oqtane</Product>
|
<Product>Oqtane</Product>
|
||||||
<Authors>Shaun Walker</Authors>
|
<Authors>Shaun Walker</Authors>
|
||||||
<Company>.NET Foundation</Company>
|
<Company>.NET Foundation</Company>
|
||||||
|
@ -10,7 +10,7 @@
|
||||||
<Copyright>.NET Foundation</Copyright>
|
<Copyright>.NET Foundation</Copyright>
|
||||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</PackageReleaseNotes>
|
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.0</PackageReleaseNotes>
|
||||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||||
<RepositoryType>Git</RepositoryType>
|
<RepositoryType>Git</RepositoryType>
|
||||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||||
|
@ -33,7 +33,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<Version>6.0.1</Version>
|
<Version>6.1.0</Version>
|
||||||
<Product>Oqtane</Product>
|
<Product>Oqtane</Product>
|
||||||
<Authors>Shaun Walker</Authors>
|
<Authors>Shaun Walker</Authors>
|
||||||
<Company>.NET Foundation</Company>
|
<Company>.NET Foundation</Company>
|
||||||
|
@ -10,7 +10,7 @@
|
||||||
<Copyright>.NET Foundation</Copyright>
|
<Copyright>.NET Foundation</Copyright>
|
||||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</PackageReleaseNotes>
|
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.0</PackageReleaseNotes>
|
||||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||||
<RepositoryType>Git</RepositoryType>
|
<RepositoryType>Git</RepositoryType>
|
||||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||||
|
@ -33,7 +33,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<!-- <TargetFrameworks>net9.0-android;net9.0-ios;net9.0-maccatalyst</TargetFrameworks> -->
|
<!-- <TargetFrameworks>net9.0-android;net9.0-ios;net9.0-maccatalyst</TargetFrameworks> -->
|
||||||
<!-- <TargetFrameworks>$(TargetFrameworks);net9.0-tizen</TargetFrameworks> -->
|
<!-- <TargetFrameworks>$(TargetFrameworks);net9.0-tizen</TargetFrameworks> -->
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<Version>6.0.1</Version>
|
<Version>6.1.0</Version>
|
||||||
<Product>Oqtane</Product>
|
<Product>Oqtane</Product>
|
||||||
<Authors>Shaun Walker</Authors>
|
<Authors>Shaun Walker</Authors>
|
||||||
<Company>.NET Foundation</Company>
|
<Company>.NET Foundation</Company>
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
<Copyright>.NET Foundation</Copyright>
|
<Copyright>.NET Foundation</Copyright>
|
||||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</PackageReleaseNotes>
|
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.0</PackageReleaseNotes>
|
||||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||||
<RepositoryType>Git</RepositoryType>
|
<RepositoryType>Git</RepositoryType>
|
||||||
<RootNamespace>Oqtane.Maui</RootNamespace>
|
<RootNamespace>Oqtane.Maui</RootNamespace>
|
||||||
|
@ -30,7 +30,7 @@
|
||||||
<ApplicationId>com.oqtane.maui</ApplicationId>
|
<ApplicationId>com.oqtane.maui</ApplicationId>
|
||||||
|
|
||||||
<!-- Versions -->
|
<!-- Versions -->
|
||||||
<ApplicationDisplayVersion>6.0.1</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>6.1.0</ApplicationDisplayVersion>
|
||||||
<ApplicationVersion>1</ApplicationVersion>
|
<ApplicationVersion>1</ApplicationVersion>
|
||||||
|
|
||||||
<!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged -->
|
<!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged -->
|
||||||
|
@ -67,14 +67,14 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="9.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="9.0.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.1" />
|
||||||
<PackageReference Include="System.Net.Http.Json" Version="9.0.0" />
|
<PackageReference Include="System.Net.Http.Json" Version="9.0.1" />
|
||||||
<PackageReference Include="Microsoft.Maui.Controls" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Maui.Controls" Version="9.0.30" />
|
||||||
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="9.0.30" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="9.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="9.0.30" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -120,13 +120,22 @@ Oqtane.Interop = {
|
||||||
this.includeLink(links[i].id, links[i].rel, links[i].href, links[i].type, links[i].integrity, links[i].crossorigin, links[i].insertbefore);
|
this.includeLink(links[i].id, links[i].rel, links[i].href, links[i].type, links[i].integrity, links[i].crossorigin, links[i].insertbefore);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
includeScript: function (id, src, integrity, crossorigin, type, content, location) {
|
includeScript: function (id, src, integrity, crossorigin, type, content, location, dataAttributes) {
|
||||||
var script;
|
var script;
|
||||||
if (src !== "") {
|
if (src !== "") {
|
||||||
script = document.querySelector("script[src=\"" + CSS.escape(src) + "\"]");
|
script = document.querySelector("script[src=\"" + CSS.escape(src) + "\"]");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
script = document.getElementById(id);
|
if (id !== "") {
|
||||||
|
script = document.getElementById(id);
|
||||||
|
} else {
|
||||||
|
const scripts = document.querySelectorAll("script:not([src])");
|
||||||
|
for (let i = 0; i < scripts.length; i++) {
|
||||||
|
if (scripts[i].textContent.includes(content)) {
|
||||||
|
script = scripts[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (script !== null) {
|
if (script !== null) {
|
||||||
script.remove();
|
script.remove();
|
||||||
|
@ -152,37 +161,36 @@ Oqtane.Interop = {
|
||||||
else {
|
else {
|
||||||
script.innerHTML = content;
|
script.innerHTML = content;
|
||||||
}
|
}
|
||||||
script.async = false;
|
if (dataAttributes !== null) {
|
||||||
this.addScript(script, location)
|
for (var key in dataAttributes) {
|
||||||
.then(() => {
|
script.setAttribute(key, dataAttributes[key]);
|
||||||
if (src !== "") {
|
}
|
||||||
console.log(src + ' loaded');
|
}
|
||||||
}
|
|
||||||
else {
|
try {
|
||||||
console.log(id + ' loaded');
|
this.addScript(script, location);
|
||||||
}
|
} catch (error) {
|
||||||
})
|
if (src !== "") {
|
||||||
.catch(() => {
|
console.error("Failed to load external script: ${src}", error);
|
||||||
if (src !== "") {
|
} else {
|
||||||
console.error(src + ' failed');
|
console.error("Failed to load inline script: ${content}", error);
|
||||||
}
|
}
|
||||||
else {
|
}
|
||||||
console.error(id + ' failed');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addScript: function (script, location) {
|
addScript: function (script, location) {
|
||||||
if (location === 'head') {
|
return new Promise((resolve, reject) => {
|
||||||
document.head.appendChild(script);
|
script.async = false;
|
||||||
}
|
script.defer = false;
|
||||||
if (location === 'body') {
|
|
||||||
document.body.appendChild(script);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((res, rej) => {
|
script.onload = () => resolve();
|
||||||
script.onload = res();
|
script.onerror = (error) => reject(error);
|
||||||
script.onerror = rej();
|
|
||||||
|
if (location === 'head') {
|
||||||
|
document.head.appendChild(script);
|
||||||
|
} else {
|
||||||
|
document.body.appendChild(script);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
includeScripts: async function (scripts) {
|
includeScripts: async function (scripts) {
|
||||||
|
@ -222,10 +230,10 @@ Oqtane.Interop = {
|
||||||
if (scripts[s].crossorigin !== '') {
|
if (scripts[s].crossorigin !== '') {
|
||||||
element.crossOrigin = scripts[s].crossorigin;
|
element.crossOrigin = scripts[s].crossorigin;
|
||||||
}
|
}
|
||||||
if (scripts[s].es6module === true) {
|
if (scripts[s].type !== '') {
|
||||||
element.type = "module";
|
element.type = scripts[s].type;
|
||||||
}
|
}
|
||||||
if (typeof scripts[s].dataAttributes !== "undefined" && scripts[s].dataAttributes !== null) {
|
if (scripts[s].dataAttributes !== null) {
|
||||||
for (var key in scripts[s].dataAttributes) {
|
for (var key in scripts[s].dataAttributes) {
|
||||||
element.setAttribute(key, scripts[s].dataAttributes[key]);
|
element.setAttribute(key, scripts[s].dataAttributes[key]);
|
||||||
}
|
}
|
||||||
|
@ -300,97 +308,107 @@ Oqtane.Interop = {
|
||||||
}
|
}
|
||||||
return files;
|
return files;
|
||||||
},
|
},
|
||||||
uploadFiles: function (posturl, folder, id, antiforgerytoken, jwt) {
|
uploadFiles: async function (posturl, folder, id, antiforgerytoken, jwt, chunksize) {
|
||||||
|
var success = true;
|
||||||
var fileinput = document.getElementById('FileInput_' + id);
|
var fileinput = document.getElementById('FileInput_' + id);
|
||||||
var progressinfo = document.getElementById('ProgressInfo_' + id);
|
var progressinfo = document.getElementById('ProgressInfo_' + id);
|
||||||
var progressbar = document.getElementById('ProgressBar_' + id);
|
var progressbar = document.getElementById('ProgressBar_' + id);
|
||||||
|
|
||||||
|
var totalSize = 0;
|
||||||
|
for (var i = 0; i < fileinput.files.length; i++) {
|
||||||
|
totalSize += fileinput.files[i].size;
|
||||||
|
}
|
||||||
|
let uploadSize = 0;
|
||||||
|
|
||||||
|
if (!chunksize || chunksize < 1) {
|
||||||
|
chunksize = 1; // 1 MB default
|
||||||
|
}
|
||||||
|
|
||||||
if (progressinfo !== null && progressbar !== null) {
|
if (progressinfo !== null && progressbar !== null) {
|
||||||
progressinfo.setAttribute("style", "display: inline;");
|
progressinfo.setAttribute('style', 'display: inline;');
|
||||||
progressinfo.innerHTML = '';
|
if (fileinput.files.length > 1) {
|
||||||
progressbar.setAttribute("style", "width: 100%; display: inline;");
|
progressinfo.innerHTML = fileinput.files[0].name + ', ...';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
progressinfo.innerHTML = fileinput.files[0].name;
|
||||||
|
}
|
||||||
|
progressbar.setAttribute('style', 'width: 100%; display: inline;');
|
||||||
progressbar.value = 0;
|
progressbar.value = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
var files = fileinput.files;
|
const uploadFile = (file) => {
|
||||||
var totalSize = 0;
|
const chunkSize = chunksize * (1024 * 1024);
|
||||||
for (var i = 0; i < files.length; i++) {
|
const totalParts = Math.ceil(file.size / chunkSize);
|
||||||
totalSize = totalSize + files[i].size;
|
let partCount = 0;
|
||||||
|
|
||||||
|
const uploadPart = () => {
|
||||||
|
const start = partCount * chunkSize;
|
||||||
|
const end = Math.min(start + chunkSize, file.size);
|
||||||
|
const chunk = file.slice(start, end);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let formdata = new FormData();
|
||||||
|
formdata.append('__RequestVerificationToken', antiforgerytoken);
|
||||||
|
formdata.append('folder', folder);
|
||||||
|
formdata.append('formfile', chunk, file.name);
|
||||||
|
|
||||||
|
var credentials = 'same-origin';
|
||||||
|
var headers = new Headers();
|
||||||
|
headers.append('PartCount', partCount + 1);
|
||||||
|
headers.append('TotalParts', totalParts);
|
||||||
|
if (jwt !== "") {
|
||||||
|
headers.append('Authorization', 'Bearer ' + jwt);
|
||||||
|
credentials = 'include';
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch(posturl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: headers,
|
||||||
|
credentials: credentials,
|
||||||
|
body: formdata
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
if (progressinfo !== null) {
|
||||||
|
progressinfo.innerHTML = 'Error: ' + response.statusText;
|
||||||
|
}
|
||||||
|
throw new Error('Failed');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
partCount++;
|
||||||
|
if (progressbar !== null) {
|
||||||
|
uploadSize += chunk.size;
|
||||||
|
var percent = Math.ceil((uploadSize / totalSize) * 100);
|
||||||
|
progressbar.value = (percent / 100);
|
||||||
|
}
|
||||||
|
if (partCount < totalParts) {
|
||||||
|
uploadPart().then(resolve).catch(reject);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
resolve(data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return uploadPart();
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const file of fileinput.files) {
|
||||||
|
await uploadFile(file);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var maxChunkSizeMB = 1;
|
fileinput.value = '';
|
||||||
var bufferChunkSize = maxChunkSizeMB * (1024 * 1024);
|
return success;
|
||||||
var uploadedSize = 0;
|
|
||||||
|
|
||||||
for (var i = 0; i < files.length; i++) {
|
|
||||||
var fileChunk = [];
|
|
||||||
var file = files[i];
|
|
||||||
var fileStreamPos = 0;
|
|
||||||
var endPos = bufferChunkSize;
|
|
||||||
|
|
||||||
while (fileStreamPos < file.size) {
|
|
||||||
fileChunk.push(file.slice(fileStreamPos, endPos));
|
|
||||||
fileStreamPos = endPos;
|
|
||||||
endPos = fileStreamPos + bufferChunkSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
var totalParts = fileChunk.length;
|
|
||||||
var partCount = 0;
|
|
||||||
|
|
||||||
while (chunk = fileChunk.shift()) {
|
|
||||||
partCount++;
|
|
||||||
var fileName = file.name + ".part_" + partCount.toString().padStart(3, '0') + "_" + totalParts.toString().padStart(3, '0');
|
|
||||||
|
|
||||||
var data = new FormData();
|
|
||||||
data.append('__RequestVerificationToken', antiforgerytoken);
|
|
||||||
data.append('folder', folder);
|
|
||||||
data.append('formfile', chunk, fileName);
|
|
||||||
var request = new XMLHttpRequest();
|
|
||||||
request.open('POST', posturl, true);
|
|
||||||
if (jwt !== "") {
|
|
||||||
request.setRequestHeader('Authorization', 'Bearer ' + jwt);
|
|
||||||
request.withCredentials = true;
|
|
||||||
}
|
|
||||||
request.upload.onloadstart = function (e) {
|
|
||||||
if (progressinfo !== null && progressbar !== null && progressinfo.innerHTML === '') {
|
|
||||||
if (files.length === 1) {
|
|
||||||
progressinfo.innerHTML = file.name;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
progressinfo.innerHTML = file.name + ", ...";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
request.upload.onprogress = function (e) {
|
|
||||||
if (progressinfo !== null && progressbar !== null) {
|
|
||||||
var percent = Math.ceil(((uploadedSize + e.loaded) / totalSize) * 100);
|
|
||||||
progressbar.value = (percent / 100);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
request.upload.onloadend = function (e) {
|
|
||||||
if (progressinfo !== null && progressbar !== null) {
|
|
||||||
uploadedSize = uploadedSize + e.total;
|
|
||||||
var percent = Math.ceil((uploadedSize / totalSize) * 100);
|
|
||||||
progressbar.value = (percent / 100);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
request.upload.onerror = function() {
|
|
||||||
if (progressinfo !== null && progressbar !== null) {
|
|
||||||
if (files.length === 1) {
|
|
||||||
progressinfo.innerHTML = file.name + ' Error: ' + request.statusText;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
progressinfo.innerHTML = ' Error: ' + request.statusText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
request.send(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i === files.length - 1) {
|
|
||||||
fileinput.value = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
refreshBrowser: function (verify, wait) {
|
refreshBrowser: function (verify, wait) {
|
||||||
async function attemptReload (verify) {
|
async function attemptReload (verify) {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<package>
|
<package>
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>Oqtane.Client</id>
|
<id>Oqtane.Client</id>
|
||||||
<version>6.0.1</version>
|
<version>6.1.0</version>
|
||||||
<authors>Shaun Walker</authors>
|
<authors>Shaun Walker</authors>
|
||||||
<owners>.NET Foundation</owners>
|
<owners>.NET Foundation</owners>
|
||||||
<title>Oqtane Framework</title>
|
<title>Oqtane Framework</title>
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||||
<license type="expression">MIT</license>
|
<license type="expression">MIT</license>
|
||||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</releaseNotes>
|
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.0</releaseNotes>
|
||||||
<readme>readme.md</readme>
|
<readme>readme.md</readme>
|
||||||
<icon>icon.png</icon>
|
<icon>icon.png</icon>
|
||||||
<tags>oqtane</tags>
|
<tags>oqtane</tags>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<package>
|
<package>
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>Oqtane.Framework</id>
|
<id>Oqtane.Framework</id>
|
||||||
<version>6.0.1</version>
|
<version>6.1.0</version>
|
||||||
<authors>Shaun Walker</authors>
|
<authors>Shaun Walker</authors>
|
||||||
<owners>.NET Foundation</owners>
|
<owners>.NET Foundation</owners>
|
||||||
<title>Oqtane Framework</title>
|
<title>Oqtane Framework</title>
|
||||||
|
@ -11,8 +11,8 @@
|
||||||
<copyright>.NET Foundation</copyright>
|
<copyright>.NET Foundation</copyright>
|
||||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||||
<license type="expression">MIT</license>
|
<license type="expression">MIT</license>
|
||||||
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v6.0.1/Oqtane.Framework.6.0.1.Upgrade.zip</projectUrl>
|
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v6.1.0/Oqtane.Framework.6.1.0.Upgrade.zip</projectUrl>
|
||||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</releaseNotes>
|
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.0</releaseNotes>
|
||||||
<readme>readme.md</readme>
|
<readme>readme.md</readme>
|
||||||
<icon>icon.png</icon>
|
<icon>icon.png</icon>
|
||||||
<tags>oqtane framework</tags>
|
<tags>oqtane framework</tags>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<package>
|
<package>
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>Oqtane.Server</id>
|
<id>Oqtane.Server</id>
|
||||||
<version>6.0.1</version>
|
<version>6.1.0</version>
|
||||||
<authors>Shaun Walker</authors>
|
<authors>Shaun Walker</authors>
|
||||||
<owners>.NET Foundation</owners>
|
<owners>.NET Foundation</owners>
|
||||||
<title>Oqtane Framework</title>
|
<title>Oqtane Framework</title>
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||||
<license type="expression">MIT</license>
|
<license type="expression">MIT</license>
|
||||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</releaseNotes>
|
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.0</releaseNotes>
|
||||||
<readme>readme.md</readme>
|
<readme>readme.md</readme>
|
||||||
<icon>icon.png</icon>
|
<icon>icon.png</icon>
|
||||||
<tags>oqtane</tags>
|
<tags>oqtane</tags>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<package>
|
<package>
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>Oqtane.Shared</id>
|
<id>Oqtane.Shared</id>
|
||||||
<version>6.0.1</version>
|
<version>6.1.0</version>
|
||||||
<authors>Shaun Walker</authors>
|
<authors>Shaun Walker</authors>
|
||||||
<owners>.NET Foundation</owners>
|
<owners>.NET Foundation</owners>
|
||||||
<title>Oqtane Framework</title>
|
<title>Oqtane Framework</title>
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||||
<license type="expression">MIT</license>
|
<license type="expression">MIT</license>
|
||||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</releaseNotes>
|
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.0</releaseNotes>
|
||||||
<readme>readme.md</readme>
|
<readme>readme.md</readme>
|
||||||
<icon>icon.png</icon>
|
<icon>icon.png</icon>
|
||||||
<tags>oqtane</tags>
|
<tags>oqtane</tags>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<package>
|
<package>
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>Oqtane.Updater</id>
|
<id>Oqtane.Updater</id>
|
||||||
<version>6.0.1</version>
|
<version>6.1.0</version>
|
||||||
<authors>Shaun Walker</authors>
|
<authors>Shaun Walker</authors>
|
||||||
<owners>.NET Foundation</owners>
|
<owners>.NET Foundation</owners>
|
||||||
<title>Oqtane Framework</title>
|
<title>Oqtane Framework</title>
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||||
<license type="expression">MIT</license>
|
<license type="expression">MIT</license>
|
||||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</releaseNotes>
|
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.0</releaseNotes>
|
||||||
<readme>readme.md</readme>
|
<readme>readme.md</readme>
|
||||||
<icon>icon.png</icon>
|
<icon>icon.png</icon>
|
||||||
<tags>oqtane</tags>
|
<tags>oqtane</tags>
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.0.1.Install.zip" -Force
|
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.1.0.Install.zip" -Force
|
||||||
|
|
|
@ -36,6 +36,7 @@ if "%%~nxi" == "%%j" set /A found=1
|
||||||
)
|
)
|
||||||
if not !found! == 1 rmdir /Q/S "%%i"
|
if not !found! == 1 rmdir /Q/S "%%i"
|
||||||
)
|
)
|
||||||
|
del "..\Oqtane.Server\bin\Release\net9.0\publish\Oqtane.Server.staticwebassets.endpoints.json"
|
||||||
del "..\Oqtane.Server\bin\Release\net9.0\publish\appsettings.json"
|
del "..\Oqtane.Server\bin\Release\net9.0\publish\appsettings.json"
|
||||||
ren "..\Oqtane.Server\bin\Release\net9.0\publish\appsettings.release.json" "appsettings.json"
|
ren "..\Oqtane.Server\bin\Release\net9.0\publish\appsettings.release.json" "appsettings.json"
|
||||||
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\install.ps1"
|
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\install.ps1"
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.0.1.Upgrade.zip" -Force
|
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.1.0.Upgrade.zip" -Force
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
@using Microsoft.AspNetCore.Localization
|
@using Microsoft.AspNetCore.Localization
|
||||||
@using Microsoft.Net.Http.Headers
|
@using Microsoft.Net.Http.Headers
|
||||||
@using Microsoft.Extensions.Primitives
|
@using Microsoft.Extensions.Primitives
|
||||||
|
@using Microsoft.AspNetCore.Authentication
|
||||||
@using Oqtane.Client
|
@using Oqtane.Client
|
||||||
@using Oqtane.UI
|
@using Oqtane.UI
|
||||||
@using Oqtane.Repository
|
@using Oqtane.Repository
|
||||||
|
@ -39,7 +40,7 @@
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<base href="/" />
|
<base href="/" />
|
||||||
<link rel="stylesheet" href="css/app.css" />
|
<link rel="stylesheet" href="css/app.css?v=@_fingerprint" />
|
||||||
@if (_scripts.Contains("PWA Manifest"))
|
@if (_scripts.Contains("PWA Manifest"))
|
||||||
{
|
{
|
||||||
<link id="app-manifest" rel="manifest" />
|
<link id="app-manifest" rel="manifest" />
|
||||||
|
@ -70,15 +71,15 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
<script src="_framework/blazor.web.js"></script>
|
<script src="_framework/blazor.web.js"></script>
|
||||||
<script src="js/app.js"></script>
|
<script src="js/app.js?v=@_fingerprint"></script>
|
||||||
<script src="js/loadjs.min.js"></script>
|
<script src="js/loadjs.min.js?v=@_fingerprint"></script>
|
||||||
<script src="js/interop.js"></script>
|
<script src="js/interop.js?v=@_fingerprint"></script>
|
||||||
|
|
||||||
@((MarkupString)_scripts)
|
@((MarkupString)_scripts)
|
||||||
@((MarkupString)_bodyResources)
|
@((MarkupString)_bodyResources)
|
||||||
@if (_renderMode == RenderModes.Static)
|
@if (_renderMode == RenderModes.Static)
|
||||||
{
|
{
|
||||||
<page-script src="./js/reload.js"></page-script>
|
<page-script src="./js/reload.js?v=@_fingerprint"></page-script>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -94,6 +95,7 @@
|
||||||
private string _renderMode = RenderModes.Interactive;
|
private string _renderMode = RenderModes.Interactive;
|
||||||
private string _runtime = Runtimes.Server;
|
private string _runtime = Runtimes.Server;
|
||||||
private bool _prerender = true;
|
private bool _prerender = true;
|
||||||
|
private string _fingerprint = "";
|
||||||
private int _visitorId = -1;
|
private int _visitorId = -1;
|
||||||
private string _antiForgeryToken = "";
|
private string _antiForgeryToken = "";
|
||||||
private string _remoteIPAddress = "";
|
private string _remoteIPAddress = "";
|
||||||
|
@ -136,6 +138,8 @@
|
||||||
_renderMode = site.RenderMode;
|
_renderMode = site.RenderMode;
|
||||||
_runtime = site.Runtime;
|
_runtime = site.Runtime;
|
||||||
_prerender = site.Prerender;
|
_prerender = site.Prerender;
|
||||||
|
_fingerprint = site.Fingerprint;
|
||||||
|
|
||||||
var modules = new List<Module>();
|
var modules = new List<Module>();
|
||||||
|
|
||||||
Route route = new Route(url, alias.Path);
|
Route route = new Route(url, alias.Path);
|
||||||
|
@ -174,7 +178,7 @@
|
||||||
// get jwt token for downstream APIs
|
// get jwt token for downstream APIs
|
||||||
if (Context.User.Identity.IsAuthenticated)
|
if (Context.User.Identity.IsAuthenticated)
|
||||||
{
|
{
|
||||||
CreateJwtToken(alias);
|
await GetJwtToken(alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
// includes resources
|
// includes resources
|
||||||
|
@ -441,13 +445,19 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateJwtToken(Alias alias)
|
private async Task GetJwtToken(Alias alias)
|
||||||
{
|
{
|
||||||
var sitesettings = Context.GetSiteSettings();
|
// bearer token may have been provided by remote Identity Provider and persisted using SaveTokens = true
|
||||||
var secret = sitesettings.GetValue("JwtOptions:Secret", "");
|
_authorizationToken = await Context.GetTokenAsync("access_token");
|
||||||
if (!string.IsNullOrEmpty(secret))
|
if (string.IsNullOrEmpty(_authorizationToken))
|
||||||
{
|
{
|
||||||
_authorizationToken = JwtManager.GenerateToken(alias, (ClaimsIdentity)Context.User.Identity, secret, sitesettings.GetValue("JwtOptions:Issuer", ""), sitesettings.GetValue("JwtOptions:Audience", ""), int.Parse(sitesettings.GetValue("JwtOptions:Lifetime", "20")));
|
// generate bearer token if a secret has been configured in User Settings
|
||||||
|
var sitesettings = Context.GetSiteSettings();
|
||||||
|
var secret = sitesettings.GetValue("JwtOptions:Secret", "");
|
||||||
|
if (!string.IsNullOrEmpty(secret))
|
||||||
|
{
|
||||||
|
_authorizationToken = JwtManager.GenerateToken(alias, (ClaimsIdentity)Context.User.Identity, secret, sitesettings.GetValue("JwtOptions:Issuer", ""), sitesettings.GetValue("JwtOptions:Audience", ""), int.Parse(sitesettings.GetValue("JwtOptions:Lifetime", "20")));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -514,7 +524,7 @@
|
||||||
private void AddScript(Resource resource, Alias alias)
|
private void AddScript(Resource resource, Alias alias)
|
||||||
{
|
{
|
||||||
var script = CreateScript(resource, alias);
|
var script = CreateScript(resource, alias);
|
||||||
if (resource.Location == Shared.ResourceLocation.Head && !resource.Reload)
|
if (resource.Location == Shared.ResourceLocation.Head && resource.LoadBehavior != ResourceLoadBehavior.BlazorPageScript)
|
||||||
{
|
{
|
||||||
if (!_headResources.Contains(script))
|
if (!_headResources.Contains(script))
|
||||||
{
|
{
|
||||||
|
@ -532,11 +542,27 @@
|
||||||
|
|
||||||
private string CreateScript(Resource resource, Alias alias)
|
private string CreateScript(Resource resource, Alias alias)
|
||||||
{
|
{
|
||||||
if (!resource.Reload)
|
if (resource.LoadBehavior == ResourceLoadBehavior.BlazorPageScript)
|
||||||
|
{
|
||||||
|
return "<page-script src=\"" + resource.Url + "\"></page-script>";
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
var url = (resource.Url.Contains("://")) ? resource.Url : alias.BaseUrl + resource.Url;
|
var url = (resource.Url.Contains("://")) ? resource.Url : alias.BaseUrl + resource.Url;
|
||||||
|
|
||||||
var dataAttributes = "";
|
var dataAttributes = "";
|
||||||
|
if (!resource.DataAttributes.ContainsKey("data-reload"))
|
||||||
|
{
|
||||||
|
switch (resource.LoadBehavior)
|
||||||
|
{
|
||||||
|
case ResourceLoadBehavior.Once:
|
||||||
|
dataAttributes += " data-reload=\"once\"";
|
||||||
|
break;
|
||||||
|
case ResourceLoadBehavior.Always:
|
||||||
|
dataAttributes += " data-reload=\"always\"";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (resource.DataAttributes != null && resource.DataAttributes.Count > 0)
|
if (resource.DataAttributes != null && resource.DataAttributes.Count > 0)
|
||||||
{
|
{
|
||||||
foreach (var attribute in resource.DataAttributes)
|
foreach (var attribute in resource.DataAttributes)
|
||||||
|
@ -552,10 +578,6 @@
|
||||||
((!string.IsNullOrEmpty(dataAttributes)) ? dataAttributes : "") +
|
((!string.IsNullOrEmpty(dataAttributes)) ? dataAttributes : "") +
|
||||||
"></script>";
|
"></script>";
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
return "<page-script src=\"" + resource.Url + "\"></page-script>";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetLocalizationCookie(string cookieValue)
|
private void SetLocalizationCookie(string cookieValue)
|
||||||
|
@ -583,13 +605,13 @@
|
||||||
var theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == themeType));
|
var theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == themeType));
|
||||||
if (theme != null)
|
if (theme != null)
|
||||||
{
|
{
|
||||||
resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName), site.RenderMode);
|
resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName), theme.Fingerprint, site.RenderMode);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// fallback to default Oqtane theme
|
// fallback to default Oqtane theme
|
||||||
theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == Constants.DefaultTheme));
|
theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == Constants.DefaultTheme));
|
||||||
resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName), site.RenderMode);
|
resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName), theme.Fingerprint, site.RenderMode);
|
||||||
}
|
}
|
||||||
var type = Type.GetType(themeType);
|
var type = Type.GetType(themeType);
|
||||||
if (type != null)
|
if (type != null)
|
||||||
|
@ -597,7 +619,7 @@
|
||||||
var obj = Activator.CreateInstance(type) as IThemeControl;
|
var obj = Activator.CreateInstance(type) as IThemeControl;
|
||||||
if (obj != null)
|
if (obj != null)
|
||||||
{
|
{
|
||||||
resources = AddResources(resources, obj.Resources, ResourceLevel.Page, alias, "Themes", type.Namespace, site.RenderMode);
|
resources = AddResources(resources, obj.Resources, ResourceLevel.Page, alias, "Themes", type.Namespace, theme.Fingerprint, site.RenderMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// theme settings components are dynamically loaded within the framework Page Management module
|
// theme settings components are dynamically loaded within the framework Page Management module
|
||||||
|
@ -607,7 +629,7 @@
|
||||||
if (settingsType != null)
|
if (settingsType != null)
|
||||||
{
|
{
|
||||||
var objSettings = Activator.CreateInstance(settingsType) as IModuleControl;
|
var objSettings = Activator.CreateInstance(settingsType) as IModuleControl;
|
||||||
resources = AddResources(resources, objSettings.Resources, ResourceLevel.Module, alias, "Modules", settingsType.Namespace, site.RenderMode);
|
resources = AddResources(resources, objSettings.Resources, ResourceLevel.Module, alias, "Modules", settingsType.Namespace, theme.Fingerprint, site.RenderMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -616,7 +638,7 @@
|
||||||
var typename = "";
|
var typename = "";
|
||||||
if (module.ModuleDefinition != null)
|
if (module.ModuleDefinition != null)
|
||||||
{
|
{
|
||||||
resources = AddResources(resources, module.ModuleDefinition.Resources, ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName), site.RenderMode);
|
resources = AddResources(resources, module.ModuleDefinition.Resources, ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName), module.ModuleDefinition.Fingerprint, site.RenderMode);
|
||||||
|
|
||||||
// handle default action
|
// handle default action
|
||||||
if (action == Constants.DefaultAction && !string.IsNullOrEmpty(module.ModuleDefinition.DefaultAction))
|
if (action == Constants.DefaultAction && !string.IsNullOrEmpty(module.ModuleDefinition.DefaultAction))
|
||||||
|
@ -662,7 +684,7 @@
|
||||||
var moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
|
var moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
|
||||||
if (moduleobject != null)
|
if (moduleobject != null)
|
||||||
{
|
{
|
||||||
resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, site.RenderMode);
|
resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, module.ModuleDefinition?.Fingerprint, site.RenderMode);
|
||||||
|
|
||||||
// settings components are dynamically loaded within the framework Settings module
|
// settings components are dynamically loaded within the framework Settings module
|
||||||
if (action.ToLower() == "settings" && module.ModuleDefinition != null)
|
if (action.ToLower() == "settings" && module.ModuleDefinition != null)
|
||||||
|
@ -683,7 +705,7 @@
|
||||||
if (moduletype != null)
|
if (moduletype != null)
|
||||||
{
|
{
|
||||||
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
|
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
|
||||||
resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, site.RenderMode);
|
resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, module.ModuleDefinition?.Fingerprint, site.RenderMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// container settings component
|
// container settings component
|
||||||
|
@ -693,7 +715,7 @@
|
||||||
if (moduletype != null)
|
if (moduletype != null)
|
||||||
{
|
{
|
||||||
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
|
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
|
||||||
resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, site.RenderMode);
|
resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, theme.Fingerprint, site.RenderMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -709,7 +731,7 @@
|
||||||
{
|
{
|
||||||
if (module.ModuleDefinition?.Resources != null)
|
if (module.ModuleDefinition?.Resources != null)
|
||||||
{
|
{
|
||||||
resources = AddResources(resources, module.ModuleDefinition.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level == ResourceLevel.Site).ToList(), ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName), site.RenderMode);
|
resources = AddResources(resources, module.ModuleDefinition.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level == ResourceLevel.Site).ToList(), ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName), module.ModuleDefinition.Fingerprint, site.RenderMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -717,7 +739,7 @@
|
||||||
return resources;
|
return resources;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Resource> AddResources(List<Resource> pageresources, List<Resource> resources, ResourceLevel level, Alias alias, string type, string name, string rendermode)
|
private List<Resource> AddResources(List<Resource> pageresources, List<Resource> resources, ResourceLevel level, Alias alias, string type, string name, string fingerprint, string rendermode)
|
||||||
{
|
{
|
||||||
if (resources != null)
|
if (resources != null)
|
||||||
{
|
{
|
||||||
|
@ -737,7 +759,7 @@
|
||||||
// ensure resource does not exist already
|
// ensure resource does not exist already
|
||||||
if (!pageresources.Exists(item => item.Url.ToLower() == resource.Url.ToLower()))
|
if (!pageresources.Exists(item => item.Url.ToLower() == resource.Url.ToLower()))
|
||||||
{
|
{
|
||||||
pageresources.Add(resource.Clone(level, name));
|
pageresources.Add(resource.Clone(level, name, fingerprint));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,8 @@ using System.Net.Http;
|
||||||
using Microsoft.AspNetCore.Cors;
|
using Microsoft.AspNetCore.Cors;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using Oqtane.Services;
|
using Oqtane.Services;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
using Microsoft.AspNetCore.Http.HttpResults;
|
||||||
|
|
||||||
// ReSharper disable StringIndexOfIsCultureSpecific.1
|
// ReSharper disable StringIndexOfIsCultureSpecific.1
|
||||||
|
|
||||||
|
@ -427,75 +429,98 @@ namespace Oqtane.Controllers
|
||||||
// POST api/<controller>/upload
|
// POST api/<controller>/upload
|
||||||
[EnableCors(Constants.MauiCorsPolicy)]
|
[EnableCors(Constants.MauiCorsPolicy)]
|
||||||
[HttpPost("upload")]
|
[HttpPost("upload")]
|
||||||
public async Task<IActionResult> UploadFile(string folder, IFormFile formfile)
|
public async Task<IActionResult> UploadFile([FromForm] string folder, IFormFile formfile)
|
||||||
{
|
{
|
||||||
|
if (string.IsNullOrEmpty(folder))
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Upload Does Not Contain A Folder");
|
||||||
|
return StatusCode((int)HttpStatusCode.Forbidden);
|
||||||
|
}
|
||||||
|
|
||||||
if (formfile == null || formfile.Length <= 0)
|
if (formfile == null || formfile.Length <= 0)
|
||||||
{
|
{
|
||||||
return NoContent();
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Upload Does Not Contain A File");
|
||||||
|
return StatusCode((int)HttpStatusCode.Forbidden);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure filename is valid
|
// ensure filename is valid
|
||||||
string token = ".part_";
|
if (!formfile.FileName.IsPathOrFileValid() || !HasValidFileExtension(formfile.FileName))
|
||||||
if (!formfile.FileName.IsPathOrFileValid() || !formfile.FileName.Contains(token) || !HasValidFileExtension(formfile.FileName.Substring(0, formfile.FileName.IndexOf(token))))
|
|
||||||
{
|
{
|
||||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Name Is Invalid Or Contains Invalid Extension {File}", formfile.FileName);
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Upload File Name Is Invalid Or Contains Invalid Extension {File}", formfile.FileName);
|
||||||
return NoContent();
|
return StatusCode((int)HttpStatusCode.Forbidden);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ensure headers exist
|
||||||
|
if (!Request.Headers.TryGetValue("PartCount", out StringValues partcount) || !int.TryParse(partcount, out int partCount) || partCount <= 0 ||
|
||||||
|
!Request.Headers.TryGetValue("TotalParts", out StringValues totalparts) || !int.TryParse(totalparts, out int totalParts) || totalParts <= 0)
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Upload Is Missing Required Headers");
|
||||||
|
return StatusCode((int)HttpStatusCode.Forbidden);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create file name using header values
|
||||||
|
string fileName = formfile.FileName + ".part_" + partCount.ToString("000") + "_" + totalParts.ToString("000");
|
||||||
string folderPath = "";
|
string folderPath = "";
|
||||||
|
|
||||||
int FolderId;
|
try
|
||||||
if (int.TryParse(folder, out FolderId))
|
|
||||||
{
|
{
|
||||||
Folder Folder = _folders.GetFolder(FolderId);
|
int FolderId;
|
||||||
if (Folder != null && Folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.Edit, Folder.PermissionList))
|
if (int.TryParse(folder, out FolderId))
|
||||||
{
|
{
|
||||||
folderPath = _folders.GetFolderPath(Folder);
|
Folder Folder = _folders.GetFolder(FolderId);
|
||||||
}
|
if (Folder != null && Folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.Edit, Folder.PermissionList))
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
FolderId = -1;
|
|
||||||
if (User.IsInRole(RoleNames.Host))
|
|
||||||
{
|
|
||||||
folderPath = GetFolderPath(folder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(folderPath))
|
|
||||||
{
|
|
||||||
CreateDirectory(folderPath);
|
|
||||||
using (var stream = new FileStream(Path.Combine(folderPath, formfile.FileName), FileMode.Create))
|
|
||||||
{
|
|
||||||
await formfile.CopyToAsync(stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
string upload = await MergeFile(folderPath, formfile.FileName);
|
|
||||||
if (upload != "" && FolderId != -1)
|
|
||||||
{
|
|
||||||
var file = CreateFile(upload, FolderId, Path.Combine(folderPath, upload));
|
|
||||||
if (file != null)
|
|
||||||
{
|
{
|
||||||
if (file.FileId == 0)
|
folderPath = _folders.GetFolderPath(Folder);
|
||||||
{
|
}
|
||||||
file = _files.AddFile(file);
|
}
|
||||||
}
|
else
|
||||||
else
|
{
|
||||||
{
|
FolderId = -1;
|
||||||
file = _files.UpdateFile(file);
|
if (User.IsInRole(RoleNames.Host))
|
||||||
}
|
{
|
||||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "File Uploaded {File}", Path.Combine(folderPath, upload));
|
folderPath = GetFolderPath(folder);
|
||||||
_syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, SyncEventActions.Create);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized File Upload Attempt {Folder} {File}", folder, formfile.FileName);
|
|
||||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NoContent();
|
if (!string.IsNullOrEmpty(folderPath))
|
||||||
|
{
|
||||||
|
CreateDirectory(folderPath);
|
||||||
|
using (var stream = new FileStream(Path.Combine(folderPath, fileName), FileMode.Create))
|
||||||
|
{
|
||||||
|
await formfile.CopyToAsync(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
string upload = await MergeFile(folderPath, fileName);
|
||||||
|
if (upload != "" && FolderId != -1)
|
||||||
|
{
|
||||||
|
var file = CreateFile(upload, FolderId, Path.Combine(folderPath, upload));
|
||||||
|
if (file != null)
|
||||||
|
{
|
||||||
|
if (file.FileId == 0)
|
||||||
|
{
|
||||||
|
file = _files.AddFile(file);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
file = _files.UpdateFile(file);
|
||||||
|
}
|
||||||
|
_logger.Log(LogLevel.Information, this, LogFunction.Create, "File Uploaded {File}", Path.Combine(folderPath, upload));
|
||||||
|
_syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, SyncEventActions.Create);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized File Upload Attempt {Folder} {File}", folder, formfile.FileName);
|
||||||
|
return StatusCode((int)HttpStatusCode.Forbidden);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Create, ex, "File Upload Attempt Failed {Folder} {File}", folder, formfile.FileName);
|
||||||
|
return StatusCode((int)HttpStatusCode.InternalServerError);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> MergeFile(string folder, string filename)
|
private async Task<string> MergeFile(string folder, string filename)
|
||||||
|
@ -510,10 +535,10 @@ namespace Oqtane.Controllers
|
||||||
filename = Path.GetFileNameWithoutExtension(filename); // base filename
|
filename = Path.GetFileNameWithoutExtension(filename); // base filename
|
||||||
string[] fileparts = Directory.GetFiles(folder, filename + token + "*"); // list of all file parts
|
string[] fileparts = Directory.GetFiles(folder, filename + token + "*"); // list of all file parts
|
||||||
|
|
||||||
// if all of the file parts exist ( note that file parts can arrive out of order )
|
// if all of the file parts exist (note that file parts can arrive out of order)
|
||||||
if (fileparts.Length == totalparts && CanAccessFiles(fileparts))
|
if (fileparts.Length == totalparts && CanAccessFiles(fileparts))
|
||||||
{
|
{
|
||||||
// merge file parts into temp file ( in case another user is trying to get the file )
|
// merge file parts into temp file (in case another user is trying to get the file)
|
||||||
bool success = true;
|
bool success = true;
|
||||||
using (var stream = new FileStream(Path.Combine(folder, filename + ".tmp"), FileMode.Create))
|
using (var stream = new FileStream(Path.Combine(folder, filename + ".tmp"), FileMode.Create))
|
||||||
{
|
{
|
||||||
|
@ -536,17 +561,23 @@ namespace Oqtane.Controllers
|
||||||
// clean up file parts
|
// clean up file parts
|
||||||
foreach (var file in Directory.GetFiles(folder, "*" + token + "*"))
|
foreach (var file in Directory.GetFiles(folder, "*" + token + "*"))
|
||||||
{
|
{
|
||||||
// file name matches part or is more than 2 hours old (ie. a prior file upload failed)
|
if (fileparts.Contains(file))
|
||||||
if (fileparts.Contains(file) || System.IO.File.GetCreationTime(file).ToUniversalTime() < DateTime.UtcNow.AddHours(-2))
|
|
||||||
{
|
{
|
||||||
System.IO.File.Delete(file);
|
try
|
||||||
|
{
|
||||||
|
System.IO.File.Delete(file);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// unable to delete part - ignore
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// rename temp file
|
// rename temp file
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
// remove file if it already exists (as well as any thumbnails)
|
// remove file if it already exists (as well as any thumbnails which may exist)
|
||||||
foreach (var file in Directory.GetFiles(folder, Path.GetFileNameWithoutExtension(filename) + ".*"))
|
foreach (var file in Directory.GetFiles(folder, Path.GetFileNameWithoutExtension(filename) + ".*"))
|
||||||
{
|
{
|
||||||
if (Path.GetExtension(file) != ".tmp")
|
if (Path.GetExtension(file) != ".tmp")
|
||||||
|
|
|
@ -60,9 +60,9 @@ namespace Oqtane.Controllers
|
||||||
{
|
{
|
||||||
installation = _databaseManager.Install(config);
|
installation = _databaseManager.Install(config);
|
||||||
|
|
||||||
if (installation.Success && config.Register)
|
if (installation.Success)
|
||||||
{
|
{
|
||||||
await RegisterContact(config.HostEmail);
|
await RegisterContact(config.HostEmail, config.HostName, config.Register);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -257,7 +257,7 @@ namespace Oqtane.Controllers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RegisterContact(string email)
|
private async Task RegisterContact(string email, string name, bool register)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -268,7 +268,7 @@ namespace Oqtane.Controllers
|
||||||
{
|
{
|
||||||
client.DefaultRequestHeaders.Add("Referer", HttpContext.Request.Scheme + "://" + HttpContext.Request.Host.Value);
|
client.DefaultRequestHeaders.Add("Referer", HttpContext.Request.Scheme + "://" + HttpContext.Request.Host.Value);
|
||||||
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(Constants.PackageId, Constants.Version));
|
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(Constants.PackageId, Constants.Version));
|
||||||
var response = await client.GetAsync(new Uri(url + $"/api/registry/contact/?id={_configManager.GetInstallationId()}&email={WebUtility.UrlEncode(email)}")).ConfigureAwait(false);
|
var response = await client.GetAsync(new Uri(url + $"/api/registry/contact/?id={_configManager.GetInstallationId()}&email={WebUtility.UrlEncode(email)}&name={WebUtility.UrlEncode(name)}®ister={register.ToString().ToLower()}")).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -278,14 +278,6 @@ namespace Oqtane.Controllers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET api/<controller>/register?email=x
|
|
||||||
[HttpPost("register")]
|
|
||||||
[Authorize(Roles = RoleNames.Host)]
|
|
||||||
public async Task Register(string email)
|
|
||||||
{
|
|
||||||
await RegisterContact(email);
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct ClientAssembly
|
public struct ClientAssembly
|
||||||
{
|
{
|
||||||
public ClientAssembly(string filepath, bool hashfilename)
|
public ClientAssembly(string filepath, bool hashfilename)
|
||||||
|
@ -294,7 +286,7 @@ namespace Oqtane.Controllers
|
||||||
DateTime lastwritetime = System.IO.File.GetLastWriteTime(filepath);
|
DateTime lastwritetime = System.IO.File.GetLastWriteTime(filepath);
|
||||||
if (hashfilename)
|
if (hashfilename)
|
||||||
{
|
{
|
||||||
HashedName = GetDeterministicHashCode(filepath).ToString("X8") + "." + lastwritetime.ToString("yyyyMMddHHmmss") + Path.GetExtension(filepath);
|
HashedName = Utilities.GenerateSimpleHash(filepath) + "." + lastwritetime.ToString("yyyyMMddHHmmss") + Path.GetExtension(filepath);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -305,25 +297,5 @@ namespace Oqtane.Controllers
|
||||||
public string FilePath { get; private set; }
|
public string FilePath { get; private set; }
|
||||||
public string HashedName { get; private set; }
|
public string HashedName { get; private set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int GetDeterministicHashCode(string value)
|
|
||||||
{
|
|
||||||
unchecked
|
|
||||||
{
|
|
||||||
int hash1 = (5381 << 16) + 5381;
|
|
||||||
int hash2 = hash1;
|
|
||||||
|
|
||||||
for (int i = 0; i < value.Length; i += 2)
|
|
||||||
{
|
|
||||||
hash1 = ((hash1 << 5) + hash1) ^ value[i];
|
|
||||||
if (i == value.Length - 1)
|
|
||||||
break;
|
|
||||||
hash2 = ((hash2 << 5) + hash2) ^ value[i + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
return hash1 + (hash2 * 1566083941);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,7 +155,7 @@ namespace Oqtane.Controllers
|
||||||
[Authorize(Roles = RoleNames.Registered)]
|
[Authorize(Roles = RoleNames.Registered)]
|
||||||
public Notification Post([FromBody] Notification notification)
|
public Notification Post([FromBody] Notification notification)
|
||||||
{
|
{
|
||||||
if (ModelState.IsValid && notification.SiteId == _alias.SiteId && IsAuthorized(notification.FromUserId))
|
if (ModelState.IsValid && notification.SiteId == _alias.SiteId && (IsAuthorized(notification.FromUserId) || (notification.FromUserId == null && User.IsInRole(RoleNames.Admin))))
|
||||||
{
|
{
|
||||||
if (!User.IsInRole(RoleNames.Admin))
|
if (!User.IsInRole(RoleNames.Admin))
|
||||||
{
|
{
|
||||||
|
@ -181,17 +181,45 @@ namespace Oqtane.Controllers
|
||||||
[Authorize(Roles = RoleNames.Registered)]
|
[Authorize(Roles = RoleNames.Registered)]
|
||||||
public Notification Put(int id, [FromBody] Notification notification)
|
public Notification Put(int id, [FromBody] Notification notification)
|
||||||
{
|
{
|
||||||
if (ModelState.IsValid && notification.SiteId == _alias.SiteId && notification.NotificationId == id && _notifications.GetNotification(notification.NotificationId, false) != null && (IsAuthorized(notification.FromUserId) || IsAuthorized(notification.ToUserId)))
|
if (ModelState.IsValid && notification.SiteId == _alias.SiteId && notification.NotificationId == id && _notifications.GetNotification(notification.NotificationId, false) != null)
|
||||||
{
|
{
|
||||||
if (!User.IsInRole(RoleNames.Admin) && notification.FromUserId != null)
|
bool update = false;
|
||||||
|
if (IsAuthorized(notification.FromUserId))
|
||||||
{
|
{
|
||||||
// content must be HTML encoded for non-admins to prevent HTML injection
|
// notification belongs to current authenticated user - update is allowed
|
||||||
notification.Subject = WebUtility.HtmlEncode(notification.Subject);
|
if (!User.IsInRole(RoleNames.Admin))
|
||||||
notification.Body = WebUtility.HtmlEncode(notification.Body);
|
{
|
||||||
|
// content must be HTML encoded for non-admins to prevent HTML injection
|
||||||
|
notification.Subject = WebUtility.HtmlEncode(notification.Subject);
|
||||||
|
notification.Body = WebUtility.HtmlEncode(notification.Body);
|
||||||
|
}
|
||||||
|
update = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (IsAuthorized(notification.ToUserId))
|
||||||
|
{
|
||||||
|
// notification was sent to current authenticated user - only isread and isdeleted properties can be updated
|
||||||
|
var isread = notification.IsRead;
|
||||||
|
var isdeleted = notification.IsDeleted;
|
||||||
|
notification = _notifications.GetNotification(notification.NotificationId);
|
||||||
|
notification.IsRead = isread;
|
||||||
|
notification.IsDeleted = isdeleted;
|
||||||
|
update = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
notification = _notifications.UpdateNotification(notification);
|
||||||
|
_syncManager.AddSyncEvent(_alias, EntityNames.Notification, notification.NotificationId, SyncEventActions.Update);
|
||||||
|
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Notification Updated {NotificationId}", notification.NotificationId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Notification Put Attempt {Notification}", notification);
|
||||||
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
|
notification = null;
|
||||||
}
|
}
|
||||||
notification = _notifications.UpdateNotification(notification);
|
|
||||||
_syncManager.AddSyncEvent(_alias, EntityNames.Notification, notification.NotificationId, SyncEventActions.Update);
|
|
||||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Notification Updated {NotificationId}", notification.NotificationId);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -12,6 +12,8 @@ using Oqtane.Infrastructure;
|
||||||
using Oqtane.Enums;
|
using Oqtane.Enums;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using Oqtane.Managers;
|
||||||
|
using System.Net;
|
||||||
// ReSharper disable PartialTypeWithSinglePart
|
// ReSharper disable PartialTypeWithSinglePart
|
||||||
|
|
||||||
namespace Oqtane.Controllers
|
namespace Oqtane.Controllers
|
||||||
|
@ -20,13 +22,15 @@ namespace Oqtane.Controllers
|
||||||
public class PackageController : Controller
|
public class PackageController : Controller
|
||||||
{
|
{
|
||||||
private readonly IInstallationManager _installationManager;
|
private readonly IInstallationManager _installationManager;
|
||||||
|
private readonly IUserManager _userManager;
|
||||||
private readonly IWebHostEnvironment _environment;
|
private readonly IWebHostEnvironment _environment;
|
||||||
private readonly IConfigManager _configManager;
|
private readonly IConfigManager _configManager;
|
||||||
private readonly ILogManager _logger;
|
private readonly ILogManager _logger;
|
||||||
|
|
||||||
public PackageController(IInstallationManager installationManager, IWebHostEnvironment environment, IConfigManager configManager, ILogManager logger)
|
public PackageController(IInstallationManager installationManager, IUserManager userManager, IWebHostEnvironment environment, IConfigManager configManager, ILogManager logger)
|
||||||
{
|
{
|
||||||
_installationManager = installationManager;
|
_installationManager = installationManager;
|
||||||
|
_userManager = userManager;
|
||||||
_environment = environment;
|
_environment = environment;
|
||||||
_configManager = configManager;
|
_configManager = configManager;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
@ -45,7 +49,7 @@ namespace Oqtane.Controllers
|
||||||
{
|
{
|
||||||
client.DefaultRequestHeaders.Add("Referer", HttpContext.Request.Scheme + "://" + HttpContext.Request.Host.Value);
|
client.DefaultRequestHeaders.Add("Referer", HttpContext.Request.Scheme + "://" + HttpContext.Request.Host.Value);
|
||||||
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(Constants.PackageId, Constants.Version));
|
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(Constants.PackageId, Constants.Version));
|
||||||
packages = await GetJson<List<Package>>(client, url + $"/api/registry/packages/?id={_configManager.GetInstallationId()}&type={type.ToLower()}&version={Constants.Version}&search={search}&price={price}&package={package}&sort={sort}");
|
packages = await GetJson<List<Package>>(client, url + $"/api/registry/packages/?id={_configManager.GetInstallationId()}&type={type.ToLower()}&version={Constants.Version}&search={WebUtility.UrlEncode(search)}&price={price}&package={package}&sort={sort}&email={WebUtility.UrlEncode(GetPackageRegistryEmail())}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return packages;
|
return packages;
|
||||||
|
@ -64,7 +68,7 @@ namespace Oqtane.Controllers
|
||||||
{
|
{
|
||||||
client.DefaultRequestHeaders.Add("Referer", HttpContext.Request.Scheme + "://" + HttpContext.Request.Host.Value);
|
client.DefaultRequestHeaders.Add("Referer", HttpContext.Request.Scheme + "://" + HttpContext.Request.Host.Value);
|
||||||
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(Constants.PackageId, Constants.Version));
|
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(Constants.PackageId, Constants.Version));
|
||||||
packages = await GetJson<List<Package>>(client, url + $"/api/registry/updates/?id={_configManager.GetInstallationId()}&version={Constants.Version}&type={type}");
|
packages = await GetJson<List<Package>>(client, url + $"/api/registry/updates/?id={_configManager.GetInstallationId()}&version={Constants.Version}&type={type}&email={WebUtility.UrlEncode(GetPackageRegistryEmail())}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return packages;
|
return packages;
|
||||||
|
@ -83,7 +87,7 @@ namespace Oqtane.Controllers
|
||||||
{
|
{
|
||||||
client.DefaultRequestHeaders.Add("Referer", HttpContext.Request.Scheme + "://" + HttpContext.Request.Host.Value);
|
client.DefaultRequestHeaders.Add("Referer", HttpContext.Request.Scheme + "://" + HttpContext.Request.Host.Value);
|
||||||
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(Constants.PackageId, Constants.Version));
|
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(Constants.PackageId, Constants.Version));
|
||||||
package = await GetJson<Package>(client, url + $"/api/registry/package/?id={_configManager.GetInstallationId()}&package={packageid}&version={version}&download={download}");
|
package = await GetJson<Package>(client, url + $"/api/registry/package/?id={_configManager.GetInstallationId()}&package={packageid}&version={version}&download={download}&email={WebUtility.UrlEncode(GetPackageRegistryEmail())}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (package != null)
|
if (package != null)
|
||||||
|
@ -117,6 +121,24 @@ namespace Oqtane.Controllers
|
||||||
return package;
|
return package;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetPackageRegistryEmail()
|
||||||
|
{
|
||||||
|
var email = _configManager.GetSetting("PackageRegistryEmail", "");
|
||||||
|
if (string.IsNullOrEmpty(email))
|
||||||
|
{
|
||||||
|
if (User.Identity.IsAuthenticated)
|
||||||
|
{
|
||||||
|
var user = _userManager.GetUser(User.Identity.Name, -1);
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
|
email = user.Email;
|
||||||
|
_configManager.AddOrUpdateSetting("PackageRegistryEmail", email, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<T> GetJson<T>(HttpClient httpClient, string url)
|
private async Task<T> GetJson<T>(HttpClient httpClient, string url)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
|
@ -9,7 +9,8 @@ using System.Net;
|
||||||
using Oqtane.Enums;
|
using Oqtane.Enums;
|
||||||
using Oqtane.Infrastructure;
|
using Oqtane.Infrastructure;
|
||||||
using Oqtane.Repository;
|
using Oqtane.Repository;
|
||||||
using System;
|
using System.Xml.Linq;
|
||||||
|
using Microsoft.AspNetCore.Diagnostics;
|
||||||
|
|
||||||
namespace Oqtane.Controllers
|
namespace Oqtane.Controllers
|
||||||
{
|
{
|
||||||
|
@ -189,15 +190,16 @@ namespace Oqtane.Controllers
|
||||||
User user = _userPermissions.GetUser(User);
|
User user = _userPermissions.GetUser(User);
|
||||||
if (parent != null && parent.SiteId == _alias.SiteId && parent.IsPersonalizable && user.UserId == int.Parse(userid))
|
if (parent != null && parent.SiteId == _alias.SiteId && parent.IsPersonalizable && user.UserId == int.Parse(userid))
|
||||||
{
|
{
|
||||||
page = _pages.GetPage(parent.Path + "/" + user.Username, parent.SiteId);
|
var path = Utilities.GetFriendlyUrl(user.Username);
|
||||||
|
page = _pages.GetPage(parent.Path + "/" + path, parent.SiteId);
|
||||||
if (page == null)
|
if (page == null)
|
||||||
{
|
{
|
||||||
page = new Page();
|
page = new Page();
|
||||||
page.SiteId = parent.SiteId;
|
page.SiteId = parent.SiteId;
|
||||||
page.ParentId = parent.PageId;
|
page.ParentId = parent.PageId;
|
||||||
page.Name = (!string.IsNullOrEmpty(user.DisplayName)) ? user.DisplayName : user.Username;
|
page.Name = user.Username;
|
||||||
page.Path = parent.Path + "/" + user.Username;
|
page.Path = parent.Path + "/" + path;
|
||||||
page.Title = page.Name + " - " + parent.Name;
|
page.Title = ((!string.IsNullOrEmpty(user.DisplayName)) ? user.DisplayName : user.Username) + " - " + parent.Name;
|
||||||
page.Order = 0;
|
page.Order = 0;
|
||||||
page.IsNavigation = false;
|
page.IsNavigation = false;
|
||||||
page.Url = "";
|
page.Url = "";
|
||||||
|
@ -250,6 +252,11 @@ namespace Oqtane.Controllers
|
||||||
|
|
||||||
_syncManager.AddSyncEvent(_alias, EntityNames.Page, page.PageId, SyncEventActions.Create);
|
_syncManager.AddSyncEvent(_alias, EntityNames.Page, page.PageId, SyncEventActions.Create);
|
||||||
_syncManager.AddSyncEvent(_alias, EntityNames.Site, page.SiteId, SyncEventActions.Refresh);
|
_syncManager.AddSyncEvent(_alias, EntityNames.Site, page.SiteId, SyncEventActions.Refresh);
|
||||||
|
|
||||||
|
// set user personalized page path
|
||||||
|
var setting = new Setting { EntityName = EntityNames.User, EntityId = page.UserId.Value, SettingName = $"PersonalizedPagePath:{page.SiteId}:{parent.PageId}", SettingValue = path, IsPrivate = false };
|
||||||
|
_settings.AddSetting(setting);
|
||||||
|
_syncManager.AddSyncEvent(_alias, EntityNames.User, user.UserId, SyncEventActions.Update);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -274,18 +281,14 @@ namespace Oqtane.Controllers
|
||||||
// get current page permissions
|
// get current page permissions
|
||||||
var currentPermissions = _permissionRepository.GetPermissions(page.SiteId, EntityNames.Page, page.PageId).ToList();
|
var currentPermissions = _permissionRepository.GetPermissions(page.SiteId, EntityNames.Page, page.PageId).ToList();
|
||||||
|
|
||||||
page = _pages.UpdatePage(page);
|
// preserve new path and deleted status
|
||||||
|
var newPath = page.Path;
|
||||||
|
var deleted = page.IsDeleted;
|
||||||
|
page.Path = currentPage.Path;
|
||||||
|
page.IsDeleted = currentPage.IsDeleted;
|
||||||
|
|
||||||
// save url mapping if page path changed
|
// update page
|
||||||
if (currentPage.Path != page.Path)
|
UpdatePage(page, page.PageId, page.Path, newPath, deleted);
|
||||||
{
|
|
||||||
var urlMapping = _urlMappings.GetUrlMapping(page.SiteId, currentPage.Path);
|
|
||||||
if (urlMapping != null)
|
|
||||||
{
|
|
||||||
urlMapping.MappedUrl = page.Path;
|
|
||||||
_urlMappings.UpdateUrlMapping(urlMapping);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get differences between current and new page permissions
|
// get differences between current and new page permissions
|
||||||
var added = GetPermissionsDifferences(page.PermissionList, currentPermissions);
|
var added = GetPermissionsDifferences(page.PermissionList, currentPermissions);
|
||||||
|
@ -315,6 +318,7 @@ namespace Oqtane.Controllers
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// permissions removed
|
// permissions removed
|
||||||
foreach (Permission permission in removed)
|
foreach (Permission permission in removed)
|
||||||
{
|
{
|
||||||
|
@ -338,8 +342,29 @@ namespace Oqtane.Controllers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_syncManager.AddSyncEvent(_alias, EntityNames.Page, page.PageId, SyncEventActions.Update);
|
|
||||||
_syncManager.AddSyncEvent(_alias, EntityNames.Site, page.SiteId, SyncEventActions.Refresh);
|
_syncManager.AddSyncEvent(_alias, EntityNames.Site, page.SiteId, SyncEventActions.Refresh);
|
||||||
|
|
||||||
|
// personalized page
|
||||||
|
if (page.UserId != null && currentPage.Path != page.Path)
|
||||||
|
{
|
||||||
|
// set user personalized page path
|
||||||
|
var settingName = $"PersonalizedPagePath:{page.SiteId}:{page.ParentId}";
|
||||||
|
var path = page.Path.Substring(page.Path.LastIndexOf("/") + 1);
|
||||||
|
var settings = _settings.GetSettings(EntityNames.User, page.UserId.Value).ToList();
|
||||||
|
var setting = settings.FirstOrDefault(item => item.SettingName == settingName);
|
||||||
|
if (setting == null)
|
||||||
|
{
|
||||||
|
setting = new Setting { EntityName = EntityNames.User, EntityId = page.UserId.Value, SettingName = settingName, SettingValue = path, IsPrivate = false };
|
||||||
|
_settings.AddSetting(setting);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setting.SettingValue = path;
|
||||||
|
_settings.UpdateSetting(setting);
|
||||||
|
}
|
||||||
|
_syncManager.AddSyncEvent(_alias, EntityNames.User, page.UserId.Value, SyncEventActions.Update);
|
||||||
|
}
|
||||||
|
|
||||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Page Updated {Page}", page);
|
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Page Updated {Page}", page);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -351,6 +376,39 @@ namespace Oqtane.Controllers
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdatePage(Page page, int pageId, string oldPath, string newPath, bool deleted)
|
||||||
|
{
|
||||||
|
var update = (page.PageId == pageId);
|
||||||
|
if (oldPath != newPath)
|
||||||
|
{
|
||||||
|
var urlMapping = _urlMappings.GetUrlMapping(page.SiteId, page.Path);
|
||||||
|
if (urlMapping != null)
|
||||||
|
{
|
||||||
|
urlMapping.MappedUrl = newPath + page.Path.Substring(oldPath.Length);
|
||||||
|
_urlMappings.UpdateUrlMapping(urlMapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
page.Path = newPath + page.Path.Substring(oldPath.Length);
|
||||||
|
update = true;
|
||||||
|
}
|
||||||
|
if (deleted != page.IsDeleted)
|
||||||
|
{
|
||||||
|
page.IsDeleted = deleted;
|
||||||
|
update = true;
|
||||||
|
}
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
_pages.UpdatePage(page);
|
||||||
|
_syncManager.AddSyncEvent(_alias, EntityNames.Page, page.PageId, SyncEventActions.Update);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update any children
|
||||||
|
foreach (var _page in _pages.GetPages(page.SiteId).Where(item => item.ParentId == page.PageId))
|
||||||
|
{
|
||||||
|
UpdatePage(_page, pageId, oldPath, newPath, deleted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private List<Permission> GetPermissionsDifferences(List<Permission> permissions1, List<Permission> permissions2)
|
private List<Permission> GetPermissionsDifferences(List<Permission> permissions1, List<Permission> permissions2)
|
||||||
{
|
{
|
||||||
var differences = new List<Permission>();
|
var differences = new List<Permission>();
|
||||||
|
|
|
@ -64,7 +64,7 @@ namespace Oqtane.Controllers
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// suppress unauthorized visitor logging as it is usually caused by clients that do not support cookies
|
// suppress unauthorized visitor logging as it is usually caused by clients that do not support cookies or private browsing sessions
|
||||||
if (entityName != EntityNames.Visitor)
|
if (entityName != EntityNames.Visitor)
|
||||||
{
|
{
|
||||||
_logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Access Settings {EntityName} {EntityId}", entityName, entityId);
|
_logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Access Settings {EntityName} {EntityId}", entityName, entityId);
|
||||||
|
|
|
@ -53,7 +53,9 @@ namespace Oqtane.Controllers
|
||||||
systeminfo.Add("Logging:LogLevel:Default", _configManager.GetSetting("Logging:LogLevel:Default", "Information"));
|
systeminfo.Add("Logging:LogLevel:Default", _configManager.GetSetting("Logging:LogLevel:Default", "Information"));
|
||||||
systeminfo.Add("Logging:LogLevel:Notify", _configManager.GetSetting("Logging:LogLevel:Notify", "Error"));
|
systeminfo.Add("Logging:LogLevel:Notify", _configManager.GetSetting("Logging:LogLevel:Notify", "Error"));
|
||||||
systeminfo.Add("UseSwagger", _configManager.GetSetting("UseSwagger", "true"));
|
systeminfo.Add("UseSwagger", _configManager.GetSetting("UseSwagger", "true"));
|
||||||
|
systeminfo.Add("CacheControl", _configManager.GetSetting("CacheControl", ""));
|
||||||
systeminfo.Add("PackageRegistryUrl", _configManager.GetSetting("PackageRegistryUrl", Constants.PackageRegistryUrl));
|
systeminfo.Add("PackageRegistryUrl", _configManager.GetSetting("PackageRegistryUrl", Constants.PackageRegistryUrl));
|
||||||
|
systeminfo.Add("PackageRegistryEmail", _configManager.GetSetting("PackageRegistryEmail", ""));
|
||||||
break;
|
break;
|
||||||
case "log":
|
case "log":
|
||||||
string log = "";
|
string log = "";
|
||||||
|
|
|
@ -280,7 +280,7 @@ namespace Oqtane.Controllers
|
||||||
{
|
{
|
||||||
{ "FrameworkVersion", theme.Version },
|
{ "FrameworkVersion", theme.Version },
|
||||||
{ "ClientReference", $"<PackageReference Include=\"Oqtane.Client\" Version=\"{theme.Version}\" />" },
|
{ "ClientReference", $"<PackageReference Include=\"Oqtane.Client\" Version=\"{theme.Version}\" />" },
|
||||||
{ "SharedReference", $"<PackageReference Include=\"Oqtane.Client\" Version=\"{theme.Version}\" />" },
|
{ "SharedReference", $"<PackageReference Include=\"Oqtane.Shared\" Version=\"{theme.Version}\" />" },
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -217,7 +217,7 @@ namespace Oqtane.Controllers
|
||||||
|
|
||||||
// DELETE api/<controller>/5?siteid=x
|
// DELETE api/<controller>/5?siteid=x
|
||||||
[HttpDelete("{id}")]
|
[HttpDelete("{id}")]
|
||||||
[Authorize(Policy = $"{EntityNames.User}:{PermissionNames.Write}:{RoleNames.Admin}")]
|
[Authorize(Policy = $"{EntityNames.User}:{PermissionNames.Write}:{RoleNames.Host}")]
|
||||||
public async Task Delete(int id, string siteid)
|
public async Task Delete(int id, string siteid)
|
||||||
{
|
{
|
||||||
User user = _users.GetUser(id, false);
|
User user = _users.GetUser(id, false);
|
||||||
|
|
|
@ -42,7 +42,7 @@ namespace Oqtane.Controllers
|
||||||
int UserId = -1;
|
int UserId = -1;
|
||||||
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId && (userid != null && int.TryParse(userid, out UserId) || rolename != null))
|
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId && (userid != null && int.TryParse(userid, out UserId) || rolename != null))
|
||||||
{
|
{
|
||||||
if (IsAuthorized(UserId, rolename))
|
if (IsAuthorized(UserId, rolename, SiteId))
|
||||||
{
|
{
|
||||||
var userroles = _userRoles.GetUserRoles(SiteId).ToList();
|
var userroles = _userRoles.GetUserRoles(SiteId).ToList();
|
||||||
if (UserId != -1)
|
if (UserId != -1)
|
||||||
|
@ -82,7 +82,7 @@ namespace Oqtane.Controllers
|
||||||
public UserRole Get(int id)
|
public UserRole Get(int id)
|
||||||
{
|
{
|
||||||
var userrole = _userRoles.GetUserRole(id);
|
var userrole = _userRoles.GetUserRole(id);
|
||||||
if (userrole != null && SiteValid(userrole.Role.SiteId) && IsAuthorized(userrole.UserId, userrole.Role.Name))
|
if (userrole != null && SiteValid(userrole.Role.SiteId) && IsAuthorized(userrole.UserId, userrole.Role.Name, userrole.Role.SiteId ?? -1))
|
||||||
{
|
{
|
||||||
return Filter(userrole, _userPermissions.GetUser().UserId);
|
return Filter(userrole, _userPermissions.GetUser().UserId);
|
||||||
}
|
}
|
||||||
|
@ -101,57 +101,59 @@ namespace Oqtane.Controllers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsAuthorized(int userId, string roleName)
|
private bool IsAuthorized(int userId, string roleName, int siteId)
|
||||||
{
|
{
|
||||||
bool authorized = true;
|
bool authorized = true;
|
||||||
if (userId != -1)
|
if (userId != -1)
|
||||||
{
|
{
|
||||||
authorized = _userPermissions.GetUser(User).UserId == userId;
|
authorized = (_userPermissions.GetUser(User).UserId == userId);
|
||||||
}
|
}
|
||||||
if (authorized && !string.IsNullOrEmpty(roleName))
|
if (authorized && !string.IsNullOrEmpty(roleName))
|
||||||
{
|
{
|
||||||
authorized = User.IsInRole(roleName);
|
authorized = User.IsInRole(roleName);
|
||||||
}
|
}
|
||||||
|
if (!authorized)
|
||||||
|
{
|
||||||
|
authorized = _userPermissions.IsAuthorized(User, siteId, EntityNames.UserRole, -1, PermissionNames.Write, RoleNames.Admin);
|
||||||
|
}
|
||||||
return authorized;
|
return authorized;
|
||||||
}
|
}
|
||||||
|
|
||||||
private UserRole Filter(UserRole userrole, int userid)
|
private UserRole Filter(UserRole userrole, int userid)
|
||||||
{
|
{
|
||||||
// clone object to avoid mutating cache
|
// include all properties if authorized
|
||||||
UserRole filtered = null;
|
if (_userPermissions.IsAuthorized(User, userrole.User.SiteId, EntityNames.UserRole, -1, PermissionNames.Write, RoleNames.Admin))
|
||||||
|
|
||||||
if (userrole != null)
|
|
||||||
{
|
{
|
||||||
filtered = new UserRole();
|
return userrole;
|
||||||
|
|
||||||
// public properties
|
|
||||||
filtered.UserRoleId = userrole.UserRoleId;
|
|
||||||
filtered.UserId = userrole.UserId;
|
|
||||||
filtered.RoleId = userrole.RoleId;
|
|
||||||
|
|
||||||
filtered.User = new User();
|
|
||||||
filtered.User.SiteId = userrole.User.SiteId;
|
|
||||||
filtered.User.UserId = userrole.User.UserId;
|
|
||||||
filtered.User.Username = userrole.User.Username;
|
|
||||||
filtered.User.DisplayName = userrole.User.DisplayName;
|
|
||||||
|
|
||||||
filtered.Role = new Role();
|
|
||||||
filtered.Role.SiteId = userrole.Role.SiteId;
|
|
||||||
filtered.Role.RoleId = userrole.Role.RoleId;
|
|
||||||
filtered.Role.Name = userrole.Role.Name;
|
|
||||||
|
|
||||||
// include private properties if administrator
|
|
||||||
if (_userPermissions.IsAuthorized(User, filtered.User.SiteId, EntityNames.UserRole, -1, PermissionNames.Write, RoleNames.Admin))
|
|
||||||
{
|
|
||||||
filtered.User.Email = userrole.User.Email;
|
|
||||||
filtered.User.PhotoFileId = userrole.User.PhotoFileId;
|
|
||||||
filtered.User.LastLoginOn = userrole.User.LastLoginOn;
|
|
||||||
filtered.User.LastIPAddress = userrole.User.LastIPAddress;
|
|
||||||
filtered.User.CreatedOn = userrole.User.CreatedOn;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// clone object to avoid mutating cache
|
||||||
|
UserRole filtered = null;
|
||||||
|
|
||||||
return filtered;
|
if (userrole != null)
|
||||||
|
{
|
||||||
|
filtered = new UserRole();
|
||||||
|
|
||||||
|
// include public properties
|
||||||
|
filtered.UserRoleId = userrole.UserRoleId;
|
||||||
|
filtered.UserId = userrole.UserId;
|
||||||
|
filtered.RoleId = userrole.RoleId;
|
||||||
|
|
||||||
|
filtered.User = new User();
|
||||||
|
filtered.User.SiteId = userrole.User.SiteId;
|
||||||
|
filtered.User.UserId = userrole.User.UserId;
|
||||||
|
filtered.User.Username = userrole.User.Username;
|
||||||
|
filtered.User.DisplayName = userrole.User.DisplayName;
|
||||||
|
|
||||||
|
filtered.Role = new Role();
|
||||||
|
filtered.Role.SiteId = userrole.Role.SiteId;
|
||||||
|
filtered.Role.RoleId = userrole.Role.RoleId;
|
||||||
|
filtered.Role.Name = userrole.Role.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST api/<controller>
|
// POST api/<controller>
|
||||||
|
|
|
@ -47,7 +47,6 @@ namespace Oqtane.Extensions
|
||||||
// default options
|
// default options
|
||||||
options.SignInScheme = Constants.AuthenticationScheme; // identity cookie
|
options.SignInScheme = Constants.AuthenticationScheme; // identity cookie
|
||||||
options.RequireHttpsMetadata = true;
|
options.RequireHttpsMetadata = true;
|
||||||
options.SaveTokens = false;
|
|
||||||
options.GetClaimsFromUserInfoEndpoint = true;
|
options.GetClaimsFromUserInfoEndpoint = true;
|
||||||
options.CallbackPath = string.IsNullOrEmpty(alias.Path) ? "/signin-" + AuthenticationProviderTypes.OpenIDConnect : "/" + alias.Path + "/signin-" + AuthenticationProviderTypes.OpenIDConnect;
|
options.CallbackPath = string.IsNullOrEmpty(alias.Path) ? "/signin-" + AuthenticationProviderTypes.OpenIDConnect : "/" + alias.Path + "/signin-" + AuthenticationProviderTypes.OpenIDConnect;
|
||||||
options.ResponseMode = OpenIdConnectResponseMode.FormPost; // recommended as most secure
|
options.ResponseMode = OpenIdConnectResponseMode.FormPost; // recommended as most secure
|
||||||
|
@ -63,6 +62,7 @@ namespace Oqtane.Extensions
|
||||||
options.ClientSecret = sitesettings.GetValue("ExternalLogin:ClientSecret", "");
|
options.ClientSecret = sitesettings.GetValue("ExternalLogin:ClientSecret", "");
|
||||||
options.ResponseType = sitesettings.GetValue("ExternalLogin:AuthResponseType", "code"); // default is authorization code flow
|
options.ResponseType = sitesettings.GetValue("ExternalLogin:AuthResponseType", "code"); // default is authorization code flow
|
||||||
options.UsePkce = bool.Parse(sitesettings.GetValue("ExternalLogin:PKCE", "false"));
|
options.UsePkce = bool.Parse(sitesettings.GetValue("ExternalLogin:PKCE", "false"));
|
||||||
|
options.SaveTokens = bool.Parse(sitesettings.GetValue("ExternalLogin:SaveTokens", "false"));
|
||||||
if (!string.IsNullOrEmpty(sitesettings.GetValue("ExternalLogin:RoleClaimType", "")))
|
if (!string.IsNullOrEmpty(sitesettings.GetValue("ExternalLogin:RoleClaimType", "")))
|
||||||
{
|
{
|
||||||
options.TokenValidationParameters.RoleClaimType = sitesettings.GetValue("ExternalLogin:RoleClaimType", "");
|
options.TokenValidationParameters.RoleClaimType = sitesettings.GetValue("ExternalLogin:RoleClaimType", "");
|
||||||
|
@ -102,7 +102,6 @@ namespace Oqtane.Extensions
|
||||||
// default options
|
// default options
|
||||||
options.SignInScheme = Constants.AuthenticationScheme; // identity cookie
|
options.SignInScheme = Constants.AuthenticationScheme; // identity cookie
|
||||||
options.CallbackPath = string.IsNullOrEmpty(alias.Path) ? "/signin-" + AuthenticationProviderTypes.OAuth2 : "/" + alias.Path + "/signin-" + AuthenticationProviderTypes.OAuth2;
|
options.CallbackPath = string.IsNullOrEmpty(alias.Path) ? "/signin-" + AuthenticationProviderTypes.OAuth2 : "/" + alias.Path + "/signin-" + AuthenticationProviderTypes.OAuth2;
|
||||||
options.SaveTokens = false;
|
|
||||||
|
|
||||||
// site options
|
// site options
|
||||||
options.AuthorizationEndpoint = sitesettings.GetValue("ExternalLogin:AuthorizationUrl", "");
|
options.AuthorizationEndpoint = sitesettings.GetValue("ExternalLogin:AuthorizationUrl", "");
|
||||||
|
@ -111,6 +110,7 @@ namespace Oqtane.Extensions
|
||||||
options.ClientId = sitesettings.GetValue("ExternalLogin:ClientId", "");
|
options.ClientId = sitesettings.GetValue("ExternalLogin:ClientId", "");
|
||||||
options.ClientSecret = sitesettings.GetValue("ExternalLogin:ClientSecret", "");
|
options.ClientSecret = sitesettings.GetValue("ExternalLogin:ClientSecret", "");
|
||||||
options.UsePkce = bool.Parse(sitesettings.GetValue("ExternalLogin:PKCE", "false"));
|
options.UsePkce = bool.Parse(sitesettings.GetValue("ExternalLogin:PKCE", "false"));
|
||||||
|
options.SaveTokens = bool.Parse(sitesettings.GetValue("ExternalLogin:SaveTokens", "false"));
|
||||||
options.Scope.Clear();
|
options.Scope.Clear();
|
||||||
foreach (var scope in sitesettings.GetValue("ExternalLogin:Scopes", "").Split(',', StringSplitOptions.RemoveEmptyEntries))
|
foreach (var scope in sitesettings.GetValue("ExternalLogin:Scopes", "").Split(',', StringSplitOptions.RemoveEmptyEntries))
|
||||||
{
|
{
|
||||||
|
@ -228,7 +228,6 @@ namespace Oqtane.Extensions
|
||||||
var identity = await ValidateUser(id, name, email, claims, context.HttpContext, context.Principal);
|
var identity = await ValidateUser(id, name, email, claims, context.HttpContext, context.Principal);
|
||||||
if (identity.Label == ExternalLoginStatus.Success)
|
if (identity.Label == ExternalLoginStatus.Success)
|
||||||
{
|
{
|
||||||
identity.AddClaim(new Claim("access_token", context.AccessToken));
|
|
||||||
context.Principal = new ClaimsPrincipal(identity);
|
context.Principal = new ClaimsPrincipal(identity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,8 +303,6 @@ namespace Oqtane.Extensions
|
||||||
var identity = await ValidateUser(id, name, email, claims, context.HttpContext, context.Principal);
|
var identity = await ValidateUser(id, name, email, claims, context.HttpContext, context.Principal);
|
||||||
if (identity.Label == ExternalLoginStatus.Success)
|
if (identity.Label == ExternalLoginStatus.Success)
|
||||||
{
|
{
|
||||||
// include access token
|
|
||||||
identity.AddClaim(new Claim("access_token", context.SecurityToken.RawData));
|
|
||||||
context.Principal = new ClaimsPrincipal(identity);
|
context.Principal = new ClaimsPrincipal(identity);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -527,11 +524,6 @@ namespace Oqtane.Extensions
|
||||||
// manage user
|
// manage user
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
// update user
|
|
||||||
user.LastLoginOn = DateTime.UtcNow;
|
|
||||||
user.LastIPAddress = httpContext.Connection.RemoteIpAddress.ToString();
|
|
||||||
_users.UpdateUser(user);
|
|
||||||
|
|
||||||
// manage roles
|
// manage roles
|
||||||
var _userRoles = httpContext.RequestServices.GetRequiredService<IUserRoleRepository>();
|
var _userRoles = httpContext.RequestServices.GetRequiredService<IUserRoleRepository>();
|
||||||
var userRoles = _userRoles.GetUserRoles(user.UserId, user.SiteId).ToList();
|
var userRoles = _userRoles.GetUserRoles(user.UserId, user.SiteId).ToList();
|
||||||
|
@ -591,64 +583,78 @@ namespace Oqtane.Extensions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// create claims identity
|
var userrole = userRoles.FirstOrDefault(item => item.Role.Name == RoleNames.Registered);
|
||||||
identityuser = await _identityUserManager.FindByNameAsync(user.Username);
|
if (!user.IsDeleted && userrole != null && Utilities.IsEffectiveAndNotExpired(userrole.EffectiveDate, userrole.ExpiryDate))
|
||||||
user.SecurityStamp = identityuser.SecurityStamp;
|
|
||||||
identity = UserSecurity.CreateClaimsIdentity(alias, user, userRoles);
|
|
||||||
identity.Label = ExternalLoginStatus.Success;
|
|
||||||
|
|
||||||
// user profile claims
|
|
||||||
if (!string.IsNullOrEmpty(httpContext.GetSiteSettings().GetValue("ExternalLogin:ProfileClaimTypes", "")))
|
|
||||||
{
|
{
|
||||||
var _settings = httpContext.RequestServices.GetRequiredService<ISettingRepository>();
|
// update user
|
||||||
var _profiles = httpContext.RequestServices.GetRequiredService<IProfileRepository>();
|
user.LastLoginOn = DateTime.UtcNow;
|
||||||
var profiles = _profiles.GetProfiles(alias.SiteId).ToList();
|
user.LastIPAddress = httpContext.Connection.RemoteIpAddress.ToString();
|
||||||
foreach (var mapping in httpContext.GetSiteSettings().GetValue("ExternalLogin:ProfileClaimTypes", "").Split(',', StringSplitOptions.RemoveEmptyEntries))
|
_users.UpdateUser(user);
|
||||||
|
|
||||||
|
// create claims identity
|
||||||
|
identityuser = await _identityUserManager.FindByNameAsync(user.Username);
|
||||||
|
user.SecurityStamp = identityuser.SecurityStamp;
|
||||||
|
identity = UserSecurity.CreateClaimsIdentity(alias, user, userRoles);
|
||||||
|
identity.Label = ExternalLoginStatus.Success;
|
||||||
|
|
||||||
|
// user profile claims
|
||||||
|
if (!string.IsNullOrEmpty(httpContext.GetSiteSettings().GetValue("ExternalLogin:ProfileClaimTypes", "")))
|
||||||
{
|
{
|
||||||
if (mapping.Contains(":"))
|
var _settings = httpContext.RequestServices.GetRequiredService<ISettingRepository>();
|
||||||
|
var _profiles = httpContext.RequestServices.GetRequiredService<IProfileRepository>();
|
||||||
|
var profiles = _profiles.GetProfiles(alias.SiteId).ToList();
|
||||||
|
foreach (var mapping in httpContext.GetSiteSettings().GetValue("ExternalLogin:ProfileClaimTypes", "").Split(',', StringSplitOptions.RemoveEmptyEntries))
|
||||||
{
|
{
|
||||||
var claim = claimsPrincipal.Claims.FirstOrDefault(item => item.Type == mapping.Split(":")[0]);
|
if (mapping.Contains(":"))
|
||||||
if (claim != null)
|
|
||||||
{
|
{
|
||||||
var profile = profiles.FirstOrDefault(item => item.Name == mapping.Split(":")[1]);
|
var claim = claimsPrincipal.Claims.FirstOrDefault(item => item.Type == mapping.Split(":")[0]);
|
||||||
if (profile != null)
|
if (claim != null)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(claim.Value))
|
var profile = profiles.FirstOrDefault(item => item.Name == mapping.Split(":")[1]);
|
||||||
|
if (profile != null)
|
||||||
{
|
{
|
||||||
var setting = _settings.GetSetting(EntityNames.User, user.UserId, profile.Name);
|
if (!string.IsNullOrEmpty(claim.Value))
|
||||||
if (setting != null)
|
|
||||||
{
|
{
|
||||||
setting.SettingValue = claim.Value;
|
var setting = _settings.GetSetting(EntityNames.User, user.UserId, profile.Name);
|
||||||
_settings.UpdateSetting(setting);
|
if (setting != null)
|
||||||
}
|
{
|
||||||
else
|
setting.SettingValue = claim.Value;
|
||||||
{
|
_settings.UpdateSetting(setting);
|
||||||
setting = new Setting { EntityName = EntityNames.User, EntityId = user.UserId, SettingName = profile.Name, SettingValue = claim.Value, IsPrivate = profile.IsPrivate };
|
}
|
||||||
_settings.AddSetting(setting);
|
else
|
||||||
|
{
|
||||||
|
setting = new Setting { EntityName = EntityNames.User, EntityId = user.UserId, SettingName = profile.Name, SettingValue = claim.Value, IsPrivate = profile.IsPrivate };
|
||||||
|
_settings.AddSetting(setting);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "The User Profile {ProfileName} Does Not Exist For The Site. Please Verify Your User Profile Definitions.", mapping.Split(":")[1]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "The User Profile {ProfileName} Does Not Exist For The Site. Please Verify Your User Profile Definitions.", mapping.Split(":")[1]);
|
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "The User Profile Claim {ClaimType} Does Not Exist. Please Use The Review Claims Feature To View The Claims Returned By Your Provider.", mapping.Split(":")[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "The User Profile Claim {ClaimType} Does Not Exist. Please Use The Review Claims Feature To View The Claims Returned By Your Provider.", mapping.Split(":")[0]);
|
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "The User Profile Claim Mapping {Mapping} Is Not Specified Correctly. It Should Be In The Format 'ClaimType:ProfileName'.", mapping);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "The User Profile Claim Mapping {Mapping} Is Not Specified Correctly. It Should Be In The Format 'ClaimType:ProfileName'.", mapping);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _syncManager = httpContext.RequestServices.GetRequiredService<ISyncManager>();
|
||||||
|
_syncManager.AddSyncEvent(alias, EntityNames.User, user.UserId, "Login");
|
||||||
|
|
||||||
|
_logger.Log(LogLevel.Information, "ExternalLogin", Enums.LogFunction.Security, "External User Login Successful For {Username} From IP Address {IPAddress} Using Provider {Provider}", user.Username, httpContext.Connection.RemoteIpAddress.ToString(), providerName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
identity.Label = ExternalLoginStatus.AccessDenied;
|
||||||
|
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "External User Login Denied For {Username}. User Account Is Deleted Or Not An Active Member Of Site {SiteId}.", user.Username, user.SiteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
var _syncManager = httpContext.RequestServices.GetRequiredService<ISyncManager>();
|
|
||||||
_syncManager.AddSyncEvent(alias, EntityNames.User, user.UserId, "Login");
|
|
||||||
|
|
||||||
_logger.Log(LogLevel.Information, "ExternalLogin", Enums.LogFunction.Security, "External User Login Successful For {Username} From IP Address {IPAddress} Using Provider {Provider}", user.Username, httpContext.Connection.RemoteIpAddress.ToString(), providerName);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else // claims invalid
|
else // claims invalid
|
||||||
|
|
|
@ -175,6 +175,12 @@ namespace Oqtane.Infrastructure
|
||||||
installationid = Guid.NewGuid().ToString();
|
installationid = Guid.NewGuid().ToString();
|
||||||
AddOrUpdateSetting("InstallationId", installationid, true);
|
AddOrUpdateSetting("InstallationId", installationid, true);
|
||||||
}
|
}
|
||||||
|
var version = GetSetting("InstallationVersion", "");
|
||||||
|
if (version != Constants.Version)
|
||||||
|
{
|
||||||
|
AddOrUpdateSetting("InstallationVersion", Constants.Version, true);
|
||||||
|
AddOrUpdateSetting("InstallationDate", DateTime.UtcNow.ToString("yyyyMMddHHmm"), true);
|
||||||
|
}
|
||||||
return installationid;
|
return installationid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,7 +91,7 @@ namespace Oqtane.Infrastructure
|
||||||
// get configuration
|
// get configuration
|
||||||
if (install == null)
|
if (install == null)
|
||||||
{
|
{
|
||||||
// startup or silent installation
|
// startup or automated installation
|
||||||
install = new InstallConfig
|
install = new InstallConfig
|
||||||
{
|
{
|
||||||
ConnectionString = _config.GetConnectionString(SettingKeys.ConnectionStringKey),
|
ConnectionString = _config.GetConnectionString(SettingKeys.ConnectionStringKey),
|
||||||
|
@ -111,7 +111,7 @@ namespace Oqtane.Infrastructure
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(install.ConnectionString) && !string.IsNullOrEmpty(install.Aliases) && !string.IsNullOrEmpty(install.HostPassword) && !string.IsNullOrEmpty(install.HostEmail))
|
if (!string.IsNullOrEmpty(install.ConnectionString) && !string.IsNullOrEmpty(install.Aliases) && !string.IsNullOrEmpty(install.HostPassword) && !string.IsNullOrEmpty(install.HostEmail))
|
||||||
{
|
{
|
||||||
// silent install
|
// automated install
|
||||||
install.SiteTemplate = GetInstallationConfig(SettingKeys.SiteTemplateKey, Constants.DefaultSiteTemplate);
|
install.SiteTemplate = GetInstallationConfig(SettingKeys.SiteTemplateKey, Constants.DefaultSiteTemplate);
|
||||||
install.DefaultTheme = GetInstallationConfig(SettingKeys.DefaultThemeKey, Constants.DefaultTheme);
|
install.DefaultTheme = GetInstallationConfig(SettingKeys.DefaultThemeKey, Constants.DefaultTheme);
|
||||||
install.DefaultContainer = GetInstallationConfig(SettingKeys.DefaultContainerKey, Constants.DefaultContainer);
|
install.DefaultContainer = GetInstallationConfig(SettingKeys.DefaultContainerKey, Constants.DefaultContainer);
|
||||||
|
@ -120,7 +120,11 @@ namespace Oqtane.Infrastructure
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// silent installation is missing required information
|
if (!string.IsNullOrEmpty(install.ConnectionString))
|
||||||
|
{
|
||||||
|
// automated installation is missing required information
|
||||||
|
result.Message = $"Error Installing Master Database For {SettingKeys.ConnectionStringKey}: {install.ConnectionString}. If You Are Trying To Execute An Automated Installation You Must Include The HostEmail, HostPassword, And DefaultAlias In appsettings.json.";
|
||||||
|
}
|
||||||
install.ConnectionString = "";
|
install.ConnectionString = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -261,6 +265,7 @@ namespace Oqtane.Infrastructure
|
||||||
var installation = IsInstalled();
|
var installation = IsInstalled();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
UpdateInstallation();
|
||||||
UpdateConnectionString(install.ConnectionString);
|
UpdateConnectionString(install.ConnectionString);
|
||||||
UpdateDatabaseType(install.DatabaseType);
|
UpdateDatabaseType(install.DatabaseType);
|
||||||
|
|
||||||
|
@ -487,6 +492,7 @@ namespace Oqtane.Infrastructure
|
||||||
moduleDefinition.Categories = moduledef.Categories;
|
moduleDefinition.Categories = moduledef.Categories;
|
||||||
// update version
|
// update version
|
||||||
moduleDefinition.Version = versions[versions.Length - 1];
|
moduleDefinition.Version = versions[versions.Length - 1];
|
||||||
|
moduleDefinition.ModifiedOn = DateTime.UtcNow;
|
||||||
db.Entry(moduleDefinition).State = EntityState.Modified;
|
db.Entry(moduleDefinition).State = EntityState.Modified;
|
||||||
db.SaveChanges();
|
db.SaveChanges();
|
||||||
}
|
}
|
||||||
|
@ -662,6 +668,11 @@ namespace Oqtane.Infrastructure
|
||||||
return connectionString;
|
return connectionString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UpdateInstallation()
|
||||||
|
{
|
||||||
|
_config.GetInstallationId();
|
||||||
|
}
|
||||||
|
|
||||||
public void UpdateConnectionString(string connectionString)
|
public void UpdateConnectionString(string connectionString)
|
||||||
{
|
{
|
||||||
connectionString = DenormalizeConnectionString(connectionString);
|
connectionString = DenormalizeConnectionString(connectionString);
|
||||||
|
@ -673,7 +684,10 @@ namespace Oqtane.Infrastructure
|
||||||
|
|
||||||
public void UpdateDatabaseType(string databaseType)
|
public void UpdateDatabaseType(string databaseType)
|
||||||
{
|
{
|
||||||
_configManager.AddOrUpdateSetting($"{SettingKeys.DatabaseSection}:{SettingKeys.DatabaseTypeKey}", databaseType, true);
|
if (_config.GetSetting($"{SettingKeys.DatabaseSection}:{SettingKeys.DatabaseTypeKey}", "") != databaseType)
|
||||||
|
{
|
||||||
|
_configManager.AddOrUpdateSetting($"{SettingKeys.DatabaseSection}:{SettingKeys.DatabaseTypeKey}", databaseType, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddEFMigrationsHistory(ISqlRepository sql, string connectionString, string databaseType, string version, bool isMaster)
|
public void AddEFMigrationsHistory(ISqlRepository sql, string connectionString, string databaseType, string version, bool isMaster)
|
||||||
|
|
|
@ -32,6 +32,7 @@ namespace Oqtane.Infrastructure
|
||||||
var logRepository = provider.GetRequiredService<ILogRepository>();
|
var logRepository = provider.GetRequiredService<ILogRepository>();
|
||||||
var visitorRepository = provider.GetRequiredService<IVisitorRepository>();
|
var visitorRepository = provider.GetRequiredService<IVisitorRepository>();
|
||||||
var notificationRepository = provider.GetRequiredService<INotificationRepository>();
|
var notificationRepository = provider.GetRequiredService<INotificationRepository>();
|
||||||
|
var urlMappingRepository = provider.GetRequiredService<IUrlMappingRepository>();
|
||||||
var installationManager = provider.GetRequiredService<IInstallationManager>();
|
var installationManager = provider.GetRequiredService<IInstallationManager>();
|
||||||
|
|
||||||
// iterate through sites for current tenant
|
// iterate through sites for current tenant
|
||||||
|
@ -95,6 +96,22 @@ namespace Oqtane.Infrastructure
|
||||||
{
|
{
|
||||||
log += $"Error Purging Notifications - {ex.Message}<br />";
|
log += $"Error Purging Notifications - {ex.Message}<br />";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// purge broken urls
|
||||||
|
retention = 30; // 30 days
|
||||||
|
if (settings.ContainsKey("UrlMappingRetention") && !string.IsNullOrEmpty(settings["UrlMappingRetention"]))
|
||||||
|
{
|
||||||
|
retention = int.Parse(settings["UrlMappingRetention"]);
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
count = urlMappingRepository.DeleteUrlMappings(site.SiteId, retention);
|
||||||
|
log += count.ToString() + " Broken Urls Purged<br />";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
log += $"Error Purging Broken Urls - {ex.Message}<br />";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// register assemblies
|
// register assemblies
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
using Oqtane.Models;
|
|
||||||
using Oqtane.Infrastructure;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Oqtane.Shared;
|
|
||||||
using Oqtane.Documentation;
|
using Oqtane.Documentation;
|
||||||
|
using Oqtane.Infrastructure;
|
||||||
|
using Oqtane.Models;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
|
||||||
namespace Oqtane.SiteTemplates
|
namespace Oqtane.SiteTemplates
|
||||||
{
|
{
|
||||||
|
@ -266,6 +266,7 @@ namespace Oqtane.SiteTemplates
|
||||||
PermissionList = new List<Permission>
|
PermissionList = new List<Permission>
|
||||||
{
|
{
|
||||||
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Registered, true), // required to support personalized pages
|
||||||
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
},
|
},
|
||||||
PageTemplateModules = new List<PageTemplateModule>
|
PageTemplateModules = new List<PageTemplateModule>
|
||||||
|
@ -276,6 +277,7 @@ namespace Oqtane.SiteTemplates
|
||||||
PermissionList = new List<Permission>
|
PermissionList = new List<Permission>
|
||||||
{
|
{
|
||||||
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Registered, true), // required to support personalized pages
|
||||||
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
},
|
},
|
||||||
Content = ""
|
Content = ""
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
using Oqtane.Models;
|
|
||||||
using Oqtane.Infrastructure;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Oqtane.Repository;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using Oqtane.Extensions;
|
|
||||||
using Oqtane.Shared;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Oqtane.Documentation;
|
using Oqtane.Documentation;
|
||||||
|
using Oqtane.Infrastructure;
|
||||||
|
using Oqtane.Models;
|
||||||
|
using Oqtane.Repository;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
|
||||||
namespace Oqtane.SiteTemplates
|
namespace Oqtane.SiteTemplates
|
||||||
{
|
{
|
||||||
|
@ -68,7 +67,7 @@ namespace Oqtane.SiteTemplates
|
||||||
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
},
|
},
|
||||||
Content = "<p>Copyright (c) 2018-2024 .NET Foundation</p>" +
|
Content = "<p>Copyright (c) 2018-2025 .NET Foundation</p>" +
|
||||||
"<p>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:</p>" +
|
"<p>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:</p>" +
|
||||||
"<p>The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.</p>" +
|
"<p>The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.</p>" +
|
||||||
"<p>THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</p>"
|
"<p>THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</p>"
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
using Oqtane.Models;
|
|
||||||
using Oqtane.Infrastructure;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Oqtane.Extensions;
|
|
||||||
using Oqtane.Repository;
|
|
||||||
using Oqtane.Shared;
|
|
||||||
using Oqtane.Documentation;
|
using Oqtane.Documentation;
|
||||||
|
using Oqtane.Infrastructure;
|
||||||
|
using Oqtane.Models;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
|
||||||
namespace Oqtane.SiteTemplates
|
namespace Oqtane.SiteTemplates
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Oqtane.Models;
|
using Oqtane.Models;
|
||||||
using Oqtane.Repository;
|
using Oqtane.Repository;
|
||||||
|
@ -71,8 +72,8 @@ namespace Oqtane.Infrastructure
|
||||||
case "5.2.1":
|
case "5.2.1":
|
||||||
Upgrade_5_2_1(tenant, scope);
|
Upgrade_5_2_1(tenant, scope);
|
||||||
break;
|
break;
|
||||||
case "6.0.1":
|
case "6.1.0":
|
||||||
Upgrade_6_0_1(tenant, scope);
|
Upgrade_6_1_0(tenant, scope);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -446,52 +447,14 @@ namespace Oqtane.Infrastructure
|
||||||
AddPagesToSites(scope, tenant, pageTemplates);
|
AddPagesToSites(scope, tenant, pageTemplates);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Upgrade_6_0_1(Tenant tenant, IServiceScope scope)
|
private void Upgrade_6_1_0(Tenant tenant, IServiceScope scope)
|
||||||
{
|
{
|
||||||
// assemblies which have been relocated to the bin/refs folder in .NET 9
|
// remove MySql.EntityFrameworkCore package (replaced by Pomelo.EntityFrameworkCore.MySql)
|
||||||
string[] assemblies = {
|
string[] assemblies = {
|
||||||
"Microsoft.AspNetCore.Authorization.dll",
|
"MySql.EntityFrameworkCore.dll"
|
||||||
"Microsoft.AspNetCore.Components.Authorization.dll",
|
|
||||||
"Microsoft.AspNetCore.Components.dll",
|
|
||||||
"Microsoft.AspNetCore.Components.Forms.dll",
|
|
||||||
"Microsoft.AspNetCore.Components.Web.dll",
|
|
||||||
"Microsoft.AspNetCore.Cryptography.Internal.dll",
|
|
||||||
"Microsoft.AspNetCore.Cryptography.KeyDerivation.dll",
|
|
||||||
"Microsoft.AspNetCore.Metadata.dll",
|
|
||||||
"Microsoft.Extensions.Caching.Memory.dll",
|
|
||||||
"Microsoft.Extensions.Configuration.Binder.dll",
|
|
||||||
"Microsoft.Extensions.Configuration.FileExtensions.dll",
|
|
||||||
"Microsoft.Extensions.Configuration.Json.dll",
|
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions.dll",
|
|
||||||
"Microsoft.Extensions.DependencyInjection.dll",
|
|
||||||
"Microsoft.Extensions.Diagnostics.Abstractions.dll",
|
|
||||||
"Microsoft.Extensions.Diagnostics.dll",
|
|
||||||
"Microsoft.Extensions.Http.dll",
|
|
||||||
"Microsoft.Extensions.Identity.Core.dll",
|
|
||||||
"Microsoft.Extensions.Identity.Stores.dll",
|
|
||||||
"Microsoft.Extensions.Localization.Abstractions.dll",
|
|
||||||
"Microsoft.Extensions.Localization.dll",
|
|
||||||
"Microsoft.Extensions.Logging.Abstractions.dll",
|
|
||||||
"Microsoft.Extensions.Logging.dll",
|
|
||||||
"Microsoft.Extensions.Options.dll",
|
|
||||||
"Microsoft.JSInterop.dll",
|
|
||||||
"System.Text.Json.dll"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var assembly in assemblies)
|
RemoveAssemblies(tenant, assemblies, "6.1.0");
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
|
|
||||||
var filepath = Path.Combine(binFolder, assembly);
|
|
||||||
if (System.IO.File.Exists(filepath)) System.IO.File.Delete(filepath);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// error deleting asesmbly
|
|
||||||
_filelogger.LogError(Utilities.LogMessage(this, $"Oqtane Error: 6.0.1 Upgrade Error Removing {assembly} - {ex}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddPagesToSites(IServiceScope scope, Tenant tenant, List<PageTemplate> pageTemplates)
|
private void AddPagesToSites(IServiceScope scope, Tenant tenant, List<PageTemplate> pageTemplates)
|
||||||
|
@ -504,5 +467,27 @@ namespace Oqtane.Infrastructure
|
||||||
sites.CreatePages(site, pageTemplates, null);
|
sites.CreatePages(site, pageTemplates, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RemoveAssemblies(Tenant tenant, string[] assemblies, string version)
|
||||||
|
{
|
||||||
|
// in a development environment assemblies cannot be removed as the debugger runs fron /bin folder and locks the files
|
||||||
|
if (tenant.Name == TenantNames.Master && !_environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
foreach (var assembly in assemblies)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
|
||||||
|
var filepath = Path.Combine(binFolder, assembly);
|
||||||
|
if (System.IO.File.Exists(filepath)) System.IO.File.Delete(filepath);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// error deleting asesmbly
|
||||||
|
_filelogger.LogError(Utilities.LogMessage(this, $"Oqtane Error: {version} Upgrade Error Removing {assembly} - {ex}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ using Oqtane.Enums;
|
||||||
using Oqtane.Infrastructure;
|
using Oqtane.Infrastructure;
|
||||||
using Oqtane.Models;
|
using Oqtane.Models;
|
||||||
using Oqtane.Repository;
|
using Oqtane.Repository;
|
||||||
|
using Oqtane.Security;
|
||||||
using Oqtane.Shared;
|
using Oqtane.Shared;
|
||||||
|
|
||||||
namespace Oqtane.Managers
|
namespace Oqtane.Managers
|
||||||
|
@ -363,28 +364,36 @@ namespace Oqtane.Managers
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
user = _users.GetUser(identityuser.UserName);
|
if (await _identityUserManager.IsEmailConfirmedAsync(identityuser))
|
||||||
if (user != null)
|
|
||||||
{
|
{
|
||||||
if (await _identityUserManager.IsEmailConfirmedAsync(identityuser))
|
user = GetUser(identityuser.UserName, alias.SiteId);
|
||||||
|
if (user != null)
|
||||||
{
|
{
|
||||||
user.IsAuthenticated = true;
|
// ensure user is registered for site
|
||||||
user.LastLoginOn = DateTime.UtcNow;
|
if (UserSecurity.ContainsRole(user.Roles, RoleNames.Registered))
|
||||||
user.LastIPAddress = LastIPAddress;
|
|
||||||
_users.UpdateUser(user);
|
|
||||||
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Login Successful For {Username} From IP Address {IPAddress}", user.Username, LastIPAddress);
|
|
||||||
|
|
||||||
_syncManager.AddSyncEvent(alias, EntityNames.User, user.UserId, "Login");
|
|
||||||
|
|
||||||
if (setCookie)
|
|
||||||
{
|
{
|
||||||
await _identitySignInManager.SignInAsync(identityuser, isPersistent);
|
user.IsAuthenticated = true;
|
||||||
|
user.LastLoginOn = DateTime.UtcNow;
|
||||||
|
user.LastIPAddress = LastIPAddress;
|
||||||
|
_users.UpdateUser(user);
|
||||||
|
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Login Successful For {Username} From IP Address {IPAddress}", user.Username, LastIPAddress);
|
||||||
|
|
||||||
|
_syncManager.AddSyncEvent(alias, EntityNames.User, user.UserId, "Login");
|
||||||
|
|
||||||
|
if (setCookie)
|
||||||
|
{
|
||||||
|
await _identitySignInManager.SignInAsync(identityuser, isPersistent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User {Username} Is Not An Active Member Of Site {SiteId}", user.Username, alias.SiteId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
{
|
else
|
||||||
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Email Address Not Verified {Username}", user.Username);
|
{
|
||||||
}
|
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Email Address Not Verified {Username}", user.Username);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -490,6 +499,9 @@ namespace Oqtane.Managers
|
||||||
var result = await _identityUserManager.ResetPasswordAsync(identityuser, token, user.Password);
|
var result = await _identityUserManager.ResetPasswordAsync(identityuser, token, user.Password);
|
||||||
if (result.Succeeded)
|
if (result.Succeeded)
|
||||||
{
|
{
|
||||||
|
user = _users.GetUser(user.Username);
|
||||||
|
_syncManager.AddSyncEvent(_tenantManager.GetAlias(), EntityNames.User, user.UserId, SyncEventActions.Update);
|
||||||
|
_syncManager.AddSyncEvent(_tenantManager.GetAlias(), EntityNames.User, user.UserId, SyncEventActions.Reload);
|
||||||
_logger.Log(LogLevel.Information, this, LogFunction.Security, "Password Reset For {Username}", user.Username);
|
_logger.Log(LogLevel.Information, this, LogFunction.Security, "Password Reset For {Username}", user.Username);
|
||||||
user.Password = "";
|
user.Password = "";
|
||||||
}
|
}
|
||||||
|
@ -512,7 +524,10 @@ namespace Oqtane.Managers
|
||||||
user = _users.GetUser(user.Username);
|
user = _users.GetUser(user.Username);
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
if (user.TwoFactorRequired && user.TwoFactorCode == token && DateTime.UtcNow < user.TwoFactorExpiry)
|
var alias = _tenantManager.GetAlias();
|
||||||
|
var twoFactorSetting = _settings.GetSetting(EntityNames.Site, alias.SiteId, "LoginOptions:TwoFactor")?.SettingValue ?? "false";
|
||||||
|
var twoFactorRequired = twoFactorSetting == "required" || user.TwoFactorRequired;
|
||||||
|
if (twoFactorRequired && user.TwoFactorCode == token && DateTime.UtcNow < user.TwoFactorExpiry)
|
||||||
{
|
{
|
||||||
user.IsAuthenticated = true;
|
user.IsAuthenticated = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,7 @@ namespace Oqtane.Migrations.EntityBuilders
|
||||||
return ActiveDatabase.AddAutoIncrementColumn(table, RewriteName(name));
|
return ActiveDatabase.AddAutoIncrementColumn(table, RewriteName(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// boolean
|
||||||
public void AddBooleanColumn(string name, bool nullable = false)
|
public void AddBooleanColumn(string name, bool nullable = false)
|
||||||
{
|
{
|
||||||
_migrationBuilder.AddColumn<bool>(RewriteName(name), RewriteName(EntityTableName), nullable: nullable, schema: Schema);
|
_migrationBuilder.AddColumn<bool>(RewriteName(name), RewriteName(EntityTableName), nullable: nullable, schema: Schema);
|
||||||
|
@ -87,6 +88,7 @@ namespace Oqtane.Migrations.EntityBuilders
|
||||||
return table.Column<bool>(name: RewriteName(name), nullable: nullable, defaultValue: defaultValue);
|
return table.Column<bool>(name: RewriteName(name), nullable: nullable, defaultValue: defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// datetime
|
||||||
public void AddDateTimeColumn(string name, bool nullable = false)
|
public void AddDateTimeColumn(string name, bool nullable = false)
|
||||||
{
|
{
|
||||||
_migrationBuilder.AddColumn<DateTime>(RewriteName(name), RewriteName(EntityTableName), nullable: nullable, schema: Schema);
|
_migrationBuilder.AddColumn<DateTime>(RewriteName(name), RewriteName(EntityTableName), nullable: nullable, schema: Schema);
|
||||||
|
@ -107,6 +109,7 @@ namespace Oqtane.Migrations.EntityBuilders
|
||||||
return table.Column<DateTime>(name: RewriteName(name), nullable: nullable, defaultValue: defaultValue);
|
return table.Column<DateTime>(name: RewriteName(name), nullable: nullable, defaultValue: defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// datetimeoffset
|
||||||
public void AddDateTimeOffsetColumn(string name, bool nullable = false)
|
public void AddDateTimeOffsetColumn(string name, bool nullable = false)
|
||||||
{
|
{
|
||||||
_migrationBuilder.AddColumn<DateTimeOffset>(RewriteName(name), RewriteName(EntityTableName), nullable: nullable, schema: Schema);
|
_migrationBuilder.AddColumn<DateTimeOffset>(RewriteName(name), RewriteName(EntityTableName), nullable: nullable, schema: Schema);
|
||||||
|
@ -127,6 +130,7 @@ namespace Oqtane.Migrations.EntityBuilders
|
||||||
return table.Column<DateTimeOffset>(name: RewriteName(name), nullable: nullable, defaultValue: defaultValue);
|
return table.Column<DateTimeOffset>(name: RewriteName(name), nullable: nullable, defaultValue: defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dateonly
|
||||||
public void AddDateOnlyColumn(string name, bool nullable = false)
|
public void AddDateOnlyColumn(string name, bool nullable = false)
|
||||||
{
|
{
|
||||||
_migrationBuilder.AddColumn<DateOnly>(RewriteName(name), RewriteName(EntityTableName), nullable: nullable, schema: Schema);
|
_migrationBuilder.AddColumn<DateOnly>(RewriteName(name), RewriteName(EntityTableName), nullable: nullable, schema: Schema);
|
||||||
|
@ -147,6 +151,7 @@ namespace Oqtane.Migrations.EntityBuilders
|
||||||
return table.Column<DateOnly>(name: RewriteName(name), nullable: nullable, defaultValue: defaultValue);
|
return table.Column<DateOnly>(name: RewriteName(name), nullable: nullable, defaultValue: defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// timeonly
|
||||||
public void AddTimeOnlyColumn(string name, bool nullable = false)
|
public void AddTimeOnlyColumn(string name, bool nullable = false)
|
||||||
{
|
{
|
||||||
_migrationBuilder.AddColumn<TimeOnly>(RewriteName(name), RewriteName(EntityTableName), nullable: nullable, schema: Schema);
|
_migrationBuilder.AddColumn<TimeOnly>(RewriteName(name), RewriteName(EntityTableName), nullable: nullable, schema: Schema);
|
||||||
|
@ -167,6 +172,7 @@ namespace Oqtane.Migrations.EntityBuilders
|
||||||
return table.Column<TimeOnly>(name: RewriteName(name), nullable: nullable, defaultValue: defaultValue);
|
return table.Column<TimeOnly>(name: RewriteName(name), nullable: nullable, defaultValue: defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// btye
|
||||||
public void AddByteColumn(string name, bool nullable = false)
|
public void AddByteColumn(string name, bool nullable = false)
|
||||||
{
|
{
|
||||||
_migrationBuilder.AddColumn<byte>(RewriteName(name), RewriteName(EntityTableName), nullable: nullable, schema: Schema);
|
_migrationBuilder.AddColumn<byte>(RewriteName(name), RewriteName(EntityTableName), nullable: nullable, schema: Schema);
|
||||||
|
@ -187,6 +193,7 @@ namespace Oqtane.Migrations.EntityBuilders
|
||||||
return table.Column<byte>(name: RewriteName(name), nullable: nullable, defaultValue: defaultValue);
|
return table.Column<byte>(name: RewriteName(name), nullable: nullable, defaultValue: defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// integer
|
||||||
public void AddIntegerColumn(string name, bool nullable = false)
|
public void AddIntegerColumn(string name, bool nullable = false)
|
||||||
{
|
{
|
||||||
_migrationBuilder.AddColumn<int>(RewriteName(name), RewriteName(EntityTableName), nullable: nullable, schema: Schema);
|
_migrationBuilder.AddColumn<int>(RewriteName(name), RewriteName(EntityTableName), nullable: nullable, schema: Schema);
|
||||||
|
@ -207,6 +214,8 @@ namespace Oqtane.Migrations.EntityBuilders
|
||||||
return table.Column<int>(name: RewriteName(name), nullable: nullable, defaultValue: defaultValue);
|
return table.Column<int>(name: RewriteName(name), nullable: nullable, defaultValue: defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// maxstring
|
||||||
public void AddMaxStringColumn(string name, bool nullable = false, bool unicode = true)
|
public void AddMaxStringColumn(string name, bool nullable = false, bool unicode = true)
|
||||||
{
|
{
|
||||||
_migrationBuilder.AddColumn<string>(RewriteName(name), RewriteName(EntityTableName), nullable: nullable, unicode: unicode, schema: Schema);
|
_migrationBuilder.AddColumn<string>(RewriteName(name), RewriteName(EntityTableName), nullable: nullable, unicode: unicode, schema: Schema);
|
||||||
|
@ -227,6 +236,7 @@ namespace Oqtane.Migrations.EntityBuilders
|
||||||
return table.Column<string>(name: RewriteName(name), nullable: nullable, unicode: unicode, defaultValue: defaultValue);
|
return table.Column<string>(name: RewriteName(name), nullable: nullable, unicode: unicode, defaultValue: defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// string
|
||||||
public void AddStringColumn(string name, int length, bool nullable = false, bool unicode = true)
|
public void AddStringColumn(string name, int length, bool nullable = false, bool unicode = true)
|
||||||
{
|
{
|
||||||
_migrationBuilder.AddColumn<string>(RewriteName(name), RewriteName(EntityTableName), maxLength: length, nullable: nullable, unicode: unicode, schema: Schema);
|
_migrationBuilder.AddColumn<string>(RewriteName(name), RewriteName(EntityTableName), maxLength: length, nullable: nullable, unicode: unicode, schema: Schema);
|
||||||
|
@ -247,6 +257,7 @@ namespace Oqtane.Migrations.EntityBuilders
|
||||||
return table.Column<string>(name: RewriteName(name), maxLength: length, nullable: nullable, unicode: unicode, defaultValue: defaultValue);
|
return table.Column<string>(name: RewriteName(name), maxLength: length, nullable: nullable, unicode: unicode, defaultValue: defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// decimal
|
||||||
public void AddDecimalColumn(string name, int precision, int scale, bool nullable = false)
|
public void AddDecimalColumn(string name, int precision, int scale, bool nullable = false)
|
||||||
{
|
{
|
||||||
_migrationBuilder.AddColumn<decimal>(RewriteName(name), RewriteName(EntityTableName), nullable: nullable, precision: precision, scale: scale, schema: Schema);
|
_migrationBuilder.AddColumn<decimal>(RewriteName(name), RewriteName(EntityTableName), nullable: nullable, precision: precision, scale: scale, schema: Schema);
|
||||||
|
@ -267,6 +278,28 @@ namespace Oqtane.Migrations.EntityBuilders
|
||||||
return table.Column<decimal>(name: RewriteName(name), nullable: nullable, precision: precision, scale: scale, defaultValue: defaultValue);
|
return table.Column<decimal>(name: RewriteName(name), nullable: nullable, precision: precision, scale: scale, defaultValue: defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// guid
|
||||||
|
public void AddGuidColumn(string name, bool nullable = false)
|
||||||
|
{
|
||||||
|
_migrationBuilder.AddColumn<Guid>(RewriteName(name), RewriteName(EntityTableName), nullable: nullable, schema: Schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddGuidColumn(string name, bool nullable, Guid defaultValue)
|
||||||
|
{
|
||||||
|
_migrationBuilder.AddColumn<Guid>(RewriteName(name), RewriteName(EntityTableName), nullable: nullable, defaultValue: defaultValue, schema: Schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected OperationBuilder<AddColumnOperation> AddGuidColumn(ColumnsBuilder table, string name, bool nullable = false)
|
||||||
|
{
|
||||||
|
return table.Column<Guid>(name: RewriteName(name), nullable: nullable);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected OperationBuilder<AddColumnOperation> AddGuidColumn(ColumnsBuilder table, string name, bool nullable, Guid defaultValue)
|
||||||
|
{
|
||||||
|
return table.Column<Guid>(name: RewriteName(name), nullable: nullable, defaultValue: defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// alter string
|
||||||
public void AlterStringColumn(string name, int length, bool nullable = false, bool unicode = true, string index = "")
|
public void AlterStringColumn(string name, int length, bool nullable = false, bool unicode = true, string index = "")
|
||||||
{
|
{
|
||||||
if (index != "")
|
if (index != "")
|
||||||
|
@ -283,6 +316,7 @@ namespace Oqtane.Migrations.EntityBuilders
|
||||||
ActiveDatabase.AlterStringColumn(_migrationBuilder, RewriteName(name), RewriteName(EntityTableName), length, nullable, unicode, index);
|
ActiveDatabase.AlterStringColumn(_migrationBuilder, RewriteName(name), RewriteName(EntityTableName), length, nullable, unicode, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// drop column
|
||||||
public void DropColumn(string name)
|
public void DropColumn(string name)
|
||||||
{
|
{
|
||||||
ActiveDatabase.DropColumn(_migrationBuilder, RewriteName(name), RewriteName(EntityTableName));
|
ActiveDatabase.DropColumn(_migrationBuilder, RewriteName(name), RewriteName(EntityTableName));
|
||||||
|
|
28
Oqtane.Server/Migrations/Master/06010001_AddThemeVersion.cs
Normal file
28
Oqtane.Server/Migrations/Master/06010001_AddThemeVersion.cs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Oqtane.Databases.Interfaces;
|
||||||
|
using Oqtane.Migrations.EntityBuilders;
|
||||||
|
using Oqtane.Repository;
|
||||||
|
|
||||||
|
namespace Oqtane.Migrations.Master
|
||||||
|
{
|
||||||
|
[DbContext(typeof(MasterDBContext))]
|
||||||
|
[Migration("Master.06.01.00.01")]
|
||||||
|
public class AddThemeVersion : MultiDatabaseMigration
|
||||||
|
{
|
||||||
|
public AddThemeVersion(IDatabase database) : base(database)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
var themeEntityBuilder = new ThemeEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||||
|
themeEntityBuilder.AddStringColumn("Version", 50, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
// not implemented
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Oqtane.Databases.Interfaces;
|
||||||
|
using Oqtane.Migrations.EntityBuilders;
|
||||||
|
using Oqtane.Repository;
|
||||||
|
|
||||||
|
namespace Oqtane.Migrations.Tenant
|
||||||
|
{
|
||||||
|
[DbContext(typeof(TenantDBContext))]
|
||||||
|
[Migration("Tenant.06.01.00.01")]
|
||||||
|
public class AddFolderCacheControl : MultiDatabaseMigration
|
||||||
|
{
|
||||||
|
public AddFolderCacheControl(IDatabase database) : base(database)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
var folderEntityBuilder = new FolderEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||||
|
folderEntityBuilder.AddStringColumn("CacheControl", 50, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
// not implemented
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,7 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<Configurations>Debug;Release</Configurations>
|
<Configurations>Debug;Release</Configurations>
|
||||||
<Version>6.0.1</Version>
|
<Version>6.1.0</Version>
|
||||||
<Product>Oqtane</Product>
|
<Product>Oqtane</Product>
|
||||||
<Authors>Shaun Walker</Authors>
|
<Authors>Shaun Walker</Authors>
|
||||||
<Company>.NET Foundation</Company>
|
<Company>.NET Foundation</Company>
|
||||||
|
@ -11,7 +11,7 @@
|
||||||
<Copyright>.NET Foundation</Copyright>
|
<Copyright>.NET Foundation</Copyright>
|
||||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</PackageReleaseNotes>
|
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.0</PackageReleaseNotes>
|
||||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||||
<RepositoryType>Git</RepositoryType>
|
<RepositoryType>Git</RepositoryType>
|
||||||
<RootNamespace>Oqtane</RootNamespace>
|
<RootNamespace>Oqtane</RootNamespace>
|
||||||
|
@ -34,21 +34,21 @@
|
||||||
<EmbeddedResource Include="Scripts\MigrateTenant.sql" />
|
<EmbeddedResource Include="Scripts\MigrateTenant.sql" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.1" />
|
||||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.0-preview2.24304.8" />
|
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.1">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="9.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="9.0.1" />
|
||||||
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="9.0.1" />
|
||||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.10" />
|
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.11-pre20241216174303" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" />
|
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.6" />
|
||||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.71" />
|
<PackageReference Include="HtmlAgilityPack" Version="1.11.72" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.0.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.2.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Oqtane.Client\Oqtane.Client.csproj" />
|
<ProjectReference Include="..\Oqtane.Client\Oqtane.Client.csproj" />
|
||||||
|
@ -57,7 +57,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ModuleTemplateFiles Include="$(ProjectDir)wwwroot\Modules\Templates\**\*.*" />
|
<ModuleTemplateFiles Include="$(ProjectDir)wwwroot\Modules\Templates\**\*.*" />
|
||||||
<ThemeTemplateFiles Include="$(ProjectDir)wwwroot\Themes\Templates\**\*.*" />
|
<ThemeTemplateFiles Include="$(ProjectDir)wwwroot\Themes\Templates\**\*.*" />
|
||||||
<MySQLFiles Include="$(OutputPath)Oqtane.Database.MySQL.dll;$(OutputPath)Oqtane.Database.MySQL.pdb;$(OutputPath)MySql.EntityFrameworkCore.dll;$(OutputPath)MySql.Data.dll" />
|
<MySQLFiles Include="$(OutputPath)Oqtane.Database.MySQL.dll;$(OutputPath)Oqtane.Database.MySQL.pdb;$(OutputPath)Pomelo.EntityFrameworkCore.MySql.dll;$(OutputPath)MySql.Data.dll" />
|
||||||
<PostgreSQLFiles Include="$(OutputPath)Oqtane.Database.PostgreSQL.dll;$(OutputPath)Oqtane.Database.PostgreSQL.pdb;$(OutputPath)EFCore.NamingConventions.dll;$(OutputPath)Npgsql.EntityFrameworkCore.PostgreSQL.dll;$(OutputPath)Npgsql.dll" />
|
<PostgreSQLFiles Include="$(OutputPath)Oqtane.Database.PostgreSQL.dll;$(OutputPath)Oqtane.Database.PostgreSQL.pdb;$(OutputPath)EFCore.NamingConventions.dll;$(OutputPath)Npgsql.EntityFrameworkCore.PostgreSQL.dll;$(OutputPath)Npgsql.dll" />
|
||||||
<SqliteFiles Include="$(OutputPath)Oqtane.Database.Sqlite.dll;$(OutputPath)Oqtane.Database.Sqlite.pdb;$(OutputPath)Microsoft.EntityFrameworkCore.Sqlite.dll" />
|
<SqliteFiles Include="$(OutputPath)Oqtane.Database.Sqlite.dll;$(OutputPath)Oqtane.Database.Sqlite.pdb;$(OutputPath)Microsoft.EntityFrameworkCore.Sqlite.dll" />
|
||||||
<SqlServerFiles Include="$(OutputPath)Oqtane.Database.SqlServer.dll;$(OutputPath)Oqtane.Database.SqlServer.pdb;$(OutputPath)Microsoft.EntityFrameworkCore.SqlServer.dll" />
|
<SqlServerFiles Include="$(OutputPath)Oqtane.Database.SqlServer.dll;$(OutputPath)Oqtane.Database.SqlServer.pdb;$(OutputPath)Microsoft.EntityFrameworkCore.SqlServer.dll" />
|
||||||
|
|
|
@ -122,17 +122,23 @@ namespace Oqtane.Pages
|
||||||
|
|
||||||
if (file.Folder.SiteId != _alias.SiteId || !_userPermissions.IsAuthorized(User, PermissionNames.View, file.Folder.PermissionList))
|
if (file.Folder.SiteId != _alias.SiteId || !_userPermissions.IsAuthorized(User, PermissionNames.View, file.Folder.PermissionList))
|
||||||
{
|
{
|
||||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized File Access Attempt For Site {SiteId} And Path {Path}", _alias.SiteId, path);
|
if (!User.Identity.IsAuthenticated && download)
|
||||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
{
|
||||||
return BrokenFile();
|
return Redirect(Utilities.NavigateUrl(_alias.Path, "login", "?returnurl=" + WebUtility.UrlEncode(Request.Path)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized File Access Attempt For Site {SiteId} And Path {Path}", _alias.SiteId, path);
|
||||||
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
|
return BrokenFile();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string etag;
|
string etag;
|
||||||
string downloadName = file.Name;
|
string downloadName = file.Name;
|
||||||
string filepath = _files.GetFilePath(file);
|
string filepath = _files.GetFilePath(file);
|
||||||
|
|
||||||
var etagValue = file.ModifiedOn.Ticks ^ file.Size;
|
// evaluate any querystring parameters
|
||||||
|
|
||||||
bool isRequestingImageManipulation = false;
|
bool isRequestingImageManipulation = false;
|
||||||
|
|
||||||
int width = 0;
|
int width = 0;
|
||||||
|
@ -140,39 +146,34 @@ namespace Oqtane.Pages
|
||||||
if (Request.Query.TryGetValue("width", out var widthStr) && int.TryParse(widthStr, out width) && width > 0)
|
if (Request.Query.TryGetValue("width", out var widthStr) && int.TryParse(widthStr, out width) && width > 0)
|
||||||
{
|
{
|
||||||
isRequestingImageManipulation = true;
|
isRequestingImageManipulation = true;
|
||||||
etagValue ^= (width * 31);
|
|
||||||
}
|
}
|
||||||
if (Request.Query.TryGetValue("height", out var heightStr) && int.TryParse(heightStr, out height) && height > 0)
|
if (Request.Query.TryGetValue("height", out var heightStr) && int.TryParse(heightStr, out height) && height > 0)
|
||||||
{
|
{
|
||||||
isRequestingImageManipulation = true;
|
isRequestingImageManipulation = true;
|
||||||
etagValue ^= (height * 17);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Request.Query.TryGetValue("mode", out var mode);
|
Request.Query.TryGetValue("mode", out var mode);
|
||||||
Request.Query.TryGetValue("position", out var position);
|
Request.Query.TryGetValue("position", out var position);
|
||||||
Request.Query.TryGetValue("background", out var background);
|
Request.Query.TryGetValue("background", out var background);
|
||||||
|
|
||||||
if (width > 0 || height > 0)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(mode)) etagValue ^= mode.ToString().GetHashCode();
|
|
||||||
if (!string.IsNullOrWhiteSpace(position)) etagValue ^= position.ToString().GetHashCode();
|
|
||||||
if (!string.IsNullOrWhiteSpace(background)) etagValue ^= background.ToString().GetHashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
int rotate;
|
int rotate;
|
||||||
if (Request.Query.TryGetValue("rotate", out var rotateStr) && int.TryParse(rotateStr, out rotate) && 360 > rotate && rotate > 0)
|
if (Request.Query.TryGetValue("rotate", out var rotateStr) && int.TryParse(rotateStr, out rotate) && 360 > rotate && rotate > 0)
|
||||||
{
|
{
|
||||||
isRequestingImageManipulation = true;
|
isRequestingImageManipulation = true;
|
||||||
etagValue ^= (rotate * 13);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Request.Query.TryGetValue("format", out var format) && _imageService.GetAvailableFormats().Contains(format.ToString()))
|
if (Request.Query.TryGetValue("format", out var format) && _imageService.GetAvailableFormats().Contains(format.ToString()))
|
||||||
{
|
{
|
||||||
isRequestingImageManipulation = true;
|
isRequestingImageManipulation = true;
|
||||||
etagValue ^= format.ToString().GetHashCode();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
etag = Convert.ToString(etagValue, 16);
|
if (isRequestingImageManipulation)
|
||||||
|
{
|
||||||
|
etag = Utilities.GenerateSimpleHash(Request.QueryString.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
etag = Convert.ToString(file.ModifiedOn.Ticks ^ file.Size, 16);
|
||||||
|
}
|
||||||
|
|
||||||
var header = "";
|
var header = "";
|
||||||
if (HttpContext.Request.Headers.TryGetValue(HeaderNames.IfNoneMatch, out var ifNoneMatch))
|
if (HttpContext.Request.Headers.TryGetValue(HeaderNames.IfNoneMatch, out var ifNoneMatch))
|
||||||
|
@ -253,12 +254,16 @@ namespace Oqtane.Pages
|
||||||
if (download)
|
if (download)
|
||||||
{
|
{
|
||||||
_syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, "Download");
|
_syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, "Download");
|
||||||
return PhysicalFile(filepath, file.GetMimeType(), downloadName);
|
return PhysicalFile(filepath, MimeUtilities.GetMimeType(downloadName), downloadName);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if (!string.IsNullOrEmpty(file.Folder.CacheControl))
|
||||||
|
{
|
||||||
|
HttpContext.Response.Headers.Append(HeaderNames.CacheControl, value: file.Folder.CacheControl);
|
||||||
|
}
|
||||||
HttpContext.Response.Headers.Append(HeaderNames.ETag, etag);
|
HttpContext.Response.Headers.Append(HeaderNames.ETag, etag);
|
||||||
return PhysicalFile(filepath, file.GetMimeType());
|
return PhysicalFile(filepath, MimeUtilities.GetMimeType(downloadName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
3
Oqtane.Server/Pages/Impersonate.cshtml
Normal file
3
Oqtane.Server/Pages/Impersonate.cshtml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
@page "/pages/impersonate"
|
||||||
|
@namespace Oqtane.Pages
|
||||||
|
@model Oqtane.Pages.ImpersonateModel
|
79
Oqtane.Server/Pages/Impersonate.cshtml.cs
Normal file
79
Oqtane.Server/Pages/Impersonate.cshtml.cs
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
using Oqtane.Enums;
|
||||||
|
using Oqtane.Extensions;
|
||||||
|
using Oqtane.Infrastructure;
|
||||||
|
using Oqtane.Managers;
|
||||||
|
using Oqtane.Security;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
|
||||||
|
namespace Oqtane.Pages
|
||||||
|
{
|
||||||
|
public class ImpersonateModel : PageModel
|
||||||
|
{
|
||||||
|
private readonly UserManager<IdentityUser> _identityUserManager;
|
||||||
|
private readonly SignInManager<IdentityUser> _identitySignInManager;
|
||||||
|
private readonly IUserManager _userManager;
|
||||||
|
private readonly ILogManager _logger;
|
||||||
|
|
||||||
|
public ImpersonateModel(UserManager<IdentityUser> identityUserManager, SignInManager<IdentityUser> identitySignInManager, IUserManager userManager, ILogManager logger)
|
||||||
|
{
|
||||||
|
_identityUserManager = identityUserManager;
|
||||||
|
_identitySignInManager = identitySignInManager;
|
||||||
|
_userManager = userManager;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> OnPostAsync(string username, string returnurl)
|
||||||
|
{
|
||||||
|
if (User.IsInRole(RoleNames.Admin) && !string.IsNullOrEmpty(username))
|
||||||
|
{
|
||||||
|
bool validuser = false;
|
||||||
|
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(username);
|
||||||
|
if (identityuser != null)
|
||||||
|
{
|
||||||
|
var alias = HttpContext.GetAlias();
|
||||||
|
var user = _userManager.GetUser(identityuser.UserName, alias.SiteId);
|
||||||
|
if (user != null && !user.IsDeleted && UserSecurity.ContainsRole(user.Roles, RoleNames.Registered) && !UserSecurity.ContainsRole(user.Roles, RoleNames.Host))
|
||||||
|
{
|
||||||
|
validuser = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validuser)
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User {Username} Successfully Impersonated By Administrator {Administrator}", username, User.Identity.Name);
|
||||||
|
|
||||||
|
// note that .NET Identity uses a hardcoded ApplicationScheme of "Identity.Application" in SignInAsync
|
||||||
|
await _identitySignInManager.SignInAsync(identityuser, false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Impersonation By Administrator {Administrator} Failed For User {Username} ", User.Identity.Name, username);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Attempt To Impersonate User {Username} By User {User}", username, User.Identity.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (returnurl == null)
|
||||||
|
{
|
||||||
|
returnurl = "";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
returnurl = WebUtility.UrlDecode(returnurl);
|
||||||
|
}
|
||||||
|
if (!returnurl.StartsWith("/"))
|
||||||
|
{
|
||||||
|
returnurl = "/" + returnurl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return LocalRedirect(Url.Content("~" + returnurl));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,8 +4,11 @@ using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
using Oqtane.Enums;
|
||||||
using Oqtane.Extensions;
|
using Oqtane.Extensions;
|
||||||
|
using Oqtane.Infrastructure;
|
||||||
using Oqtane.Managers;
|
using Oqtane.Managers;
|
||||||
|
using Oqtane.Security;
|
||||||
using Oqtane.Shared;
|
using Oqtane.Shared;
|
||||||
|
|
||||||
namespace Oqtane.Pages
|
namespace Oqtane.Pages
|
||||||
|
@ -16,12 +19,14 @@ namespace Oqtane.Pages
|
||||||
private readonly UserManager<IdentityUser> _identityUserManager;
|
private readonly UserManager<IdentityUser> _identityUserManager;
|
||||||
private readonly SignInManager<IdentityUser> _identitySignInManager;
|
private readonly SignInManager<IdentityUser> _identitySignInManager;
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
|
private readonly ILogManager _logger;
|
||||||
|
|
||||||
public LoginModel(UserManager<IdentityUser> identityUserManager, SignInManager<IdentityUser> identitySignInManager, IUserManager userManager)
|
public LoginModel(UserManager<IdentityUser> identityUserManager, SignInManager<IdentityUser> identitySignInManager, IUserManager userManager, ILogManager logger)
|
||||||
{
|
{
|
||||||
_identityUserManager = identityUserManager;
|
_identityUserManager = identityUserManager;
|
||||||
_identitySignInManager = identitySignInManager;
|
_identitySignInManager = identitySignInManager;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> OnPostAsync(string username, string password, bool remember, string returnurl)
|
public async Task<IActionResult> OnPostAsync(string username, string password, bool remember, string returnurl)
|
||||||
|
@ -37,7 +42,7 @@ namespace Oqtane.Pages
|
||||||
{
|
{
|
||||||
var alias = HttpContext.GetAlias();
|
var alias = HttpContext.GetAlias();
|
||||||
var user = _userManager.GetUser(identityuser.UserName, alias.SiteId);
|
var user = _userManager.GetUser(identityuser.UserName, alias.SiteId);
|
||||||
if (user != null && !user.IsDeleted)
|
if (user != null && !user.IsDeleted && UserSecurity.ContainsRole(user.Roles, RoleNames.Registered))
|
||||||
{
|
{
|
||||||
validuser = true;
|
validuser = true;
|
||||||
}
|
}
|
||||||
|
@ -48,7 +53,16 @@ namespace Oqtane.Pages
|
||||||
{
|
{
|
||||||
// note that .NET Identity uses a hardcoded ApplicationScheme of "Identity.Application" in SignInAsync
|
// note that .NET Identity uses a hardcoded ApplicationScheme of "Identity.Application" in SignInAsync
|
||||||
await _identitySignInManager.SignInAsync(identityuser, remember);
|
await _identitySignInManager.SignInAsync(identityuser, remember);
|
||||||
|
_logger.Log(LogLevel.Information, this, LogFunction.Security, "Login Successful For User {Username}", username);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Login Failed For User {Username}", username);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Attempt To Login User {Username}", username);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (returnurl == null)
|
if (returnurl == null)
|
||||||
|
|
|
@ -25,7 +25,10 @@ namespace Oqtane.Server
|
||||||
filelogger.LogError($"[Oqtane.Server.Program.Main] {install.Message}");
|
filelogger.LogError($"[Oqtane.Server.Program.Main] {install.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
host.Run();
|
else
|
||||||
|
{
|
||||||
|
host.Run();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IWebHost BuildWebHost(string[] args) =>
|
public static IWebHost BuildWebHost(string[] args) =>
|
||||||
|
|
|
@ -46,10 +46,10 @@ namespace Oqtane.Repository
|
||||||
files = db.File.AsNoTracking().Where(item => item.FolderId == folderId).Include(item => item.Folder).ToList();
|
files = db.File.AsNoTracking().Where(item => item.FolderId == folderId).Include(item => item.Folder).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var alias = _tenants.GetAlias();
|
||||||
foreach (var file in files)
|
foreach (var file in files)
|
||||||
{
|
{
|
||||||
file.Folder.PermissionList = permissions.ToList();
|
file.Folder.PermissionList = permissions.ToList();
|
||||||
var alias = _tenants.GetAlias();
|
|
||||||
file.Url = GetFileUrl(file, alias);
|
file.Url = GetFileUrl(file, alias);
|
||||||
}
|
}
|
||||||
return files;
|
return files;
|
||||||
|
|
|
@ -13,5 +13,6 @@ namespace Oqtane.Repository
|
||||||
UrlMapping GetUrlMapping(int urlMappingId, bool tracking);
|
UrlMapping GetUrlMapping(int urlMappingId, bool tracking);
|
||||||
UrlMapping GetUrlMapping(int siteId, string url);
|
UrlMapping GetUrlMapping(int siteId, string url);
|
||||||
void DeleteUrlMapping(int urlMappingId);
|
void DeleteUrlMapping(int urlMappingId);
|
||||||
|
int DeleteUrlMappings(int siteId, int age);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,6 +101,7 @@ namespace Oqtane.Repository
|
||||||
ModuleDefinition.Resources = moduleDefinition.Resources;
|
ModuleDefinition.Resources = moduleDefinition.Resources;
|
||||||
ModuleDefinition.IsEnabled = moduleDefinition.IsEnabled;
|
ModuleDefinition.IsEnabled = moduleDefinition.IsEnabled;
|
||||||
ModuleDefinition.PackageName = moduleDefinition.PackageName;
|
ModuleDefinition.PackageName = moduleDefinition.PackageName;
|
||||||
|
ModuleDefinition.Fingerprint = Utilities.GenerateSimpleHash(moduleDefinition.ModifiedOn.ToString("yyyyMMddHHmm"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ModuleDefinition;
|
return ModuleDefinition;
|
||||||
|
|
|
@ -4,6 +4,7 @@ using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Reflection.Metadata;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Oqtane.Infrastructure;
|
using Oqtane.Infrastructure;
|
||||||
|
@ -87,6 +88,7 @@ namespace Oqtane.Repository
|
||||||
Theme.ThemeSettingsType = theme.ThemeSettingsType;
|
Theme.ThemeSettingsType = theme.ThemeSettingsType;
|
||||||
Theme.ContainerSettingsType = theme.ContainerSettingsType;
|
Theme.ContainerSettingsType = theme.ContainerSettingsType;
|
||||||
Theme.PackageName = theme.PackageName;
|
Theme.PackageName = theme.PackageName;
|
||||||
|
Theme.Fingerprint = Utilities.GenerateSimpleHash(theme.ModifiedOn.ToString("yyyyMMddHHmm"));
|
||||||
Themes.Add(Theme);
|
Themes.Add(Theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,6 +128,13 @@ namespace Oqtane.Repository
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if (theme.Version != Theme.Version)
|
||||||
|
{
|
||||||
|
// update theme version
|
||||||
|
theme.Version = Theme.Version;
|
||||||
|
_db.SaveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
// override user customizable property values
|
// override user customizable property values
|
||||||
Theme.Name = (!string.IsNullOrEmpty(theme.Name)) ? theme.Name : Theme.Name;
|
Theme.Name = (!string.IsNullOrEmpty(theme.Name)) ? theme.Name : Theme.Name;
|
||||||
|
|
||||||
|
|
|
@ -22,11 +22,11 @@ namespace Oqtane.Repository
|
||||||
using var db = _dbContextFactory.CreateDbContext();
|
using var db = _dbContextFactory.CreateDbContext();
|
||||||
if (isMapped)
|
if (isMapped)
|
||||||
{
|
{
|
||||||
return db.UrlMapping.Where(item => item.SiteId == siteId && !string.IsNullOrEmpty(item.MappedUrl)).Take(200).ToList();
|
return db.UrlMapping.Where(item => item.SiteId == siteId && !string.IsNullOrEmpty(item.MappedUrl)).ToList();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return db.UrlMapping.Where(item => item.SiteId == siteId && string.IsNullOrEmpty(item.MappedUrl)).Take(200).ToList();
|
return db.UrlMapping.Where(item => item.SiteId == siteId && string.IsNullOrEmpty(item.MappedUrl)).ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,5 +101,24 @@ namespace Oqtane.Repository
|
||||||
db.UrlMapping.Remove(urlMapping);
|
db.UrlMapping.Remove(urlMapping);
|
||||||
db.SaveChanges();
|
db.SaveChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int DeleteUrlMappings(int siteId, int age)
|
||||||
|
{
|
||||||
|
using var db = _dbContextFactory.CreateDbContext();
|
||||||
|
// delete in batches of 100 records
|
||||||
|
var count = 0;
|
||||||
|
var purgedate = DateTime.UtcNow.AddDays(-age);
|
||||||
|
var urlMappings = db.UrlMapping.Where(item => item.SiteId == siteId && string.IsNullOrEmpty(item.MappedUrl) && item.RequestedOn < purgedate)
|
||||||
|
.OrderBy(item => item.RequestedOn).Take(100).ToList();
|
||||||
|
while (urlMappings.Count > 0)
|
||||||
|
{
|
||||||
|
count += urlMappings.Count;
|
||||||
|
db.UrlMapping.RemoveRange(urlMappings);
|
||||||
|
db.SaveChanges();
|
||||||
|
urlMappings = db.UrlMapping.Where(item => item.SiteId == siteId && string.IsNullOrEmpty(item.MappedUrl) && item.RequestedOn < purgedate)
|
||||||
|
.OrderBy(item => item.RequestedOn).Take(100).ToList();
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,12 +29,13 @@ namespace Oqtane.Services
|
||||||
private readonly ISettingRepository _settings;
|
private readonly ISettingRepository _settings;
|
||||||
private readonly ITenantManager _tenantManager;
|
private readonly ITenantManager _tenantManager;
|
||||||
private readonly ISyncManager _syncManager;
|
private readonly ISyncManager _syncManager;
|
||||||
|
private readonly IConfigManager _configManager;
|
||||||
private readonly ILogManager _logger;
|
private readonly ILogManager _logger;
|
||||||
private readonly IMemoryCache _cache;
|
private readonly IMemoryCache _cache;
|
||||||
private readonly IHttpContextAccessor _accessor;
|
private readonly IHttpContextAccessor _accessor;
|
||||||
private readonly string _private = "[PRIVATE]";
|
private readonly string _private = "[PRIVATE]";
|
||||||
|
|
||||||
public ServerSiteService(ISiteRepository sites, IPageRepository pages, IThemeRepository themes, IPageModuleRepository pageModules, IModuleDefinitionRepository moduleDefinitions, ILanguageRepository languages, IUserPermissions userPermissions, ISettingRepository settings, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger, IMemoryCache cache, IHttpContextAccessor accessor)
|
public ServerSiteService(ISiteRepository sites, IPageRepository pages, IThemeRepository themes, IPageModuleRepository pageModules, IModuleDefinitionRepository moduleDefinitions, ILanguageRepository languages, IUserPermissions userPermissions, ISettingRepository settings, ITenantManager tenantManager, ISyncManager syncManager, IConfigManager configManager, ILogManager logger, IMemoryCache cache, IHttpContextAccessor accessor)
|
||||||
{
|
{
|
||||||
_sites = sites;
|
_sites = sites;
|
||||||
_pages = pages;
|
_pages = pages;
|
||||||
|
@ -46,6 +47,7 @@ namespace Oqtane.Services
|
||||||
_settings = settings;
|
_settings = settings;
|
||||||
_tenantManager = tenantManager;
|
_tenantManager = tenantManager;
|
||||||
_syncManager = syncManager;
|
_syncManager = syncManager;
|
||||||
|
_configManager = configManager;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_cache = cache;
|
_cache = cache;
|
||||||
_accessor = accessor;
|
_accessor = accessor;
|
||||||
|
@ -143,6 +145,9 @@ namespace Oqtane.Services
|
||||||
|
|
||||||
// themes
|
// themes
|
||||||
site.Themes = _themes.FilterThemes(_themes.GetThemes().ToList());
|
site.Themes = _themes.FilterThemes(_themes.GetThemes().ToList());
|
||||||
|
|
||||||
|
// installation date used for fingerprinting static assets
|
||||||
|
site.Fingerprint = Utilities.GenerateSimpleHash(_configManager.GetSetting("InstallationDate", DateTime.UtcNow.ToString("yyyyMMddHHmm")));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -155,46 +160,6 @@ namespace Oqtane.Services
|
||||||
return site;
|
return site;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<Page> GetPagesHierarchy(List<Page> pages)
|
|
||||||
{
|
|
||||||
List<Page> hierarchy = new List<Page>();
|
|
||||||
Action<List<Page>, Page> getPath = null;
|
|
||||||
getPath = (pageList, page) =>
|
|
||||||
{
|
|
||||||
IEnumerable<Page> children;
|
|
||||||
int level;
|
|
||||||
if (page == null)
|
|
||||||
{
|
|
||||||
level = -1;
|
|
||||||
children = pages.Where(item => item.ParentId == null);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
level = page.Level;
|
|
||||||
children = pages.Where(item => item.ParentId == page.PageId);
|
|
||||||
}
|
|
||||||
foreach (Page child in children)
|
|
||||||
{
|
|
||||||
child.Level = level + 1;
|
|
||||||
child.HasChildren = pages.Any(item => item.ParentId == child.PageId && !item.IsDeleted && item.IsNavigation);
|
|
||||||
hierarchy.Add(child);
|
|
||||||
getPath(pageList, child);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
pages = pages.OrderBy(item => item.Order).ToList();
|
|
||||||
getPath(pages, null);
|
|
||||||
|
|
||||||
// add any non-hierarchical items to the end of the list
|
|
||||||
foreach (Page page in pages)
|
|
||||||
{
|
|
||||||
if (hierarchy.Find(item => item.PageId == page.PageId) == null)
|
|
||||||
{
|
|
||||||
hierarchy.Add(page);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hierarchy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<Site> AddSiteAsync(Site site)
|
public Task<Site> AddSiteAsync(Site site)
|
||||||
{
|
{
|
||||||
if (_accessor.HttpContext.User.IsInRole(RoleNames.Host))
|
if (_accessor.HttpContext.User.IsInRole(RoleNames.Host))
|
||||||
|
|
|
@ -23,6 +23,7 @@ using OqtaneSSR.Extensions;
|
||||||
using Microsoft.AspNetCore.Components.Authorization;
|
using Microsoft.AspNetCore.Components.Authorization;
|
||||||
using Oqtane.Providers;
|
using Oqtane.Providers;
|
||||||
using Microsoft.AspNetCore.Cors.Infrastructure;
|
using Microsoft.AspNetCore.Cors.Infrastructure;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
namespace Oqtane
|
namespace Oqtane
|
||||||
{
|
{
|
||||||
|
@ -98,7 +99,7 @@ namespace Oqtane
|
||||||
{
|
{
|
||||||
options.HeaderName = Constants.AntiForgeryTokenHeaderName;
|
options.HeaderName = Constants.AntiForgeryTokenHeaderName;
|
||||||
options.Cookie.Name = Constants.AntiForgeryTokenCookieName;
|
options.Cookie.Name = Constants.AntiForgeryTokenCookieName;
|
||||||
options.Cookie.SameSite = SameSiteMode.Strict;
|
options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict;
|
||||||
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
|
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
|
||||||
options.Cookie.HttpOnly = true;
|
options.Cookie.HttpOnly = true;
|
||||||
});
|
});
|
||||||
|
@ -202,9 +203,15 @@ namespace Oqtane
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
app.UseStaticFiles(new StaticFileOptions
|
app.UseStaticFiles(new StaticFileOptions
|
||||||
{
|
{
|
||||||
ServeUnknownFileTypes = true,
|
|
||||||
OnPrepareResponse = (ctx) =>
|
OnPrepareResponse = (ctx) =>
|
||||||
{
|
{
|
||||||
|
// static asset caching
|
||||||
|
var cachecontrol = Configuration.GetSection("CacheControl");
|
||||||
|
if (!string.IsNullOrEmpty(cachecontrol.Value))
|
||||||
|
{
|
||||||
|
ctx.Context.Response.Headers.Append(HeaderNames.CacheControl, cachecontrol.Value);
|
||||||
|
}
|
||||||
|
// CORS headers for .NET MAUI clients
|
||||||
var policy = corsPolicyProvider.GetPolicyAsync(ctx.Context, Constants.MauiCorsPolicy)
|
var policy = corsPolicyProvider.GetPolicyAsync(ctx.Context, Constants.MauiCorsPolicy)
|
||||||
.ConfigureAwait(false).GetAwaiter().GetResult();
|
.ConfigureAwait(false).GetAwaiter().GetResult();
|
||||||
corsService.ApplyResult(corsService.EvaluatePolicy(ctx.Context, policy), ctx.Context.Response);
|
corsService.ApplyResult(corsService.EvaluatePolicy(ctx.Context, policy), ctx.Context.Response);
|
||||||
|
|
|
@ -55,4 +55,4 @@
|
||||||
"Default": "Information"
|
"Default": "Information"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -21,7 +21,7 @@ namespace [Owner].Module.[Module].Services
|
||||||
|
|
||||||
public async Task<Models.[Module]> Get[Module]Async(int [Module]Id, int ModuleId)
|
public async Task<Models.[Module]> Get[Module]Async(int [Module]Id, int ModuleId)
|
||||||
{
|
{
|
||||||
return await GetJsonAsync<Models.[Module]>(CreateAuthorizationPolicyUrl($"{Apiurl}/{[Module]Id}", EntityNames.Module, ModuleId));
|
return await GetJsonAsync<Models.[Module]>(CreateAuthorizationPolicyUrl($"{Apiurl}/{[Module]Id}/{ModuleId}", EntityNames.Module, ModuleId));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Models.[Module]> Add[Module]Async(Models.[Module] [Module])
|
public async Task<Models.[Module]> Add[Module]Async(Models.[Module] [Module])
|
||||||
|
@ -36,7 +36,7 @@ namespace [Owner].Module.[Module].Services
|
||||||
|
|
||||||
public async Task Delete[Module]Async(int [Module]Id, int ModuleId)
|
public async Task Delete[Module]Async(int [Module]Id, int ModuleId)
|
||||||
{
|
{
|
||||||
await DeleteAsync(CreateAuthorizationPolicyUrl($"{Apiurl}/{[Module]Id}", EntityNames.Module, ModuleId));
|
await DeleteAsync(CreateAuthorizationPolicyUrl($"{Apiurl}/{[Module]Id}/{ModuleId}", EntityNames.Module, ModuleId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,11 +13,11 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.1" />
|
||||||
<PackageReference Include="System.Net.Http.Json" Version="9.0.0" />
|
<PackageReference Include="System.Net.Http.Json" Version="9.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -5,31 +5,32 @@ using Microsoft.AspNetCore.Http;
|
||||||
using Oqtane.Shared;
|
using Oqtane.Shared;
|
||||||
using Oqtane.Enums;
|
using Oqtane.Enums;
|
||||||
using Oqtane.Infrastructure;
|
using Oqtane.Infrastructure;
|
||||||
using [Owner].Module.[Module].Repository;
|
using [Owner].Module.[Module].Services;
|
||||||
using Oqtane.Controllers;
|
using Oqtane.Controllers;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace [Owner].Module.[Module].Controllers
|
namespace [Owner].Module.[Module].Controllers
|
||||||
{
|
{
|
||||||
[Route(ControllerRoutes.ApiRoute)]
|
[Route(ControllerRoutes.ApiRoute)]
|
||||||
public class [Module]Controller : ModuleControllerBase
|
public class [Module]Controller : ModuleControllerBase
|
||||||
{
|
{
|
||||||
private readonly I[Module]Repository _[Module]Repository;
|
private readonly I[Module]Service _[Module]Service;
|
||||||
|
|
||||||
public [Module]Controller(I[Module]Repository [Module]Repository, ILogManager logger, IHttpContextAccessor accessor) : base(logger, accessor)
|
public [Module]Controller(I[Module]Service [Module]Service, ILogManager logger, IHttpContextAccessor accessor) : base(logger, accessor)
|
||||||
{
|
{
|
||||||
_[Module]Repository = [Module]Repository;
|
_[Module]Service = [Module]Service;
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET: api/<controller>?moduleid=x
|
// GET: api/<controller>?moduleid=x
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Authorize(Policy = PolicyNames.ViewModule)]
|
[Authorize(Policy = PolicyNames.ViewModule)]
|
||||||
public IEnumerable<Models.[Module]> Get(string moduleid)
|
public async Task<IEnumerable<Models.[Module]>> Get(string moduleid)
|
||||||
{
|
{
|
||||||
int ModuleId;
|
int ModuleId;
|
||||||
if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId))
|
if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId))
|
||||||
{
|
{
|
||||||
return _[Module]Repository.Get[Module]s(ModuleId);
|
return await _[Module]Service.Get[Module]sAsync(ModuleId);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -40,18 +41,18 @@ namespace [Owner].Module.[Module].Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET api/<controller>/5
|
// GET api/<controller>/5
|
||||||
[HttpGet("{id}")]
|
[HttpGet("{id}/{moduleid}")]
|
||||||
[Authorize(Policy = PolicyNames.ViewModule)]
|
[Authorize(Policy = PolicyNames.ViewModule)]
|
||||||
public Models.[Module] Get(int id)
|
public async Task<Models.[Module]> Get(int id, int moduleid)
|
||||||
{
|
{
|
||||||
Models.[Module] [Module] = _[Module]Repository.Get[Module](id);
|
Models.[Module] [Module] = await _[Module]Service.Get[Module]Async(id, moduleid);
|
||||||
if ([Module] != null && IsAuthorizedEntityId(EntityNames.Module, [Module].ModuleId))
|
if ([Module] != null && IsAuthorizedEntityId(EntityNames.Module, [Module].ModuleId))
|
||||||
{
|
{
|
||||||
return [Module];
|
return [Module];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Get Attempt {[Module]Id}", id);
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Get Attempt {[Module]Id} {ModuleId}", id, moduleid);
|
||||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -60,12 +61,11 @@ namespace [Owner].Module.[Module].Controllers
|
||||||
// POST api/<controller>
|
// POST api/<controller>
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Authorize(Policy = PolicyNames.EditModule)]
|
[Authorize(Policy = PolicyNames.EditModule)]
|
||||||
public Models.[Module] Post([FromBody] Models.[Module] [Module])
|
public async Task<Models.[Module]> Post([FromBody] Models.[Module] [Module])
|
||||||
{
|
{
|
||||||
if (ModelState.IsValid && IsAuthorizedEntityId(EntityNames.Module, [Module].ModuleId))
|
if (ModelState.IsValid && IsAuthorizedEntityId(EntityNames.Module, [Module].ModuleId))
|
||||||
{
|
{
|
||||||
[Module] = _[Module]Repository.Add[Module]([Module]);
|
[Module] = await _[Module]Service.Add[Module]Async([Module]);
|
||||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "[Module] Added {[Module]}", [Module]);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -79,12 +79,11 @@ namespace [Owner].Module.[Module].Controllers
|
||||||
// PUT api/<controller>/5
|
// PUT api/<controller>/5
|
||||||
[HttpPut("{id}")]
|
[HttpPut("{id}")]
|
||||||
[Authorize(Policy = PolicyNames.EditModule)]
|
[Authorize(Policy = PolicyNames.EditModule)]
|
||||||
public Models.[Module] Put(int id, [FromBody] Models.[Module] [Module])
|
public async Task<Models.[Module]> Put(int id, [FromBody] Models.[Module] [Module])
|
||||||
{
|
{
|
||||||
if (ModelState.IsValid && [Module].[Module]Id == id && IsAuthorizedEntityId(EntityNames.Module, [Module].ModuleId) && _[Module]Repository.Get[Module]([Module].[Module]Id, false) != null)
|
if (ModelState.IsValid && [Module].[Module]Id == id && IsAuthorizedEntityId(EntityNames.Module, [Module].ModuleId))
|
||||||
{
|
{
|
||||||
[Module] = _[Module]Repository.Update[Module]([Module]);
|
[Module] = await _[Module]Service.Update[Module]Async([Module]);
|
||||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "[Module] Updated {[Module]}", [Module]);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -96,19 +95,18 @@ namespace [Owner].Module.[Module].Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
// DELETE api/<controller>/5
|
// DELETE api/<controller>/5
|
||||||
[HttpDelete("{id}")]
|
[HttpDelete("{id}/{moduleid}")]
|
||||||
[Authorize(Policy = PolicyNames.EditModule)]
|
[Authorize(Policy = PolicyNames.EditModule)]
|
||||||
public void Delete(int id)
|
public async Task Delete(int id, int moduleid)
|
||||||
{
|
{
|
||||||
Models.[Module] [Module] = _[Module]Repository.Get[Module](id);
|
Models.[Module] [Module] = await _[Module]Service.Get[Module]Async(id, moduleid);
|
||||||
if ([Module] != null && IsAuthorizedEntityId(EntityNames.Module, [Module].ModuleId))
|
if ([Module] != null && IsAuthorizedEntityId(EntityNames.Module, [Module].ModuleId))
|
||||||
{
|
{
|
||||||
_[Module]Repository.Delete[Module](id);
|
await _[Module]Service.Delete[Module]Async(id, [Module].ModuleId);
|
||||||
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "[Module] Deleted {[Module]Id}", id);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Delete Attempt {[Module]Id}", id);
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Delete Attempt {[Module]Id} {ModuleId}", id, moduleid);
|
||||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,10 +19,10 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -13,9 +13,9 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -308,97 +308,107 @@ Oqtane.Interop = {
|
||||||
}
|
}
|
||||||
return files;
|
return files;
|
||||||
},
|
},
|
||||||
uploadFiles: function (posturl, folder, id, antiforgerytoken, jwt) {
|
uploadFiles: async function (posturl, folder, id, antiforgerytoken, jwt, chunksize) {
|
||||||
|
var success = true;
|
||||||
var fileinput = document.getElementById('FileInput_' + id);
|
var fileinput = document.getElementById('FileInput_' + id);
|
||||||
var progressinfo = document.getElementById('ProgressInfo_' + id);
|
var progressinfo = document.getElementById('ProgressInfo_' + id);
|
||||||
var progressbar = document.getElementById('ProgressBar_' + id);
|
var progressbar = document.getElementById('ProgressBar_' + id);
|
||||||
|
|
||||||
|
var totalSize = 0;
|
||||||
|
for (var i = 0; i < fileinput.files.length; i++) {
|
||||||
|
totalSize += fileinput.files[i].size;
|
||||||
|
}
|
||||||
|
let uploadSize = 0;
|
||||||
|
|
||||||
|
if (!chunksize || chunksize < 1) {
|
||||||
|
chunksize = 1; // 1 MB default
|
||||||
|
}
|
||||||
|
|
||||||
if (progressinfo !== null && progressbar !== null) {
|
if (progressinfo !== null && progressbar !== null) {
|
||||||
progressinfo.setAttribute("style", "display: inline;");
|
progressinfo.setAttribute('style', 'display: inline;');
|
||||||
progressinfo.innerHTML = '';
|
if (fileinput.files.length > 1) {
|
||||||
progressbar.setAttribute("style", "width: 100%; display: inline;");
|
progressinfo.innerHTML = fileinput.files[0].name + ', ...';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
progressinfo.innerHTML = fileinput.files[0].name;
|
||||||
|
}
|
||||||
|
progressbar.setAttribute('style', 'width: 100%; display: inline;');
|
||||||
progressbar.value = 0;
|
progressbar.value = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
var files = fileinput.files;
|
const uploadFile = (file) => {
|
||||||
var totalSize = 0;
|
const chunkSize = chunksize * (1024 * 1024);
|
||||||
for (var i = 0; i < files.length; i++) {
|
const totalParts = Math.ceil(file.size / chunkSize);
|
||||||
totalSize = totalSize + files[i].size;
|
let partCount = 0;
|
||||||
|
|
||||||
|
const uploadPart = () => {
|
||||||
|
const start = partCount * chunkSize;
|
||||||
|
const end = Math.min(start + chunkSize, file.size);
|
||||||
|
const chunk = file.slice(start, end);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let formdata = new FormData();
|
||||||
|
formdata.append('__RequestVerificationToken', antiforgerytoken);
|
||||||
|
formdata.append('folder', folder);
|
||||||
|
formdata.append('formfile', chunk, file.name);
|
||||||
|
|
||||||
|
var credentials = 'same-origin';
|
||||||
|
var headers = new Headers();
|
||||||
|
headers.append('PartCount', partCount + 1);
|
||||||
|
headers.append('TotalParts', totalParts);
|
||||||
|
if (jwt !== "") {
|
||||||
|
headers.append('Authorization', 'Bearer ' + jwt);
|
||||||
|
credentials = 'include';
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch(posturl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: headers,
|
||||||
|
credentials: credentials,
|
||||||
|
body: formdata
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
if (progressinfo !== null) {
|
||||||
|
progressinfo.innerHTML = 'Error: ' + response.statusText;
|
||||||
|
}
|
||||||
|
throw new Error('Failed');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
partCount++;
|
||||||
|
if (progressbar !== null) {
|
||||||
|
uploadSize += chunk.size;
|
||||||
|
var percent = Math.ceil((uploadSize / totalSize) * 100);
|
||||||
|
progressbar.value = (percent / 100);
|
||||||
|
}
|
||||||
|
if (partCount < totalParts) {
|
||||||
|
uploadPart().then(resolve).catch(reject);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
resolve(data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return uploadPart();
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const file of fileinput.files) {
|
||||||
|
await uploadFile(file);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var maxChunkSizeMB = 1;
|
fileinput.value = '';
|
||||||
var bufferChunkSize = maxChunkSizeMB * (1024 * 1024);
|
return success;
|
||||||
var uploadedSize = 0;
|
|
||||||
|
|
||||||
for (var i = 0; i < files.length; i++) {
|
|
||||||
var fileChunk = [];
|
|
||||||
var file = files[i];
|
|
||||||
var fileStreamPos = 0;
|
|
||||||
var endPos = bufferChunkSize;
|
|
||||||
|
|
||||||
while (fileStreamPos < file.size) {
|
|
||||||
fileChunk.push(file.slice(fileStreamPos, endPos));
|
|
||||||
fileStreamPos = endPos;
|
|
||||||
endPos = fileStreamPos + bufferChunkSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
var totalParts = fileChunk.length;
|
|
||||||
var partCount = 0;
|
|
||||||
|
|
||||||
while (chunk = fileChunk.shift()) {
|
|
||||||
partCount++;
|
|
||||||
var fileName = file.name + ".part_" + partCount.toString().padStart(3, '0') + "_" + totalParts.toString().padStart(3, '0');
|
|
||||||
|
|
||||||
var data = new FormData();
|
|
||||||
data.append('__RequestVerificationToken', antiforgerytoken);
|
|
||||||
data.append('folder', folder);
|
|
||||||
data.append('formfile', chunk, fileName);
|
|
||||||
var request = new XMLHttpRequest();
|
|
||||||
request.open('POST', posturl, true);
|
|
||||||
if (jwt !== "") {
|
|
||||||
request.setRequestHeader('Authorization', 'Bearer ' + jwt);
|
|
||||||
request.withCredentials = true;
|
|
||||||
}
|
|
||||||
request.upload.onloadstart = function (e) {
|
|
||||||
if (progressinfo !== null && progressbar !== null && progressinfo.innerHTML === '') {
|
|
||||||
if (files.length === 1) {
|
|
||||||
progressinfo.innerHTML = file.name;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
progressinfo.innerHTML = file.name + ", ...";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
request.upload.onprogress = function (e) {
|
|
||||||
if (progressinfo !== null && progressbar !== null) {
|
|
||||||
var percent = Math.ceil(((uploadedSize + e.loaded) / totalSize) * 100);
|
|
||||||
progressbar.value = (percent / 100);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
request.upload.onloadend = function (e) {
|
|
||||||
if (progressinfo !== null && progressbar !== null) {
|
|
||||||
uploadedSize = uploadedSize + e.total;
|
|
||||||
var percent = Math.ceil((uploadedSize / totalSize) * 100);
|
|
||||||
progressbar.value = (percent / 100);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
request.upload.onerror = function() {
|
|
||||||
if (progressinfo !== null && progressbar !== null) {
|
|
||||||
if (files.length === 1) {
|
|
||||||
progressinfo.innerHTML = file.name + ' Error: ' + request.statusText;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
progressinfo.innerHTML = ' Error: ' + request.statusText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
request.send(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i === files.length - 1) {
|
|
||||||
fileinput.value = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
refreshBrowser: function (verify, wait) {
|
refreshBrowser: function (verify, wait) {
|
||||||
async function attemptReload (verify) {
|
async function attemptReload (verify) {
|
||||||
|
|
|
@ -1,67 +1,70 @@
|
||||||
const scriptInfoBySrc = new Map();
|
const scriptKeys = new Set();
|
||||||
|
|
||||||
|
export function onUpdate() {
|
||||||
|
// determine if this is an enhanced navigation
|
||||||
|
let enhancedNavigation = scriptKeys.size !== 0;
|
||||||
|
|
||||||
|
// iterate over all script elements in document
|
||||||
|
const scripts = document.getElementsByTagName('script');
|
||||||
|
for (const script of Array.from(scripts)) {
|
||||||
|
// only process scripts that include a data-reload attribute
|
||||||
|
if (script.hasAttribute('data-reload')) {
|
||||||
|
let key = getKey(script);
|
||||||
|
|
||||||
|
if (enhancedNavigation) {
|
||||||
|
// reload the script if data-reload is "always" or "true"... or if the script has not been loaded previously and data-reload is "once"
|
||||||
|
let dataReload = script.getAttribute('data-reload');
|
||||||
|
if ((dataReload === 'always' || dataReload === 'true') || (!scriptKeys.has(key) && dataReload == 'once')) {
|
||||||
|
reloadScript(script);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// save the script key
|
||||||
|
if (!scriptKeys.has(key)) {
|
||||||
|
scriptKeys.add(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getKey(script) {
|
function getKey(script) {
|
||||||
if (script.hasAttribute("src") && script.src !== "") {
|
if (script.src) {
|
||||||
return script.src;
|
return script.src;
|
||||||
|
} else if (script.id) {
|
||||||
|
return script.id;
|
||||||
} else {
|
} else {
|
||||||
return script.innerHTML;
|
return script.innerHTML;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onUpdate() {
|
function reloadScript(script) {
|
||||||
let timestamp = Date.now();
|
try {
|
||||||
let enhancedNavigation = scriptInfoBySrc.size !== 0;
|
if (isValid(script)) {
|
||||||
|
replaceScript(script);
|
||||||
// iterate over all script elements in page
|
|
||||||
const scripts = document.getElementsByTagName("script");
|
|
||||||
for (const script of Array.from(scripts)) {
|
|
||||||
let key = getKey(script);
|
|
||||||
let scriptInfo = scriptInfoBySrc.get(key);
|
|
||||||
if (!scriptInfo) {
|
|
||||||
// new script added
|
|
||||||
scriptInfo = { timestamp: timestamp };
|
|
||||||
scriptInfoBySrc.set(key, scriptInfo);
|
|
||||||
if (enhancedNavigation) {
|
|
||||||
reloadScript(script);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// existing script
|
|
||||||
scriptInfo.timestamp = timestamp;
|
|
||||||
if (script.hasAttribute("data-reload") && script.getAttribute("data-reload") === "true") {
|
|
||||||
reloadScript(script);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove scripts that are no longer referenced
|
|
||||||
for (const [key, scriptInfo] of scriptInfoBySrc) {
|
|
||||||
if (scriptInfo.timestamp !== timestamp) {
|
|
||||||
scriptInfoBySrc.delete(key);
|
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Blazor Script Reload failed to load script: ${getKey(script)}`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function reloadScript(script) {
|
function isValid(script) {
|
||||||
try {
|
if (script.innerHTML.includes('document.write(')) {
|
||||||
replaceScript(script);
|
console.log(`Blazor Script Reload does not support scripts using document.write(): ${script.innerHTML}`);
|
||||||
} catch (error) {
|
return false;
|
||||||
if (script.hasAttribute("src") && script.src !== "") {
|
|
||||||
console.error("Failed to load external script: ${script.src}", error);
|
|
||||||
} else {
|
|
||||||
console.error("Failed to load inline script: ${script.innerHtml}", error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function replaceScript(script) {
|
function replaceScript(script) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
var newScript = document.createElement("script");
|
var newScript = document.createElement('script');
|
||||||
|
|
||||||
// replicate attributes and content
|
// replicate attributes and content
|
||||||
for (let i = 0; i < script.attributes.length; i++) {
|
for (let i = 0; i < script.attributes.length; i++) {
|
||||||
newScript.setAttribute(script.attributes[i].name, script.attributes[i].value);
|
newScript.setAttribute(script.attributes[i].name, script.attributes[i].value);
|
||||||
}
|
}
|
||||||
newScript.innerHTML = script.innerHTML;
|
newScript.innerHTML = script.innerHTML;
|
||||||
|
newScript.removeAttribute('data-reload');
|
||||||
|
|
||||||
// dynamically injected scripts cannot be async or deferred
|
// dynamically injected scripts cannot be async or deferred
|
||||||
newScript.async = false;
|
newScript.async = false;
|
||||||
|
@ -70,11 +73,10 @@ function replaceScript(script) {
|
||||||
newScript.onload = () => resolve();
|
newScript.onload = () => resolve();
|
||||||
newScript.onerror = (error) => reject(error);
|
newScript.onerror = (error) => reject(error);
|
||||||
|
|
||||||
// remove existing script
|
// remove existing script element
|
||||||
script.remove();
|
script.remove();
|
||||||
|
|
||||||
// replace with new script to force reload in Blazor
|
// replace with new script element to force reload in Blazor
|
||||||
document.head.appendChild(newScript);
|
document.head.appendChild(newScript);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user