Merge remote-tracking branch 'oqtane/dev' into dev
This commit is contained in:
@ -2,7 +2,6 @@
|
||||
@inject IInstallationService InstallationService
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject SiteState SiteState
|
||||
@inject IServiceProvider ServiceProvider
|
||||
|
||||
@if (_initialized)
|
||||
{
|
||||
@ -50,29 +49,21 @@
|
||||
[Parameter]
|
||||
public string AuthorizationToken { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
HttpContext HttpContext { get; set; }
|
||||
|
||||
private bool _initialized = false;
|
||||
private string _display = "display: none;";
|
||||
private Installation _installation = new Installation { Success = false, Message = "" };
|
||||
|
||||
private PageState PageState { get; set; }
|
||||
|
||||
private IHttpContextAccessor accessor;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
SiteState.RemoteIPAddress = RemoteIPAddress;
|
||||
SiteState.AntiForgeryToken = AntiForgeryToken;
|
||||
SiteState.AuthorizationToken = AuthorizationToken;
|
||||
|
||||
accessor = (IHttpContextAccessor)ServiceProvider.GetService(typeof(IHttpContextAccessor));
|
||||
if (accessor != null)
|
||||
{
|
||||
SiteState.IsPrerendering = !accessor.HttpContext.Response.HasStarted;
|
||||
}
|
||||
else
|
||||
{
|
||||
SiteState.IsPrerendering = true;
|
||||
}
|
||||
SiteState.IsPrerendering = (HttpContext != null) ? true : false;
|
||||
|
||||
_installation = await InstallationService.IsInstalled();
|
||||
if (_installation.Alias != null)
|
||||
|
@ -15,7 +15,7 @@
|
||||
<div class="row">
|
||||
<div class="mx-auto text-center">
|
||||
<img src="oqtane-black.png" />
|
||||
<div style="font-weight: bold">@SharedLocalizer["Version"] @Constants.Version (.NET 7)</div>
|
||||
<div style="font-weight: bold">@SharedLocalizer["Version"] @Constants.Version (.NET 8)</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="app-rule" />
|
||||
|
@ -12,11 +12,12 @@
|
||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
||||
{
|
||||
string url = NavigateUrl(p.Path);
|
||||
<div class="col-md-2 mx-auto text-center mb-3">
|
||||
<p class="col-md-2 mx-auto text-center mb-3">
|
||||
<NavLink class="nav-link text-primary" href="@url" Match="NavLinkMatch.All">
|
||||
<h2><span class="@p.Icon" aria-hidden="true"></span></h2>@SharedLocalizer[p.Name]
|
||||
<h2><span class="@p.Icon" aria-hidden="true"></span></h2>
|
||||
<p class="lead">@((MarkupString)SharedLocalizer[p.Name].ToString().Replace(" ", "<br />"))</p>
|
||||
</NavLink>
|
||||
</div>
|
||||
</p>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
@ -67,6 +67,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<br /><br />
|
||||
|
||||
@if (!_isSystem)
|
||||
{
|
||||
@ -79,8 +80,7 @@
|
||||
@((MarkupString)" ")
|
||||
<ActionDialog Header="Delete Folder" Message="Are You Sure You Wish To Delete This Folder?" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteFolder())" ResourceKey="DeleteFolder" />
|
||||
}
|
||||
<br />
|
||||
<br />
|
||||
<br /><br />
|
||||
@if (PageState.QueryString.ContainsKey("id"))
|
||||
{
|
||||
<AuditInfo CreatedBy="@_createdBy" CreatedOn="@_createdOn" ModifiedBy="@_modifiedBy" ModifiedOn="@_modifiedOn"></AuditInfo>
|
||||
|
@ -8,27 +8,28 @@
|
||||
|
||||
@if (_files != null)
|
||||
{
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<div class="col-sm-2">
|
||||
<label class="control-label">@Localizer["Folder"] </label>
|
||||
<div class="row">
|
||||
<div class="col-md mb-1">
|
||||
<ActionLink Action="Edit" Text="Add Folder" Class="btn btn-secondary" ResourceKey="AddFolder" />
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="col-md-8 mb-1">
|
||||
<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>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<ActionLink Action="Edit" Text="Edit Folder" Class="btn btn-secondary" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="EditFolder" />
|
||||
<ActionLink Action="Edit" Text="Add Folder" Class="btn btn-secondary" ResourceKey="AddFolder" />
|
||||
</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>
|
||||
<Pager Items="@_files">
|
||||
|
||||
<Pager Items="@_files" SearchProperties="Name">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
|
@ -15,7 +15,7 @@ else
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<Pager Items="@_jobs">
|
||||
<Pager Items="@_jobs" SearchProperties="Name">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
|
@ -14,7 +14,7 @@ else
|
||||
{
|
||||
<ActionLink Action="Add" Text="Add Language" ResourceKey="AddLanguage" />
|
||||
|
||||
<Pager Items="@_languages">
|
||||
<Pager Items="@_languages" SearchProperties="Name,Code">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@SharedLocalizer["Name"]</th>
|
||||
|
@ -3,6 +3,7 @@
|
||||
@inherits ModuleBase
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IUserService UserService
|
||||
@inject ISettingService SettingService
|
||||
@inject IServiceProvider ServiceProvider
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
@ -35,10 +36,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mt-2">
|
||||
@if (!_alwaysremember)
|
||||
{
|
||||
<div class="form-check">
|
||||
<input id="remember" type="checkbox" class="form-check-input" @bind="@_remember" />
|
||||
<Label Class="control-label" For="remember" HelpText="Specify if you would like to be signed back in automatically the next time you visit this site" ResourceKey="Remember">Remember Me?</Label>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
@ -77,6 +81,7 @@
|
||||
private string _passwordtype = "password";
|
||||
private string _togglepassword = string.Empty;
|
||||
private bool _remember = false;
|
||||
private bool _alwaysremember = false;
|
||||
private string _code = string.Empty;
|
||||
|
||||
private string _returnUrl = string.Empty;
|
||||
@ -92,18 +97,11 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
_allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false;
|
||||
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
|
||||
|
||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||
|
||||
if (PageState.Site.Settings.ContainsKey("LoginOptions:AllowSiteLogin") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:AllowSiteLogin"]))
|
||||
{
|
||||
_allowsitelogin = bool.Parse(PageState.Site.Settings["LoginOptions:AllowSiteLogin"]);
|
||||
}
|
||||
|
||||
if (PageState.Site.Settings.ContainsKey("ExternalLogin:ProviderType") && !string.IsNullOrEmpty(PageState.Site.Settings["ExternalLogin:ProviderType"]))
|
||||
{
|
||||
_allowexternallogin = true;
|
||||
}
|
||||
|
||||
if (PageState.QueryString.ContainsKey("returnurl"))
|
||||
{
|
||||
_returnUrl = PageState.QueryString["returnurl"];
|
||||
@ -157,6 +155,10 @@
|
||||
AddModuleMessage(Localizer["ExternalLoginStatus." + PageState.QueryString["status"]], MessageType.Info);
|
||||
}
|
||||
}
|
||||
if (PageState.Site.Settings.TryGetValue("LoginOptions:AlwaysRemember", out string alwaysRememberStr))
|
||||
{
|
||||
_alwaysremember = Convert.ToBoolean(alwaysRememberStr);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -167,7 +169,7 @@
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender && PageState.User == null)
|
||||
if (firstRender && PageState.User == null && _allowsitelogin)
|
||||
{
|
||||
await username.FocusAsync();
|
||||
}
|
||||
@ -192,6 +194,13 @@
|
||||
|
||||
if (!twofactor)
|
||||
{
|
||||
bool alwaysRemember = false;
|
||||
if (PageState.Site.Settings.TryGetValue("LoginOptions:AlwaysRemember", out string alwaysRememberStr))
|
||||
{
|
||||
alwaysRemember = Convert.ToBoolean(alwaysRememberStr);
|
||||
}
|
||||
bool remember = alwaysRemember || _remember;
|
||||
_remember = remember;
|
||||
user = await UserService.LoginUserAsync(user, hybrid, _remember);
|
||||
}
|
||||
else
|
||||
@ -206,8 +215,7 @@
|
||||
if (hybrid)
|
||||
{
|
||||
// hybrid apps utilize an interactive login
|
||||
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider
|
||||
.GetService(typeof(IdentityAuthenticationStateProvider));
|
||||
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
|
||||
authstateprovider.NotifyAuthenticationChanged();
|
||||
NavigationManager.NavigateTo(NavigateUrl(WebUtility.UrlDecode(_returnUrl), true));
|
||||
}
|
||||
@ -221,7 +229,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && PageState.Site.Settings["LoginOptions:TwoFactor"] == "required") || user.TwoFactorRequired)
|
||||
if (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "required" || user.TwoFactorRequired)
|
||||
{
|
||||
twofactor = true;
|
||||
validated = false;
|
||||
|
@ -66,6 +66,7 @@
|
||||
<div class="container-fluid px-0">
|
||||
<div class="row g-0 mb-2">
|
||||
<div class="col-4">
|
||||
<a href="@context.ProductUrl" target="_blank">
|
||||
@if (context.LogoUrl != null)
|
||||
{
|
||||
<img src="@context.LogoUrl" class="img-fluid" alt="@context.Name" />
|
||||
@ -74,6 +75,7 @@
|
||||
{
|
||||
<img src="/package.png" class="img-fluid" alt="@context.Name" />
|
||||
}
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-8 text-end">
|
||||
<small>@SharedLocalizer["Search.Version"]:</small> <strong>@context.Version</strong>
|
||||
|
@ -88,9 +88,12 @@
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
if (!NavigationManager.BaseUri.Contains("localhost:"))
|
||||
{
|
||||
AddModuleMessage(Localizer["Info.Module.Development"], MessageType.Info);
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
@ -114,6 +117,8 @@
|
||||
try
|
||||
{
|
||||
if (IsValid(_owner) && IsValid(_module) && _owner != _module && _template != "-")
|
||||
{
|
||||
if (IsValidXML(_description))
|
||||
{
|
||||
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
||||
var moduleDefinition = new ModuleDefinition { Owner = _owner, Name = _module, Description = _description, Template = _template, Version = _reference, ModuleDefinitionName = template.Namespace };
|
||||
@ -122,6 +127,11 @@
|
||||
AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Require.ValidDescription"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Require.ValidName"], MessageType.Warning);
|
||||
}
|
||||
@ -143,6 +153,12 @@
|
||||
return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
|
||||
}
|
||||
|
||||
private bool IsValidXML(string description)
|
||||
{
|
||||
// must contain letters, digits, or spaces
|
||||
return Regex.IsMatch(description, "^[A-Za-z0-9 .,!?]+$");
|
||||
}
|
||||
|
||||
private void TemplateChanged(ChangeEventArgs e)
|
||||
{
|
||||
_template = (string)e.Value;
|
||||
|
@ -26,6 +26,17 @@
|
||||
<input id="title" type="text" class="form-control" @bind="@_title" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="pane" HelpText="The pane where the module will be displayed" ResourceKey="Pane">Pane: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select class="form-select" @bind="@_pane">
|
||||
@foreach (string pane in PageState.Page.Panes)
|
||||
{
|
||||
<option value="@pane">@pane Pane</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="container" HelpText="Select the module's container" ResourceKey="Container">Container: </Label>
|
||||
<div class="col-sm-9">
|
||||
@ -112,6 +123,7 @@
|
||||
private List<ThemeControl> _containers = new List<ThemeControl>();
|
||||
private string _module;
|
||||
private string _title;
|
||||
private string _pane;
|
||||
private string _containerType;
|
||||
private string _allPages = "false";
|
||||
private string _permissionNames = "";
|
||||
@ -134,6 +146,7 @@
|
||||
{
|
||||
_module = ModuleState.ModuleDefinition.Name;
|
||||
_title = ModuleState.Title;
|
||||
_pane = ModuleState.Pane;
|
||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, PageState.Page.ThemeType);
|
||||
_containerType = ModuleState.ContainerType;
|
||||
_allPages = ModuleState.AllPages.ToString();
|
||||
@ -206,6 +219,7 @@
|
||||
var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
|
||||
pagemodule.PageId = int.Parse(_pageId);
|
||||
pagemodule.Title = _title;
|
||||
pagemodule.Pane = _pane;
|
||||
pagemodule.ContainerType = (_containerType != "-") ? _containerType : string.Empty;
|
||||
if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Page.DefaultContainerType)
|
||||
{
|
||||
|
@ -40,9 +40,9 @@
|
||||
<Label Class="col-sm-3" For="insert" HelpText="Select the location where you would like the page to be inserted in relation to other pages" ResourceKey="Insert">Insert: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="insert" class="form-select" @bind="@_insert" required>
|
||||
<option value="<<">@Localizer["AtBeginning"]</option>
|
||||
@if (_children != null && _children.Count > 0)
|
||||
{
|
||||
<option value="<<">@Localizer["AtBeginning"]</option>
|
||||
<option value="<">@Localizer["Before"]</option>
|
||||
<option value=">">@Localizer["After"]</option>
|
||||
}
|
||||
@ -257,7 +257,14 @@
|
||||
_themes = ThemeService.GetThemeControls(PageState.Site.Themes);
|
||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
||||
_containertype = PageState.Site.DefaultContainerType;
|
||||
_children = PageState.Pages.Where(item => item.ParentId == null).ToList();
|
||||
_children = new List<Page>();
|
||||
foreach (Page p in PageState.Pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
|
||||
{
|
||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
||||
{
|
||||
_children.Add(p);
|
||||
}
|
||||
}
|
||||
ThemeSettings();
|
||||
_initialized = true;
|
||||
}
|
||||
@ -280,26 +287,13 @@
|
||||
{
|
||||
_parentid = (string)e.Value;
|
||||
_children = new List<Page>();
|
||||
if (_parentid == "-1")
|
||||
{
|
||||
foreach (Page p in PageState.Pages.Where(item => item.ParentId == null))
|
||||
foreach (Page p in PageState.Pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
|
||||
{
|
||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
||||
{
|
||||
_children.Add(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (Page p in PageState.Pages.Where(item => item.ParentId == int.Parse(_parentid)))
|
||||
{
|
||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
||||
{
|
||||
_children.Add(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -48,13 +48,16 @@
|
||||
{
|
||||
<option value="="><@Localizer["ThisLocation.Keep"]></option>
|
||||
}
|
||||
<option value="<<">@Localizer["ToBeginning"]</option>
|
||||
@if (_children != null && _children.Count > 0)
|
||||
{
|
||||
<option value="<<">@Localizer["ToBeginning"]</option>
|
||||
<option value="<">@Localizer["Before"]</option>
|
||||
<option value=">">@Localizer["After"]</option>
|
||||
}
|
||||
@if (_parentid != _currentparentid)
|
||||
{
|
||||
<option value=">>">@Localizer["ToEnd"]</option>
|
||||
}
|
||||
</select>
|
||||
@if (_children != null && _children.Count > 0 && (_insert == "<" || _insert == ">"))
|
||||
{
|
||||
@ -324,7 +327,6 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
_children = PageState.Pages.Where(item => item.ParentId == null).ToList();
|
||||
_pageId = Int32.Parse(PageState.QueryString["id"]);
|
||||
_page = await PageService.GetPageAsync(_pageId);
|
||||
_icons = await SystemService.GetIconsAsync();
|
||||
@ -342,6 +344,14 @@
|
||||
_parentid = _page.ParentId.ToString();
|
||||
_parent = PageState.Pages.FirstOrDefault(item => item.PageId == _page.ParentId);
|
||||
}
|
||||
_children = new List<Page>();
|
||||
foreach (Page p in PageState.Pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
|
||||
{
|
||||
if (p.PageId != _pageId && UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
||||
{
|
||||
_children.Add(p);
|
||||
}
|
||||
}
|
||||
_currentparentid = _parentid;
|
||||
_isnavigation = _page.IsNavigation.ToString();
|
||||
_isclickable = _page.IsClickable.ToString();
|
||||
@ -365,7 +375,7 @@
|
||||
// appearance
|
||||
_title = _page.Title;
|
||||
_themetype = _page.ThemeType;
|
||||
if (string.IsNullOrEmpty(_themetype) || ThemeService.GetTheme(PageState.Site.Themes, _themetype)?.ThemeName != ThemeService.GetTheme(PageState.Site.Themes, PageState.Site.DefaultThemeType)?.ThemeName)
|
||||
if (string.IsNullOrEmpty(_themetype))
|
||||
{
|
||||
_themetype = PageState.Site.DefaultThemeType;
|
||||
}
|
||||
@ -417,34 +427,14 @@
|
||||
{
|
||||
_parentid = (string)e.Value;
|
||||
_children = new List<Page>();
|
||||
if (_parentid == "-1")
|
||||
foreach (Page p in PageState.Pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
|
||||
{
|
||||
foreach (Page p in PageState.Pages.Where(item => item.ParentId == null))
|
||||
{
|
||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
||||
if (p.PageId != _pageId && UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
||||
{
|
||||
_children.Add(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (Page p in PageState.Pages.Where(item => item.ParentId == int.Parse(_parentid)))
|
||||
{
|
||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
||||
{
|
||||
_children.Add(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_parentid == _currentparentid)
|
||||
{
|
||||
_insert = "=";
|
||||
}
|
||||
else
|
||||
{
|
||||
_insert = ">>";
|
||||
}
|
||||
_insert = (_parentid == _currentparentid) ? "=" : ">>";
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -9,7 +9,7 @@
|
||||
{
|
||||
<ActionLink Action="Add" Text="Add Page" ResourceKey="AddPage" />
|
||||
|
||||
<Pager Items="@PageState.Pages.Where(item => !item.IsDeleted)">
|
||||
<Pager Items="@PageState.Pages.Where(item => !item.IsDeleted)" SearchProperties="Name">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
|
@ -22,7 +22,7 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="description" HelpText="The help text displayed to the user for this profile item" ResourceKey="Description">Description: </Label>
|
||||
<div class="col-sm-9">
|
||||
<textarea id="description" class="form-control" @bind="@_description" rows="5" maxlength="256" required ></textarea>
|
||||
<textarea id="description" class="form-control" @bind="@_description" rows="3" maxlength="256" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
@ -34,13 +34,19 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="order" HelpText="The index order of where this profile item should be displayed" ResourceKey="Order">Order: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="order" class="form-control" @bind="@_vieworder" maxlength="4" required />
|
||||
<input id="order" class="form-control" @bind="@_vieworder" min="0" max="99" type="number" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="length" HelpText="The max number of characters this profile item should accept (enter zero for unlimited)" ResourceKey="Length">Length: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="length" class="form-control" @bind="@_maxlength" maxlength="4" required />
|
||||
<input id="length" class="form-control" @bind="@_maxlength" min="0" max="524288" type="number" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="rows" HelpText="The number of rows for text entry (one is the default)" ResourceKey="Rows">Rows: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="rows" class="form-control" @bind="@_rows" min="1" max="10" type="number" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
@ -101,6 +107,7 @@
|
||||
private string _category = string.Empty;
|
||||
private string _vieworder = "0";
|
||||
private string _maxlength = "0";
|
||||
private string _rows = "1";
|
||||
private string _defaultvalue = string.Empty;
|
||||
private string _options = string.Empty;
|
||||
private string _validation = string.Empty;
|
||||
@ -131,6 +138,7 @@
|
||||
_category = profile.Category;
|
||||
_vieworder = profile.ViewOrder.ToString();
|
||||
_maxlength = profile.MaxLength.ToString();
|
||||
_rows = profile.Rows.ToString();
|
||||
_defaultvalue = profile.DefaultValue;
|
||||
_options = profile.Options;
|
||||
_validation = profile.Validation;
|
||||
@ -175,6 +183,7 @@
|
||||
profile.Category = _category;
|
||||
profile.ViewOrder = int.Parse(_vieworder);
|
||||
profile.MaxLength = int.Parse(_maxlength);
|
||||
profile.Rows = int.Parse(_rows);
|
||||
profile.DefaultValue = _defaultvalue;
|
||||
profile.Options = _options;
|
||||
profile.Validation = _validation;
|
||||
|
@ -12,16 +12,22 @@ else
|
||||
{
|
||||
<ActionLink Action="Add" Text="Add Profile" Security="SecurityAccessLevel.Edit" ResourceKey="AddProfile" />
|
||||
|
||||
<Pager Items="@_profiles">
|
||||
<Pager Items="@_profiles" SearchProperties="Title,Category">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@SharedLocalizer["Name"]</th>
|
||||
<th>@Localizer["Title"]</th>
|
||||
<th>@Localizer["Category"]</th>
|
||||
<th>@Localizer["Order"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ProfileId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="EditProfile" /></td>
|
||||
<td><ActionDialog Header="Delete Profile" Message="@string.Format(Localizer["Confirm.Profile.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteProfile(context.ProfileId))" ResourceKey="DeleteProfile" /></td>
|
||||
<td>@context.Name</td>
|
||||
<td>@context.Title</td>
|
||||
<td>@context.Category</td>
|
||||
<td>@context.ViewOrder</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="username" HelpText="Your username will be populated from the link you received in the password reset notification" ResourceKey="Username">Username: </Label>
|
||||
@ -45,12 +46,14 @@
|
||||
private string _passwordtype = "password";
|
||||
private string _togglepassword = string.Empty;
|
||||
private string _confirm = string.Empty;
|
||||
private string _passwordrequirements;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||
|
||||
if (PageState.QueryString.ContainsKey("name") && PageState.QueryString.ContainsKey("token"))
|
||||
{
|
||||
|
@ -12,7 +12,7 @@ else
|
||||
{
|
||||
<ActionLink Action="Add" Text="Add Role" Security="SecurityAccessLevel.Edit" ResourceKey="AddRole" />
|
||||
|
||||
<Pager Items="@_roles">
|
||||
<Pager Items="@_roles" SearchProperties="Name">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
|
@ -37,7 +37,7 @@ else
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="defaultTheme" HelpText="Select the default theme for the website" ResourceKey="DefaultTheme">Default Theme: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="defaultTheme" class="form-select" @onchange="(e => ThemeChanged(e))" required>
|
||||
<select id="defaultTheme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
|
||||
<option value="-"><@Localizer["Theme.Select"]></option>
|
||||
@foreach (var theme in _themes)
|
||||
{
|
||||
@ -58,19 +58,6 @@ else
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="adminContainer" HelpText="Select the admin container for the site" ResourceKey="AdminContainer">Admin Container: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="adminContainer" class="form-select" @bind="@_admincontainertype" required>
|
||||
<option value="-"><@Localizer["Container.Select"]></option>
|
||||
<option value=""><@Localizer["DefaultContainer.Admin"]></option>
|
||||
@foreach (var container in _containers)
|
||||
{
|
||||
<option value="@container.TypeName">@container.Name</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="siteTemplate" HelpText="Select the site template" ResourceKey="SiteTemplate">Site Template: </Label>
|
||||
<div class="col-sm-9">
|
||||
@ -105,7 +92,7 @@ else
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="tenant" HelpText="Select the database for the site" ResourceKey="Tenant">Database: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="tenant" class="form-select" @onchange="(e => TenantChanged(e))" required>
|
||||
<select id="tenant" class="form-select" value="@_tenantid" @onchange="(e => TenantChanged(e))" required>
|
||||
<option value="-"><@Localizer["Tenant.Select"]></option>
|
||||
<option value="+"><@Localizer["Tenant.Add"]></option>
|
||||
@foreach (Tenant tenant in _tenants)
|
||||
@ -214,7 +201,6 @@ else
|
||||
private string _urls = string.Empty;
|
||||
private string _themetype = "-";
|
||||
private string _containertype = "-";
|
||||
private string _admincontainertype = "";
|
||||
private string _sitetemplatetype = "-";
|
||||
private string _runtime = "Server";
|
||||
private string _prerender = "Prerendered";
|
||||
@ -224,10 +210,24 @@ else
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_tenants = await TenantService.GetTenantsAsync();
|
||||
if (_tenants.Any(item => item.Name == TenantNames.Master))
|
||||
{
|
||||
_tenantid = _tenants.First(item => item.Name == TenantNames.Master).TenantId.ToString();
|
||||
}
|
||||
_urls = PageState.Alias.Name;
|
||||
_themeList = await ThemeService.GetThemesAsync();
|
||||
_themes = ThemeService.GetThemeControls(_themeList);
|
||||
if (_themes.Any(item => item.TypeName == Constants.DefaultTheme))
|
||||
{
|
||||
_themetype = Constants.DefaultTheme;
|
||||
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
|
||||
_containertype = _containers.First().TypeName;
|
||||
}
|
||||
_siteTemplates = await SiteTemplateService.GetSiteTemplatesAsync();
|
||||
if (_siteTemplates.Any(item => item.TypeName == Constants.DefaultSiteTemplate))
|
||||
{
|
||||
_sitetemplatetype = Constants.DefaultSiteTemplate;
|
||||
}
|
||||
|
||||
_databases = await DatabaseService.GetDatabasesAsync();
|
||||
if (_databases.Exists(item => item.IsDefault))
|
||||
@ -295,7 +295,6 @@ else
|
||||
_containers = new List<ThemeControl>();
|
||||
_containertype = "-";
|
||||
}
|
||||
_admincontainertype = "";
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -399,7 +398,7 @@ else
|
||||
config.Aliases = _urls;
|
||||
config.DefaultTheme = _themetype;
|
||||
config.DefaultContainer = _containertype;
|
||||
config.DefaultAdminContainer = _admincontainertype;
|
||||
config.DefaultAdminContainer = "";
|
||||
config.SiteTemplate = _sitetemplatetype;
|
||||
config.Runtime = _runtime;
|
||||
config.RenderMode = _runtime + _prerender;
|
||||
|
@ -14,7 +14,7 @@ else
|
||||
{
|
||||
<ActionLink Action="Add" Text="Add Site" ResourceKey="AddSite" />
|
||||
|
||||
<Pager Items="@_sites">
|
||||
<Pager Items="@_sites" SearchProperties="Name">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
|
@ -66,6 +66,7 @@
|
||||
<div class="container-fluid px-0">
|
||||
<div class="row g-0 mb-2">
|
||||
<div class="col-4">
|
||||
<a href="@context.ProductUrl" target="_blank">
|
||||
@if (context.LogoUrl != null)
|
||||
{
|
||||
<img src="@context.LogoUrl" class="img-fluid" alt="@context.Name" />
|
||||
@ -74,6 +75,7 @@
|
||||
{
|
||||
<img src="/package.png" class="img-fluid" alt="@context.Name" />
|
||||
}
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-8 text-end">
|
||||
<small>@SharedLocalizer["Search.Version"]:</small> <strong>@context.Version</strong>
|
||||
|
@ -79,9 +79,12 @@
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
if (!NavigationManager.BaseUri.Contains("localhost:"))
|
||||
{
|
||||
AddModuleMessage(Localizer["Info.Theme.CreatorIntent"], MessageType.Info);
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
|
@ -7,6 +7,8 @@
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@if (_initialized)
|
||||
{
|
||||
<TabStrip>
|
||||
<TabPanel Name="Download" ResourceKey="Download">
|
||||
@if (_package != null && _upgradeavailable)
|
||||
@ -21,7 +23,7 @@
|
||||
}
|
||||
</TabPanel>
|
||||
<TabPanel Name="Upload" ResourceKey="Upload">
|
||||
<ModuleMessage Type="MessageType.Info" Message=@Localizer["MessgeUpgrade.Text"]></ModuleMessage>
|
||||
<ModuleMessage Type="MessageType.Info" Message=@Localizer["MessageUpgrade.Text"]></ModuleMessage>
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" HelpText="Upload A Framework Package And Then Select Upgrade" ResourceKey="Framework">Framework: </Label>
|
||||
@ -33,8 +35,10 @@
|
||||
<button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button>
|
||||
</TabPanel>
|
||||
</TabStrip>
|
||||
}
|
||||
|
||||
@code {
|
||||
private bool _initialized = false;
|
||||
private Package _package;
|
||||
private bool _upgradeavailable = false;
|
||||
|
||||
@ -43,6 +47,12 @@
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (NavigationManager.BaseUri.Contains("localhost:"))
|
||||
{
|
||||
AddModuleMessage(Localizer["Localhost.Text"], MessageType.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
List<Package> packages = await PackageService.GetPackagesAsync("framework", "", "", "");
|
||||
if (packages != null)
|
||||
@ -57,6 +67,8 @@
|
||||
_package = new Package { Name = Constants.PackageId, Version = Constants.Version };
|
||||
}
|
||||
}
|
||||
_initialized = true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
@ -28,7 +28,7 @@ else
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<Pager Items="@_urlMappings">
|
||||
<Pager Items="@_urlMappings" SearchProperties="Url">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
|
@ -2,6 +2,7 @@
|
||||
@inherits ModuleBase
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IUserService UserService
|
||||
@inject IUserRoleService UserRoleService
|
||||
@inject INotificationService NotificationService
|
||||
@inject IStringLocalizer<Add> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
@ -10,9 +11,9 @@
|
||||
{
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="to" HelpText="Enter the username you wish to send a message to" ResourceKey="To">To: </Label>
|
||||
<Label Class="col-sm-3" For="to" HelpText="Enter the user you wish to send a message to" ResourceKey="To">To: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="to" class="form-control" @bind="@username" />
|
||||
<AutoComplete OnSearch="GetUsers" Placeholder="@Localizer["Username.Enter"]" @ref="username" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
@ -30,11 +31,11 @@
|
||||
</div>
|
||||
<br/>
|
||||
<button type="button" class="btn btn-primary" @onclick="Send">@SharedLocalizer["Send"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||
}
|
||||
|
||||
@code {
|
||||
private string username = "";
|
||||
private AutoComplete username;
|
||||
private string subject = "";
|
||||
private string body = "";
|
||||
|
||||
@ -42,23 +43,37 @@
|
||||
|
||||
public override string Title => "Send Notification";
|
||||
|
||||
private async Task<Dictionary<string, string>> GetUsers(string filter)
|
||||
{
|
||||
var users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
|
||||
return users.Where(item => item.User.Username.Contains(filter, StringComparison.OrdinalIgnoreCase))
|
||||
.ToDictionary(item => item.UserId.ToString(), item => item.User.Username);
|
||||
}
|
||||
|
||||
private async Task Send()
|
||||
{
|
||||
try
|
||||
{
|
||||
var user = await UserService.GetUserAsync(username, PageState.Site.SiteId);
|
||||
if (!string.IsNullOrEmpty(username.Key) && !string.IsNullOrEmpty(subject))
|
||||
{
|
||||
var user = await UserService.GetUserAsync(int.Parse(username.Key), ModuleState.SiteId);
|
||||
if (user != null)
|
||||
{
|
||||
var notification = new Notification(PageState.Site.SiteId, PageState.User, user, subject, body);
|
||||
notification = await NotificationService.AddNotificationAsync(notification);
|
||||
await logger.LogInformation("Notification Created {NotificationId}", notification.NotificationId);
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.User.Invalid"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Required"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Adding Notification {Error}", ex.Message);
|
||||
|
@ -11,6 +11,8 @@
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@if (_initialized)
|
||||
{
|
||||
@if (PageState.User != null && photo != null)
|
||||
{
|
||||
<img src="@ImageUrl(photofileid, 400, 400)" alt="@displayname" style="max-width: 400px" class="rounded-circle mx-auto d-block">
|
||||
@ -21,8 +23,6 @@ else
|
||||
}
|
||||
<TabStrip>
|
||||
<TabPanel Name="Identity" ResourceKey="Identity">
|
||||
@if (profiles != null && settings != null)
|
||||
{
|
||||
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
@ -83,11 +83,8 @@ else
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
}
|
||||
</TabPanel>
|
||||
<TabPanel Name="Profile" ResourceKey="Profile">
|
||||
@if (profiles != null && settings != null)
|
||||
{
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
@foreach (Profile profile in profiles)
|
||||
@ -122,6 +119,8 @@ else
|
||||
</select>
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (p.Rows == 1)
|
||||
{
|
||||
@if (p.IsRequired)
|
||||
{
|
||||
@ -132,6 +131,18 @@ else
|
||||
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (p.IsRequired)
|
||||
{
|
||||
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))"></textarea>
|
||||
}
|
||||
else
|
||||
{
|
||||
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"></textarea>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@ -140,19 +151,19 @@ else
|
||||
</div>
|
||||
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
}
|
||||
</TabPanel>
|
||||
<TabPanel Name="Notifications" ResourceKey="Notifications">
|
||||
@if (notifications != null)
|
||||
{
|
||||
<ActionLink Action="Add" Text="Send Notification" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="SendNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" />
|
||||
<br />
|
||||
<br />
|
||||
<select class="form-select" @onchange="(e => FilterChanged(e))">
|
||||
<option value="to">@Localizer["Inbox"]</option>
|
||||
<option value="from">@Localizer["Items.Sent"]</option>
|
||||
</select>
|
||||
<br />
|
||||
<ActionLink Action="Add" Text="Send Notification" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="SendNotification" />
|
||||
<br /><br />
|
||||
@if (filter == "to")
|
||||
{
|
||||
@if (notifications.Any())
|
||||
{
|
||||
<Pager Items="@notifications">
|
||||
<Header>
|
||||
@ -163,7 +174,7 @@ else
|
||||
<th>@Localizer["Received"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" /></td>
|
||||
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" /></td>
|
||||
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
|
||||
|
||||
@if (context.IsRead)
|
||||
@ -203,19 +214,30 @@ else
|
||||
</td>
|
||||
</Detail>
|
||||
</Pager>
|
||||
<br />
|
||||
<ActionDialog Header="Clear Notifications" Message="Are You Sure You Wish To Permanently Delete All Notifications ?" Action="Delete All Notifications" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllNotifications())" ResourceKey="DeleteAllNotifications" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="no-notifications-text">
|
||||
@Localizer["NoNotificationsReceived.Text"]
|
||||
</div>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (notifications.Any())
|
||||
{
|
||||
<Pager Items="@notifications">
|
||||
<Header>
|
||||
<th> </th>
|
||||
<th> </th>
|
||||
<th style="width: 1px;"></th>
|
||||
<th style="width: 1px;"></th>
|
||||
<th>@Localizer["To"]</th>
|
||||
<th>@Localizer["Subject"]</th>
|
||||
<th>@Localizer["Sent"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" /></td>
|
||||
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" /></td>
|
||||
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
|
||||
|
||||
@if (context.IsRead)
|
||||
@ -256,18 +278,24 @@ else
|
||||
</td>
|
||||
</Detail>
|
||||
</Pager>
|
||||
}
|
||||
@if (notifications.Any())
|
||||
{
|
||||
<br />
|
||||
<ActionDialog Header="Clear Notifications" Message="Are You Sure You Wish To Permanently Delete All Notifications ?" Action="Delete All Notifications" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllNotifications())" ResourceKey="DeleteAllNotifications" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="no-notifications-text">
|
||||
@Localizer["NoNotificationsSent.Text"]
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</TabPanel>
|
||||
</TabStrip>
|
||||
<br /><br />
|
||||
<br />
|
||||
<br />
|
||||
}
|
||||
|
||||
@code {
|
||||
private bool _initialized = false;
|
||||
private string _passwordrequirements;
|
||||
private string username = string.Empty;
|
||||
private string _password = string.Empty;
|
||||
@ -282,27 +310,25 @@ else
|
||||
private int folderid = -1;
|
||||
private int photofileid = -1;
|
||||
private File photo = null;
|
||||
|
||||
private List<Profile> profiles;
|
||||
private Dictionary<string, string> settings;
|
||||
private string category = string.Empty;
|
||||
|
||||
private string filter = "to";
|
||||
private List<Notification> notifications;
|
||||
private string notificationSummary = string.Empty;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||
|
||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||
|
||||
if (PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:TwoFactor"]))
|
||||
{
|
||||
allowtwofactor = (PageState.Site.Settings["LoginOptions:TwoFactor"] == "true");
|
||||
}
|
||||
allowtwofactor = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "true");
|
||||
profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
||||
|
||||
if (PageState.User != null)
|
||||
{
|
||||
@ -329,10 +355,11 @@ else
|
||||
photo = null;
|
||||
}
|
||||
|
||||
profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
||||
settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
|
||||
|
||||
await LoadNotificationsAsync();
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -366,9 +393,11 @@ else
|
||||
{
|
||||
try
|
||||
{
|
||||
if (username != string.Empty && email != string.Empty && ValidateProfiles())
|
||||
if (username != string.Empty && email != string.Empty)
|
||||
{
|
||||
if (_password == confirm)
|
||||
{
|
||||
if (ValidateProfiles())
|
||||
{
|
||||
var user = PageState.User;
|
||||
user.Username = username;
|
||||
@ -406,6 +435,7 @@ else
|
||||
AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Password.Invalid"], MessageType.Warning);
|
||||
@ -427,27 +457,33 @@ else
|
||||
|
||||
private bool ValidateProfiles()
|
||||
{
|
||||
bool valid = true;
|
||||
foreach (Profile profile in profiles)
|
||||
{
|
||||
if (string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)) && !string.IsNullOrEmpty(profile.DefaultValue))
|
||||
var value = GetProfileValue(profile.Name, string.Empty);
|
||||
if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue))
|
||||
{
|
||||
settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue);
|
||||
}
|
||||
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||
{
|
||||
if (valid == true && profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)))
|
||||
if (profile.IsRequired && string.IsNullOrEmpty(value))
|
||||
{
|
||||
valid = false;
|
||||
AddModuleMessage(string.Format(SharedLocalizer["ProfileRequired"], profile.Title), MessageType.Warning);
|
||||
return false;
|
||||
}
|
||||
if (valid == true && !string.IsNullOrEmpty(profile.Validation))
|
||||
if (!string.IsNullOrEmpty(profile.Validation))
|
||||
{
|
||||
Regex regex = new Regex(profile.Validation);
|
||||
valid = regex.Match(SettingService.GetSetting(settings, profile.Name, string.Empty)).Success;
|
||||
bool valid = regex.Match(value).Success;
|
||||
if (!valid)
|
||||
{
|
||||
AddModuleMessage(string.Format(SharedLocalizer["ProfileInvalid"], profile.Title), MessageType.Warning);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Cancel()
|
||||
@ -489,7 +525,6 @@ else
|
||||
private async void FilterChanged(ChangeEventArgs e)
|
||||
{
|
||||
filter = (string)e.Value;
|
||||
|
||||
await LoadNotificationsAsync();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
@ -7,95 +7,72 @@
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@if (PageState.User != null)
|
||||
{
|
||||
@if (title == "From")
|
||||
{
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<label Class="col-sm-3">@Localizer["Title"] </label>
|
||||
@if (title == "From")
|
||||
{
|
||||
<div class="col-sm-3">
|
||||
<input class="form-control" @bind="@username" readonly />
|
||||
</div>
|
||||
}
|
||||
@if (title == "To")
|
||||
{
|
||||
<div class="col-sm-3">
|
||||
<input class="form-control" @bind="@username" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<label Class="col-sm-3">@Localizer["Subject"] </label>
|
||||
@if (title == "From")
|
||||
{
|
||||
<div class="col-sm-3">
|
||||
<input class="form-control" @bind="@subject" readonly />
|
||||
</div>
|
||||
}
|
||||
@if (title == "To")
|
||||
{
|
||||
<div class="col-sm-3">
|
||||
<input class="form-control" @bind="@subject" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
@if (title == "From")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<label class="col-sm-3">@Localizer["Date"] </label>
|
||||
<Label Class="col-sm-3" For="username" HelpText="The user who sent the message" ResourceKey="From">From:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input class="form-control" @bind="@createdon" readonly />
|
||||
<input id="username" class="form-control" @bind="@username" readonly />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (title == "From")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<label class="col-sm-3">@Localizer["Message"] </label>
|
||||
<Label Class="col-sm-3" For="subject" HelpText="The subject of the message" ResourceKey="Subject">Subject:</Label>
|
||||
<div class="col-sm-9">
|
||||
<textarea class="form-control" @bind="@body" rows="5" readonly />
|
||||
<input id="subject" class="form-control" @bind="@subject" readonly />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
}
|
||||
@if (title == "To")
|
||||
{
|
||||
|
||||
<div class="row mb-1 align-items-center">
|
||||
<label class="col-sm-3">@Localizer["Message"] </label>
|
||||
<Label class="col-sm-3" For="date" HelpText="The date the message was sent" ResourceKey="Date">Sent:</Label>
|
||||
<div class="col-sm-9">
|
||||
<textarea class="form-control" @bind="@body" rows="5" readonly />
|
||||
<input id="date" class="form-control" @bind="@createdon" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label class="col-sm-3" For="message" HelpText="The contents of the message" ResourceKey="Message">Message:</Label>
|
||||
<div class="col-sm-9">
|
||||
<textarea id="message" class="form-control" @bind="@body" rows="5" readonly />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@if (reply != string.Empty)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick="Send">@SharedLocalizer["Send"]</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
if (title == "From")
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick="Reply">@Localizer["Reply"]</button>
|
||||
}
|
||||
}
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
<br />
|
||||
<br />
|
||||
@if (title == "To")
|
||||
{
|
||||
<div class="control-group">
|
||||
<label class="control-label">@Localizer["OriginalMessage"] </label>
|
||||
<textarea class="form-control" @bind="@reply" rows="5" readonly />
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="username" HelpText="The user who will be the recipient of the message" ResourceKey="To">To:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="username" class="form-control" @bind="@username" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="subject" HelpText="The subject of the message" ResourceKey="Subject">Subject:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="subject" class="form-control" @bind="@subject" readonly="@(!reply)" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label class="col-sm-3" For="message" HelpText="The content of the message" ResourceKey="Message">Message:</Label>
|
||||
<div class="col-sm-9">
|
||||
<textarea id="message" class="form-control" @bind="@body" rows="5" readonly="@(!reply)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (reply)
|
||||
{
|
||||
<button type="button" class="btn btn-primary me-2" @onclick="Send">@SharedLocalizer["Send"]</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
if (title == "From" && username != Localizer["System"])
|
||||
{
|
||||
<button type="button" class="btn btn-primary me-2" @onclick="Reply">@Localizer["Reply"]</button>
|
||||
}
|
||||
}
|
||||
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||
}
|
||||
|
||||
@code {
|
||||
@ -105,7 +82,7 @@
|
||||
private string subject = string.Empty;
|
||||
private string createdon = string.Empty;
|
||||
private string body = string.Empty;
|
||||
private string reply = string.Empty;
|
||||
private bool reply = false;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
||||
public override string Title => "View Notification";
|
||||
@ -118,9 +95,6 @@
|
||||
Notification notification = await NotificationService.GetNotificationAsync(notificationid);
|
||||
if (notification != null)
|
||||
{
|
||||
notification.IsRead = true;
|
||||
notification = await NotificationService.UpdateNotificationAsync(notification);
|
||||
|
||||
int userid = -1;
|
||||
if (notification.ToUserId == PageState.User.UserId)
|
||||
{
|
||||
@ -148,11 +122,17 @@
|
||||
}
|
||||
if (username == "")
|
||||
{
|
||||
username = "System";
|
||||
username = Localizer["System"];
|
||||
}
|
||||
subject = notification.Subject;
|
||||
createdon = notification.CreatedOn.ToString();
|
||||
body = notification.Body;
|
||||
|
||||
if (title == "From")
|
||||
{
|
||||
notification.IsRead = true;
|
||||
notification = await NotificationService.UpdateNotificationAsync(notification);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -165,12 +145,16 @@
|
||||
private void Reply()
|
||||
{
|
||||
title = "To";
|
||||
if (!subject.Contains("RE:"))
|
||||
if (!subject.Contains(Localizer["RE:"]))
|
||||
{
|
||||
subject = "RE: " + subject;
|
||||
subject = Localizer["RE"] + " " + subject;
|
||||
}
|
||||
reply = body;
|
||||
body = "\n\n____________________________________________\nSent: " + createdon + "\nSubject: " + subject + "\n\n" + body;
|
||||
body = $"\n\n____________________________________________\n" +
|
||||
$"{Localizer["From.Text"]} {username}\n" +
|
||||
$"{Localizer["Date.Text"]} {createdon}\n" +
|
||||
$"{Localizer["Subject.Text"]} {subject}\n\n" +
|
||||
body;
|
||||
reply = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
@ -184,7 +168,7 @@
|
||||
var notification = new Notification(PageState.Site.SiteId, PageState.User, user, subject, body, notificationid);
|
||||
notification = await NotificationService.AddNotificationAsync(notification);
|
||||
await logger.LogInformation("Notification Created {NotificationId}", notification.NotificationId);
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -8,6 +8,8 @@
|
||||
@inject IStringLocalizer<Add> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@if (_initialized)
|
||||
{
|
||||
<TabStrip>
|
||||
<TabPanel Name="Identity" ResourceKey="Identity">
|
||||
@if (profiles != null)
|
||||
@ -17,7 +19,7 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="username" HelpText="A unique username for a user. Note that this field can not be modified once it is saved." ResourceKey="Username"></Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="username" class="form-control" @bind="@username" />
|
||||
<input id="username" class="form-control" @bind="@_username" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
@ -33,7 +35,7 @@
|
||||
<Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm"></Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@confirm" autocomplete="new-password" required />
|
||||
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" required />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -41,22 +43,28 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email"></Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="email" class="form-control" @bind="@email" />
|
||||
<input id="email" class="form-control" @bind="@_email" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="displayname" HelpText="The full name of the user" ResourceKey="DisplayName"></Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="displayname" class="form-control" @bind="@displayname" />
|
||||
<input id="displayname" class="form-control" @bind="@_displayname" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="notify" HelpText="Indicate if new users should receive an email notification" ResourceKey="Notify">Notify? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="notify" class="form-select" @bind="@_notify" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
}
|
||||
</TabPanel>
|
||||
<TabPanel Name="Profile" ResourceKey="Profile">
|
||||
@if (profiles != null)
|
||||
{
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
@foreach (Profile profile in profiles)
|
||||
@ -72,37 +80,58 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
|
||||
<div class="col-sm-9">
|
||||
@if (p.IsRequired)
|
||||
@if (!string.IsNullOrEmpty(p.Options))
|
||||
{
|
||||
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" />
|
||||
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
|
||||
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
|
||||
{
|
||||
<option value="@option" selected>@option</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@option">@option</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (p.Rows == 1)
|
||||
{
|
||||
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"></textarea>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</TabPanel>
|
||||
</TabStrip>
|
||||
<br />
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
}
|
||||
|
||||
|
||||
@code {
|
||||
private bool _initialized = false;
|
||||
private string _passwordrequirements;
|
||||
private string username = string.Empty;
|
||||
private string _username = string.Empty;
|
||||
private string _password = string.Empty;
|
||||
private string _passwordtype = "password";
|
||||
private string _togglepassword = string.Empty;
|
||||
private string confirm = string.Empty;
|
||||
private string email = string.Empty;
|
||||
private string displayname = string.Empty;
|
||||
private string _confirm = string.Empty;
|
||||
private string _email = string.Empty;
|
||||
private string _displayname = string.Empty;
|
||||
private string _notify = "True";
|
||||
private List<Profile> profiles;
|
||||
private Dictionary<string, string> settings;
|
||||
private string category = string.Empty;
|
||||
@ -117,6 +146,7 @@
|
||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||
profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
||||
settings = new Dictionary<string, string>();
|
||||
_initialized = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -139,17 +169,20 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
if (username != string.Empty && _password != string.Empty && confirm != string.Empty && email != string.Empty && ValidateProfiles())
|
||||
if (_username != string.Empty && _password != string.Empty && _confirm != string.Empty && _email != string.Empty)
|
||||
{
|
||||
if (_password == confirm)
|
||||
if (_password == _confirm)
|
||||
{
|
||||
if (ValidateProfiles())
|
||||
{
|
||||
var user = new User();
|
||||
user.SiteId = PageState.Site.SiteId;
|
||||
user.Username = username;
|
||||
user.Username = _username;
|
||||
user.Password = _password;
|
||||
user.Email = email;
|
||||
user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname;
|
||||
user.Email = _email;
|
||||
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
||||
user.PhotoFileId = null;
|
||||
user.SuppressNotification = !bool.Parse(_notify);
|
||||
|
||||
user = await UserService.AddUserAsync(user);
|
||||
|
||||
@ -161,10 +194,11 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
await logger.LogError("Error Adding User {Username} {Email}", username, email);
|
||||
await logger.LogError("Error Adding User {Username} {Email}", _username, _email);
|
||||
AddModuleMessage(Localizer["Error.User.AddCheckPass"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Password.NoMatch"], MessageType.Warning);
|
||||
@ -177,34 +211,40 @@
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Adding User {Username} {Email} {Error}", username, email, ex.Message);
|
||||
await logger.LogError(ex, "Error Adding User {Username} {Email} {Error}", _username, _email, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.User.Add"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ValidateProfiles()
|
||||
{
|
||||
bool valid = true;
|
||||
foreach (Profile profile in profiles)
|
||||
{
|
||||
if (string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)) && !string.IsNullOrEmpty(profile.DefaultValue))
|
||||
var value = GetProfileValue(profile.Name, string.Empty);
|
||||
if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue))
|
||||
{
|
||||
settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue);
|
||||
}
|
||||
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||
{
|
||||
if (valid == true && profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)))
|
||||
if (profile.IsRequired && string.IsNullOrEmpty(value))
|
||||
{
|
||||
valid = false;
|
||||
AddModuleMessage(string.Format(SharedLocalizer["ProfileRequired"], profile.Title), MessageType.Warning);
|
||||
return false;
|
||||
}
|
||||
if (valid == true && !string.IsNullOrEmpty(profile.Validation))
|
||||
if (!string.IsNullOrEmpty(profile.Validation))
|
||||
{
|
||||
Regex regex = new Regex(profile.Validation);
|
||||
valid = regex.Match(SettingService.GetSetting(settings, profile.Name, string.Empty)).Success;
|
||||
bool valid = regex.Match(value).Success;
|
||||
if (!valid)
|
||||
{
|
||||
AddModuleMessage(string.Format(SharedLocalizer["ProfileInvalid"], profile.Title), MessageType.Warning);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ProfileChanged(ChangeEventArgs e, string SettingName)
|
||||
|
@ -9,18 +9,10 @@
|
||||
@inject IStringLocalizer<Edit> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@if (PageState.User != null && photo != null)
|
||||
@if (_initialized)
|
||||
{
|
||||
<img src="@photo.Url" alt="@displayname" style="max-width: 400px" class="rounded-circle mx-auto d-block">
|
||||
}
|
||||
else
|
||||
{
|
||||
<br />
|
||||
}
|
||||
<TabStrip>
|
||||
<TabPanel Name="Identity" ResourceKey="Identity">
|
||||
@if (profiles != null)
|
||||
{
|
||||
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
@ -59,12 +51,6 @@ else
|
||||
<input id="displayname" class="form-control" @bind="@displayname" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="@photofileid.ToString()" HelpText="A photo of the user" ResourceKey="Photo"></Label>
|
||||
<div class="col-sm-9">
|
||||
<FileManager FileId="@photofileid" @ref="filemanager" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="isdeleted" HelpText="Indicate if the user is active" ResourceKey="IsDeleted"></Label>
|
||||
<div class="col-sm-9">
|
||||
@ -87,11 +73,8 @@ else
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</TabPanel>
|
||||
<TabPanel Name="Profile" ResourceKey="Profile">
|
||||
@if (profiles != null)
|
||||
{
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
@foreach (Profile profile in profiles)
|
||||
@ -125,21 +108,20 @@ else
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (p.IsRequired)
|
||||
{
|
||||
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" />
|
||||
}
|
||||
else
|
||||
@if (p.Rows == 1)
|
||||
{
|
||||
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"></textarea>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</TabPanel>
|
||||
</TabStrip>
|
||||
|
||||
@ -148,8 +130,10 @@ else
|
||||
<br />
|
||||
<br />
|
||||
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon" DeletedBy="@deletedby" DeletedOn="@deletedon"></AuditInfo>
|
||||
}
|
||||
|
||||
@code {
|
||||
private bool _initialized = false;
|
||||
private string _passwordrequirements;
|
||||
private int userid;
|
||||
private string username = string.Empty;
|
||||
@ -159,9 +143,6 @@ else
|
||||
private string confirm = string.Empty;
|
||||
private string email = string.Empty;
|
||||
private string displayname = string.Empty;
|
||||
private FileManager filemanager;
|
||||
private int photofileid = -1;
|
||||
private File photo = null;
|
||||
private string isdeleted;
|
||||
private string lastlogin;
|
||||
private string lastipaddress;
|
||||
@ -179,32 +160,23 @@ else
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (PageState.QueryString.ContainsKey("id"))
|
||||
{
|
||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||
profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
|
||||
userid = Int32.Parse(PageState.QueryString["id"]);
|
||||
|
||||
if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int UserId))
|
||||
{
|
||||
userid = UserId;
|
||||
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
|
||||
if (user != null)
|
||||
{
|
||||
username = user.Username;
|
||||
email = user.Email;
|
||||
displayname = user.DisplayName;
|
||||
if (user.PhotoFileId != null)
|
||||
{
|
||||
photofileid = user.PhotoFileId.Value;
|
||||
photo = await FileService.GetFileAsync(photofileid);
|
||||
}
|
||||
else
|
||||
{
|
||||
photofileid = -1;
|
||||
photo = null;
|
||||
}
|
||||
isdeleted = user.IsDeleted.ToString();
|
||||
lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn);
|
||||
lastipaddress = user.LastIPAddress;
|
||||
@ -218,6 +190,8 @@ else
|
||||
deletedon = user.DeletedOn;
|
||||
}
|
||||
}
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -240,9 +214,11 @@ else
|
||||
{
|
||||
try
|
||||
{
|
||||
if (username != string.Empty && email != string.Empty && ValidateProfiles())
|
||||
if (username != string.Empty && email != string.Empty)
|
||||
{
|
||||
if (_password == confirm)
|
||||
{
|
||||
if (ValidateProfiles())
|
||||
{
|
||||
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
|
||||
user.SiteId = PageState.Site.SiteId;
|
||||
@ -251,7 +227,6 @@ else
|
||||
user.Email = email;
|
||||
user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname;
|
||||
user.PhotoFileId = null;
|
||||
user.PhotoFileId = filemanager.GetFileId();
|
||||
if (user.PhotoFileId == -1)
|
||||
{
|
||||
user.PhotoFileId = null;
|
||||
@ -271,6 +246,7 @@ else
|
||||
AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Password.NoMatch"], MessageType.Warning);
|
||||
@ -290,27 +266,33 @@ else
|
||||
|
||||
private bool ValidateProfiles()
|
||||
{
|
||||
bool valid = true;
|
||||
foreach (Profile profile in profiles)
|
||||
{
|
||||
if (string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)) && !string.IsNullOrEmpty(profile.DefaultValue))
|
||||
var value = GetProfileValue(profile.Name, string.Empty);
|
||||
if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue))
|
||||
{
|
||||
settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue);
|
||||
}
|
||||
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||
{
|
||||
if (valid == true && profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)))
|
||||
if (profile.IsRequired && string.IsNullOrEmpty(value))
|
||||
{
|
||||
valid = false;
|
||||
AddModuleMessage(string.Format(SharedLocalizer["ProfileRequired"], profile.Title), MessageType.Warning);
|
||||
return false;
|
||||
}
|
||||
if (valid == true && !string.IsNullOrEmpty(profile.Validation))
|
||||
if (!string.IsNullOrEmpty(profile.Validation))
|
||||
{
|
||||
Regex regex = new Regex(profile.Validation);
|
||||
valid = regex.Match(SettingService.GetSetting(settings, profile.Name, string.Empty)).Success;
|
||||
bool valid = regex.Match(value).Success;
|
||||
if (!valid)
|
||||
{
|
||||
AddModuleMessage(string.Format(SharedLocalizer["ProfileInvalid"], profile.Title), MessageType.Warning);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ProfileChanged(ChangeEventArgs e, string SettingName)
|
||||
|
@ -17,26 +17,17 @@ else
|
||||
{
|
||||
<TabStrip>
|
||||
<TabPanel Name="Users" Heading="Users" ResourceKey="Users">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<div class="col-sm-4">
|
||||
<ActionLink Action="Add" Text="Add User" Security="SecurityAccessLevel.Edit" ResourceKey="AddUser" />
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<input class="form-control" @bind="@_search" />
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<button type="button" class="btn btn-secondary" @onclick="OnSearch">@SharedLocalizer["Search"]</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Pager Items="@users" RowClass="align-middle">
|
||||
<ActionLink Action="Add" Text="Add User" Security="SecurityAccessLevel.Edit" ResourceKey="AddUser" />
|
||||
<ActionLink Text="Import Users" Class="btn btn-secondary ms-2" Action="Users" Security="SecurityAccessLevel.Admin" ResourceKey="ImportUsers"/>
|
||||
|
||||
<Pager Items="@users" RowClass="align-middle" SearchProperties="User.Username,User.Email,User.DisplayName">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th class="app-sort-th link-primary text-decoration-underline" @onclick="@(() => SortTable("Username"))">@Localizer["Username"]<i class="@(SetSortIcon("Username"))"></i></th>
|
||||
<th class="app-sort-th link-primary text-decoration-underline" @onclick="@(() => SortTable("DisplayName"))">@Localizer["Name"]<i class="@(SetSortIcon("DisplayName"))"></i></th>
|
||||
<th class="app-sort-th link-primary text-decoration-underline" @onclick="@(() => SortTable("Email"))">@Localizer["Email"]<i class="@(SetSortIcon("Email"))"></i></th>
|
||||
<th class="app-sort-th link-primary text-decoration-underline" @onclick="@(() => SortTable("LastLoginOn"))">@Localizer["LastLoginOn"]<i class="@(SetSortIcon("LastLoginOn"))"></i></th>
|
||||
</Header>
|
||||
<Row>
|
||||
@ -50,7 +41,8 @@ else
|
||||
<ActionLink Action="Roles" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="Roles" />
|
||||
</td>
|
||||
<td>@context.User.Username</td>
|
||||
<td>@((MarkupString)string.Format("<a href=\"mailto:{0}\">{1}</a>", @context.User.Email, @context.User.DisplayName))</td>
|
||||
<td>@context.User.DisplayName</td>
|
||||
<td>@((MarkupString)string.Format("<a href=\"mailto:{0}\">{1}</a>", @context.User.Email, @context.User.Email))</td>
|
||||
<td>@((context.User.LastLoginOn != DateTime.MinValue) ? string.Format("{0:dd-MMM-yyyy HH:mm:ss}", context.User.LastLoginOn) : "")</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
@ -106,6 +98,21 @@ else
|
||||
<input id="cookiename" class="form-control" @bind="@_cookiename" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="cookieexpiration" HelpText="You can choose to use a custom authentication cookie expiration timespan for each site (e.g. '08:00:00' for 8 hours). The default is 14 days if not specified." ResourceKey="CookieExpiration">Cookie Expiration Timespan:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="cookieexpiration" class="form-control" @bind="@_cookieexpiration" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="alwaysremember" HelpText="Enabling this option will set a permanent cookie in conjunction with the Cookie Expiration Timespan, which will automatically sign in users the next time they visit the site. By default the site will use session cookies." ResourceKey="AlwaysRemember">Always Remember User?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="alwaysremember" class="form-select" @bind="@_alwaysremember">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</Section>
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
@ -259,6 +266,21 @@ else
|
||||
<input id="parameters" class="form-control" @bind="@_parameters" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="authresponsetype" HelpText="Specify the authorization response type" ResourceKey="AuthResponseType">Authorization Response Type</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="authresponsetype" class="form-select" @bind="@_authresponsetype" required>
|
||||
<option value="code">@Localizer["AuthFlow.Code"]</option>
|
||||
<option value="code id_token">@Localizer["AuthFlow.CodeIdToken"]</option>
|
||||
<option value="code id_token token">@Localizer["AuthFlow.CodeIdTokenToken"]</option>
|
||||
<option value="code token">@Localizer["AuthFlow.CodeToken"]</option>
|
||||
<option value="id_token">@Localizer["AuthFlow.IdToken"]</option>
|
||||
<option value="id_token token">@Localizer["AuthFlow.IdTokenToken"]</option>
|
||||
<option value="token">@Localizer["AuthFlow.Token"]</option>
|
||||
<option value="none">@Localizer["AuthFlow.None"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="pkce" HelpText="Indicate if the provider supports Proof Key for Code Exchange (PKCE)" ResourceKey="PKCE">Use PKCE?</Label>
|
||||
<div class="col-sm-9">
|
||||
@ -316,6 +338,15 @@ else
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="verifyusers" HelpText="Do you want existing users to perform an additional email verification step to link their external login? If you disable this option, existing users will be linked automatically." ResourceKey="VerifyUsers">Verify Existing Users?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="verifyusers" class="form-select" @bind="@_verifyusers">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</Section>
|
||||
<Section Name="Token" Heading="Token Settings" ResourceKey="TokenSettings">
|
||||
@ -365,14 +396,14 @@ else
|
||||
}
|
||||
|
||||
@code {
|
||||
private List<UserRole> allusers;
|
||||
private List<UserRole> users;
|
||||
private string _search = "";
|
||||
|
||||
private string _allowregistration;
|
||||
private string _allowsitelogin;
|
||||
private string _twofactor;
|
||||
private string _cookiename;
|
||||
private string _cookieexpiration;
|
||||
private string _alwaysremember;
|
||||
|
||||
private string _minimumlength;
|
||||
private string _uniquecharacters;
|
||||
@ -397,6 +428,7 @@ else
|
||||
private string _scopes;
|
||||
private string _parameters;
|
||||
private string _pkce;
|
||||
private string _authresponsetype;
|
||||
private string _redirecturl;
|
||||
private string _identifierclaimtype;
|
||||
private string _emailclaimtype;
|
||||
@ -404,6 +436,7 @@ else
|
||||
private string _profileclaimtypes;
|
||||
private string _domainfilter;
|
||||
private string _createusers;
|
||||
private string _verifyusers;
|
||||
|
||||
private string _secret;
|
||||
private string _secrettype = "password";
|
||||
@ -420,7 +453,6 @@ else
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadUserSettingsAsync();
|
||||
await LoadUsersAsync(true);
|
||||
|
||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
@ -431,6 +463,8 @@ else
|
||||
{
|
||||
_twofactor = SettingService.GetSetting(settings, "LoginOptions:TwoFactor", "false");
|
||||
_cookiename = SettingService.GetSetting(settings, "LoginOptions:CookieName", ".AspNetCore.Identity.Application");
|
||||
_cookieexpiration = SettingService.GetSetting(settings, "LoginOptions:CookieExpiration", "");
|
||||
_alwaysremember = SettingService.GetSetting(settings, "LoginOptions:AlwaysRemember", "false");
|
||||
|
||||
_minimumlength = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredLength", "6");
|
||||
_uniquecharacters = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", "1");
|
||||
@ -455,6 +489,7 @@ else
|
||||
_scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", "");
|
||||
_parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", "");
|
||||
_pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false");
|
||||
_authresponsetype = SettingService.GetSetting(settings, "ExternalLogin:AuthResponseType", "code");
|
||||
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;
|
||||
_identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "sub");
|
||||
_emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "email");
|
||||
@ -462,6 +497,7 @@ else
|
||||
_profileclaimtypes = SettingService.GetSetting(settings, "ExternalLogin:ProfileClaimTypes", "");
|
||||
_domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", "");
|
||||
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
|
||||
_verifyusers = SettingService.GetSetting(settings, "ExternalLogin:VerifyUsers", "true");
|
||||
|
||||
_secret = SettingService.GetSetting(settings, "JwtOptions:Secret", "");
|
||||
_togglesecret = SharedLocalizer["ShowPassword"];
|
||||
@ -475,32 +511,14 @@ else
|
||||
{
|
||||
if (load)
|
||||
{
|
||||
allusers = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
|
||||
users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
var hosts = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Host);
|
||||
allusers.AddRange(hosts);
|
||||
allusers = allusers.OrderBy(u => u.User.DisplayName).ToList();
|
||||
users.AddRange(hosts);
|
||||
users = users.OrderBy(u => u.User.DisplayName).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
users = allusers;
|
||||
if (!string.IsNullOrEmpty(_search))
|
||||
{
|
||||
users = users.Where(item =>
|
||||
(
|
||||
item.User.Username.Contains(_search, StringComparison.OrdinalIgnoreCase) ||
|
||||
item.User.Email.Contains(_search, StringComparison.OrdinalIgnoreCase) ||
|
||||
item.User.DisplayName.Contains(_search, StringComparison.OrdinalIgnoreCase)
|
||||
)
|
||||
).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnSearch()
|
||||
{
|
||||
await UpdateUserSettingsAsync();
|
||||
await LoadUsersAsync(false);
|
||||
}
|
||||
|
||||
private async Task DeleteUser(UserRole UserRole)
|
||||
@ -523,21 +541,6 @@ else
|
||||
}
|
||||
}
|
||||
|
||||
private string settingSearch = "AU-search";
|
||||
|
||||
private async Task LoadUserSettingsAsync()
|
||||
{
|
||||
Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
|
||||
_search = SettingService.GetSetting(settings, settingSearch, "");
|
||||
}
|
||||
|
||||
private async Task UpdateUserSettingsAsync()
|
||||
{
|
||||
Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
|
||||
settings = SettingService.SetSetting(settings, settingSearch, _search);
|
||||
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
|
||||
}
|
||||
|
||||
private async Task SaveSiteSettings()
|
||||
{
|
||||
try
|
||||
@ -553,6 +556,8 @@ else
|
||||
{
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:AlwaysRemember", _alwaysremember, false);
|
||||
|
||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredLength", _minimumlength, true);
|
||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", _uniquecharacters, true);
|
||||
@ -576,12 +581,14 @@ else
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:Scopes", _scopes, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:Parameters", _parameters, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:AuthResponseType", _authresponsetype, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:IdentifierClaimType", _identifierclaimtype, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:EmailClaimType", _emailclaimtype, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimType", _roleclaimtype, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ProfileClaimTypes", _profileclaimtypes, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:VerifyUsers", _verifyusers, true);
|
||||
|
||||
if (!string.IsNullOrEmpty(_secret) && _secret.Length < 16) _secret = (_secret + "????????????????").Substring(0, 16);
|
||||
settings = SettingService.SetSetting(settings, "JwtOptions:Secret", _secret, true);
|
||||
|
69
Oqtane.Client/Modules/Admin/Users/Users.razor
Normal file
69
Oqtane.Client/Modules/Admin/Users/Users.razor
Normal file
@ -0,0 +1,69 @@
|
||||
@namespace Oqtane.Modules.Admin.Users
|
||||
@inherits ModuleBase
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IUserService UserService
|
||||
@inject IStringLocalizer<Users> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="importfile" HelpText="Upload or select a tab delimited text file containing user information. The file must be in the Template format specified (Roles can be specified as a comma delimited list)." ResourceKey="ImportFile">Import File:</Label>
|
||||
<div class="col-sm-9">
|
||||
<FileManager Id="importfile" @ref="_filemanager" Filter="txt" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="notify" HelpText="Indicate if new users should receive an email notification" ResourceKey="Notify">Notify? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="notify" class="form-select" @bind="@_notify" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="ImportUsers">@Localizer["Import"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
<a class="btn btn-info" href="/users.txt" target="_new">@Localizer["Template"]</a>
|
||||
|
||||
@code {
|
||||
private FileManager _filemanager;
|
||||
|
||||
public override string Title => "Import Users";
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||
|
||||
private string _notify = "True";
|
||||
|
||||
private async Task ImportUsers()
|
||||
{
|
||||
try
|
||||
{
|
||||
var fileid = _filemanager.GetFileId();
|
||||
if (fileid != -1)
|
||||
{
|
||||
ShowProgressIndicator();
|
||||
var results = await UserService.ImportUsersAsync(PageState.Site.SiteId, fileid, bool.Parse(_notify));
|
||||
if (bool.Parse(results["Success"]))
|
||||
{
|
||||
AddModuleMessage(string.Format(Localizer["Message.Import.Success"], results["Users"]), MessageType.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Import.Failure"], MessageType.Error);
|
||||
}
|
||||
HideProgressIndicator();
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Import.Validation"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Importing Users {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Import"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
}
|
@ -40,10 +40,17 @@
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FileId != -1 && _file != null && !UploadMultiple)
|
||||
{
|
||||
<input class="form-control" @bind="@_file.Name" disabled />
|
||||
}
|
||||
}
|
||||
@if (ShowUpload && _haseditpermission)
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col mt-2">
|
||||
<div class="row mt-2">
|
||||
<div class="col">
|
||||
@if (UploadMultiple)
|
||||
{
|
||||
<input type="file" id="@_fileinputid" name="file" accept="@_filter" multiple />
|
||||
@ -53,9 +60,9 @@
|
||||
<input type="file" id="@_fileinputid" name="file" accept="@_filter" />
|
||||
}
|
||||
</div>
|
||||
<div class="col mt-2 text-end">
|
||||
<div class="col-auto">
|
||||
<button type="button" class="btn btn-success" @onclick="UploadFiles">@SharedLocalizer["Upload"]</button>
|
||||
@if (GetFileId() != -1)
|
||||
@if (FileId != -1 && !UploadMultiple)
|
||||
{
|
||||
<button type="button" class="btn btn-danger mx-1" @onclick="DeleteFile">@SharedLocalizer["Delete"]</button>
|
||||
}
|
||||
@ -341,7 +348,8 @@
|
||||
string restricted = "";
|
||||
foreach (var upload in uploads)
|
||||
{
|
||||
var extension = (upload.LastIndexOf(".") != -1) ? upload.Substring(upload.LastIndexOf(".") + 1) : "";
|
||||
var filename = upload.Split(':')[0];
|
||||
var extension = (filename.LastIndexOf(".") != -1) ? filename.Substring(filename.LastIndexOf(".") + 1) : "";
|
||||
if (!Constants.UploadableFiles.Split(',').Contains(extension.ToLower()))
|
||||
{
|
||||
restricted += (restricted == "" ? "" : ",") + extension;
|
||||
@ -368,24 +376,32 @@
|
||||
while (upload < uploads.Length && success)
|
||||
{
|
||||
success = false;
|
||||
// note that progressive retry will only wait a maximum of 15 seconds which may not be long enough for very large file uploads
|
||||
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 = 2; // 2 Mbps (3G ranges from 300Kbps to 3Mbps)
|
||||
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 < 5 && !success)
|
||||
while (attempts < maxattempts && !success)
|
||||
{
|
||||
attempts += 1;
|
||||
Thread.Sleep(1000 * attempts); // progressive retry
|
||||
Thread.Sleep(sleep);
|
||||
|
||||
if (Folder == Constants.PackagesFolder)
|
||||
{
|
||||
var files = await FileService.GetFilesAsync(folder);
|
||||
if (files != null && files.Any(item => item.Name == uploads[upload]))
|
||||
if (files != null && files.Any(item => item.Name == filename))
|
||||
{
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var file = await FileService.GetFileAsync(int.Parse(folder), uploads[upload]);
|
||||
var file = await FileService.GetFileAsync(int.Parse(folder), filename);
|
||||
if (file != null)
|
||||
{
|
||||
success = true;
|
||||
@ -433,7 +449,7 @@
|
||||
else
|
||||
{
|
||||
// set FileId to first file in upload collection
|
||||
var file = await FileService.GetFileAsync(int.Parse(folder), uploads[0]);
|
||||
var file = await FileService.GetFileAsync(int.Parse(folder), uploads[0].Split(":")[0]);
|
||||
if (file != null)
|
||||
{
|
||||
FileId = file.FileId;
|
||||
|
@ -1,10 +1,20 @@
|
||||
@namespace Oqtane.Modules.Controls
|
||||
@inherits ModuleControlBase
|
||||
@inject IStringLocalizerFactory LocalizerFactory
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
@typeparam TableItem
|
||||
|
||||
@if (ItemList != null)
|
||||
{
|
||||
@if (!string.IsNullOrEmpty(SearchProperties))
|
||||
{
|
||||
<div class="input-group my-3">
|
||||
<input id="search" class="form-control" placeholder=@string.Format(Localizer["SearchPlaceholder"], FormatSearchProperties()) @bind="@_search" />
|
||||
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if ((Toolbar == "Top" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
|
||||
{
|
||||
<ul class="pagination justify-content-center my-2">
|
||||
@ -175,6 +185,9 @@
|
||||
private int _startPage = 0;
|
||||
private int _endPage = 0;
|
||||
private int _columns = 0;
|
||||
private string _search = "";
|
||||
|
||||
private IEnumerable<TableItem> AllItems;
|
||||
|
||||
[Parameter]
|
||||
public string Format { get; set; } // Table or Grid
|
||||
@ -221,6 +234,9 @@
|
||||
[Parameter]
|
||||
public Action<int> OnPageChange { get; set; } // a method to be executed in the calling component when the page changes
|
||||
|
||||
[Parameter]
|
||||
public string SearchProperties { get; set; } // comma delimited list of property names to include in search
|
||||
|
||||
private IEnumerable<TableItem> ItemList { get; set; }
|
||||
|
||||
protected override void OnInitialized()
|
||||
@ -276,6 +292,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(SearchProperties))
|
||||
{
|
||||
AllItems = Items; // only used in search
|
||||
if (!string.IsNullOrEmpty(_search))
|
||||
{
|
||||
Search();
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(PageSize))
|
||||
{
|
||||
_maxItems = int.Parse(PageSize);
|
||||
@ -369,4 +394,75 @@
|
||||
|
||||
UpdateList(_page);
|
||||
}
|
||||
|
||||
public void Search()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_search))
|
||||
{
|
||||
Items = AllItems.Where(item =>
|
||||
{
|
||||
var values = SearchProperties.Split(',')
|
||||
.Select(itemType => GetPropertyValue(item, itemType))
|
||||
.Where(value => value != null)
|
||||
.Select(value => value.ToString().ToLower());
|
||||
|
||||
return values.Any(value => value.Contains(_search.ToLower()));
|
||||
}).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
Items = AllItems;
|
||||
}
|
||||
_pages = (int)Math.Ceiling(Items.Count() / (decimal)_maxItems);
|
||||
UpdateList(1);
|
||||
}
|
||||
|
||||
private object GetPropertyValue(object obj, string propertyName)
|
||||
{
|
||||
var index = propertyName.IndexOf(".");
|
||||
if (index != -1)
|
||||
{
|
||||
var propertyInfo = obj.GetType().GetProperty(propertyName.Substring(0, index));
|
||||
if (propertyInfo != null)
|
||||
{
|
||||
return GetPropertyValue(propertyInfo.GetValue(obj), propertyName.Substring(index + 1));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
var propertyInfo = obj.GetType().GetProperty(propertyName);
|
||||
if (propertyInfo != null)
|
||||
{
|
||||
return propertyInfo.GetValue(obj);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_search = "";
|
||||
Items = AllItems;
|
||||
_pages = (int)Math.Ceiling(Items.Count() / (decimal)_maxItems);
|
||||
UpdateList(1);
|
||||
}
|
||||
|
||||
private string FormatSearchProperties()
|
||||
{
|
||||
var properties = new List<string>();
|
||||
foreach (var property in SearchProperties.Split(',', StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
var index = property.LastIndexOf(".");
|
||||
if (index != -1)
|
||||
{
|
||||
properties.Add(property.Substring(index + 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
properties.Add(property);
|
||||
}
|
||||
}
|
||||
return string.Join(",", properties);
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Version>4.0.3</Version>
|
||||
<Version>5.0.0</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -12,7 +12,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.0</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<RootNamespace>Oqtane</RootNamespace>
|
||||
@ -21,12 +21,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.5" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="7.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="7.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
|
||||
<PackageReference Include="System.Net.Http.Json" Version="7.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageReference Include="System.Net.Http.Json" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -132,6 +132,9 @@
|
||||
<data name="Message.Require.ValidName" xml:space="preserve">
|
||||
<value>You Must Provide A Valid Owner Name And Module Name ( ie. No Punctuation Or Spaces And The Values Cannot Be The Same ) And Choose A Template</value>
|
||||
</data>
|
||||
<data name="Message.Require.ValidDescription" xml:space="preserve">
|
||||
<value>You Must Provide A Valid Description (ie. No Punctuation)</value>
|
||||
</data>
|
||||
<data name="OwnerName.HelpText" xml:space="preserve">
|
||||
<value>Enter the name of the organization who is developing this module. It should not contain spaces or punctuation.</value>
|
||||
</data>
|
||||
|
@ -159,4 +159,10 @@
|
||||
<data name="Module Settings" xml:space="preserve">
|
||||
<value>Module Settings</value>
|
||||
</data>
|
||||
<data name="Pane.HelpText" xml:space="preserve">
|
||||
<value>The pane where the module will be displayed</value>
|
||||
</data>
|
||||
<data name="Pane.Text" xml:space="preserve">
|
||||
<value>Pane:</value>
|
||||
</data>
|
||||
</root>
|
@ -189,4 +189,10 @@
|
||||
<data name="Validation.Text" xml:space="preserve">
|
||||
<value>Validation: </value>
|
||||
</data>
|
||||
<data name="Rows.HelpText" xml:space="preserve">
|
||||
<value>The number of rows for text entry (one is the default)</value>
|
||||
</data>
|
||||
<data name="Rows.Text" xml:space="preserve">
|
||||
<value>Rows: </value>
|
||||
</data>
|
||||
</root>
|
@ -138,4 +138,13 @@
|
||||
<data name="EditProfile.Text" xml:space="preserve">
|
||||
<value>Edit</value>
|
||||
</data>
|
||||
<data name="Category" xml:space="preserve">
|
||||
<value>Category</value>
|
||||
</data>
|
||||
<data name="Order" xml:space="preserve">
|
||||
<value>Order</value>
|
||||
</data>
|
||||
<data name="Title" xml:space="preserve">
|
||||
<value>Title</value>
|
||||
</data>
|
||||
</root>
|
@ -136,7 +136,7 @@
|
||||
<value>User Account Created. Please Check Your Email For Verification Instructions.</value>
|
||||
</data>
|
||||
<data name="Error.User.AddInfo" xml:space="preserve">
|
||||
<value>Error Adding User. Please Ensure Password Meets Complexity Requirements And Username Is Not Already In Use.</value>
|
||||
<value>Error Adding User. Please Ensure Password Meets Complexity Requirements And Username And Email Is Not Already In Use.</value>
|
||||
</data>
|
||||
<data name="Message.Password.NoMatch" xml:space="preserve">
|
||||
<value>Passwords Entered Do Not Match</value>
|
||||
|
@ -396,4 +396,10 @@
|
||||
<data name="SiteGuid.Text" xml:space="preserve">
|
||||
<value>ID:</value>
|
||||
</data>
|
||||
<data name="Retention.HelpText" xml:space="preserve">
|
||||
<value>Number of days of notifications to retain</value>
|
||||
</data>
|
||||
<data name="Retention.Text" xml:space="preserve">
|
||||
<value>Retention (Days):</value>
|
||||
</data>
|
||||
</root>
|
@ -132,9 +132,6 @@
|
||||
<data name="Theme.Select" xml:space="preserve">
|
||||
<value>Select Theme</value>
|
||||
</data>
|
||||
<data name="DefaultContainer.Admin" xml:space="preserve">
|
||||
<value>Default Admin Container</value>
|
||||
</data>
|
||||
<data name="Aliases.HelpText" xml:space="preserve">
|
||||
<value>The urls for the site (comman delimited). This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or a virtual folder (ie. domain.com/folder).</value>
|
||||
</data>
|
||||
@ -183,9 +180,6 @@
|
||||
<data name="DefaultTheme.HelpText" xml:space="preserve">
|
||||
<value>Select the default theme for the site</value>
|
||||
</data>
|
||||
<data name="AdminContainer.HelpText" xml:space="preserve">
|
||||
<value>Select the admin container for the site</value>
|
||||
</data>
|
||||
<data name="SiteTemplate.HelpText" xml:space="preserve">
|
||||
<value>Select the site template</value>
|
||||
</data>
|
||||
@ -207,9 +201,6 @@
|
||||
<data name="Name.Text" xml:space="preserve">
|
||||
<value>Site Name: </value>
|
||||
</data>
|
||||
<data name="AdminContainer.Text" xml:space="preserve">
|
||||
<value>Admin Container: </value>
|
||||
</data>
|
||||
<data name="SiteTemplate.Text" xml:space="preserve">
|
||||
<value>Site Template: </value>
|
||||
</data>
|
||||
|
@ -144,7 +144,10 @@
|
||||
<data name="Message.Text" xml:space="preserve">
|
||||
<value>Framework Is Already Up To Date</value>
|
||||
</data>
|
||||
<data name="MessgeUpgrade.Text" xml:space="preserve">
|
||||
<data name="MessageUpgrade.Text" xml:space="preserve">
|
||||
<value>Upload A Framework Package (Oqtane.Framework.version.nupkg) And Then Select Upgrade</value>
|
||||
</data>
|
||||
<data name="Localhost.Text" xml:space="preserve">
|
||||
<value>You Cannot Perform A System Update In A Development Environment</value>
|
||||
</data>
|
||||
</root>
|
@ -121,7 +121,7 @@
|
||||
<value>Message: </value>
|
||||
</data>
|
||||
<data name="Message.User.Invalid" xml:space="preserve">
|
||||
<value>User Does Not Exist. Please Verify That The Username Provided Is Correct.</value>
|
||||
<value>The User Specified Does Not Exist</value>
|
||||
</data>
|
||||
<data name="Error.Notification.Add" xml:space="preserve">
|
||||
<value>Error Adding Notification</value>
|
||||
@ -133,7 +133,7 @@
|
||||
<value>Enter the subject of the message</value>
|
||||
</data>
|
||||
<data name="Message.HelpText" xml:space="preserve">
|
||||
<value>Enter the message</value>
|
||||
<value>Enter the message content</value>
|
||||
</data>
|
||||
<data name="To.Text" xml:space="preserve">
|
||||
<value>To: </value>
|
||||
@ -144,4 +144,10 @@
|
||||
<data name="Send Notification" xml:space="preserve">
|
||||
<value>Send Notification</value>
|
||||
</data>
|
||||
<data name="Message.Required" xml:space="preserve">
|
||||
<value>You Must Enter All Required Information</value>
|
||||
</data>
|
||||
<data name="Username.Enter" xml:space="preserve">
|
||||
<value>Enter Username</value>
|
||||
</data>
|
||||
</root>
|
@ -234,4 +234,10 @@
|
||||
<data name="DeleteNotification.Text" xml:space="preserve">
|
||||
<value>Delete</value>
|
||||
</data>
|
||||
<data name="NoNotificationsReceived.Text" xml:space="preserve">
|
||||
<value>No notifications have been received</value>
|
||||
</data>
|
||||
<data name="NoNotificationsSent.Text" xml:space="preserve">
|
||||
<value>No notifications have been sent</value>
|
||||
</data>
|
||||
</root>
|
@ -126,25 +126,43 @@
|
||||
<data name="Error.Notification.Add" xml:space="preserve">
|
||||
<value>Error Adding Notification</value>
|
||||
</data>
|
||||
<data name="Title" xml:space="preserve">
|
||||
<value>Title:</value>
|
||||
</data>
|
||||
<data name="Subject" xml:space="preserve">
|
||||
<value>Subject:</value>
|
||||
</data>
|
||||
<data name="Date" xml:space="preserve">
|
||||
<value>Date:</value>
|
||||
</data>
|
||||
<data name="Message" xml:space="preserve">
|
||||
<value>Message:</value>
|
||||
</data>
|
||||
<data name="Reply" xml:space="preserve">
|
||||
<value>Reply</value>
|
||||
</data>
|
||||
<data name="OriginalMessage" xml:space="preserve">
|
||||
<value>Original Message</value>
|
||||
</data>
|
||||
<data name="View Notification" xml:space="preserve">
|
||||
<value>View Notification</value>
|
||||
</data>
|
||||
<data name="Date.HelpText" xml:space="preserve">
|
||||
<value>The date the message was sent</value>
|
||||
</data>
|
||||
<data name="Date.Text" xml:space="preserve">
|
||||
<value>Sent:</value>
|
||||
</data>
|
||||
<data name="From.HelpText" xml:space="preserve">
|
||||
<value>The user who sent the message</value>
|
||||
</data>
|
||||
<data name="From.Text" xml:space="preserve">
|
||||
<value>From:</value>
|
||||
</data>
|
||||
<data name="Message.HelpText" xml:space="preserve">
|
||||
<value>The content of the message</value>
|
||||
</data>
|
||||
<data name="Message.Text" xml:space="preserve">
|
||||
<value>Message:</value>
|
||||
</data>
|
||||
<data name="RE" xml:space="preserve">
|
||||
<value>RE:</value>
|
||||
</data>
|
||||
<data name="Subject.HelpText" xml:space="preserve">
|
||||
<value>The subject of the message</value>
|
||||
</data>
|
||||
<data name="Subject.Text" xml:space="preserve">
|
||||
<value>Subject:</value>
|
||||
</data>
|
||||
<data name="System" xml:space="preserve">
|
||||
<value>System</value>
|
||||
</data>
|
||||
<data name="To.HelpText" xml:space="preserve">
|
||||
<value>The user who will be the recipient of the message</value>
|
||||
</data>
|
||||
<data name="To.Text" xml:space="preserve">
|
||||
<value>To:</value>
|
||||
</data>
|
||||
</root>
|
@ -118,7 +118,7 @@
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Error.User.AddCheckPass" xml:space="preserve">
|
||||
<value>Error Adding User. Please Ensure Password Meets Complexity Requirements And Username Is Not Already In Use.</value>
|
||||
<value>Error Adding User. Please Ensure Password Meets Complexity Requirements And Username And Email Is Not Already In Use.</value>
|
||||
</data>
|
||||
<data name="Message.Password.NoMatch" xml:space="preserve">
|
||||
<value>Passwords Entered Do Not Match</value>
|
||||
@ -171,4 +171,10 @@
|
||||
<data name="Password.Placeholder" xml:space="preserve">
|
||||
<value>Password</value>
|
||||
</data>
|
||||
<data name="Notify.HelpText" xml:space="preserve">
|
||||
<value>Indicate if new users should receive an email notification</value>
|
||||
</data>
|
||||
<data name="Notify.Text" xml:space="preserve">
|
||||
<value>Notify?</value>
|
||||
</data>
|
||||
</root>
|
@ -168,12 +168,6 @@
|
||||
<data name="Password.Text" xml:space="preserve">
|
||||
<value>Password:</value>
|
||||
</data>
|
||||
<data name="Photo.HelpText" xml:space="preserve">
|
||||
<value>A photo of the user</value>
|
||||
</data>
|
||||
<data name="Photo.Text" xml:space="preserve">
|
||||
<value>Photo:</value>
|
||||
</data>
|
||||
<data name="Username.HelpText" xml:space="preserve">
|
||||
<value>The unique username for a user. Note that this field can not be modified.</value>
|
||||
</data>
|
||||
|
@ -402,4 +402,55 @@
|
||||
<data name="Name" xml:space="preserve">
|
||||
<value>Name</value>
|
||||
</data>
|
||||
<data name="Email" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
</data>
|
||||
<data name="ImportUsers.Text" xml:space="preserve">
|
||||
<value>Import Users</value>
|
||||
</data>
|
||||
<data name="AuthFlow.Code" xml:space="preserve">
|
||||
<value>code</value>
|
||||
</data>
|
||||
<data name="AuthFlow.CodeIdToken" xml:space="preserve">
|
||||
<value>code id_token</value>
|
||||
</data>
|
||||
<data name="AuthFlow.CodeIdTokenToken" xml:space="preserve">
|
||||
<value>code id_token token</value>
|
||||
</data>
|
||||
<data name="AuthFlow.CodeToken" xml:space="preserve">
|
||||
<value>code token</value>
|
||||
</data>
|
||||
<data name="AuthFlow.IdToken" xml:space="preserve">
|
||||
<value>id_token</value>
|
||||
</data>
|
||||
<data name="AuthFlow.IdTokenToken" xml:space="preserve">
|
||||
<value>id_token token</value>
|
||||
</data>
|
||||
<data name="AuthFlow.None" xml:space="preserve">
|
||||
<value>none</value>
|
||||
</data>
|
||||
<data name="AuthFlow.Token" xml:space="preserve">
|
||||
<value>token</value>
|
||||
</data>
|
||||
<data name="AuthResponseType" xml:space="preserve">
|
||||
<value>Authorization Response Type</value>
|
||||
</data>
|
||||
<data name="VerifyUsers.HelpText" xml:space="preserve">
|
||||
<value>Do you want existing users to perform an additional email verification step to link their external login? If you disable this option, existing users will be linked automatically.</value>
|
||||
</data>
|
||||
<data name="VerifyUsers.Text" xml:space="preserve">
|
||||
<value>Verify Existing Users?</value>
|
||||
</data>
|
||||
<data name="AlwaysRemember.HelpText" xml:space="preserve">
|
||||
<value>Enabling this option will set a permanent cookie in conjunction with the Cookie Expiration Timespan, which will automatically sign in users the next time they visit the site. By default the site will use session cookies.</value>
|
||||
</data>
|
||||
<data name="AlwaysRemember.Text" xml:space="preserve">
|
||||
<value>Always Remember User?</value>
|
||||
</data>
|
||||
<data name="CookieExpiration.HelpText" xml:space="preserve">
|
||||
<value>You can choose to use a custom authentication cookie expiration timespan for each site (e.g. '08:00:00' for 8 hours). The default is 14 days if not specified.</value>
|
||||
</data>
|
||||
<data name="CookieExpiration.Text" xml:space="preserve">
|
||||
<value>Cookie Expiration Timespan:</value>
|
||||
</data>
|
||||
</root>
|
@ -117,61 +117,34 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Template.Text" xml:space="preserve">
|
||||
<value>Template: </value>
|
||||
<data name="ImportFile.HelpText" xml:space="preserve">
|
||||
<value>Upload or select a tab delimited text file containing user information. The file must be in the Template format specified (Roles can be specified as a comma delimited list).</value>
|
||||
</data>
|
||||
<data name="Template.Select" xml:space="preserve">
|
||||
<value>Select Template</value>
|
||||
<data name="ImportFile.Text" xml:space="preserve">
|
||||
<value>Import File:</value>
|
||||
</data>
|
||||
<data name="Module.Create" xml:space="preserve">
|
||||
<value>Create Module</value>
|
||||
<data name="Error.Import" xml:space="preserve">
|
||||
<value>Error Importing Users</value>
|
||||
</data>
|
||||
<data name="Module.Activate" xml:space="preserve">
|
||||
<value>Activate Module</value>
|
||||
<data name="Import" xml:space="preserve">
|
||||
<value>Import</value>
|
||||
</data>
|
||||
<data name="Info.Module.Creator" xml:space="preserve">
|
||||
<value>Please Note That The Module Creator Is Only Intended To Be Used In A Development Environment</value>
|
||||
<data name="Message.Import.Failure" xml:space="preserve">
|
||||
<value>User Import Failed. Please Review Your Event Log For More Detailed Information.</value>
|
||||
</data>
|
||||
<data name="Info.Module.Activate" xml:space="preserve">
|
||||
<value>Once You Have Compiled The Module And Restarted The Application You Can Activate The Module Below</value>
|
||||
<data name="Message.Import.Success" xml:space="preserve">
|
||||
<value>User Import Successful. {0} Users Imported.</value>
|
||||
</data>
|
||||
<data name="Success.Module.Create" xml:space="preserve">
|
||||
<value>The Source Code For Your Module Has Been Created At The Location Specified Below And Must Be Compiled In Order To Make It Functional. Once It Has Been Compiled You Must <a href={0}>Restart</a> Your Application To Apply These Changes.</value>
|
||||
<data name="Message.Import.Validation" xml:space="preserve">
|
||||
<value>You Must Specify A User File For Import</value>
|
||||
</data>
|
||||
<data name="Message.Require.ValidName" xml:space="preserve">
|
||||
<value>You Must Provide A Valid Owner Name And Module Name ( ie. No Punctuation Or Spaces And The Values Cannot Be The Same ) And Choose A Template</value>
|
||||
<data name="Template" xml:space="preserve">
|
||||
<value>Template</value>
|
||||
</data>
|
||||
<data name="OwnerName.HelpText" xml:space="preserve">
|
||||
<value>Enter the name of the organization who is developing this module. It should not contain spaces or punctuation.</value>
|
||||
<data name="Notify.HelpText" xml:space="preserve">
|
||||
<value>Indicate if new users should receive an email notification</value>
|
||||
</data>
|
||||
<data name="ModuleName.HelpText" xml:space="preserve">
|
||||
<value>Enter a name for this module. It should not contain spaces or punctuation.</value>
|
||||
</data>
|
||||
<data name="Description.HelpText" xml:space="preserve">
|
||||
<value>Enter a short description for the module</value>
|
||||
</data>
|
||||
<data name="Template.HelpText" xml:space="preserve">
|
||||
<value>Select a module template. Templates are located in the wwwroot/Modules/Templates folder on the server.</value>
|
||||
</data>
|
||||
<data name="FrameworkReference.HelpText" xml:space="preserve">
|
||||
<value>Select a framework reference version</value>
|
||||
</data>
|
||||
<data name="Location.HelpText" xml:space="preserve">
|
||||
<value>Location where the module will be created</value>
|
||||
</data>
|
||||
<data name="OwnerName.Text" xml:space="preserve">
|
||||
<value>Owner Name: </value>
|
||||
</data>
|
||||
<data name="ModuleName.Text" xml:space="preserve">
|
||||
<value>Module Name: </value>
|
||||
</data>
|
||||
<data name="Description.Text" xml:space="preserve">
|
||||
<value>Description: </value>
|
||||
</data>
|
||||
<data name="FrameworkReference.Text" xml:space="preserve">
|
||||
<value>Framework Reference: </value>
|
||||
</data>
|
||||
<data name="Location.Text" xml:space="preserve">
|
||||
<value>Location: </value>
|
||||
<data name="Notify.Text" xml:space="preserve">
|
||||
<value>Notify?</value>
|
||||
</data>
|
||||
</root>
|
@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
@ -120,4 +120,7 @@
|
||||
<data name="PageOfPages" xml:space="preserve">
|
||||
<value>Page {0} of {1}</value>
|
||||
</data>
|
||||
<data name="SearchPlaceholder" xml:space="preserve">
|
||||
<value>Search: {0}</value>
|
||||
</data>
|
||||
</root>
|
@ -426,4 +426,10 @@
|
||||
<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>
|
||||
</data>
|
||||
<data name="ProfileInvalid" xml:space="preserve">
|
||||
<value>{0} Is Not Valid</value>
|
||||
</data>
|
||||
<data name="ProfileRequired" xml:space="preserve">
|
||||
<value>{0} Is Required</value>
|
||||
</data>
|
||||
</root>
|
@ -189,4 +189,13 @@
|
||||
<data name="Confirm.Page.Delete" xml:space="preserve">
|
||||
<value>Are You Sure You Want To Delete This Page?</value>
|
||||
</data>
|
||||
<data name="Location" xml:space="preserve">
|
||||
<value>Location:</value>
|
||||
</data>
|
||||
<data name="LocationBottom" xml:space="preserve">
|
||||
<value>Bottom</value>
|
||||
</data>
|
||||
<data name="LocationTop" xml:space="preserve">
|
||||
<value>Top</value>
|
||||
</data>
|
||||
</root>
|
@ -142,5 +142,14 @@ namespace Oqtane.Services
|
||||
/// <param name="siteId">ID of a <see cref="Site"/></param>
|
||||
/// <returns></returns>
|
||||
Task<string> GetPasswordRequirementsAsync(int siteId);
|
||||
|
||||
/// <summary>
|
||||
/// Bulk import of users
|
||||
/// </summary>
|
||||
/// <param name="siteId">ID of a <see cref="Site"/></param>
|
||||
/// <param name="fileId">ID of a <see cref="File"/></param>
|
||||
/// <param name="notify">Indicates if new users should be notified by email</param>
|
||||
/// <returns></returns>
|
||||
Task<Dictionary<string, string>> ImportUsersAsync(int siteId, int fileId, bool notify);
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ using System.Net.Http;
|
||||
using System;
|
||||
using Oqtane.Documentation;
|
||||
using Oqtane.Shared;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Oqtane.Services
|
||||
{
|
||||
@ -18,7 +19,7 @@ namespace Oqtane.Services
|
||||
/// <inheritdoc />
|
||||
public async Task<Sync> GetSyncEventsAsync(DateTime lastSyncDate)
|
||||
{
|
||||
return await GetJsonAsync<Sync>($"{ApiUrl}/{lastSyncDate.ToString("yyyyMMddHHmmssfff")}");
|
||||
return await GetJsonAsync<Sync>($"{ApiUrl}/{lastSyncDate.ToString("yyyyMMddHHmmssfff", CultureInfo.InvariantCulture)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,9 @@ using Oqtane.Documentation;
|
||||
using System.Net;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Internal;
|
||||
using Oqtane.Modules.Admin.Roles;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Oqtane.Services
|
||||
{
|
||||
@ -123,5 +126,10 @@ namespace Oqtane.Services
|
||||
// format requirements
|
||||
return string.Format(passwordValidationCriteriaTemplate, minimumlength, uniquecharacters, digitRequirement, uppercaseRequirement, lowercaseRequirement, punctuationRequirement);
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, string>> ImportUsersAsync(int siteId, int fileId, bool notify)
|
||||
{
|
||||
return await PostJsonAsync<Dictionary<string, string>>($"{Apiurl}/import?siteid={siteId}&fileid={fileId}¬ify={notify}", null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
<div class="main g-0">
|
||||
<div class="top-row px-4">
|
||||
<div class="ms-auto"><UserProfile /> <Login /> <ControlPanel /></div>
|
||||
<div class="ms-auto"><UserProfile /> <Login /> <ControlPanel LanguageDropdownAlignment="right" /></div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="row px-4">
|
||||
|
@ -10,12 +10,14 @@
|
||||
@inject IPageModuleService PageModuleService
|
||||
@inject ILogService logger
|
||||
@inject ISettingService SettingService
|
||||
@inject IJSRuntime jsRuntime
|
||||
@inject IServiceProvider ServiceProvider
|
||||
@inject IStringLocalizer<ControlPanel> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@if (ShowLanguageSwitcher)
|
||||
{
|
||||
<LanguageSwitcher />
|
||||
<LanguageSwitcher DropdownAlignment="@LanguageDropdownAlignment" />
|
||||
}
|
||||
|
||||
@if (_showEditMode || (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered)))
|
||||
@ -109,10 +111,7 @@
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList))
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col text-center">
|
||||
<label for="Module" class="control-label">@Localizer["Module.Manage"]</label>
|
||||
@ -184,8 +183,6 @@
|
||||
<input type="text" name="Title" class="form-control" @bind="@Title" />
|
||||
</div>
|
||||
</div>
|
||||
@if (_pane.Length > 1)
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col text-center">
|
||||
<label for="Pane" class="control-label">@Localizer["Pane"]</label>
|
||||
@ -197,7 +194,15 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row">
|
||||
<div class="col text-center">
|
||||
<label for="Insert" class="control-label">@Localizer["Location"]</label>
|
||||
<select class="form-select" @bind="@Location">
|
||||
<option value="@int.MinValue">@Localizer["LocationTop"]</option>
|
||||
<option value="@int.MaxValue">@Localizer["LocationBottom"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col text-center">
|
||||
<label for="Container" class="control-label">@Localizer["Container"]</label>
|
||||
@ -220,13 +225,38 @@
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary col-12 mt-4" @onclick="@AddModule">@Localizer["Page.Module.Add"]</button>
|
||||
@((MarkupString)Message)
|
||||
<hr class="app-rule" />
|
||||
}
|
||||
|
||||
<div class="row d-flex">
|
||||
<div class="col">
|
||||
<button type="button" data-bs-dismiss="offcanvas" class="btn btn-secondary col-12" @onclick=@(async () => await LogoutUser())>@Localizer["Logout"]</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code{
|
||||
[Parameter]
|
||||
public string ButtonClass { get; set; } = "btn-outline-secondary";
|
||||
|
||||
[Parameter]
|
||||
public string ContainerClass { get; set; } = "offcanvas offcanvas-end";
|
||||
|
||||
[Parameter]
|
||||
public string HeaderClass { get; set; } = "offcanvas-header";
|
||||
|
||||
[Parameter]
|
||||
public string BodyClass { get; set; } = "offcanvas-body overflow-auto";
|
||||
|
||||
[Parameter]
|
||||
public bool ShowLanguageSwitcher { get; set; } = true;
|
||||
|
||||
[Parameter]
|
||||
public string LanguageDropdownAlignment { get; set; } = string.Empty; // Empty or Left or Right
|
||||
|
||||
private bool _canViewAdminDashboard = false;
|
||||
private bool _showEditMode = false;
|
||||
private bool _deleteConfirmation = false;
|
||||
@ -237,12 +267,22 @@
|
||||
private List<Module> _modules = new List<Module>();
|
||||
private List<ThemeControl> _containers = new List<ThemeControl>();
|
||||
private string _category = "Common";
|
||||
private string _pane = "";
|
||||
|
||||
protected string PageId { get; private set; } = "-";
|
||||
protected string ModuleId { get; private set; } = "-";
|
||||
protected string ModuleType { get; private set; } = "new";
|
||||
protected string ModuleDefinitionName { get; private set; } = "-";
|
||||
|
||||
protected string Title { get; private set; } = "";
|
||||
protected string ContainerType { get; private set; } = "";
|
||||
protected int Location { get; private set; } = int.MaxValue;
|
||||
protected string Visibility { get; private set; } = "view";
|
||||
protected string Message { get; private set; } = "";
|
||||
|
||||
private string settingCategory = "CP-category";
|
||||
private string settingPane = "CP-pane";
|
||||
|
||||
protected string Category
|
||||
{
|
||||
get => _category;
|
||||
@ -273,27 +313,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
protected string Title { get; private set; } = "";
|
||||
protected string ContainerType { get; private set; } = "";
|
||||
protected string Visibility { get; private set; } = "view";
|
||||
protected string Message { get; private set; } = "";
|
||||
|
||||
[Parameter]
|
||||
public string ButtonClass { get; set; } = "btn-outline-secondary";
|
||||
|
||||
[Parameter]
|
||||
public string ContainerClass { get; set; } = "offcanvas offcanvas-end";
|
||||
|
||||
[Parameter]
|
||||
public string HeaderClass { get; set; } = "offcanvas-header";
|
||||
|
||||
[Parameter]
|
||||
public string BodyClass { get; set; } = "offcanvas-body overflow-auto";
|
||||
|
||||
[Parameter]
|
||||
public bool ShowLanguageSwitcher { get; set; } = true;
|
||||
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
_canViewAdminDashboard = CanViewAdminDashboard();
|
||||
@ -301,8 +320,9 @@
|
||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList))
|
||||
{
|
||||
_showEditMode = true;
|
||||
_pages?.Clear();
|
||||
LoadSettingsAsync();
|
||||
|
||||
_pages?.Clear();
|
||||
foreach (Page p in PageState.Pages)
|
||||
{
|
||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
||||
@ -310,7 +330,6 @@
|
||||
_pages.Add(p);
|
||||
}
|
||||
}
|
||||
await LoadSettingsAsync();
|
||||
|
||||
var themes = await ThemeService.GetThemesAsync();
|
||||
_containers = ThemeService.GetContainerControls(themes, PageState.Page.ThemeType);
|
||||
@ -434,7 +453,7 @@
|
||||
}
|
||||
|
||||
pageModule.Pane = Pane;
|
||||
pageModule.Order = int.MaxValue;
|
||||
pageModule.Order = Location;
|
||||
pageModule.ContainerType = ContainerType;
|
||||
|
||||
if (pageModule.ContainerType == PageState.Site.DefaultContainerType)
|
||||
@ -613,15 +632,41 @@
|
||||
}
|
||||
}
|
||||
|
||||
private string settingCategory = "CP-category";
|
||||
private string settingPane = "CP-pane";
|
||||
private string _pane = "";
|
||||
|
||||
private async Task LoadSettingsAsync()
|
||||
// the following code is duplicated from LoginBase
|
||||
private async Task LogoutUser()
|
||||
{
|
||||
Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
|
||||
_category = SettingService.GetSetting(settings, settingCategory, "Common");
|
||||
var pane = SettingService.GetSetting(settings, settingPane, "");
|
||||
await LoggingService.Log(PageState.Alias, PageState.Page.PageId, null, PageState.User?.UserId, GetType().AssemblyQualifiedName, "Logout", LogFunction.Security, LogLevel.Information, null, "User Logout For Username {Username}", PageState.User?.Username);
|
||||
|
||||
Route route = new Route(PageState.Uri.AbsoluteUri, PageState.Alias.Path);
|
||||
var url = route.PathAndQuery;
|
||||
|
||||
// verify if anonymous users can access page
|
||||
if (!UserSecurity.IsAuthorized(null, PermissionNames.View, PageState.Page.PermissionList))
|
||||
{
|
||||
url = PageState.Alias.Path;
|
||||
}
|
||||
|
||||
if (PageState.Runtime == Shared.Runtime.Hybrid)
|
||||
{
|
||||
// hybrid apps utilize an interactive logout
|
||||
await UserService.LogoutUserAsync(PageState.User);
|
||||
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
|
||||
authstateprovider.NotifyAuthenticationChanged();
|
||||
NavigationManager.NavigateTo(url, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// post to the Logout page to complete the logout process
|
||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, returnurl = url };
|
||||
var interop = new Interop(jsRuntime);
|
||||
await interop.SubmitForm(Utilities.TenantUrl(PageState.Alias, "/pages/logout/"), fields);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadSettingsAsync()
|
||||
{
|
||||
_category = SettingService.GetSetting(PageState.User.Settings, settingCategory, "Common");
|
||||
var pane = SettingService.GetSetting(PageState.User.Settings, settingPane, "");
|
||||
if (PageState.Page.Panes.Contains(pane))
|
||||
{
|
||||
_pane = pane;
|
||||
|
@ -8,11 +8,11 @@
|
||||
|
||||
@if (_supportedCultures?.Count() > 1)
|
||||
{
|
||||
<div class="btn-group" role="group">
|
||||
<div class="btn-group pe-1" role="group">
|
||||
<button id="btnCultures" type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="oi oi-globe"></span>
|
||||
</button>
|
||||
<div class="dropdown-menu" aria-labelledby="btnCultures">
|
||||
<div class="dropdown-menu @MenuAlignment" aria-labelledby="btnCultures">
|
||||
@foreach (var culture in _supportedCultures)
|
||||
{
|
||||
<a class="dropdown-item @(CultureInfo.CurrentUICulture.Name == culture.Name ? "active" : String.Empty)" href="#" @onclick="@(async e => await SetCultureAsync(culture.Name))">@culture.DisplayName</a>
|
||||
@ -23,9 +23,14 @@
|
||||
|
||||
@code{
|
||||
private IEnumerable<Culture> _supportedCultures;
|
||||
[Parameter]
|
||||
public string DropdownAlignment { get; set; } = string.Empty; // Empty or Left or Right
|
||||
private string MenuAlignment = string.Empty;
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
MenuAlignment = DropdownAlignment.ToLower() == "right" ? "dropdown-menu-end" : string.Empty;
|
||||
|
||||
var languages = PageState.Languages;
|
||||
_supportedCultures = languages.Select(l => new Culture { Name = l.Code, DisplayName = l.Name });
|
||||
}
|
||||
|
@ -17,13 +17,29 @@ namespace Oqtane.Themes.Controls
|
||||
{
|
||||
[Inject] public NavigationManager NavigationManager { get; set; }
|
||||
[Inject] public IUserService UserService { get; set; }
|
||||
[Inject] public ISettingService SettingService { get; set; }
|
||||
[Inject] public IJSRuntime jsRuntime { get; set; }
|
||||
[Inject] public IServiceProvider ServiceProvider { get; set; }
|
||||
|
||||
protected void LoginUser()
|
||||
{
|
||||
var allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false;
|
||||
var allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
|
||||
|
||||
Route route = new Route(PageState.Uri.AbsoluteUri, PageState.Alias.Path);
|
||||
NavigationManager.NavigateTo(NavigateUrl("login", "?returnurl=" + WebUtility.UrlEncode(route.PathAndQuery)));
|
||||
var returnurl = WebUtility.UrlEncode(route.PathAndQuery);
|
||||
|
||||
if (allowexternallogin && !allowsitelogin)
|
||||
{
|
||||
// external login
|
||||
NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + returnurl), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// local login
|
||||
NavigationManager.NavigateTo(NavigateUrl("login", "?returnurl=" + returnurl));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected async Task LogoutUser()
|
||||
|
@ -6,7 +6,7 @@
|
||||
<nav class="navbar navbar-dark bg-primary fixed-top">
|
||||
<Logo /><Menu Orientation="Horizontal" />
|
||||
<div class="controls ms-auto">
|
||||
<div class="controls-group"><UserProfile ShowRegister="@_register" /> <Login ShowLogin="@_login" /> <ControlPanel /></div>
|
||||
<div class="controls-group"><UserProfile ShowRegister="@_register" /> <Login ShowLogin="@_login" /> <ControlPanel LanguageDropdownAlignment="right" /></div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="content">
|
||||
|
@ -146,6 +146,10 @@
|
||||
{
|
||||
editmode = false; // reset edit mode when navigating to different page
|
||||
}
|
||||
if (querystring.ContainsKey("edit") && querystring["edit"] == "true")
|
||||
{
|
||||
editmode = true; // querystring can set edit mode
|
||||
}
|
||||
|
||||
// get user
|
||||
if (PageState == null || refresh || PageState.Alias.SiteId != SiteState.Alias.SiteId)
|
||||
@ -290,7 +294,7 @@
|
||||
var urlMapping = await UrlMappingService.GetUrlMappingAsync(site.SiteId, route.PagePath);
|
||||
if (urlMapping != null && !string.IsNullOrEmpty(urlMapping.MappedUrl))
|
||||
{
|
||||
var url = (urlMapping.MappedUrl.StartsWith("http")) ? urlMapping.MappedUrl : route.SiteUrl + "/" + urlMapping.MappedUrl;
|
||||
var url = (urlMapping.MappedUrl.StartsWith("http")) ? urlMapping.MappedUrl : route.SiteUrl + "/" + urlMapping.MappedUrl + route.Query;
|
||||
NavigationManager.NavigateTo(url, false);
|
||||
}
|
||||
else // not mapped
|
||||
|
@ -72,7 +72,7 @@
|
||||
while (index >= 0)
|
||||
{
|
||||
var element = content.Substring(index, content.IndexOf(">", index) - index + 1);
|
||||
if (!string.IsNullOrEmpty(element) && !element.ToLower().StartsWith("<script"))
|
||||
if (!string.IsNullOrEmpty(element) && !element.ToLower().StartsWith("<script") && !element.ToLower().StartsWith("</script"))
|
||||
{
|
||||
if (!headcontent.Contains(element))
|
||||
{
|
||||
@ -166,7 +166,7 @@
|
||||
if (!string.IsNullOrEmpty(src))
|
||||
{
|
||||
src = (src.Contains("://")) ? src : PageState.Alias.BaseUrl + src;
|
||||
scripts.Add(new { href = src, bundle = "", integrity = integrity, crossorigin = crossorigin, es6module = (type == "module"), location = location });
|
||||
scripts.Add(new { href = src, bundle = "", integrity = integrity, crossorigin = crossorigin, es6module = (type == "module"), location = location.ToString().ToLower() });
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1,8 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<Version>4.0.3</Version>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Version>5.0.0</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -10,7 +10,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.0</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
@ -29,7 +29,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MySql.EntityFrameworkCore" Version="7.0.2" />
|
||||
<PackageReference Include="MySql.EntityFrameworkCore" Version="8.0.0-preview" />
|
||||
<PackageReference Include="MySql.Data" Version="8.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Database.MySQL</id>
|
||||
<version>4.0.3</version>
|
||||
<version>5.0.0</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane MySQL Provider</title>
|
||||
@ -12,15 +12,15 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.0</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="bin\net7.0\Oqtane.Database.MySQL.dll" target="lib\net7.0" />
|
||||
<file src="bin\net7.0\Oqtane.Database.MySQL.pdb" target="lib\net7.0" />
|
||||
<file src="bin\net7.0\Mysql.EntityFrameworkCore.dll" target="lib\net7.0" />
|
||||
<file src="bin\net7.0\Mysql.Data.dll" target="lib\net7.0" />
|
||||
<file src="bin\net8.0\Oqtane.Database.MySQL.dll" target="lib\net8.0" />
|
||||
<file src="bin\net8.0\Oqtane.Database.MySQL.pdb" target="lib\net8.0" />
|
||||
<file src="bin\net8.0\MySql.EntityFrameworkCore.dll" target="lib\net8.0" />
|
||||
<file src="bin\net8.0\MySql.Data.dll" target="lib\net8.0" />
|
||||
<file src="icon.png" target="" />
|
||||
</files>
|
||||
</package>
|
@ -1,8 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<Version>4.0.3</Version>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Version>5.0.0</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -10,7 +10,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.0</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
@ -29,9 +29,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="EFCore.NamingConventions" Version="7.0.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.5" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.4" />
|
||||
<PackageReference Include="EFCore.NamingConventions" Version="8.0.0-rc.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.0" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0-rc.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Database.PostgreSQL</id>
|
||||
<version>4.0.3</version>
|
||||
<version>5.0.0</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane PostgreSQL Provider</title>
|
||||
@ -12,16 +12,16 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.0</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="bin\net7.0\Oqtane.Database.PostgreSQL.dll" target="lib\net7.0" />
|
||||
<file src="bin\net7.0\Oqtane.Database.PostgreSQL.pdb" target="lib\net7.0" />
|
||||
<file src="bin\net7.0\EFCore.NamingConventions.dll" target="lib\net7.0" />
|
||||
<file src="bin\net7.0\Npgsql.EntityFrameworkCore.PostgreSQL.dll" target="lib\net7.0" />
|
||||
<file src="bin\net7.0\Npgsql.dll" target="lib\net7.0" />
|
||||
<file src="bin\net8.0\Oqtane.Database.PostgreSQL.dll" target="lib\net8.0" />
|
||||
<file src="bin\net8.0\Oqtane.Database.PostgreSQL.pdb" target="lib\net8.0" />
|
||||
<file src="bin\net8.0\EFCore.NamingConventions.dll" target="lib\net8.0" />
|
||||
<file src="bin\net8.0\Npgsql.EntityFrameworkCore.PostgreSQL.dll" target="lib\net8.0" />
|
||||
<file src="bin\net8.0\Npgsql.dll" target="lib\net8.0" />
|
||||
<file src="icon.png" target="" />
|
||||
</files>
|
||||
</package>
|
@ -1,8 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<Version>4.0.3</Version>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Version>5.0.0</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -10,7 +10,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.0</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
@ -29,7 +29,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Database.SqlServer</id>
|
||||
<version>4.0.3</version>
|
||||
<version>5.0.0</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane SQL Server Provider</title>
|
||||
@ -12,14 +12,14 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.0</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="bin\net7.0\Oqtane.Database.SqlServer.dll" target="lib\net7.0" />
|
||||
<file src="bin\net7.0\Oqtane.Database.SqlServer.pdb" target="lib\net7.0" />
|
||||
<file src="bin\net7.0\Microsoft.EntityFrameworkCore.SqlServer.dll" target="lib\net7.0" />
|
||||
<file src="bin\net8.0\Oqtane.Database.SqlServer.dll" target="lib\net8.0" />
|
||||
<file src="bin\net8.0\Oqtane.Database.SqlServer.pdb" target="lib\net8.0" />
|
||||
<file src="bin\net8.0\Microsoft.EntityFrameworkCore.SqlServer.dll" target="lib\net8.0" />
|
||||
<file src="icon.png" target="" />
|
||||
</files>
|
||||
</package>
|
@ -1,8 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<Version>4.0.3</Version>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Version>5.0.0</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -10,7 +10,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.0</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
@ -29,7 +29,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Database.Sqlite</id>
|
||||
<version>4.0.3</version>
|
||||
<version>5.0.0</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane SQLite Provider</title>
|
||||
@ -12,14 +12,14 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.0</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="bin\net7.0\Oqtane.Database.Sqlite.dll" target="lib\net7.0" />
|
||||
<file src="bin\net7.0\Oqtane.Database.Sqlite.pdb" target="lib\net7.0" />
|
||||
<file src="bin\net7.0\Microsoft.EntityFrameworkCore.Sqlite.dll" target="lib\net7.0" />
|
||||
<file src="bin\net8.0\Oqtane.Database.Sqlite.dll" target="lib\net8.0" />
|
||||
<file src="bin\net8.0\Oqtane.Database.Sqlite.pdb" target="lib\net8.0" />
|
||||
<file src="bin\net8.0\Microsoft.EntityFrameworkCore.Sqlite.dll" target="lib\net8.0" />
|
||||
<file src="icon.png" target="" />
|
||||
</files>
|
||||
</package>
|
@ -1,12 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net7.0-windows10.0.19041.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net8.0-windows10.0.19041.0</TargetFrameworks>
|
||||
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
|
||||
<!-- <TargetFrameworks>net7.0-android;net7.0-ios;net7.0-maccatalyst</TargetFrameworks> -->
|
||||
<!-- <TargetFrameworks>$(TargetFrameworks);net7.0-tizen</TargetFrameworks> -->
|
||||
<!-- <TargetFrameworks>net8.0-android;net8.0-ios;net8.0-maccatalyst</TargetFrameworks> -->
|
||||
<!-- <TargetFrameworks>$(TargetFrameworks);net8.0-tizen</TargetFrameworks> -->
|
||||
<OutputType>Exe</OutputType>
|
||||
<Version>4.0.3</Version>
|
||||
<Version>5.0.0</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -14,7 +14,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.0</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<RootNamespace>Oqtane.Maui</RootNamespace>
|
||||
@ -31,7 +31,7 @@
|
||||
<ApplicationIdGuid>0E29FC31-1B83-48ED-B6E0-9F3C67B775D4</ApplicationIdGuid>
|
||||
|
||||
<!-- Versions -->
|
||||
<ApplicationDisplayVersion>4.0.3</ApplicationDisplayVersion>
|
||||
<ApplicationDisplayVersion>5.0.0</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>1</ApplicationVersion>
|
||||
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion>
|
||||
@ -65,20 +65,22 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="7.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="7.0.5" />
|
||||
<PackageReference Include="System.Net.Http.Json" Version="7.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.0" />
|
||||
<PackageReference Include="System.Net.Http.Json" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls" Version="8.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="8.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Oqtane.Client">
|
||||
<HintPath>..\Oqtane.Server\bin\Debug\net7.0\Oqtane.Client.dll</HintPath>
|
||||
<HintPath>..\Oqtane.Server\bin\Debug\net8.0\Oqtane.Client.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Oqtane.Shared">
|
||||
<HintPath>..\Oqtane.Server\bin\Debug\net7.0\Oqtane.Shared.dll</HintPath>
|
||||
<HintPath>..\Oqtane.Server\bin\Debug\net8.0\Oqtane.Shared.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Client</id>
|
||||
<version>4.0.3</version>
|
||||
<version>5.0.0</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -12,13 +12,14 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.0</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="..\Oqtane.Client\bin\Release\net7.0\Oqtane.Client.dll" target="lib\net7.0" />
|
||||
<file src="..\Oqtane.Client\bin\Release\net7.0\Oqtane.Client.pdb" target="lib\net7.0" />
|
||||
<file src="..\Oqtane.Client\bin\Release\net8.0\Oqtane.Client.dll" target="lib\net8.0" />
|
||||
<file src="..\Oqtane.Client\bin\Release\net8.0\Oqtane.Client.pdb" target="lib\net8.0" />
|
||||
<file src="..\Oqtane.Server\bin\Release\net8.0\Oqtane.Licensing.Client.Oqtane.dll" target="lib\net8.0" />
|
||||
<file src="icon.png" target="" />
|
||||
</files>
|
||||
</package>
|
@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Framework</id>
|
||||
<version>4.0.3</version>
|
||||
<version>5.0.0</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -11,8 +11,8 @@
|
||||
<copyright>.NET Foundation</copyright>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v4.0.3/Oqtane.Framework.4.0.3.Upgrade.zip</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3</releaseNotes>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v5.0.0Oqtane.Framework.5.0.0.Upgrade.zip</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.0</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane framework</tags>
|
||||
</metadata>
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Server</id>
|
||||
<version>4.0.3</version>
|
||||
<version>5.0.0</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -12,13 +12,13 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.0</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="..\Oqtane.Server\bin\Release\net7.0\Oqtane.Server.dll" target="lib\net7.0" />
|
||||
<file src="..\Oqtane.Server\bin\Release\net7.0\Oqtane.Server.pdb" target="lib\net7.0" />
|
||||
<file src="..\Oqtane.Server\bin\Release\net8.0\Oqtane.Server.dll" target="lib\net8.0" />
|
||||
<file src="..\Oqtane.Server\bin\Release\net8.0\Oqtane.Server.pdb" target="lib\net8.0" />
|
||||
<file src="icon.png" target="" />
|
||||
</files>
|
||||
</package>
|
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Shared</id>
|
||||
<version>4.0.3</version>
|
||||
<version>5.0.0</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -12,13 +12,14 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.0</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="..\Oqtane.Shared\bin\Release\net7.0\Oqtane.Shared.dll" target="lib\net7.0" />
|
||||
<file src="..\Oqtane.Shared\bin\Release\net7.0\Oqtane.Shared.pdb" target="lib\net7.0" />
|
||||
<file src="..\Oqtane.Shared\bin\Release\net8.0\Oqtane.Shared.dll" target="lib\net8.0" />
|
||||
<file src="..\Oqtane.Shared\bin\Release\net8.0\Oqtane.Shared.pdb" target="lib\net8.0" />
|
||||
<file src="..\Oqtane.Server\bin\Release\net8.0\Oqtane.Licensing.Shared.Oqtane.dll" target="lib\net8.0" />
|
||||
<file src="icon.png" target="" />
|
||||
</files>
|
||||
</package>
|
@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Updater</id>
|
||||
<version>4.0.3</version>
|
||||
<version>5.0.0</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -12,12 +12,12 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.0</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="..\Oqtane.Updater\bin\Release\net7.0\publish\*.*" target="lib\net7.0" />
|
||||
<file src="..\Oqtane.Updater\bin\Release\net8.0\publish\*.*" target="lib\net8.0" />
|
||||
<file src="icon.png" target="" />
|
||||
</files>
|
||||
</package>
|
@ -1 +1 @@
|
||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net7.0\publish\*" -DestinationPath "Oqtane.Framework.4.0.3.Install.zip" -Force
|
||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.0.0.Install.zip" -Force
|
@ -8,14 +8,14 @@ nuget.exe pack Oqtane.Client.nuspec
|
||||
nuget.exe pack Oqtane.Server.nuspec
|
||||
nuget.exe pack Oqtane.Shared.nuspec
|
||||
nuget.exe pack Oqtane.Framework.nuspec
|
||||
del /F/Q/S "..\Oqtane.Server\bin\Release\net7.0\publish" > NUL
|
||||
rmdir /Q/S "..\Oqtane.Server\bin\Release\net7.0\publish"
|
||||
del /F/Q/S "..\Oqtane.Server\bin\Release\net8.0\publish" > NUL
|
||||
rmdir /Q/S "..\Oqtane.Server\bin\Release\net8.0\publish"
|
||||
dotnet publish ..\Oqtane.Server\Oqtane.Server.csproj /p:Configuration=Release
|
||||
del /F/Q/S "..\Oqtane.Server\bin\Release\net7.0\publish\wwwroot\Content" > NUL
|
||||
rmdir /Q/S "..\Oqtane.Server\bin\Release\net7.0\publish\wwwroot\Content"
|
||||
del /F/Q/S "..\Oqtane.Server\bin\Release\net8.0\publish\wwwroot\Content" > NUL
|
||||
rmdir /Q/S "..\Oqtane.Server\bin\Release\net8.0\publish\wwwroot\Content"
|
||||
setlocal ENABLEDELAYEDEXPANSION
|
||||
set retain=Oqtane.Modules.Admin.Login,Oqtane.Modules.HtmlText
|
||||
for /D %%i in ("..\Oqtane.Server\bin\Release\net7.0\publish\wwwroot\Modules\*") do (
|
||||
for /D %%i in ("..\Oqtane.Server\bin\Release\net8.0\publish\wwwroot\Modules\*") do (
|
||||
set /A found=0
|
||||
for %%j in (%retain%) do (
|
||||
if "%%~nxi" == "%%j" set /A found=1
|
||||
@ -23,18 +23,18 @@ if "%%~nxi" == "%%j" set /A found=1
|
||||
if not !found! == 1 rmdir /Q/S "%%i"
|
||||
)
|
||||
set retain=Oqtane.Themes.BlazorTheme,Oqtane.Themes.OqtaneTheme
|
||||
for /D %%i in ("..\Oqtane.Server\bin\Release\net7.0\publish\wwwroot\Themes\*") do (
|
||||
for /D %%i in ("..\Oqtane.Server\bin\Release\net8.0\publish\wwwroot\Themes\*") do (
|
||||
set /A found=0
|
||||
for %%j in (%retain%) do (
|
||||
if "%%~nxi" == "%%j" set /A found=1
|
||||
)
|
||||
if not !found! == 1 rmdir /Q/S "%%i"
|
||||
)
|
||||
del "..\Oqtane.Server\bin\Release\net7.0\publish\appsettings.json"
|
||||
ren "..\Oqtane.Server\bin\Release\net7.0\publish\appsettings.release.json" "appsettings.json"
|
||||
del "..\Oqtane.Server\bin\Release\net8.0\publish\appsettings.json"
|
||||
ren "..\Oqtane.Server\bin\Release\net8.0\publish\appsettings.release.json" "appsettings.json"
|
||||
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\install.ps1"
|
||||
del "..\Oqtane.Server\bin\Release\net7.0\publish\appsettings.json"
|
||||
del "..\Oqtane.Server\bin\Release\net7.0\publish\web.config"
|
||||
del "..\Oqtane.Server\bin\Release\net8.0\publish\appsettings.json"
|
||||
del "..\Oqtane.Server\bin\Release\net8.0\publish\web.config"
|
||||
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\upgrade.ps1"
|
||||
dotnet clean -c Release ..\Oqtane.Updater.sln
|
||||
dotnet build -c Release ..\Oqtane.Updater.sln
|
||||
|
@ -1 +1 @@
|
||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net7.0\publish\*" -DestinationPath "Oqtane.Framework.4.0.3.Upgrade.zip" -Force
|
||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.0.0.Upgrade.zip" -Force
|
@ -95,6 +95,33 @@ namespace Oqtane.Controllers
|
||||
folderPath += "/";
|
||||
}
|
||||
Folder folder = _folders.GetFolder(siteId, folderPath);
|
||||
if (folder == null && User.IsInRole(RoleNames.Host) && path.StartsWith("Users/"))
|
||||
{
|
||||
// create the user folder on this site for the host user
|
||||
var userId = int.Parse(path.ReplaceMultiple(new string[] { "Users", "/" }, ""));
|
||||
folder = _folders.GetFolder(siteId, "Users/");
|
||||
if (folder != null)
|
||||
{
|
||||
folder = _folders.AddFolder(new Folder
|
||||
{
|
||||
SiteId = folder.SiteId,
|
||||
ParentId = folder.FolderId,
|
||||
Name = "My Folder",
|
||||
Type = FolderTypes.Private,
|
||||
Path = path,
|
||||
Order = 1,
|
||||
ImageSizes = "",
|
||||
Capacity = Constants.UserFolderCapacity,
|
||||
IsSystem = true,
|
||||
PermissionList = new List<Permission>
|
||||
{
|
||||
new Permission(PermissionNames.Browse, userId, true),
|
||||
new Permission(PermissionNames.View, RoleNames.Everyone, true),
|
||||
new Permission(PermissionNames.Edit, userId, true)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (folder != null && folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.View, folder.PermissionList))
|
||||
{
|
||||
return folder;
|
||||
|
@ -350,9 +350,9 @@ namespace Oqtane.Controllers
|
||||
if (moduleDefinition.Version == "local")
|
||||
{
|
||||
text = text.Replace("[FrameworkVersion]", Constants.Version);
|
||||
text = text.Replace("[ClientReference]", $"<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net7.0\\Oqtane.Client.dll</HintPath></Reference>");
|
||||
text = text.Replace("[ServerReference]", $"<Reference Include=\"Oqtane.Server\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net7.0\\Oqtane.Server.dll</HintPath></Reference>");
|
||||
text = text.Replace("[SharedReference]", $"<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net7.0\\Oqtane.Shared.dll</HintPath></Reference>");
|
||||
text = text.Replace("[ClientReference]", $"<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Client.dll</HintPath></Reference>");
|
||||
text = text.Replace("[ServerReference]", $"<Reference Include=\"Oqtane.Server\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Server.dll</HintPath></Reference>");
|
||||
text = text.Replace("[SharedReference]", $"<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Shared.dll</HintPath></Reference>");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -179,7 +179,7 @@ namespace Oqtane.Controllers
|
||||
[Authorize(Roles = RoleNames.Registered)]
|
||||
public Notification Put(int id, [FromBody] Notification notification)
|
||||
{
|
||||
if (ModelState.IsValid && notification.SiteId == _alias.SiteId && _notifications.GetNotification(notification.NotificationId, false) != null && IsAuthorized(notification.FromUserId))
|
||||
if (ModelState.IsValid && notification.SiteId == _alias.SiteId && _notifications.GetNotification(notification.NotificationId, false) != null && (IsAuthorized(notification.FromUserId) || IsAuthorized(notification.ToUserId)))
|
||||
{
|
||||
notification = _notifications.UpdateNotification(notification);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Notification, notification.NotificationId, SyncEventActions.Update);
|
||||
|
@ -280,16 +280,10 @@ namespace Oqtane.Controllers
|
||||
if (currentPage.Path != page.Path)
|
||||
{
|
||||
var urlMapping = _urlMappings.GetUrlMapping(page.SiteId, currentPage.Path);
|
||||
if (urlMapping == null)
|
||||
if (urlMapping != null)
|
||||
{
|
||||
urlMapping = new UrlMapping();
|
||||
urlMapping.SiteId = page.SiteId;
|
||||
urlMapping.Url = currentPage.Path;
|
||||
urlMapping.MappedUrl = page.Path;
|
||||
urlMapping.Requests = 0;
|
||||
urlMapping.CreatedOn = System.DateTime.UtcNow;
|
||||
urlMapping.RequestedOn = System.DateTime.UtcNow;
|
||||
_urlMappings.AddUrlMapping(urlMapping);
|
||||
_urlMappings.UpdateUrlMapping(urlMapping);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ using Oqtane.Infrastructure;
|
||||
using Oqtane.Repository;
|
||||
using Oqtane.Security;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using System;
|
||||
|
||||
namespace Oqtane.Controllers
|
||||
{
|
||||
@ -133,14 +133,22 @@ namespace Oqtane.Controllers
|
||||
var page = _pages.GetPage(pageid);
|
||||
if (page != null && page.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, page.SiteId, EntityNames.Page, pageid, PermissionNames.Edit))
|
||||
{
|
||||
var panes = pane;
|
||||
if (pane == PaneNames.Default || pane == PaneNames.Admin)
|
||||
{
|
||||
// treat default and admin panes as a single pane
|
||||
panes = PaneNames.Default + "," + PaneNames.Admin;
|
||||
pane = PaneNames.Default;
|
||||
}
|
||||
int order = 1;
|
||||
List<PageModule> pagemodules = _pageModules.GetPageModules(page.SiteId)
|
||||
.Where(item => item.PageId == pageid && item.Pane == pane).OrderBy(item => item.Order).ToList();
|
||||
.Where(item => item.PageId == pageid && panes.Split(',').Contains(item.Pane)).OrderBy(item => item.Order).ToList();
|
||||
foreach (PageModule pagemodule in pagemodules)
|
||||
{
|
||||
if (pagemodule.Order != order)
|
||||
if (pagemodule.Order != order || pagemodule.Pane != pane)
|
||||
{
|
||||
pagemodule.Order = order;
|
||||
pagemodule.Pane = pane;
|
||||
_pageModules.UpdatePageModule(pagemodule);
|
||||
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.PageModule, pagemodule.PageModuleId, SyncEventActions.Update);
|
||||
}
|
||||
|
@ -237,8 +237,8 @@ namespace Oqtane.Controllers
|
||||
if (theme.Version == "local")
|
||||
{
|
||||
text = text.Replace("[FrameworkVersion]", Constants.Version);
|
||||
text = text.Replace("[ClientReference]", $"<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net7.0\\Oqtane.Client.dll</HintPath></Reference>");
|
||||
text = text.Replace("[SharedReference]", $"<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net7.0\\Oqtane.Shared.dll</HintPath></Reference>");
|
||||
text = text.Replace("[ClientReference]", $"<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Client.dll</HintPath></Reference>");
|
||||
text = text.Replace("[SharedReference]", $"<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Shared.dll</HintPath></Reference>");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -28,9 +28,10 @@ namespace Oqtane.Controllers
|
||||
private readonly IUserPermissions _userPermissions;
|
||||
private readonly ISettingRepository _settings;
|
||||
private readonly IJwtManager _jwtManager;
|
||||
private readonly IFileRepository _files;
|
||||
private readonly ILogManager _logger;
|
||||
|
||||
public UserController(IUserRepository users, ITenantManager tenantManager, IUserManager userManager, ISiteRepository sites, IUserPermissions userPermissions, ISettingRepository settings, IJwtManager jwtManager, ILogManager logger)
|
||||
public UserController(IUserRepository users, ITenantManager tenantManager, IUserManager userManager, ISiteRepository sites, IUserPermissions userPermissions, ISettingRepository settings, IJwtManager jwtManager, IFileRepository files, ILogManager logger)
|
||||
{
|
||||
_users = users;
|
||||
_tenantManager = tenantManager;
|
||||
@ -39,6 +40,7 @@ namespace Oqtane.Controllers
|
||||
_userPermissions = userPermissions;
|
||||
_settings = settings;
|
||||
_jwtManager = jwtManager;
|
||||
_files = files;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@ -369,5 +371,41 @@ namespace Oqtane.Controllers
|
||||
|
||||
return requirements;
|
||||
}
|
||||
|
||||
// POST api/<controller>/import?siteid=x&fileid=y¬ify=z
|
||||
[HttpPost("import")]
|
||||
[Authorize(Roles = RoleNames.Admin)]
|
||||
public async Task<Dictionary<string, string>> Import(string siteid, string fileid, string notify)
|
||||
{
|
||||
if (int.TryParse(siteid, out int SiteId) && SiteId == _tenantManager.GetAlias().SiteId && int.TryParse(fileid, out int FileId) && bool.TryParse(notify, out bool Notify))
|
||||
{
|
||||
var file = _files.GetFile(FileId);
|
||||
if (file != null)
|
||||
{
|
||||
if (_userPermissions.IsAuthorized(User, PermissionNames.View, file.Folder.PermissionList))
|
||||
{
|
||||
return await _userManager.ImportUsers(SiteId, _files.GetFilePath(file), Notify);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized User Import Attempt {SiteId} {FileId}", siteid, fileid);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Import File Does Not Exist {SiteId} {FileId}", siteid, fileid);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized User Import Attempt {SiteId} {FileId}", siteid, fileid);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -121,6 +121,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
|
||||
public static IServiceCollection ConfigureOqtaneCookieOptions(this IServiceCollection services)
|
||||
{
|
||||
// note that ConfigureApplicationCookie internally uses an ApplicationScheme of "Identity.Application"
|
||||
services.ConfigureApplicationCookie(options =>
|
||||
{
|
||||
options.Cookie.HttpOnly = false;
|
||||
|
@ -28,9 +28,15 @@ namespace Oqtane.Extensions
|
||||
public static OqtaneSiteOptionsBuilder WithSiteAuthentication(this OqtaneSiteOptionsBuilder builder)
|
||||
{
|
||||
// site cookie authentication options
|
||||
builder.AddSiteOptions<CookieAuthenticationOptions>((options, alias, sitesettings) =>
|
||||
builder.AddSiteNamedOptions<CookieAuthenticationOptions>(Constants.AuthenticationScheme, (options, alias, sitesettings) =>
|
||||
{
|
||||
options.Cookie.Name = sitesettings.GetValue("LoginOptions:CookieName", ".AspNetCore.Identity.Application");
|
||||
string cookieExpStr = sitesettings.GetValue("LoginOptions:CookieExpiration", "");
|
||||
if (!string.IsNullOrEmpty(cookieExpStr) && TimeSpan.TryParse(cookieExpStr, out TimeSpan cookieExpTS))
|
||||
{
|
||||
options.Cookie.Expiration = cookieExpTS;
|
||||
options.ExpireTimeSpan = cookieExpTS;
|
||||
}
|
||||
});
|
||||
|
||||
// site OpenId Connect options
|
||||
@ -44,7 +50,7 @@ namespace Oqtane.Extensions
|
||||
options.SaveTokens = false;
|
||||
options.GetClaimsFromUserInfoEndpoint = true;
|
||||
options.CallbackPath = string.IsNullOrEmpty(alias.Path) ? "/signin-" + AuthenticationProviderTypes.OpenIDConnect : "/" + alias.Path + "/signin-" + AuthenticationProviderTypes.OpenIDConnect;
|
||||
options.ResponseType = OpenIdConnectResponseType.Code; // authorization code flow
|
||||
options.ResponseType = sitesettings.GetValue("ExternalLogin:AuthResponseType", "code"); // authorization code flow
|
||||
options.ResponseMode = OpenIdConnectResponseMode.FormPost; // recommended as most secure
|
||||
|
||||
// cookie config is required to avoid Correlation Failed errors
|
||||
@ -298,6 +304,7 @@ namespace Oqtane.Extensions
|
||||
if (identityuser != null)
|
||||
{
|
||||
user = _users.GetUser(identityuser.UserName);
|
||||
user.SiteId = alias.SiteId;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -351,7 +358,7 @@ namespace Oqtane.Extensions
|
||||
_notifications.AddNotification(notification);
|
||||
|
||||
// add user login
|
||||
await _identityUserManager.AddLoginAsync(identityuser, new UserLoginInfo(providerType + ":" + alias.SiteId.ToString(), id, providerName));
|
||||
await _identityUserManager.AddLoginAsync(identityuser, new UserLoginInfo(providerType + ":" + user.SiteId.ToString(), id, providerName));
|
||||
|
||||
_logger.Log(user.SiteId, LogLevel.Information, "ExternalLogin", Enums.LogFunction.Create, "User Added {User}", user);
|
||||
}
|
||||
@ -380,7 +387,9 @@ namespace Oqtane.Extensions
|
||||
var login = logins.FirstOrDefault(item => item.LoginProvider == (providerType + ":" + alias.SiteId.ToString()));
|
||||
if (login == null)
|
||||
{
|
||||
// new external login using existing user account - verification required
|
||||
if (bool.Parse(httpContext.GetSiteSettings().GetValue("ExternalLogin:VerifyUsers", "true")))
|
||||
{
|
||||
// external login using existing user account - verification required
|
||||
var _notifications = httpContext.RequestServices.GetRequiredService<INotificationRepository>();
|
||||
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
||||
string url = httpContext.Request.Scheme + "://" + alias.Name;
|
||||
@ -394,6 +403,24 @@ namespace Oqtane.Extensions
|
||||
_logger.Log(alias.SiteId, LogLevel.Information, "ExternalLogin", Enums.LogFunction.Create, "External Login Linkage Verification For Provider {Provider} Sent To {Email}", providerName, email);
|
||||
}
|
||||
else
|
||||
{
|
||||
// external login using existing user account - link automatically
|
||||
user = _users.GetUser(identityuser.UserName);
|
||||
user.SiteId = alias.SiteId;
|
||||
|
||||
var _notifications = httpContext.RequestServices.GetRequiredService<INotificationRepository>();
|
||||
string url = httpContext.Request.Scheme + "://" + alias.Name;
|
||||
string body = "You Recently Used An External Account To Sign In To Our Site.\n\n" + url + "\n\nThank You!";
|
||||
var notification = new Notification(user.SiteId, user, "User Account Notification", body);
|
||||
_notifications.AddNotification(notification);
|
||||
|
||||
// add user login
|
||||
await _identityUserManager.AddLoginAsync(identityuser, new UserLoginInfo(providerType + ":" + user.SiteId.ToString(), id, providerName));
|
||||
|
||||
_logger.Log(user.SiteId, LogLevel.Information, "ExternalLogin", Enums.LogFunction.Create, "External Login Linkage Created For User {Username} And Provider {Provider}", user.Username, providerName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// provider keys do not match
|
||||
identity.Label = ExternalLoginStatus.ProviderKeyMismatch;
|
||||
|
@ -17,10 +17,22 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
}
|
||||
|
||||
public OqtaneSiteOptionsBuilder AddSiteOptions<TOptions>(
|
||||
Action<TOptions, Alias, Dictionary<string, string>> action) where TOptions : class, new()
|
||||
Action<TOptions, Alias, Dictionary<string, string>> configureOptions) where TOptions : class, new()
|
||||
{
|
||||
Services.TryAddSingleton<IOptionsMonitorCache<TOptions>, SiteOptionsCache<TOptions>>();
|
||||
Services.AddSingleton<ISiteOptions<TOptions>, SiteOptions<TOptions>> (sp => new SiteOptions<TOptions>(action));
|
||||
Services.AddSingleton<ISiteOptions<TOptions>, SiteOptions<TOptions>> (_ => new SiteOptions<TOptions>(configureOptions));
|
||||
Services.TryAddTransient<IOptionsFactory<TOptions>, SiteOptionsFactory<TOptions>>();
|
||||
Services.TryAddScoped<IOptionsSnapshot<TOptions>>(sp => BuildOptionsManager<TOptions>(sp));
|
||||
Services.TryAddSingleton<IOptions<TOptions>>(sp => BuildOptionsManager<TOptions>(sp));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public OqtaneSiteOptionsBuilder AddSiteNamedOptions<TOptions>(string name,
|
||||
Action<TOptions, Alias, Dictionary<string, string>> configureOptions) where TOptions : class, new()
|
||||
{
|
||||
Services.TryAddSingleton<IOptionsMonitorCache<TOptions>, SiteOptionsCache<TOptions>>();
|
||||
Services.AddSingleton<ISiteNamedOptions<TOptions>, SiteNamedOptions<TOptions>>(_ => new SiteNamedOptions<TOptions>(name, configureOptions));
|
||||
Services.TryAddTransient<IOptionsFactory<TOptions>, SiteOptionsFactory<TOptions>>();
|
||||
Services.TryAddScoped<IOptionsSnapshot<TOptions>>(sp => BuildOptionsManager<TOptions>(sp));
|
||||
Services.TryAddSingleton<IOptions<TOptions>>(sp => BuildOptionsManager<TOptions>(sp));
|
||||
|
@ -250,6 +250,9 @@ namespace Oqtane.Infrastructure
|
||||
// legacy support for assets that were stored as absolute paths
|
||||
string filepath = asset.StartsWith("\\") ? Path.Combine(_environment.ContentRootPath, asset.Substring(1)) : asset;
|
||||
if (File.Exists(filepath))
|
||||
{
|
||||
// do not remove licensing assemblies - this is a temporary fix until a more robust dependency management solution is available
|
||||
if (!filepath.Contains("Oqtane.Licensing."))
|
||||
{
|
||||
File.Delete(filepath);
|
||||
if (!Directory.EnumerateFiles(Path.GetDirectoryName(filepath)).Any())
|
||||
@ -258,6 +261,7 @@ namespace Oqtane.Infrastructure
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// clean up package asset manifests
|
||||
foreach (string asset in packages)
|
||||
|
11
Oqtane.Server/Infrastructure/Options/ISiteNamedOptions.cs
Normal file
11
Oqtane.Server/Infrastructure/Options/ISiteNamedOptions.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using Oqtane.Models;
|
||||
|
||||
namespace Oqtane.Infrastructure
|
||||
{
|
||||
public interface ISiteNamedOptions<TOptions>
|
||||
where TOptions : class, new()
|
||||
{
|
||||
void Configure(string name, TOptions options, Alias alias, Dictionary<string, string> sitesettings);
|
||||
}
|
||||
}
|
28
Oqtane.Server/Infrastructure/Options/SiteNamedOptions.cs
Normal file
28
Oqtane.Server/Infrastructure/Options/SiteNamedOptions.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Oqtane.Models;
|
||||
|
||||
namespace Oqtane.Infrastructure
|
||||
{
|
||||
public class SiteNamedOptions<TOptions> : ISiteNamedOptions<TOptions>
|
||||
where TOptions : class, new()
|
||||
{
|
||||
public string Name { get; }
|
||||
|
||||
private readonly Action<TOptions, Alias, Dictionary<string, string>> configureOptions;
|
||||
|
||||
public SiteNamedOptions(string name, Action<TOptions, Alias, Dictionary<string, string>> configureOptions)
|
||||
{
|
||||
Name = name;
|
||||
this.configureOptions = configureOptions;
|
||||
}
|
||||
|
||||
public void Configure(string name, TOptions options, Alias alias, Dictionary<string, string> sitesettings)
|
||||
{
|
||||
if (name == Name)
|
||||
{
|
||||
configureOptions(options, alias, sitesettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -11,13 +11,15 @@ namespace Oqtane.Infrastructure
|
||||
private readonly IConfigureOptions<TOptions>[] _configureOptions;
|
||||
private readonly IPostConfigureOptions<TOptions>[] _postConfigureOptions;
|
||||
private readonly ISiteOptions<TOptions>[] _siteOptions;
|
||||
private readonly ISiteNamedOptions<TOptions>[] _siteNamedOptions;
|
||||
private readonly IHttpContextAccessor _accessor;
|
||||
|
||||
public SiteOptionsFactory(IEnumerable<IConfigureOptions<TOptions>> configureOptions, IEnumerable<IPostConfigureOptions<TOptions>> postConfigureOptions, IEnumerable<ISiteOptions<TOptions>> siteOptions, IHttpContextAccessor accessor)
|
||||
public SiteOptionsFactory(IEnumerable<IConfigureOptions<TOptions>> configureOptions, IEnumerable<IPostConfigureOptions<TOptions>> postConfigureOptions, IEnumerable<ISiteOptions<TOptions>> siteOptions, IEnumerable<ISiteNamedOptions<TOptions>> siteNamedOptions, IHttpContextAccessor accessor)
|
||||
{
|
||||
_configureOptions = configureOptions as IConfigureOptions<TOptions>[] ?? new List<IConfigureOptions<TOptions>>(configureOptions).ToArray();
|
||||
_postConfigureOptions = postConfigureOptions as IPostConfigureOptions<TOptions>[] ?? new List<IPostConfigureOptions<TOptions>>(postConfigureOptions).ToArray();
|
||||
_siteOptions = siteOptions as ISiteOptions<TOptions>[] ?? new List<ISiteOptions<TOptions>>(siteOptions).ToArray();
|
||||
_siteNamedOptions = siteNamedOptions as ISiteNamedOptions<TOptions>[] ?? new List<ISiteNamedOptions<TOptions>>(siteNamedOptions).ToArray();
|
||||
_accessor = accessor;
|
||||
}
|
||||
|
||||
@ -44,6 +46,11 @@ namespace Oqtane.Infrastructure
|
||||
{
|
||||
siteOption.Configure(options, _accessor.HttpContext.GetAlias(), _accessor.HttpContext.GetSiteSettings());
|
||||
}
|
||||
|
||||
foreach (var siteNamedOption in _siteNamedOptions)
|
||||
{
|
||||
siteNamedOption.Configure(name, options, _accessor.HttpContext.GetAlias(), _accessor.HttpContext.GetSiteSettings());
|
||||
}
|
||||
}
|
||||
|
||||
// post configuration
|
||||
|
@ -1,3 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Oqtane.Models;
|
||||
|
||||
@ -18,5 +19,6 @@ namespace Oqtane.Managers
|
||||
User VerifyTwoFactor(User user, string token);
|
||||
Task<User> LinkExternalAccount(User user, string token, string type, string key, string name);
|
||||
Task<bool> ValidatePassword(string password);
|
||||
Task<Dictionary<string, string>> ImportUsers(int siteId, string filePath, bool notify);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
@ -16,24 +17,32 @@ namespace Oqtane.Managers
|
||||
public class UserManager : IUserManager
|
||||
{
|
||||
private readonly IUserRepository _users;
|
||||
private readonly IRoleRepository _roles;
|
||||
private readonly IUserRoleRepository _userRoles;
|
||||
private readonly UserManager<IdentityUser> _identityUserManager;
|
||||
private readonly SignInManager<IdentityUser> _identitySignInManager;
|
||||
private readonly ITenantManager _tenantManager;
|
||||
private readonly INotificationRepository _notifications;
|
||||
private readonly IFolderRepository _folders;
|
||||
private readonly IFileRepository _files;
|
||||
private readonly IProfileRepository _profiles;
|
||||
private readonly ISettingRepository _settings;
|
||||
private readonly ISyncManager _syncManager;
|
||||
private readonly ILogManager _logger;
|
||||
|
||||
public UserManager(IUserRepository users, IUserRoleRepository userRoles, UserManager<IdentityUser> identityUserManager, SignInManager<IdentityUser> identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, ISyncManager syncManager, ILogManager logger)
|
||||
public UserManager(IUserRepository users, IRoleRepository roles, IUserRoleRepository userRoles, UserManager<IdentityUser> identityUserManager, SignInManager<IdentityUser> identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, IFileRepository files, IProfileRepository profiles, ISettingRepository settings, ISyncManager syncManager, ILogManager logger)
|
||||
{
|
||||
_users = users;
|
||||
_roles = roles;
|
||||
_userRoles = userRoles;
|
||||
_identityUserManager = identityUserManager;
|
||||
_identitySignInManager = identitySignInManager;
|
||||
_tenantManager = tenantManager;
|
||||
_notifications = notifications;
|
||||
_folders = folders;
|
||||
_files = files;
|
||||
_profiles = profiles;
|
||||
_settings = settings;
|
||||
_syncManager = syncManager;
|
||||
_logger = logger;
|
||||
}
|
||||
@ -95,6 +104,13 @@ namespace Oqtane.Managers
|
||||
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username);
|
||||
if (identityuser == null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(user.Password))
|
||||
{
|
||||
// create random interal password based on random date and punctuation ie. Jan-23-1981+14:43:12!
|
||||
Random rnd = new Random();
|
||||
var date = DateTime.UtcNow.AddDays(-rnd.Next(50 * 365)).AddHours(rnd.Next(0, 24)).AddMinutes(rnd.Next(0, 60)).AddSeconds(rnd.Next(0, 60));
|
||||
user.Password = date.ToString("MMM-dd-yyyy+HH:mm:ss", CultureInfo.InvariantCulture) + (char)rnd.Next(33, 47);
|
||||
}
|
||||
identityuser = new IdentityUser();
|
||||
identityuser.UserName = user.Username;
|
||||
identityuser.Email = user.Email;
|
||||
@ -141,12 +157,15 @@ namespace Oqtane.Managers
|
||||
_notifications.AddNotification(notification);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!user.SuppressNotification)
|
||||
{
|
||||
string url = alias.Protocol + alias.Name;
|
||||
string body = "Dear " + user.DisplayName + ",\n\nA User Account Has Been Successfully Created For You With The Username " + user.Username + ". Please Visit " + url + " And Use The Login Option To Sign In. If You Do Not Know Your Password, Use The Forgot Password Option On The Login Page To Reset Your Account.\n\nThank You!";
|
||||
var notification = new Notification(user.SiteId, User, "User Account Notification", body);
|
||||
_notifications.AddNotification(notification);
|
||||
}
|
||||
}
|
||||
|
||||
User.Password = ""; // remove sensitive information
|
||||
_logger.Log(user.SiteId, LogLevel.Information, this, LogFunction.Create, "User Added {User}", User);
|
||||
@ -165,9 +184,8 @@ namespace Oqtane.Managers
|
||||
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username);
|
||||
if (identityuser != null)
|
||||
{
|
||||
identityuser.Email = user.Email;
|
||||
var valid = true;
|
||||
if (user.Password != "")
|
||||
if (!string.IsNullOrEmpty(user.Password))
|
||||
{
|
||||
var validator = new PasswordValidator<IdentityUser>();
|
||||
var result = await validator.ValidateAsync(_identityUserManager, null, user.Password);
|
||||
@ -179,7 +197,17 @@ namespace Oqtane.Managers
|
||||
}
|
||||
if (valid)
|
||||
{
|
||||
await _identityUserManager.UpdateAsync(identityuser);
|
||||
if (!string.IsNullOrEmpty(user.Password))
|
||||
{
|
||||
await _identityUserManager.UpdateAsync(identityuser); // requires password to be provided
|
||||
}
|
||||
|
||||
if (user.Email != identityuser.Email)
|
||||
{
|
||||
await _identityUserManager.SetEmailAsync(identityuser, user.Email);
|
||||
var emailConfirmationToken = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
||||
await _identityUserManager.ConfirmEmailAsync(identityuser, emailConfirmationToken);
|
||||
}
|
||||
|
||||
user = _users.UpdateUser(user);
|
||||
_syncManager.AddSyncEvent(_tenantManager.GetAlias().TenantId, EntityNames.User, user.UserId, SyncEventActions.Update);
|
||||
@ -257,6 +285,8 @@ namespace Oqtane.Managers
|
||||
var LastIPAddress = user.LastIPAddress ?? "";
|
||||
|
||||
user = _users.GetUser(user.Username);
|
||||
if (!user.IsDeleted)
|
||||
{
|
||||
if (user.TwoFactorRequired)
|
||||
{
|
||||
var token = await _identityUserManager.GenerateTwoFactorTokenAsync(identityuser, "Email");
|
||||
@ -299,6 +329,11 @@ namespace Oqtane.Managers
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Login Failed - Account Deleted {Username}", user.Username);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (result.IsLockedOut)
|
||||
{
|
||||
@ -436,5 +471,189 @@ namespace Oqtane.Managers
|
||||
var result = await validator.ValidateAsync(_identityUserManager, null, password);
|
||||
return result.Succeeded;
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, string>> ImportUsers(int siteId, string filePath, bool notify)
|
||||
{
|
||||
var success = true;
|
||||
int rows = 0;
|
||||
int users = 0;
|
||||
|
||||
if (System.IO.File.Exists(filePath))
|
||||
{
|
||||
var roles = _roles.GetRoles(siteId).ToList();
|
||||
var profiles = _profiles.GetProfiles(siteId).ToList();
|
||||
|
||||
try
|
||||
{
|
||||
string row = "";
|
||||
using (var reader = new StreamReader(filePath))
|
||||
{
|
||||
// header row
|
||||
if (reader.Peek() > -1)
|
||||
{
|
||||
row = reader.ReadLine();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(row.Trim()))
|
||||
{
|
||||
var header = row.Replace("\"", "").Split('\t');
|
||||
if (header[0].Trim() == "Email")
|
||||
{
|
||||
for (int index = 4; index < header.Length - 1; index++)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(header[index].Trim()) && !profiles.Any(item => item.Name == header[index].Trim()))
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Create, "User Import Contains Profile Name {Profile} Which Does Not Exist", header[index]);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Create, "User Import File Is Not In Correct Format. Please Use Template Provided.");
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
// detail rows
|
||||
while (reader.Peek() > -1)
|
||||
{
|
||||
row = reader.ReadLine();
|
||||
rows++;
|
||||
|
||||
if (!string.IsNullOrEmpty(row.Trim()))
|
||||
{
|
||||
var values = row.Replace("\"", "").Split('\t');
|
||||
|
||||
// user
|
||||
var email = (values.Length > 0) ? values[0].Trim() : "";
|
||||
var username = (values.Length > 1) ? values[1].Trim() : "";
|
||||
var displayname = (values.Length > 2) ? values[2].Trim() : "";
|
||||
|
||||
var user = _users.GetUser(username, email);
|
||||
if (user == null)
|
||||
{
|
||||
user = new User();
|
||||
user.SiteId = siteId;
|
||||
user.Email = values[0];
|
||||
user.Username = (!string.IsNullOrEmpty(username)) ? username : user.Email;
|
||||
user.DisplayName = (!string.IsNullOrEmpty(displayname)) ? displayname : user.Username;
|
||||
user.EmailConfirmed = true;
|
||||
user.SuppressNotification = !notify;
|
||||
user = await AddUser(user);
|
||||
if (user == null)
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Create, "User Import Error Importing User {Email} {Username} {DisplayName}", email, username, displayname);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!string.IsNullOrEmpty(displayname))
|
||||
{
|
||||
user.DisplayName = displayname;
|
||||
user.Password = "";
|
||||
user = await UpdateUser(user);
|
||||
}
|
||||
}
|
||||
|
||||
var rolenames = (values.Length > 3) ? values[3].Trim() : "";
|
||||
if (user != null && !string.IsNullOrEmpty(rolenames))
|
||||
{
|
||||
// roles (comma delimited)
|
||||
foreach (var rolename in rolenames.Split(','))
|
||||
{
|
||||
var role = roles.FirstOrDefault(item => item.Name == rolename.Trim());
|
||||
if (role == null)
|
||||
{
|
||||
role = new Role();
|
||||
role.SiteId = siteId;
|
||||
role.Name = rolename.Trim();
|
||||
role.Description = rolename.Trim();
|
||||
role = _roles.AddRole(role);
|
||||
roles.Add(role);
|
||||
}
|
||||
if (role != null)
|
||||
{
|
||||
var userrole = _userRoles.GetUserRole(user.UserId, role.RoleId, false);
|
||||
if (userrole == null)
|
||||
{
|
||||
userrole = new UserRole();
|
||||
userrole.UserId = user.UserId;
|
||||
userrole.RoleId = role.RoleId;
|
||||
_userRoles.AddUserRole(userrole);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (user != null && values.Length > 4)
|
||||
{
|
||||
// profiles
|
||||
var settings = _settings.GetSettings(EntityNames.User, user.UserId);
|
||||
for (int index = 4; index < values.Length - 1; index++)
|
||||
{
|
||||
if (header.Length > index && !string.IsNullOrEmpty(values[index].Trim()))
|
||||
{
|
||||
var profile = profiles.FirstOrDefault(item => item.Name == header[index].Trim());
|
||||
if (profile != null)
|
||||
{
|
||||
var setting = settings.FirstOrDefault(item => item.SettingName == profile.Name);
|
||||
if (setting == null)
|
||||
{
|
||||
setting = new Setting();
|
||||
setting.EntityName = EntityNames.User;
|
||||
setting.EntityId = user.UserId;
|
||||
setting.SettingName = profile.Name;
|
||||
setting.SettingValue = values[index].Trim();
|
||||
_settings.AddSetting(setting);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (setting.SettingValue != values[index].Trim())
|
||||
{
|
||||
setting.SettingValue = values[index].Trim();
|
||||
_settings.UpdateSetting(setting);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
users++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
success = false;
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Create, "User Import File Contains No Header Row");
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "User Import: {Rows} Rows Processed, {Users} Users Imported", rows, users);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
success = false;
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Create, ex, "Error Importing User Import File {SiteId} {FilePath} {Notify}", siteId, filePath, notify);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
success = false;
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Create, "User Import File Does Not Exist {FilePath}", filePath);
|
||||
}
|
||||
|
||||
// return results
|
||||
var result = new Dictionary<string, string>();
|
||||
result.Add("Success", success.ToString());
|
||||
result.Add("Users", users.ToString());
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
29
Oqtane.Server/Migrations/Tenant/04000601_AddProfileRows.cs
Normal file
29
Oqtane.Server/Migrations/Tenant/04000601_AddProfileRows.cs
Normal file
@ -0,0 +1,29 @@
|
||||
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.04.00.06.01")]
|
||||
public class AddProfileRows : MultiDatabaseMigration
|
||||
{
|
||||
public AddProfileRows(IDatabase database) : base(database)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var profileEntityBuilder = new ProfileEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
profileEntityBuilder.AddIntegerColumn("Rows", true);
|
||||
profileEntityBuilder.UpdateColumn("Rows", "1");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// not implemented
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Version>4.0.3</Version>
|
||||
<Version>5.0.0</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -11,7 +11,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.0</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<RootNamespace>Oqtane</RootNamespace>
|
||||
@ -32,20 +32,21 @@
|
||||
<EmbeddedResource Include="Scripts\MigrateTenant.sql" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="7.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="7.0.5" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.1.1" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="7.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.5">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.0-preview3.23201.1" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="7.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.0" />
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.4" />
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.7-pre20231110210158" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="7.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.0" />
|
||||
<PackageReference Include="Oqtane.Licensing" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Oqtane.Client\Oqtane.Client.csproj" />
|
||||
|
@ -3,6 +3,7 @@ using System.IO;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
@ -96,7 +97,7 @@ namespace Oqtane.Pages
|
||||
}
|
||||
else
|
||||
{
|
||||
HttpContext.Response.Headers.Add(HeaderNames.ETag, etag);
|
||||
HttpContext.Response.Headers.Append(HeaderNames.ETag, etag);
|
||||
return PhysicalFile(filepath, file.GetMimeType());
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,9 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Oqtane.Extensions;
|
||||
using Oqtane.Managers;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace Oqtane.Pages
|
||||
{
|
||||
@ -12,11 +15,13 @@ namespace Oqtane.Pages
|
||||
{
|
||||
private readonly UserManager<IdentityUser> _identityUserManager;
|
||||
private readonly SignInManager<IdentityUser> _identitySignInManager;
|
||||
private readonly IUserManager _userManager;
|
||||
|
||||
public LoginModel(UserManager<IdentityUser> identityUserManager, SignInManager<IdentityUser> identitySignInManager)
|
||||
public LoginModel(UserManager<IdentityUser> identityUserManager, SignInManager<IdentityUser> identitySignInManager, IUserManager userManager)
|
||||
{
|
||||
_identityUserManager = identityUserManager;
|
||||
_identitySignInManager = identitySignInManager;
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAsync(string username, string password, bool remember, string returnurl)
|
||||
@ -29,13 +34,19 @@ namespace Oqtane.Pages
|
||||
{
|
||||
var result = await _identitySignInManager.CheckPasswordSignInAsync(identityuser, password, true);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
var alias = HttpContext.GetAlias();
|
||||
var user = _userManager.GetUser(identityuser.UserName, alias.SiteId);
|
||||
if (user != null && !user.IsDeleted)
|
||||
{
|
||||
validuser = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (validuser)
|
||||
{
|
||||
// note that .NET Identity uses a hardcoded ApplicationScheme of "Identity.Application" in SignInAsync
|
||||
await _identitySignInManager.SignInAsync(identityuser, remember);
|
||||
}
|
||||
}
|
||||
|
@ -128,11 +128,6 @@ namespace Oqtane.Pages
|
||||
RenderMode = site.RenderMode;
|
||||
}
|
||||
|
||||
if (site.VisitorTracking)
|
||||
{
|
||||
TrackVisitor(site.SiteId);
|
||||
}
|
||||
|
||||
var page = _pages.GetPage(route.PagePath, site.SiteId);
|
||||
if (page == null && route.PagePath == "" && site.HomePageId != null)
|
||||
{
|
||||
@ -156,6 +151,11 @@ namespace Oqtane.Pages
|
||||
}
|
||||
}
|
||||
|
||||
if (site.VisitorTracking)
|
||||
{
|
||||
TrackVisitor(site.SiteId);
|
||||
}
|
||||
|
||||
// get jwt token for downstream APIs
|
||||
if (User.Identity.IsAuthenticated)
|
||||
{
|
||||
@ -437,17 +437,17 @@ namespace Oqtane.Pages
|
||||
"</script>";
|
||||
}
|
||||
|
||||
private string ParseScripts(string headcontent)
|
||||
private string ParseScripts(string content)
|
||||
{
|
||||
// iterate scripts
|
||||
var scripts = "";
|
||||
if (!string.IsNullOrEmpty(headcontent))
|
||||
if (!string.IsNullOrEmpty(content))
|
||||
{
|
||||
var index = headcontent.IndexOf("<script");
|
||||
var index = content.IndexOf("<script");
|
||||
while (index >= 0)
|
||||
{
|
||||
scripts += headcontent.Substring(index, headcontent.IndexOf("</script>", index) + 9 - index);
|
||||
index = headcontent.IndexOf("<script", index + 1);
|
||||
scripts += content.Substring(index, content.IndexOf("</script>", index) + 9 - index);
|
||||
index = content.IndexOf("<script", index + 1);
|
||||
}
|
||||
}
|
||||
return scripts;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user