Compare commits

..

53 Commits

Author SHA1 Message Date
8b80f72313 Merge pull request #3386 from oqtane/master
4.0.6 Release
2023-10-16 15:00:27 -04:00
06ebce31ca Merge pull request #3385 from oqtane/dev
4.0.6 Release
2023-10-16 14:59:59 -04:00
59db8ba997 Merge pull request #3383 from sbwalker/dev
4.0.6 data provider packages
2023-10-16 09:08:17 -04:00
df37d4aa7f 4.0.6 data provider packages 2023-10-16 09:08:03 -04:00
fd9935c800 Merge pull request #3382 from sbwalker/dev
prepare for 4.0.6 release
2023-10-16 08:43:42 -04:00
c057430488 prepare for 4.0.6 release 2023-10-16 08:43:31 -04:00
767fa78e22 Merge pull request #3380 from sbwalker/dev
resolve issue in making Pager search work with sorting
2023-10-16 08:03:42 -04:00
a0e289dcd6 resolve issue in making Pager search work with sorting 2023-10-16 08:03:31 -04:00
c62d147254 Merge pull request #3375 from leigh-pointer/AllowPunctuation
Module Creator allow punctuation in Description field
2023-10-13 08:46:15 -04:00
f739bee353 Merge pull request #3376 from leigh-pointer/BootswatchThemeIssue
Fix for #3374 Backward compatability
2023-10-13 08:46:03 -04:00
db4dfb69fb Merge pull request #3377 from sbwalker/dev
improve Pager search to support properties on child objects
2023-10-13 08:39:23 -04:00
9729a5ef16 improve Pager search to support properties on child objects 2023-10-13 08:39:02 -04:00
6f12818352 Fix for #3374 Backward compatability
Fixes the issue in the Language selector.
2023-10-13 14:06:21 +02:00
75fc318da0 Module Creator all punctuation in description
Modified the Regex to all punctuation in the description.
2023-10-13 11:50:00 +02:00
acf71cc2c2 Merge pull request #3370 from leigh-pointer/LanguageAlignment
Language Selector Alignment fix  #3368
2023-10-12 11:10:47 -04:00
42efe10473 Merge pull request #3372 from sbwalker/dev
optimize Pager search to remove redundant AllowSearch parameter and avoid consuming memory when not in use
2023-10-12 11:10:35 -04:00
b77e72880b optimize Pager search to remove redundant AllowSearch parameter and avoid consuming memory when not in use 2023-10-12 11:10:14 -04:00
297c1524b4 Language Selector Alignment fix #3368
Themes fixed also
2023-10-11 15:48:05 +02:00
6140743769 Merge pull request #3352 from Rodien/dev
Introduce a dropdown menu for authorization response types
2023-10-11 09:08:48 -04:00
d5ca700828 Merge pull request #3366 from sbwalker/dev
change name of RESX key and value to reflect purpose
2023-10-10 10:33:39 -04:00
a7e1fe76c3 change name of RESX key and value to reflect purpose 2023-10-10 10:33:11 -04:00
f6c55279d1 Merge pull request #3364 from leigh-pointer/PagerSearch
Pager Search Localization
2023-10-10 10:25:47 -04:00
826f4835bb Merge branch 'dev' into PagerSearch 2023-10-10 10:25:37 -04:00
119a28def1 Pager Search Localization
Localization is now referencing the Shared Resources
2023-10-10 14:33:16 +02:00
c1ffb8bc33 Merge pull request #3365 from sbwalker/dev
use SharedLocalizer in Pager
2023-10-10 08:07:21 -04:00
60cc9d14af use SharedLocalizer in Pager 2023-10-10 08:07:07 -04:00
7cf4f8fdaa Pager Search Localization
Localized the new Search Buttons
Added a Localized Placeholder that will display what columns are being searched.
2023-10-10 11:28:57 +02:00
1b84e83061 Merge pull request #3363 from sbwalker/dev
add Search capability to Pager and include in management UIs
2023-10-09 16:59:54 -04:00
b0d2ee8760 add Search capability to Pager and include in management UIs 2023-10-09 16:59:36 -04:00
2022432d35 Merge pull request #3362 from sbwalker/dev
Add Rows option to Profile Management. Improve Profile validation feedback. Fix Add User so that profile Options are supported.
2023-10-09 14:27:24 -04:00
c0ed335d84 Add Rows option to Profile Management. Improve Profile validation feedback. Fix Add User so that profile Options are supported. 2023-10-09 14:26:56 -04:00
4964562866 Merge pull request #3361 from sbwalker/dev
include Pane in Module Settings
2023-10-09 13:19:00 -04:00
f78dc443ad include Pane in Module Settings 2023-10-09 13:18:43 -04:00
56b47db778 Merge pull request #3360 from sbwalker/dev
allow module to be inserted at top of pane
2023-10-09 12:58:15 -04:00
2d4bf17b28 allow module to be inserted at top of pane 2023-10-09 12:58:01 -04:00
3b1819c68d Merge pull request #3359 from sbwalker/dev
optimize UrlCombine method
2023-10-09 12:00:51 -04:00
575bbdb53b optimize UrlCombine method 2023-10-09 12:00:34 -04:00
2fa7482028 Introduce a dropdown menu in the 'External Login' settings area for authentication flow response types. 2023-10-03 09:00:53 +02:00
e5062317e2 Merge pull request #3347 from sbwalker/dev
do not show uploaded file name or delete button when multiple uploads are enabled
2023-09-29 11:49:05 -04:00
543f4fa3c2 do not show uploaded file name or delete button when multiple uploads are enabled 2023-09-29 11:48:45 -04:00
49cdf69815 Merge pull request #3344 from Mostafa-Moafi/dev
Fixed issue #3282
2023-09-28 15:36:00 -04:00
04196a1a19 Merge pull request #3346 from sbwalker/dev
display uploaded file name in FileManager when ShowFiles = false
2023-09-28 15:35:26 -04:00
b8622b8708 display uploaded file name in FileManager when ShowFiles = false 2023-09-28 15:35:13 -04:00
a2feca0ba3 Fixed Bug #3282 2023-09-28 19:23:31 +03:30
ae6c6a75b2 Merge pull request #3342 from sbwalker/dev
style page names in Admin Dashboard so that they are always visible in Bootstrap themes
2023-09-28 07:51:05 -04:00
bd987e9531 style page names in Admin Dashboard so that they are always visible in Bootstrap themes 2023-09-27 16:26:59 -04:00
24d69fd820 Merge pull request #3341 from sbwalker/dev
include CopyLocalLockFileAssemblies property in Client project in Default Module Template
2023-09-27 16:11:46 -04:00
9e2fa8d7f1 include CopyLocalLockFileAssemblies property in Client project in Default Module Template 2023-09-27 16:11:29 -04:00
74ba2f7283 Merge pull request #3340 from sbwalker/dev
add hyperlinks to product logos
2023-09-27 16:09:30 -04:00
7603b5c63f add hyperlinks to product logos 2023-09-27 16:09:19 -04:00
03566afe66 Merge pull request #3339 from sbwalker/dev
fix UrlCombine so that it handles leading and trailing path delimiters and returns proper paths
2023-09-27 16:07:32 -04:00
3c2e314e2d fix UrlCombine so that it handles leading and trailing path delimiters and returns proper paths 2023-09-27 16:07:13 -04:00
1fcf08b5cd Update README.md 2023-09-26 14:06:18 -04:00
63 changed files with 655 additions and 355 deletions

View File

@ -12,11 +12,12 @@
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList)) if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{ {
string url = NavigateUrl(p.Path); 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"> <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> </NavLink>
</div> </p>
} }
} }
</div> </div>

View File

@ -67,6 +67,7 @@
</div> </div>
</div> </div>
</form> </form>
<br /><br />
@if (!_isSystem) @if (!_isSystem)
{ {
@ -79,8 +80,7 @@
@((MarkupString)"&nbsp;") @((MarkupString)"&nbsp;")
<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" /> <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")) @if (PageState.QueryString.ContainsKey("id"))
{ {
<AuditInfo CreatedBy="@_createdBy" CreatedOn="@_createdOn" ModifiedBy="@_modifiedBy" ModifiedOn="@_modifiedOn"></AuditInfo> <AuditInfo CreatedBy="@_createdBy" CreatedOn="@_createdOn" ModifiedBy="@_modifiedBy" ModifiedOn="@_modifiedOn"></AuditInfo>

View File

@ -8,27 +8,28 @@
@if (_files != null) @if (_files != null)
{ {
<div class="container"> <div class="row">
<div class="row mb-1 align-items-center"> <div class="col-md mb-1">
<div class="col-sm-2"> <ActionLink Action="Edit" Text="Add Folder" Class="btn btn-secondary" ResourceKey="AddFolder" />
<label class="control-label">@Localizer["Folder"] </label> </div>
</div> <div class="col-md-8 mb-1">
<div class="col-sm-6"> <div class="input-group">
<span class="input-group-text">@Localizer["Folder"]:</span>
<select class="form-select" @onchange="(e => FolderChanged(e))"> <select class="form-select" @onchange="(e => FolderChanged(e))">
@foreach (Folder folder in _folders) @foreach (Folder folder in _folders)
{ {
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option> <option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
} }
</select> </select>
</div>
<div class="col-sm-4">
<ActionLink Action="Edit" Text="Edit Folder" Class="btn btn-secondary" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="EditFolder" />&nbsp; <ActionLink Action="Edit" Text="Edit Folder" Class="btn btn-secondary" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="EditFolder" />&nbsp;
<ActionLink Action="Edit" Text="Add Folder" Class="btn btn-secondary" ResourceKey="AddFolder" />&nbsp;
<ActionLink Action="Add" Text="Upload Files" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="UploadFiles" />
</div> </div>
</div> </div>
<div class="col-md mb-1 text-end">
<ActionLink Action="Add" Text="Upload Files" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="UploadFiles" />
</div>
</div> </div>
<Pager Items="@_files">
<Pager Items="@_files" SearchProperties="Name">
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>

View File

@ -15,7 +15,7 @@ else
<br /> <br />
<br /> <br />
<Pager Items="@_jobs"> <Pager Items="@_jobs" SearchProperties="Name">
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>

View File

@ -14,7 +14,7 @@ else
{ {
<ActionLink Action="Add" Text="Add Language" ResourceKey="AddLanguage" /> <ActionLink Action="Add" Text="Add Language" ResourceKey="AddLanguage" />
<Pager Items="@_languages"> <Pager Items="@_languages" SearchProperties="Name,Code">
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>

View File

@ -66,6 +66,7 @@
<div class="container-fluid px-0"> <div class="container-fluid px-0">
<div class="row g-0 mb-2"> <div class="row g-0 mb-2">
<div class="col-4"> <div class="col-4">
<a href="@context.ProductUrl" target="_blank">
@if (context.LogoUrl != null) @if (context.LogoUrl != null)
{ {
<img src="@context.LogoUrl" class="img-fluid" alt="@context.Name" /> <img src="@context.LogoUrl" class="img-fluid" alt="@context.Name" />
@ -74,6 +75,7 @@
{ {
<img src="/package.png" class="img-fluid" alt="@context.Name" /> <img src="/package.png" class="img-fluid" alt="@context.Name" />
} }
</a>
</div> </div>
<div class="col-8 text-end"> <div class="col-8 text-end">
<small>@SharedLocalizer["Search.Version"]:</small> <strong>@context.Version</strong> <small>@SharedLocalizer["Search.Version"]:</small> <strong>@context.Version</strong>

View File

@ -156,7 +156,7 @@
private bool IsValidXML(string description) private bool IsValidXML(string description)
{ {
// must contain letters, digits, or spaces // must contain letters, digits, or spaces
return Regex.IsMatch(description, "^[A-Za-z0-9 ]+$"); return Regex.IsMatch(description, "^[A-Za-z0-9 .,!?]+$");
} }
private void TemplateChanged(ChangeEventArgs e) private void TemplateChanged(ChangeEventArgs e)

View File

@ -26,6 +26,17 @@
<input id="title" type="text" class="form-control" @bind="@_title" required /> <input id="title" type="text" class="form-control" @bind="@_title" required />
</div> </div>
</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"> <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> <Label Class="col-sm-3" For="container" HelpText="Select the module's container" ResourceKey="Container">Container: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
@ -112,6 +123,7 @@
private List<ThemeControl> _containers = new List<ThemeControl>(); private List<ThemeControl> _containers = new List<ThemeControl>();
private string _module; private string _module;
private string _title; private string _title;
private string _pane;
private string _containerType; private string _containerType;
private string _allPages = "false"; private string _allPages = "false";
private string _permissionNames = ""; private string _permissionNames = "";
@ -134,80 +146,82 @@
{ {
_module = ModuleState.ModuleDefinition.Name; _module = ModuleState.ModuleDefinition.Name;
_title = ModuleState.Title; _title = ModuleState.Title;
_pane = ModuleState.Pane;
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, PageState.Page.ThemeType); _containers = ThemeService.GetContainerControls(PageState.Site.Themes, PageState.Page.ThemeType);
_containerType = ModuleState.ContainerType; _containerType = ModuleState.ContainerType;
_allPages = ModuleState.AllPages.ToString(); _allPages = ModuleState.AllPages.ToString();
_permissions = ModuleState.PermissionList; _permissions = ModuleState.PermissionList;
_pageId = ModuleState.PageId.ToString(); _pageId = ModuleState.PageId.ToString();
createdby = ModuleState.CreatedBy; createdby = ModuleState.CreatedBy;
createdon = ModuleState.CreatedOn; createdon = ModuleState.CreatedOn;
modifiedby = ModuleState.ModifiedBy; modifiedby = ModuleState.ModifiedBy;
modifiedon = ModuleState.ModifiedOn; modifiedon = ModuleState.ModifiedOn;
if (ModuleState.ModuleDefinition != null) if (ModuleState.ModuleDefinition != null)
{ {
_permissionNames = ModuleState.ModuleDefinition?.PermissionNames; _permissionNames = ModuleState.ModuleDefinition?.PermissionNames;
if (!string.IsNullOrEmpty(ModuleState.ModuleDefinition.SettingsType)) if (!string.IsNullOrEmpty(ModuleState.ModuleDefinition.SettingsType))
{ {
// module settings type explicitly declared in IModule interface // module settings type explicitly declared in IModule interface
_moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.SettingsType); _moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.SettingsType);
} }
else else
{ {
// legacy support - module settings type determined by convention ( ie. existence of a "Settings.razor" component in module ) // legacy support - module settings type determined by convention ( ie. existence of a "Settings.razor" component in module )
_moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, PageState.Action), false, true); _moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, PageState.Action), false, true);
} }
if (_moduleSettingsType != null) if (_moduleSettingsType != null)
{ {
var moduleobject = Activator.CreateInstance(_moduleSettingsType) as IModuleControl; var moduleobject = Activator.CreateInstance(_moduleSettingsType) as IModuleControl;
if (!string.IsNullOrEmpty(moduleobject.Title)) if (!string.IsNullOrEmpty(moduleobject.Title))
{ {
_moduleSettingsTitle = moduleobject.Title; _moduleSettingsTitle = moduleobject.Title;
} }
ModuleSettingsComponent = builder => ModuleSettingsComponent = builder =>
{ {
builder.OpenComponent(0, _moduleSettingsType); builder.OpenComponent(0, _moduleSettingsType);
builder.AddComponentReferenceCapture(1, inst => { _moduleSettings = Convert.ChangeType(inst, _moduleSettingsType); }); builder.AddComponentReferenceCapture(1, inst => { _moduleSettings = Convert.ChangeType(inst, _moduleSettingsType); });
builder.CloseComponent(); builder.CloseComponent();
}; };
} }
} }
else else
{ {
AddModuleMessage(string.Format(Localizer["Error.Module.Load"], ModuleState.ModuleDefinitionName), MessageType.Error); AddModuleMessage(string.Format(Localizer["Error.Module.Load"], ModuleState.ModuleDefinitionName), MessageType.Error);
} }
var theme = PageState.Site.Themes.FirstOrDefault(item => item.Containers.Any(themecontrol => themecontrol.TypeName.Equals(_containerType))); var theme = PageState.Site.Themes.FirstOrDefault(item => item.Containers.Any(themecontrol => themecontrol.TypeName.Equals(_containerType)));
if (theme != null && !string.IsNullOrEmpty(theme.ContainerSettingsType)) if (theme != null && !string.IsNullOrEmpty(theme.ContainerSettingsType))
{ {
_containerSettingsType = Type.GetType(theme.ContainerSettingsType); _containerSettingsType = Type.GetType(theme.ContainerSettingsType);
if (_containerSettingsType != null) if (_containerSettingsType != null)
{ {
ContainerSettingsComponent = builder => ContainerSettingsComponent = builder =>
{ {
builder.OpenComponent(0, _containerSettingsType); builder.OpenComponent(0, _containerSettingsType);
builder.AddComponentReferenceCapture(1, inst => { _containerSettings = Convert.ChangeType(inst, _containerSettingsType); }); builder.AddComponentReferenceCapture(1, inst => { _containerSettings = Convert.ChangeType(inst, _containerSettingsType); });
builder.CloseComponent(); builder.CloseComponent();
}; };
} }
} }
} }
private async Task SaveModule() private async Task SaveModule()
{ {
validated = true; validated = true;
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
if (await interop.FormValid(form)) if (await interop.FormValid(form))
{ {
if (!string.IsNullOrEmpty(_title)) if (!string.IsNullOrEmpty(_title))
{ {
var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId); var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
pagemodule.PageId = int.Parse(_pageId); pagemodule.PageId = int.Parse(_pageId);
pagemodule.Title = _title; pagemodule.Title = _title;
pagemodule.ContainerType = (_containerType != "-") ? _containerType : string.Empty; pagemodule.Pane = _pane;
if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Page.DefaultContainerType) pagemodule.ContainerType = (_containerType != "-") ? _containerType : string.Empty;
if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Page.DefaultContainerType)
{ {
pagemodule.ContainerType = string.Empty; pagemodule.ContainerType = string.Empty;
} }

View File

@ -9,7 +9,7 @@
{ {
<ActionLink Action="Add" Text="Add Page" ResourceKey="AddPage" /> <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> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>

View File

@ -22,7 +22,7 @@
<div class="row mb-1 align-items-center"> <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> <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"> <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> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
@ -43,6 +43,12 @@
<input id="length" class="form-control" @bind="@_maxlength" maxlength="4" required /> <input id="length" class="form-control" @bind="@_maxlength" maxlength="4" required />
</div> </div>
</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" maxlength="2" required />
</div>
</div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultVal" HelpText="The default value for this profile item" ResourceKey="DefaultValue">Default Value: </Label> <Label Class="col-sm-3" For="defaultVal" HelpText="The default value for this profile item" ResourceKey="DefaultValue">Default Value: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
@ -101,6 +107,7 @@
private string _category = string.Empty; private string _category = string.Empty;
private string _vieworder = "0"; private string _vieworder = "0";
private string _maxlength = "0"; private string _maxlength = "0";
private string _rows = "1";
private string _defaultvalue = string.Empty; private string _defaultvalue = string.Empty;
private string _options = string.Empty; private string _options = string.Empty;
private string _validation = string.Empty; private string _validation = string.Empty;
@ -131,6 +138,7 @@
_category = profile.Category; _category = profile.Category;
_vieworder = profile.ViewOrder.ToString(); _vieworder = profile.ViewOrder.ToString();
_maxlength = profile.MaxLength.ToString(); _maxlength = profile.MaxLength.ToString();
_rows = profile.Rows.ToString();
_defaultvalue = profile.DefaultValue; _defaultvalue = profile.DefaultValue;
_options = profile.Options; _options = profile.Options;
_validation = profile.Validation; _validation = profile.Validation;
@ -175,6 +183,7 @@
profile.Category = _category; profile.Category = _category;
profile.ViewOrder = int.Parse(_vieworder); profile.ViewOrder = int.Parse(_vieworder);
profile.MaxLength = int.Parse(_maxlength); profile.MaxLength = int.Parse(_maxlength);
profile.Rows = int.Parse(_rows);
profile.DefaultValue = _defaultvalue; profile.DefaultValue = _defaultvalue;
profile.Options = _options; profile.Options = _options;
profile.Validation = _validation; profile.Validation = _validation;

View File

@ -12,7 +12,7 @@ else
{ {
<ActionLink Action="Add" Text="Add Profile" Security="SecurityAccessLevel.Edit" ResourceKey="AddProfile" /> <ActionLink Action="Add" Text="Add Profile" Security="SecurityAccessLevel.Edit" ResourceKey="AddProfile" />
<Pager Items="@_profiles"> <Pager Items="@_profiles" SearchProperties="Title,Category">
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>

View File

@ -12,7 +12,7 @@ else
{ {
<ActionLink Action="Add" Text="Add Role" Security="SecurityAccessLevel.Edit" ResourceKey="AddRole" /> <ActionLink Action="Add" Text="Add Role" Security="SecurityAccessLevel.Edit" ResourceKey="AddRole" />
<Pager Items="@_roles"> <Pager Items="@_roles" SearchProperties="Name">
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>

View File

@ -14,7 +14,7 @@ else
{ {
<ActionLink Action="Add" Text="Add Site" ResourceKey="AddSite" /> <ActionLink Action="Add" Text="Add Site" ResourceKey="AddSite" />
<Pager Items="@_sites"> <Pager Items="@_sites" SearchProperties="Name">
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>

View File

@ -66,6 +66,7 @@
<div class="container-fluid px-0"> <div class="container-fluid px-0">
<div class="row g-0 mb-2"> <div class="row g-0 mb-2">
<div class="col-4"> <div class="col-4">
<a href="@context.ProductUrl" target="_blank">
@if (context.LogoUrl != null) @if (context.LogoUrl != null)
{ {
<img src="@context.LogoUrl" class="img-fluid" alt="@context.Name" /> <img src="@context.LogoUrl" class="img-fluid" alt="@context.Name" />
@ -74,6 +75,7 @@
{ {
<img src="/package.png" class="img-fluid" alt="@context.Name" /> <img src="/package.png" class="img-fluid" alt="@context.Name" />
} }
</a>
</div> </div>
<div class="col-8 text-end"> <div class="col-8 text-end">
<small>@SharedLocalizer["Search.Version"]:</small> <strong>@context.Version</strong> <small>@SharedLocalizer["Search.Version"]:</small> <strong>@context.Version</strong>

View File

@ -28,7 +28,7 @@ else
</div> </div>
</div> </div>
<br/> <br/>
<Pager Items="@_urlMappings"> <Pager Items="@_urlMappings" SearchProperties="Url">
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>

View File

@ -123,13 +123,27 @@ else
} }
else else
{ {
@if (p.IsRequired) @if (p.Rows == 1)
{ {
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" /> @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
{
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
}
} }
else else
{ {
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" /> @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>
@ -282,9 +296,11 @@ else
private int folderid = -1; private int folderid = -1;
private int photofileid = -1; private int photofileid = -1;
private File photo = null; private File photo = null;
private List<Profile> profiles; private List<Profile> profiles;
private Dictionary<string, string> settings; private Dictionary<string, string> settings;
private string category = string.Empty; private string category = string.Empty;
private string filter = "to"; private string filter = "to";
private List<Notification> notifications; private List<Notification> notifications;
private string notificationSummary = string.Empty; private string notificationSummary = string.Empty;
@ -363,44 +379,47 @@ else
{ {
try try
{ {
if (username != string.Empty && email != string.Empty && ValidateProfiles()) if (username != string.Empty && email != string.Empty)
{ {
if (_password == confirm) if (_password == confirm)
{ {
var user = PageState.User; if (ValidateProfiles())
user.Username = username;
user.Password = _password;
user.TwoFactorRequired = bool.Parse(twofactor);
user.Email = email;
user.DisplayName = (displayname == string.Empty ? username : displayname);
user.PhotoFileId = filemanager.GetFileId();
if (user.PhotoFileId == -1)
{ {
user.PhotoFileId = null; var user = PageState.User;
} user.Username = username;
if (user.PhotoFileId != null) user.Password = _password;
{ user.TwoFactorRequired = bool.Parse(twofactor);
photofileid = user.PhotoFileId.Value; user.Email = email;
photo = await FileService.GetFileAsync(photofileid); user.DisplayName = (displayname == string.Empty ? username : displayname);
} user.PhotoFileId = filemanager.GetFileId();
else if (user.PhotoFileId == -1)
{ {
photofileid = -1; user.PhotoFileId = null;
photo = null; }
} if (user.PhotoFileId != null)
{
photofileid = user.PhotoFileId.Value;
photo = await FileService.GetFileAsync(photofileid);
}
else
{
photofileid = -1;
photo = null;
}
user = await UserService.UpdateUserAsync(user); user = await UserService.UpdateUserAsync(user);
if (user != null) if (user != null)
{ {
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId); await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
await logger.LogInformation("User Profile Saved"); await logger.LogInformation("User Profile Saved");
AddModuleMessage(Localizer["Success.Profile.Update"], MessageType.Success); AddModuleMessage(Localizer["Success.Profile.Update"], MessageType.Success);
StateHasChanged(); StateHasChanged();
} }
else else
{ {
AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error); AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error);
}
} }
} }
else else
@ -424,7 +443,6 @@ else
private bool ValidateProfiles() private bool ValidateProfiles()
{ {
bool valid = true;
foreach (Profile profile in profiles) foreach (Profile profile in profiles)
{ {
if (string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)) && !string.IsNullOrEmpty(profile.DefaultValue)) if (string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)) && !string.IsNullOrEmpty(profile.DefaultValue))
@ -433,18 +451,24 @@ else
} }
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) 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(SettingService.GetSetting(settings, profile.Name, string.Empty)))
{ {
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); Regex regex = new Regex(profile.Validation);
valid = regex.Match(SettingService.GetSetting(settings, profile.Name, string.Empty)).Success; bool valid = regex.Match(SettingService.GetSetting(settings, profile.Name, string.Empty)).Success;
if (!valid)
{
AddModuleMessage(string.Format(SharedLocalizer["ProfileInvalid"], profile.Title), MessageType.Warning);
return false;
}
} }
} }
} }
return valid; return true;
} }
private void Cancel() private void Cancel()

View File

@ -80,18 +80,50 @@
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label> <Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
<div class="col-sm-9"> <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 else
{ {
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" /> @if (p.Rows == 1)
{
@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
{
<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>
</div> </div>
} }
</div> </div>
</div> </div>
} }
@ -148,31 +180,34 @@
{ {
try 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)
{ {
var user = new User(); if (ValidateProfiles())
user.SiteId = PageState.Site.SiteId;
user.Username = _username;
user.Password = _password;
user.Email = _email;
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
user.PhotoFileId = null;
user.SuppressNotification = !bool.Parse(_notify);
user = await UserService.AddUserAsync(user);
if (user != null)
{ {
await SettingService.UpdateUserSettingsAsync(settings, user.UserId); var user = new User();
await logger.LogInformation("User Created {User}", user); user.SiteId = PageState.Site.SiteId;
NavigationManager.NavigateTo(NavigateUrl()); user.Username = _username;
} user.Password = _password;
else user.Email = _email;
{ user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
await logger.LogError("Error Adding User {Username} {Email}", _username, _email); user.PhotoFileId = null;
AddModuleMessage(Localizer["Error.User.AddCheckPass"], MessageType.Error); user.SuppressNotification = !bool.Parse(_notify);
user = await UserService.AddUserAsync(user);
if (user != null)
{
await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
await logger.LogInformation("User Created {User}", user);
NavigationManager.NavigateTo(NavigateUrl());
}
else
{
await logger.LogError("Error Adding User {Username} {Email}", _username, _email);
AddModuleMessage(Localizer["Error.User.AddCheckPass"], MessageType.Error);
}
} }
} }
else else
@ -194,7 +229,6 @@
private bool ValidateProfiles() private bool ValidateProfiles()
{ {
bool valid = true;
foreach (Profile profile in profiles) foreach (Profile profile in profiles)
{ {
if (string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)) && !string.IsNullOrEmpty(profile.DefaultValue)) if (string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)) && !string.IsNullOrEmpty(profile.DefaultValue))
@ -203,18 +237,24 @@
} }
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) 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(SettingService.GetSetting(settings, profile.Name, string.Empty)))
{ {
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); Regex regex = new Regex(profile.Validation);
valid = regex.Match(SettingService.GetSetting(settings, profile.Name, string.Empty)).Success; bool valid = regex.Match(SettingService.GetSetting(settings, profile.Name, string.Empty)).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) private void ProfileChanged(ChangeEventArgs e, string SettingName)

View File

@ -125,13 +125,27 @@ else
} }
else else
{ {
@if (p.IsRequired) @if (p.Rows == 1)
{ {
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" /> @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
{
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
}
} }
else else
{ {
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" /> @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>
@ -240,35 +254,38 @@ else
{ {
try try
{ {
if (username != string.Empty && email != string.Empty && ValidateProfiles()) if (username != string.Empty && email != string.Empty)
{ {
if (_password == confirm) if (_password == confirm)
{ {
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId); if (ValidateProfiles())
user.SiteId = PageState.Site.SiteId;
user.Username = username;
user.Password = _password;
user.Email = email;
user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname;
user.PhotoFileId = null;
user.PhotoFileId = filemanager.GetFileId();
if (user.PhotoFileId == -1)
{ {
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
user.SiteId = PageState.Site.SiteId;
user.Username = username;
user.Password = _password;
user.Email = email;
user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname;
user.PhotoFileId = null; user.PhotoFileId = null;
} user.PhotoFileId = filemanager.GetFileId();
if (user.PhotoFileId == -1)
{
user.PhotoFileId = null;
}
user.IsDeleted = (isdeleted == null ? true : Boolean.Parse(isdeleted)); user.IsDeleted = (isdeleted == null ? true : Boolean.Parse(isdeleted));
user = await UserService.UpdateUserAsync(user); user = await UserService.UpdateUserAsync(user);
if (user != null) if (user != null)
{ {
await SettingService.UpdateUserSettingsAsync(settings, user.UserId); await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
await logger.LogInformation("User Saved {User}", user); await logger.LogInformation("User Saved {User}", user);
NavigationManager.NavigateTo(NavigateUrl()); NavigationManager.NavigateTo(NavigateUrl());
} }
else else
{ {
AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error); AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error);
}
} }
} }
else else
@ -290,7 +307,6 @@ else
private bool ValidateProfiles() private bool ValidateProfiles()
{ {
bool valid = true;
foreach (Profile profile in profiles) foreach (Profile profile in profiles)
{ {
if (string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)) && !string.IsNullOrEmpty(profile.DefaultValue)) if (string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)) && !string.IsNullOrEmpty(profile.DefaultValue))
@ -299,18 +315,24 @@ else
} }
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) 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(SettingService.GetSetting(settings, profile.Name, string.Empty)))
{ {
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); Regex regex = new Regex(profile.Validation);
valid = regex.Match(SettingService.GetSetting(settings, profile.Name, string.Empty)).Success; bool valid = regex.Match(SettingService.GetSetting(settings, profile.Name, string.Empty)).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) private void ProfileChanged(ChangeEventArgs e, string SettingName)

View File

@ -17,21 +17,10 @@ else
{ {
<TabStrip> <TabStrip>
<TabPanel Name="Users" Heading="Users" ResourceKey="Users"> <TabPanel Name="Users" Heading="Users" ResourceKey="Users">
<div class="container"> <ActionLink Action="Add" Text="Add User" Security="SecurityAccessLevel.Edit" ResourceKey="AddUser" />&nbsp;
<div class="row mb-1 align-items-center"> <ActionLink Text="Import Users" Class="btn btn-secondary ms-2" Action="Users" Security="SecurityAccessLevel.Admin" ResourceKey="ImportUsers"/>
<div class="col-sm-4">
<ActionLink Action="Add" Text="Add User" Security="SecurityAccessLevel.Edit" ResourceKey="AddUser" />&nbsp; <Pager Items="@users" RowClass="align-middle" SearchProperties="User.Username,User.Email,User.DisplayName">
<ActionLink Text="Import Users" Class="btn btn-secondary" Action="Users" Security="SecurityAccessLevel.Admin" ResourceKey="ImportUsers"/>
</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">
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
@ -262,7 +251,22 @@ else
<input id="parameters" class="form-control" @bind="@_parameters" /> <input id="parameters" class="form-control" @bind="@_parameters" />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="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> <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"> <div class="col-sm-9">
<select id="pkce" class="form-select" @bind="@_pkce" required> <select id="pkce" class="form-select" @bind="@_pkce" required>
@ -368,9 +372,7 @@ else
} }
@code { @code {
private List<UserRole> allusers;
private List<UserRole> users; private List<UserRole> users;
private string _search = "";
private string _allowregistration; private string _allowregistration;
private string _allowsitelogin; private string _allowsitelogin;
@ -400,6 +402,7 @@ else
private string _scopes; private string _scopes;
private string _parameters; private string _parameters;
private string _pkce; private string _pkce;
private string _authresponsetype;
private string _redirecturl; private string _redirecturl;
private string _identifierclaimtype; private string _identifierclaimtype;
private string _emailclaimtype; private string _emailclaimtype;
@ -423,7 +426,6 @@ else
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
await LoadUserSettingsAsync();
await LoadUsersAsync(true); await LoadUsersAsync(true);
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
@ -458,6 +460,7 @@ else
_scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", ""); _scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", "");
_parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", ""); _parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", "");
_pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false"); _pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false");
_authresponsetype = SettingService.GetSetting(settings, "ExternalLogin:AuthResponseType", "code");
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype; _redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;
_identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "sub"); _identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "sub");
_emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "email"); _emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "email");
@ -478,32 +481,14 @@ else
{ {
if (load) 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)) if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{ {
var hosts = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Host); var hosts = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Host);
allusers.AddRange(hosts); users.AddRange(hosts);
allusers = allusers.OrderBy(u => u.User.DisplayName).ToList(); 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) private async Task DeleteUser(UserRole UserRole)
@ -526,21 +511,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() private async Task SaveSiteSettings()
{ {
try try
@ -579,6 +549,7 @@ else
settings = SettingService.SetSetting(settings, "ExternalLogin:Scopes", _scopes, true); settings = SettingService.SetSetting(settings, "ExternalLogin:Scopes", _scopes, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:Parameters", _parameters, true); settings = SettingService.SetSetting(settings, "ExternalLogin:Parameters", _parameters, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, 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:IdentifierClaimType", _identifierclaimtype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:EmailClaimType", _emailclaimtype, true); settings = SettingService.SetSetting(settings, "ExternalLogin:EmailClaimType", _emailclaimtype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimType", _roleclaimtype, true); settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimType", _roleclaimtype, true);

View File

@ -40,6 +40,13 @@
</div> </div>
</div> </div>
} }
else
{
if (GetFileId() != -1 && !UploadMultiple)
{
<input class="form-control" @bind="@_file.Name" disabled />
}
}
@if (ShowUpload && _haseditpermission) @if (ShowUpload && _haseditpermission)
{ {
<div class="row"> <div class="row">
@ -55,7 +62,7 @@
</div> </div>
<div class="col mt-2 text-end"> <div class="col mt-2 text-end">
<button type="button" class="btn btn-success" @onclick="UploadFiles">@SharedLocalizer["Upload"]</button> <button type="button" class="btn btn-success" @onclick="UploadFiles">@SharedLocalizer["Upload"]</button>
@if (GetFileId() != -1) @if (GetFileId() != -1 && !UploadMultiple)
{ {
<button type="button" class="btn btn-danger mx-1" @onclick="DeleteFile">@SharedLocalizer["Delete"]</button> <button type="button" class="btn btn-danger mx-1" @onclick="DeleteFile">@SharedLocalizer["Delete"]</button>
} }
@ -371,7 +378,7 @@
success = false; success = false;
var filename = uploads[upload].Split(':')[0]; var filename = uploads[upload].Split(':')[0];
var size = Int64.Parse(uploads[upload].Split(':')[1]); var size = Int64.Parse(uploads[upload].Split(':')[1]);
var maxattempts = (int)Math.Ceiling(size / 500000.0) + 1; // 30 MB takes 1 minute at 5 Mbps var maxattempts = (int)Math.Ceiling(size / 500000.0) + 4; // 30 MB takes 1 minute at 5 Mbps (min 5 attempts)
int attempts = 0; int attempts = 0;
while (attempts < maxattempts && !success) while (attempts < maxattempts && !success)

View File

@ -1,10 +1,20 @@
@namespace Oqtane.Modules.Controls @namespace Oqtane.Modules.Controls
@inherits ModuleControlBase @inherits ModuleControlBase
@inject IStringLocalizerFactory LocalizerFactory @inject IStringLocalizerFactory LocalizerFactory
@inject IStringLocalizer<SharedResources> SharedLocalizer
@typeparam TableItem @typeparam TableItem
@if (ItemList != null) @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) @if ((Toolbar == "Top" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
{ {
<ul class="pagination justify-content-center my-2"> <ul class="pagination justify-content-center my-2">
@ -175,6 +185,9 @@
private int _startPage = 0; private int _startPage = 0;
private int _endPage = 0; private int _endPage = 0;
private int _columns = 0; private int _columns = 0;
private string _search = "";
private IEnumerable<TableItem> AllItems;
[Parameter] [Parameter]
public string Format { get; set; } // Table or Grid public string Format { get; set; } // Table or Grid
@ -221,6 +234,9 @@
[Parameter] [Parameter]
public Action<int> OnPageChange { get; set; } // a method to be executed in the calling component when the page changes 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; } private IEnumerable<TableItem> ItemList { get; set; }
protected override void OnInitialized() 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)) if (!string.IsNullOrEmpty(PageSize))
{ {
_maxItems = int.Parse(PageSize); _maxItems = int.Parse(PageSize);
@ -323,10 +348,10 @@
{ {
_endPage = _pages; _endPage = _pages;
} }
ItemList = Items.Skip((_page - 1) * _maxItems).Take(_maxItems); ItemList = Items.Skip((_page - 1) * _maxItems).Take(_maxItems);
StateHasChanged(); StateHasChanged();
OnPageChange?.Invoke(_page); OnPageChange?.Invoke(_page);
} }
public void UpdateList(int page) public void UpdateList(int page)
{ {
@ -369,4 +394,75 @@
UpdateList(_page); 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);
}
} }

View File

@ -4,7 +4,7 @@
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Version>4.0.5</Version> <Version>4.0.6</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -12,7 +12,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.5</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.6</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace> <RootNamespace>Oqtane</RootNamespace>

View File

@ -159,4 +159,10 @@
<data name="Module Settings" xml:space="preserve"> <data name="Module Settings" xml:space="preserve">
<value>Module Settings</value> <value>Module Settings</value>
</data> </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> </root>

View File

@ -408,4 +408,31 @@
<data name="ImportUsers.Text" xml:space="preserve"> <data name="ImportUsers.Text" xml:space="preserve">
<value>Import Users</value> <value>Import Users</value>
</data> </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>
</root> </root>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!-- <!--
Microsoft ResX Schema Microsoft ResX Schema
@ -120,4 +120,7 @@
<data name="PageOfPages" xml:space="preserve"> <data name="PageOfPages" xml:space="preserve">
<value>Page {0} of {1}</value> <value>Page {0} of {1}</value>
</data> </data>
<data name="SearchPlaceholder" xml:space="preserve">
<value>Search: {0}</value>
</data>
</root> </root>

View File

@ -426,4 +426,10 @@
<data name="Password.ValidationCriteria" xml:space="preserve"> <data name="Password.ValidationCriteria" xml:space="preserve">
<value>Passwords Must Have A Minimum Length Of {0} Characters, Including At Least {1} Unique Character(s), {2}{3}{4}{5} To Satisfy Password Compexity Requirements For This Site.</value> <value>Passwords Must Have A Minimum Length Of {0} Characters, Including At Least {1} Unique Character(s), {2}{3}{4}{5} To Satisfy Password Compexity Requirements For This Site.</value>
</data> </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> </root>

View File

@ -189,4 +189,13 @@
<data name="Confirm.Page.Delete" xml:space="preserve"> <data name="Confirm.Page.Delete" xml:space="preserve">
<value>Are You Sure You Want To Delete This Page?</value> <value>Are You Sure You Want To Delete This Page?</value>
</data> </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> </root>

View File

@ -4,6 +4,7 @@ using System.Net.Http;
using System; using System;
using Oqtane.Documentation; using Oqtane.Documentation;
using Oqtane.Shared; using Oqtane.Shared;
using System.Globalization;
namespace Oqtane.Services namespace Oqtane.Services
{ {
@ -18,7 +19,7 @@ namespace Oqtane.Services
/// <inheritdoc /> /// <inheritdoc />
public async Task<Sync> GetSyncEventsAsync(DateTime lastSyncDate) 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)}");
} }
} }
} }

View File

@ -15,7 +15,7 @@
<div class="main g-0"> <div class="main g-0">
<div class="top-row px-4"> <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>
<div class="container"> <div class="container">
<div class="row px-4"> <div class="row px-4">

View File

@ -17,7 +17,7 @@
@if (ShowLanguageSwitcher) @if (ShowLanguageSwitcher)
{ {
<LanguageSwitcher /> <LanguageSwitcher DropdownAlignment="@LanguageDropdownAlignment" />
} }
@if (_showEditMode || (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered))) @if (_showEditMode || (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered)))
@ -114,7 +114,7 @@
<div class="row"> <div class="row">
<div class="col text-center"> <div class="col text-center">
<label for="Module" class="control-label">@Localizer["Module.Manage"] </label> <label for="Module" class="control-label">@Localizer["Module.Manage"]</label>
<select class="form-select" @bind="@ModuleType"> <select class="form-select" @bind="@ModuleType">
<option value="new">@Localizer["Module.AddNew"]</option> <option value="new">@Localizer["Module.AddNew"]</option>
<option value="existing">@Localizer["Module.AddExisting"]</option> <option value="existing">@Localizer["Module.AddExisting"]</option>
@ -179,27 +179,33 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col text-center"> <div class="col text-center">
<label for="Title" class="control-label">@Localizer["Title"] </label> <label for="Title" class="control-label">@Localizer["Title"]</label>
<input type="text" name="Title" class="form-control" @bind="@Title" /> <input type="text" name="Title" class="form-control" @bind="@Title" />
</div> </div>
</div> </div>
@if (_pane.Length > 1)
{
<div class="row">
<div class="col text-center">
<label for="Pane" class="control-label">@Localizer["Pane"] </label>
<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"> <div class="row">
<div class="col text-center"> <div class="col text-center">
<label for="Container" class="control-label">@Localizer["Container"] </label> <label for="Pane" class="control-label">@Localizer["Pane"]</label>
<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">
<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>
<select class="form-select" @bind="@ContainerType"> <select class="form-select" @bind="@ContainerType">
@foreach (var container in _containers) @foreach (var container in _containers)
{ {
@ -233,6 +239,24 @@
} }
@code{ @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 _canViewAdminDashboard = false;
private bool _showEditMode = false; private bool _showEditMode = false;
private bool _deleteConfirmation = false; private bool _deleteConfirmation = false;
@ -243,6 +267,7 @@
private List<Module> _modules = new List<Module>(); private List<Module> _modules = new List<Module>();
private List<ThemeControl> _containers = new List<ThemeControl>(); private List<ThemeControl> _containers = new List<ThemeControl>();
private string _category = "Common"; private string _category = "Common";
private string _pane = "";
protected string PageId { get; private set; } = "-"; protected string PageId { get; private set; } = "-";
protected string ModuleId { get; private set; } = "-"; protected string ModuleId { get; private set; } = "-";
@ -251,12 +276,12 @@
protected string Title { get; private set; } = ""; protected string Title { get; private set; } = "";
protected string ContainerType { 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 Visibility { get; private set; } = "view";
protected string Message { get; private set; } = ""; protected string Message { get; private set; } = "";
private string settingCategory = "CP-category"; private string settingCategory = "CP-category";
private string settingPane = "CP-pane"; private string settingPane = "CP-pane";
private string _pane = "";
protected string Category protected string Category
{ {
@ -288,21 +313,6 @@
} }
} }
[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() protected override async Task OnParametersSetAsync()
{ {
_canViewAdminDashboard = CanViewAdminDashboard(); _canViewAdminDashboard = CanViewAdminDashboard();
@ -310,8 +320,9 @@
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList)) if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList))
{ {
_showEditMode = true; _showEditMode = true;
_pages?.Clear(); LoadSettingsAsync();
_pages?.Clear();
foreach (Page p in PageState.Pages) foreach (Page p in PageState.Pages)
{ {
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList)) if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
@ -319,7 +330,6 @@
_pages.Add(p); _pages.Add(p);
} }
} }
await LoadSettingsAsync();
var themes = await ThemeService.GetThemesAsync(); var themes = await ThemeService.GetThemesAsync();
_containers = ThemeService.GetContainerControls(themes, PageState.Page.ThemeType); _containers = ThemeService.GetContainerControls(themes, PageState.Page.ThemeType);
@ -443,7 +453,7 @@
} }
pageModule.Pane = Pane; pageModule.Pane = Pane;
pageModule.Order = int.MaxValue; pageModule.Order = Location;
pageModule.ContainerType = ContainerType; pageModule.ContainerType = ContainerType;
if (pageModule.ContainerType == PageState.Site.DefaultContainerType) if (pageModule.ContainerType == PageState.Site.DefaultContainerType)
@ -653,11 +663,10 @@
} }
} }
private async Task LoadSettingsAsync() private void LoadSettingsAsync()
{ {
Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId); _category = SettingService.GetSetting(PageState.User.Settings, settingCategory, "Common");
_category = SettingService.GetSetting(settings, settingCategory, "Common"); var pane = SettingService.GetSetting(PageState.User.Settings, settingPane, "");
var pane = SettingService.GetSetting(settings, settingPane, "");
if (PageState.Page.Panes.Contains(pane)) if (PageState.Page.Panes.Contains(pane))
{ {
_pane = pane; _pane = pane;

View File

@ -12,7 +12,7 @@
<button id="btnCultures" type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <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> <span class="oi oi-globe"></span>
</button> </button>
<div class="dropdown-menu" aria-labelledby="btnCultures"> <div class="dropdown-menu @MenuAlignment" aria-labelledby="btnCultures">
@foreach (var culture in _supportedCultures) @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> <a class="dropdown-item @(CultureInfo.CurrentUICulture.Name == culture.Name ? "active" : String.Empty)" href="#" @onclick="@(async e => await SetCultureAsync(culture.Name))">@culture.DisplayName</a>
@ -23,10 +23,15 @@
@code{ @code{
private IEnumerable<Culture> _supportedCultures; 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() protected override void OnParametersSet()
{ {
var languages = PageState.Languages; 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 }); _supportedCultures = languages.Select(l => new Culture { Name = l.Code, DisplayName = l.Name });
} }

View File

@ -6,7 +6,7 @@
<nav class="navbar navbar-dark bg-primary fixed-top"> <nav class="navbar navbar-dark bg-primary fixed-top">
<Logo /><Menu Orientation="Horizontal" /> <Logo /><Menu Orientation="Horizontal" />
<div class="controls ms-auto"> <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> </div>
</nav> </nav>
<div class="content"> <div class="content">

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<Version>4.0.5</Version> <Version>4.0.6</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.5</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.6</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

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

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<Version>4.0.5</Version> <Version>4.0.6</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.5</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.6</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

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

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<Version>4.0.5</Version> <Version>4.0.6</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.5</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.6</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

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

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<Version>4.0.5</Version> <Version>4.0.6</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.5</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.6</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

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

View File

@ -6,7 +6,7 @@
<!-- <TargetFrameworks>net7.0-android;net7.0-ios;net7.0-maccatalyst</TargetFrameworks> --> <!-- <TargetFrameworks>net7.0-android;net7.0-ios;net7.0-maccatalyst</TargetFrameworks> -->
<!-- <TargetFrameworks>$(TargetFrameworks);net7.0-tizen</TargetFrameworks> --> <!-- <TargetFrameworks>$(TargetFrameworks);net7.0-tizen</TargetFrameworks> -->
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<Version>4.0.5</Version> <Version>4.0.6</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -14,7 +14,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.5</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.6</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane.Maui</RootNamespace> <RootNamespace>Oqtane.Maui</RootNamespace>
@ -31,7 +31,7 @@
<ApplicationIdGuid>0E29FC31-1B83-48ED-B6E0-9F3C67B775D4</ApplicationIdGuid> <ApplicationIdGuid>0E29FC31-1B83-48ED-B6E0-9F3C67B775D4</ApplicationIdGuid>
<!-- Versions --> <!-- Versions -->
<ApplicationDisplayVersion>4.0.5</ApplicationDisplayVersion> <ApplicationDisplayVersion>4.0.6</ApplicationDisplayVersion>
<ApplicationVersion>1</ApplicationVersion> <ApplicationVersion>1</ApplicationVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion> <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1 +1 @@
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net7.0\publish\*" -DestinationPath "Oqtane.Framework.4.0.5.Install.zip" -Force Compress-Archive -Path "..\Oqtane.Server\bin\Release\net7.0\publish\*" -DestinationPath "Oqtane.Framework.4.0.6.Install.zip" -Force

View File

@ -1 +1 @@
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net7.0\publish\*" -DestinationPath "Oqtane.Framework.4.0.5.Upgrade.zip" -Force Compress-Archive -Path "..\Oqtane.Server\bin\Release\net7.0\publish\*" -DestinationPath "Oqtane.Framework.4.0.6.Upgrade.zip" -Force

View File

@ -44,7 +44,7 @@ namespace Oqtane.Extensions
options.SaveTokens = false; options.SaveTokens = false;
options.GetClaimsFromUserInfoEndpoint = true; options.GetClaimsFromUserInfoEndpoint = true;
options.CallbackPath = string.IsNullOrEmpty(alias.Path) ? "/signin-" + AuthenticationProviderTypes.OpenIDConnect : "/" + alias.Path + "/signin-" + AuthenticationProviderTypes.OpenIDConnect; options.CallbackPath = string.IsNullOrEmpty(alias.Path) ? "/signin-" + AuthenticationProviderTypes.OpenIDConnect : "/" + alias.Path + "/signin-" + AuthenticationProviderTypes.OpenIDConnect;
options.ResponseType = OpenIdConnectResponseType.Code; // authorization code flow options.ResponseType = sitesettings.GetValue("ExternalLogin:AuthResponseType", "code"); // authorization code flow
options.ResponseMode = OpenIdConnectResponseMode.FormPost; // recommended as most secure options.ResponseMode = OpenIdConnectResponseMode.FormPost; // recommended as most secure
// cookie config is required to avoid Correlation Failed errors // cookie config is required to avoid Correlation Failed errors

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

View File

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

View File

@ -32,8 +32,9 @@ namespace Oqtane.Repository
public IEnumerable<File> GetFiles(int folderId, bool tracking) public IEnumerable<File> GetFiles(int folderId, bool tracking)
{ {
var alias = _tenants.GetAlias(); var folder = _folderRepository.GetFolder(folderId, false);
IEnumerable<Permission> permissions = _permissions.GetPermissions(alias.SiteId, EntityNames.Folder, folderId).ToList(); IEnumerable<Permission> permissions = _permissions.GetPermissions(folder.SiteId, EntityNames.Folder, folderId).ToList();
IEnumerable<File> files; IEnumerable<File> files;
if (tracking) if (tracking)
{ {
@ -46,6 +47,7 @@ namespace Oqtane.Repository
foreach (File file in files) foreach (File file in files)
{ {
file.Folder.PermissionList = permissions.ToList(); file.Folder.PermissionList = permissions.ToList();
var alias = _tenants.GetAlias();
file.Url = GetFileUrl(file, alias); file.Url = GetFileUrl(file, alias);
} }
return files; return files;

View File

@ -9,6 +9,7 @@
<Product>[Owner].Module.[Module]</Product> <Product>[Owner].Module.[Module]</Product>
<Copyright>[Owner]</Copyright> <Copyright>[Owner]</Copyright>
<AssemblyName>[Owner].Module.[Module].Client.Oqtane</AssemblyName> <AssemblyName>[Owner].Module.[Module].Client.Oqtane</AssemblyName>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -73,5 +73,10 @@ namespace Oqtane.Models
/// Optional RegExp validation expression /// Optional RegExp validation expression
/// </summary> /// </summary>
public string Validation { get; set; } public string Validation { get; set; }
/// <summary>
/// Optional number of rows (textarea)
/// </summary>
public int Rows { get; set; }
} }
} }

View File

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

View File

@ -7,8 +7,8 @@ namespace Oqtane.Shared
{ {
public class Constants public class Constants
{ {
public static readonly string Version = "4.0.5"; public static readonly string Version = "4.0.6";
public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3,4.0.4,4.0.5"; public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6";
public const string PackageId = "Oqtane.Framework"; public const string PackageId = "Oqtane.Framework";
public const string ClientId = "Oqtane.Client"; public const string ClientId = "Oqtane.Client";
public const string UpdaterPackageId = "Oqtane.Updater"; public const string UpdaterPackageId = "Oqtane.Updater";

View File

@ -1,8 +1,6 @@
using Oqtane.Models; using Oqtane.Models;
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -389,13 +387,20 @@ namespace Oqtane.Shared
} }
public static string UrlCombine(params string[] segments) public static string UrlCombine(params string[] segments)
{ {
segments = segments.Where(item => !string.IsNullOrEmpty(item) && item != "/" && item != "\\").ToArray(); var url = new List<string>();
for (int i = 1; i < segments.Length; i++) for (int i = 0; i < segments.Length; i++)
{ {
segments[i] = segments[i].Replace("\\", "/"); segments[i] = segments[i].Replace("\\", "/");
if (!string.IsNullOrEmpty(segments[i]) && segments[i] != "/")
{
foreach (var segment in segments[i].Split('/', StringSplitOptions.RemoveEmptyEntries))
{
url.Add(segment);
}
}
} }
return string.Join("/", segments); return string.Join("/", url);
} }
public static bool IsPathValid(this Folder folder) public static bool IsPathValid(this Folder folder)

View File

@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<Version>4.0.5</Version> <Version>4.0.6</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -11,7 +11,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.5</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.6</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace> <RootNamespace>Oqtane</RootNamespace>

View File

@ -1,6 +1,6 @@
# Latest Release # Latest Release
[4.0.4](https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.4) was released on Sep 25, 2023 and is primarily focused on stabilization. This release includes 53 pull requests by 4 different contributors, pushing the total number of project commits all-time to over 4100. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. [4.0.5](https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.5) was released on Sep 26, 2023 and is primarily focused on stabilization. This release includes 53 pull requests by 4 different contributors, pushing the total number of project commits all-time to over 4100. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers.
[![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Foqtane%2Foqtane.framework%2Fmaster%2Fazuredeploy.json) [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Foqtane%2Foqtane.framework%2Fmaster%2Fazuredeploy.json)
@ -53,6 +53,9 @@ Backlog (TBD)
5.0.0 (Q4 2023) 5.0.0 (Q4 2023)
- [ ] Migration to .NET 8 - [ ] Migration to .NET 8
[4.0.5](https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.5) ( Sep 26, 2023 )
- [x] Stabilization improvements
[4.0.4](https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.4) ( Sep 25, 2023 ) [4.0.4](https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.4) ( Sep 25, 2023 )
- [x] Stabilization improvements - [x] Stabilization improvements
- [x] User Import - [x] User Import