Compare commits

..

122 Commits

Author SHA1 Message Date
a84b497fae Merge pull request #1999 from artmedia/patch-1
typo correction for closing tag
2022-02-15 09:37:39 -05:00
c33d1bcd3c typo correction for closing label
typo correction for closing label
2022-02-15 10:19:58 +01:00
4071e14a7e Update README.md 2022-02-14 16:25:58 -05:00
b5b3f190b7 Merge pull request #1998 from leigh-pointer/ModuleCount
null reference exception still occurring
2022-02-14 13:07:29 -05:00
d43a3e132c null reference exception still occurring
added a '?' operator after the m.ModuleDefinition
2022-02-14 19:06:00 +01:00
a90c21f80a Merge pull request #1997 from sbwalker/dev
prepare for 3.0.3 release
2022-02-11 16:49:21 -05:00
2b768165e5 prepare for 3.0.3 release 2022-02-11 16:59:48 -05:00
e8425ba03a improve performance by reducing database calls in initial client request scenarios 2022-02-11 15:42:34 -05:00
b0a6f402e9 Merge pull request #1996 from sbwalker/dev
improve performance by reducing database calls in initial client request scenarios
2022-02-11 15:32:16 -05:00
02e86a940b Merge pull request #1973 from 2sic-forks/refs
fix #1972 Missing Preprocessor Directives during Runtime Compile
2022-02-10 07:56:52 -05:00
79d03eb43e Merge pull request #1988 from 2sic-forks/content
fix #1987 Nuspec to include 'content'
2022-02-10 07:56:08 -05:00
b564955f85 Merge pull request #1994 from sbwalker/dev
fixed #1989 - installation on SQLite failing due to DropColumn, fixed #1986 - IClientStartup not getting called for External Modules, added ability to correlate new visitors by IP address
2022-02-10 07:55:51 -05:00
5aed64f614 fixed #1989 - installation on SQLite failing due to DropColumn, fixed #1986 - IClientStartup not getting called for External Modules, added ability to correlate new visitors by IP address 2022-02-10 08:05:55 -05:00
a823a4d9b7 remove precompile symbol OQTANE3_0_OR_GREATER as it have the same effect as simple OQTANE 2022-02-08 20:15:50 +01:00
4eed2193f4 Merge branch 'oqtane:dev' into refs 2022-02-08 19:55:34 +01:00
48e7a41af6 fix #1987 Nuspec to include 'content' 2022-02-08 19:38:31 +01:00
bba5caecf7 Merge branch 'oqtane:dev' into content 2022-02-08 19:34:15 +01:00
ede6a45f15 more RichTextEditor refactoring 2022-02-08 07:42:47 -05:00
9c65d23229 Merge pull request #1992 from sbwalker/dev
more RichTextEditor refactoring
2022-02-08 07:32:21 -05:00
5dedfe9295 fix oqtane#1987 Nuspec to include 'content' ('contentFiles') 2022-02-07 14:09:14 +01:00
95d8c368c8 Meta tags should not be HTML encoded 2022-02-06 18:54:09 -05:00
49fbfb8bbc Merge pull request #1985 from sbwalker/dev
Meta tags should not be HTML encoded
2022-02-06 18:43:52 -05:00
aa3d2a5289 Merge pull request #1969 from 2sic-forks/dev
fix #1272 - add support for refs folder in package installation
2022-02-06 12:11:29 -05:00
48ae6df4b7 Merge pull request #1984 from sbwalker/dev
resolved UI error when closing Event Log and Visitor Management, made button class consistent in Recycle Bin, refactored RichTextEditor, made use of ConfigManager consistently throughout framework, added support for deleted Sites, removed reference to Runtime in Startup as it is now set per Site, added versioning to Html/Text, added Meta tag support to Page Management
2022-02-06 12:09:40 -05:00
c635351a12 resolved UI error when closing Event Log and Visitor Management, made button class consistent in Recycle Bin, refactored RichTextEditor, made use of ConfigManager consistently throughout framework, added support for deleted Sites, removed reference to Runtime in Startup as it is now set per Site, added versioning to Html/Text, added Meta tag support to Page Management 2022-02-06 12:19:42 -05:00
d9ff77fd9a fix #1972 Missing Preprocessor Directives during Runtime Compile 2022-01-31 18:27:56 +01:00
e1a7954307 fix #1272 - add support for refs folder in package installation 2022-01-29 06:45:51 +01:00
efe6421133 Merge pull request #1957 from Rodien/dev
Added web.Release.config to include remove WebDAV during the publish stage of a release
2022-01-28 13:00:56 -05:00
79b62f4407 Merge pull request #1968 from sbwalker/dev
improved UX in Event Log by preserving criteria when viewing Details, added RowClass and ColumnClass parameters to Pager component, added initial-scale=1.0 to viewport specification in _host, added default visitor tracking filter, fixed "The given key 'level' was not present in the dictionary" issue in Visitor Management - Details by ensuring data was fully loaded
2022-01-27 18:02:20 -05:00
9d17804ac7 improved UX in Event Log by preserving criteria when viewing Details, added RowClass and ColumnClass parameters to Pager component, added initial-scale=1.0 to viewport specification in _host, added default visitor tracking filter, fixed "The given key 'level' was not present in the dictionary" issue in Visitor Management - Details by ensuring data was fully loaded 2022-01-27 18:12:04 -05:00
5986355504 Merge pull request #1965 from leigh-pointer/ModuleCount#1963
Fix for Version 3 module definitions error #1963
2022-01-25 13:38:58 -05:00
192e6fde92 Fix for Version 3 module definitions error #1963
The code was assuming that the ModuleDefinitionId exists in the PageState.Modules collection. Instead of using .Count()  code now uses .FirstOrDefault() != null
2022-01-25 06:23:07 +01:00
ad090e62cc enhance Pager to support pure responsive Grid format (Columns = 0) 2022-01-23 10:40:41 -05:00
5c072fea62 Merge pull request #1960 from sbwalker/dev
enhance Pager to support pure responsive Grid format (Columns = 0)
2022-01-23 10:30:54 -05:00
fd01a40810 Merge pull request #1958 from leigh-pointer/AddUserId
Add the Username to the User And Roles display.
2022-01-22 19:25:20 -05:00
7b9a83a273 Merge pull request #1959 from sbwalker/dev
added router support for url fragments, added language attribute to HTML document tag to improve validation, fixed Theme Settings so they can only be invoked via the Control Panel, added support for webp image files
2022-01-22 19:24:51 -05:00
f964e0e502 added router support for url fragments, added language attribute to HTML document tag to improve validation, fixed Theme Settings so they can only be invoked via the Control Panel, added support for webp image files 2022-01-22 19:34:30 -05:00
22acb7c74b Comments cleanup
Code cleanup. I removed the unnecessary comments.

This code will remove WebDAV during the publish stage of a release.
2022-01-21 11:58:24 +01:00
6a99e81e75 Add the Username to the display.
When looking through Users and Roles it would seem ideal to also show the username.
2022-01-21 09:36:32 +01:00
93b6de1caf Added web.Release.config to include remove WebDAV during the publish stage of a release 2022-01-21 02:32:48 +01:00
1fbab5db2b Merge pull request #1954 from sbwalker/dev
enhance Pager component with OnPageChanged event and implement in Visitor Management, allow PermissionGrid component to support Host role, fix unhandled exception in RichTextEditor component related to rerendering, make Quill resource declarations forward compatible, update Blazor theme to Boostrap 5.1.3, add missing RemoteIPAddress parameter in _Host app component, include logic to enable bypass of non-default alias redirection
2022-01-19 17:40:01 -05:00
950e852dee Merge branch 'dev' of https://github.com/sbwalker/oqtane.framework into dev 2022-01-19 17:47:39 -05:00
826898e3fe enhance Pager component with OnPageChanged event and implement in Visitor Management, allow PermissionGrid component to support Host role, fix unhandled exception in RichTextEditor component related to rerendering, make Quill resource declarations forward compatible, update Blazor theme to Boostrap 5.1.3, add missing RemoteIPAddress parameter in _Host app component, include logic to enable bypass of non-default alias redirection 2022-01-19 17:47:27 -05:00
1268149d83 Merge pull request #1944 from oqtane/master
Merge pull request #1943 from oqtane/dev
2022-01-16 11:06:34 -05:00
908299970f Merge pull request #1943 from oqtane/dev
3.0.2 release
2022-01-16 11:05:56 -05:00
861dde8627 Update README.md 2022-01-15 12:59:50 -05:00
cc9802a0d8 use PageState.Uri rather than creating a new Uri object 2022-01-15 12:58:47 -05:00
69d1f3aa53 Merge pull request #1940 from sbwalker/dev
use PageState.Uri rather than creating a new Uri object
2022-01-15 12:49:00 -05:00
ea4587d842 Update README.md 2022-01-15 12:38:23 -05:00
fb4c95f945 Update README.md 2022-01-15 12:37:52 -05:00
95a27af5f2 Update README.md 2022-01-15 12:34:34 -05:00
9d7b25ade6 Merge pull request #1939 from sbwalker/dev
improvement for updating private/public Settings
2022-01-15 09:34:56 -05:00
3a8f4199cd improvement for updating private/public Settings 2022-01-15 09:44:36 -05:00
11002efc02 hide deleted pages in Admin Dashboard, impove Settings API by replacing IsPublic with IsPrivate, isolate Setting updates to not affect PageState, make Pager horizintally scrollable on narrow viewports, improve LocalizableComponent to support embedded controls 2022-01-14 13:26:24 -05:00
367c1c3568 Merge pull request #1938 from sbwalker/dev
hide deleted pages in Admin Dashboard, impove Settings API by replacing IsPublic with IsPrivate, isolate Setting updates to not affect PageState, make Pager horizintally scrollable on narrow viewports, improve LocalizableComponent to support embedded controls
2022-01-14 13:16:42 -05:00
9e04230d99 added interop method for setting scroll position, persisted RemoteIPAddress in PageState so it is available on Blazor Server, added support for forwarded headers from load balancers and proxy servers, replaced DateTime.Now references DateTimeUtcNow for consistency, fixed issue where upgrade logic was being executed for prior version 2022-01-13 07:18:37 -05:00
21304db7c9 Merge pull request #1936 from sbwalker/dev
added interop method for setting scroll position, persisted RemoteIPAddress in PageState so it is available on Blazor Server, added support for forwarded headers from load balancers and proxy servers, replaced DateTime.Now references DateTimeUtcNow for consistency, fixed issue where upgrade logic was being executed for prior version
2022-01-13 07:10:15 -05:00
f4f6e98045 Merge pull request #1935 from leigh-pointer/DeadResxKey
Empty Resx Value on Page Edit
2022-01-12 16:13:00 -05:00
ad41eff38a Empty Resx Value on Page Edit 2022-01-12 20:42:59 +01:00
dbd6cc4148 Merge pull request #1934 from oqtane/revert-1931-dev
Revert "Fixed first render js bug"
2022-01-12 14:09:08 -05:00
dda71e5ccd Revert "Fixed first render js bug" 2022-01-12 14:07:50 -05:00
cfe8059176 Merge pull request #1931 from zzmzaizai/dev
Fixed first render js bug
2022-01-12 13:57:13 -05:00
8b00784ecc Merge pull request #1933 from sbwalker/dev
fix z-index for Blazor theme on mobile
2022-01-12 07:50:44 -05:00
9bcc6bbad0 fix z-index for Blazor theme on mobile 2022-01-12 08:00:25 -05:00
ce7995966d Fixed first render js bug
Solve the problem that when the page is rendered for the first time and JS is executed, the reference to the JS file has not been successful, and the page is abnormally wrong
2022-01-12 10:36:10 +08:00
cea5f86df4 prepare for 3.0.2 release 2022-01-11 17:46:36 -05:00
0912253b1b Merge pull request #1930 from sbwalker/dev
prepare for 3.0.2 release
2022-01-11 17:36:54 -05:00
5aecc4be03 remove invalid app tag, fix page title not being set on first render 2022-01-11 15:07:54 -05:00
e09178c14c Merge pull request #1927 from sbwalker/dev
remove invalid app tag, fix page title not being set on first render
2022-01-11 14:58:13 -05:00
477ded6a4a Merge pull request #1921 from zzmzaizai/dev
Fixed first render css bug
2022-01-11 14:49:57 -05:00
311c48becb Merge pull request #1926 from sbwalker/dev
improve UX of password reset
2022-01-11 10:48:18 -05:00
ec924a7ddf improve UX of password reset 2022-01-11 10:57:58 -05:00
e39416a786 Update README.md 2022-01-11 09:14:43 -05:00
51b356cc0e enhanced scheduler to support one-time jobs, fixed pager component so that top/bottom have consistent UX, fixed Blazor theme z-index issues caused by input-group in Bootstrap 5, improved password reset instructions in email notification 2022-01-10 19:58:58 -05:00
66b13bdb8b Merge pull request #1925 from sbwalker/dev
enhanced scheduler to support one-time jobs, fixed pager component so that top/bottom have consistent UX, fixed Blazor theme z-index issues caused by input-group in Bootstrap 5, improved password reset instructions in email notification
2022-01-10 19:49:25 -05:00
4ade58da01 Fixed first render css bug
Fixed the bug that CSS could not be render when the module was loaded for the first
2022-01-10 16:06:48 +08:00
efcfc0783c Merge pull request #1917 from leigh-pointer/LabelCSS
Fix for  #1914 Label Control appending Class to LabelClass
2022-01-08 14:03:28 -05:00
aa22db7fe5 Merge pull request #1916 from sbwalker/dev
add error handling in purge job
2022-01-08 10:12:27 -05:00
5e0f008b65 add error handling in purge job 2022-01-08 10:22:05 -05:00
eaf840e1da improvements to purge job 2022-01-08 10:17:10 -05:00
fc9e47778b Fix for #1914 Label Control appending Class to LabelClass
Modified so that the Class parameter is not constantly appended when a new Class is applied.
2022-01-08 16:12:27 +01:00
35edf78aed Merge pull request #1915 from sbwalker/dev
improvements to purge job
2022-01-08 10:07:36 -05:00
07718f0449 add option to Control Panel to specify module visibility 2022-01-08 08:44:18 -05:00
6759156519 Merge pull request #1913 from sbwalker/dev
add option to Control Panel to specify module visibility
2022-01-08 08:34:45 -05:00
e2688e6feb include purge job for maintaining event logs and visitor logs 2022-01-07 23:30:29 -05:00
65ba6423b1 Merge pull request #1912 from sbwalker/dev
include purge job for maintaining event logs and visitor logs
2022-01-07 23:21:06 -05:00
a2f8fe3694 convention shortcut to suppress title in container 2022-01-06 17:24:18 -05:00
5273a17ab6 Merge pull request #1911 from sbwalker/dev
convention shortcut to suppress title in container
2022-01-06 17:14:45 -05:00
f7c1e7b706 alias management improvements 2022-01-06 13:37:29 -05:00
45bbc4c681 Merge pull request #1910 from sbwalker/dev
alias management improvements
2022-01-06 13:27:51 -05:00
6af5682548 increment copyright date to 2022, allow scheduled jobs to support weekly interval, improve dynamic image generation, add defensive logic to router 2022-01-05 14:28:42 -05:00
24ed06626d Merge pull request #1909 from sbwalker/dev
increment copyright date to 2022, allow scheduled jobs to support weekly interval, improve dynamic image generation, add defensive logic to router
2022-01-05 14:19:16 -05:00
eeff4af167 make Url Mappings relative rather than absolute 2022-01-03 10:56:13 -05:00
17f46afe14 Merge pull request #1903 from sbwalker/dev
make Url Mappings relative rather than absolute
2022-01-03 10:46:42 -05:00
224618cf21 improve Scheduled Job start/stop user experience, utilize start time when setting next job execution 2022-01-02 21:01:55 -05:00
ea93ab2a83 Merge pull request #1902 from sbwalker/dev
improve Scheduled Job start/stop user experience, utilize start time when setting next job execution
2022-01-02 20:53:00 -05:00
b9f7c39550 improve capture of request attributes 2021-12-30 14:13:58 -05:00
86b4b8e43a Merge pull request #1901 from sbwalker/dev
improve capture of request attributes
2021-12-30 14:04:27 -05:00
f54d07548e separate PWA service worker script from manifest script 2021-12-23 09:46:03 -05:00
037db8a3e4 Merge pull request #1898 from sbwalker/dev
separate PWA service worker script from manifest script
2021-12-23 09:36:43 -05:00
8f00e85abd Merge pull request #1897 from leigh-pointer/MissingResx
Missing Resx Keys
2021-12-23 07:50:35 -05:00
9ccc4c4059 Missing Resx Keys
Added missing Keys for Login and Visitor
2021-12-23 11:36:20 +01:00
8408f98693 Merge pull request #1896 from sbwalker/dev
encode PWA Script
2021-12-22 15:43:13 -05:00
cde271fd5b encode PWA Script 2021-12-22 15:52:31 -05:00
c21a097fd2 added support for default alias specification, alias auto registration, alias redirect, alias line break delimiters 2021-12-22 15:43:59 -05:00
83c32d4963 Merge pull request #1895 from sbwalker/dev
added support for default alias specification, alias auto registration, alias redirect, alias line break delimiters
2021-12-22 15:34:49 -05:00
22c2d56da0 imrove custom entity support in settings 2021-12-20 07:58:15 -05:00
bd8d6e0480 Merge pull request #1886 from sbwalker/dev
imrove custom entity support in settings
2021-12-20 07:49:10 -05:00
825eb700b1 Merge pull request #1885 from chlupac/SearchUserFix
Search user work again
2021-12-20 07:46:06 -05:00
e59ee70f88 Search user work again 2021-12-20 13:06:33 +01:00
1173a29ed5 Merge pull request #1884 from sbwalker/dev
Add support for IsPublic to all Setting types, enable Url Mapping for internal links
2021-12-18 10:26:26 -05:00
6a2ff369ea Add support for IsPublic to all Setting types, enable Url Mapping for internal links 2021-12-18 10:35:22 -05:00
e22606ae79 Merge pull request #1882 from leigh-pointer/#1880GetModuleDefinitionSettings
#1880 Issue with new SettingService
2021-12-16 15:37:22 -05:00
bf56c2a9fa Merge pull request #1883 from leigh-pointer/RichTextContent
Rework to #1848 RawHTML not being saved
2021-12-16 15:37:14 -05:00
6567b55ea3 Removed RichTextEditor OnInitialized
Redundant procedure call.
2021-12-16 20:11:07 +01:00
20e90c0de4 Rework to #1848 RawHTML not being saved
Restructured the execution of code.
RawHTML now works as it did in previous versions as well as the new functionality.
2021-12-16 20:07:40 +01:00
2892d5ec6f Update README.md 2021-12-16 09:38:10 -05:00
e034811e92 #1880 Issue with new SettingService
Needed to update the SettingService to apply the IsPublic field when updating settings for the ModuleDefinition.
2021-12-15 18:54:26 +01:00
ee18bbd145 Merge pull request #1881 from sbwalker/dev
add logging for the logout event to the UI component, relocate module setting deletion to repository
2021-12-15 10:24:17 -05:00
e3ebbde767 add logging for the logout event to the UI component, relocate module setting deletion to repository 2021-12-15 10:33:12 -05:00
6a57980439 Merge pull request #1879 from leigh-pointer/#1877#1878-ModuleDelete
1877 Module data not being delete when recycle bin is purged
2021-12-15 10:14:38 -05:00
765760f3a5 Fix for #1877 #1878 Module data not being deleted
Fixed the permissions validation and added functionality to remove all the settings for the deleted module.
2021-12-15 08:26:00 +01:00
157 changed files with 6578 additions and 2899 deletions

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2018-2021 .NET Foundation Copyright (c) 2018-2022 .NET Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -30,38 +30,38 @@
} }
@code { @code {
[Parameter] [Parameter]
public string AntiForgeryToken { get; set; } public string AntiForgeryToken { get; set; }
[Parameter] [Parameter]
public string Runtime { get; set; } public string Runtime { get; set; }
[Parameter] [Parameter]
public string RenderMode { get; set; } public string RenderMode { get; set; }
[Parameter] [Parameter]
public int VisitorId { get; set; } public int VisitorId { get; set; }
private bool _initialized = false; [Parameter]
private string _display = "display: none;"; public string RemoteIPAddress { get; set; }
private Installation _installation = new Installation { Success = false, Message = "" };
private PageState PageState { get; set; } private bool _initialized = false;
private string _display = "display: none;";
private Installation _installation = new Installation { Success = false, Message = "" };
protected override async Task OnParametersSetAsync() private PageState PageState { get; set; }
{
SiteState.AntiForgeryToken = AntiForgeryToken; protected override async Task OnParametersSetAsync()
InstallationService.SetAntiForgeryTokenHeader(AntiForgeryToken); {
SiteState.RemoteIPAddress = RemoteIPAddress;
SiteState.AntiForgeryToken = AntiForgeryToken;
InstallationService.SetAntiForgeryTokenHeader(AntiForgeryToken);
_installation = await InstallationService.IsInstalled(); _installation = await InstallationService.IsInstalled();
if (_installation.Alias != null) if (_installation.Alias != null)
{ {
SiteState.Alias = _installation.Alias; SiteState.Alias = _installation.Alias;
} }
else
{
_installation.Message = "Site Not Configured Correctly - No Matching Alias Exists For Host Name";
}
_initialized = true; _initialized = true;
} }

View File

@ -27,6 +27,6 @@
protected override void OnInitialized() protected override void OnInitialized()
{ {
var admin = PageState.Pages.FirstOrDefault(item => item.Path == "admin"); var admin = PageState.Pages.FirstOrDefault(item => item.Path == "admin");
_pages = PageState.Pages.Where(item => item.ParentId == admin?.PageId).ToList(); _pages = PageState.Pages.Where(item => item.ParentId == admin?.PageId && !item.IsDeleted).ToList();
} }
} }

View File

@ -36,7 +36,9 @@
<option value="m">@Localizer["Minute(s)"]</option> <option value="m">@Localizer["Minute(s)"]</option>
<option value="H">@Localizer["Hour(s)"]</option> <option value="H">@Localizer["Hour(s)"]</option>
<option value="d">@Localizer["Day(s)"]</option> <option value="d">@Localizer["Day(s)"]</option>
<option value="w">@Localizer["Week(s)"]</option>
<option value="M">@Localizer["Month(s)"]</option> <option value="M">@Localizer["Month(s)"]</option>
<option value="O">@Localizer["Once"]</option>
</select> </select>
</div> </div>
</div> </div>
@ -95,82 +97,89 @@
</form> </form>
@code { @code {
private ElementReference form; private ElementReference form;
private bool validated = false; private bool validated = false;
private int _jobId; private int _jobId;
private string _name = string.Empty; private string _name = string.Empty;
private string _jobType = string.Empty; private string _jobType = string.Empty;
private string _isEnabled = "True"; private string _isEnabled = "True";
private string _interval = string.Empty; private string _interval = string.Empty;
private string _frequency = string.Empty; private string _frequency = string.Empty;
private DateTime? _startDate = null; private DateTime? _startDate = null;
private string _startTime = string.Empty; private string _startTime = string.Empty;
private DateTime? _endDate = null; private DateTime? _endDate = null;
private string _endTime = string.Empty; private string _endTime = string.Empty;
private string _retentionHistory = string.Empty; private string _retentionHistory = string.Empty;
private DateTime? _nextDate = null; private DateTime? _nextDate = null;
private string _nextTime = string.Empty; private string _nextTime = string.Empty;
private string createdby; private string createdby;
private DateTime createdon; private DateTime createdon;
private string modifiedby; private string modifiedby;
private DateTime modifiedon; private DateTime modifiedon;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
try try
{ {
_jobId = Int32.Parse(PageState.QueryString["id"]); _jobId = Int32.Parse(PageState.QueryString["id"]);
Job job = await JobService.GetJobAsync(_jobId); Job job = await JobService.GetJobAsync(_jobId);
if (job != null) if (job != null)
{ {
_name = job.Name; _name = job.Name;
_jobType = job.JobType; _jobType = job.JobType;
_isEnabled = job.IsEnabled.ToString(); _isEnabled = job.IsEnabled.ToString();
_interval = job.Interval.ToString(); _interval = job.Interval.ToString();
_frequency = job.Frequency; _frequency = job.Frequency;
_startDate = job.StartDate; _startDate = job.StartDate;
if (job.StartDate != null && job.StartDate.Value.TimeOfDay.TotalSeconds != 0) if (job.StartDate != null && job.StartDate.Value.TimeOfDay.TotalSeconds != 0)
{ {
_startTime = job.StartDate.Value.ToString("HH:mm"); _startTime = job.StartDate.Value.ToString("HH:mm");
} }
_endDate = job.EndDate; _endDate = job.EndDate;
if (job.EndDate != null && job.EndDate.Value.TimeOfDay.TotalSeconds != 0) if (job.EndDate != null && job.EndDate.Value.TimeOfDay.TotalSeconds != 0)
{ {
_endTime = job.EndDate.Value.ToString("HH:mm"); _endTime = job.EndDate.Value.ToString("HH:mm");
} }
_retentionHistory = job.RetentionHistory.ToString(); _retentionHistory = job.RetentionHistory.ToString();
_nextDate = job.NextExecution; _nextDate = job.NextExecution;
if (job.NextExecution != null && job.NextExecution.Value.TimeOfDay.TotalSeconds != 0) if (job.NextExecution != null && job.NextExecution.Value.TimeOfDay.TotalSeconds != 0)
{ {
_nextTime = job.NextExecution.Value.ToString("HH:mm"); _nextTime = job.NextExecution.Value.ToString("HH:mm");
} }
createdby = job.CreatedBy; createdby = job.CreatedBy;
createdon = job.CreatedOn; createdon = job.CreatedOn;
modifiedby = job.ModifiedBy; modifiedby = job.ModifiedBy;
modifiedon = job.ModifiedOn; modifiedon = job.ModifiedOn;
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Job {JobId} {Error}", _jobId, ex.Message); await logger.LogError(ex, "Error Loading Job {JobId} {Error}", _jobId, ex.Message);
AddModuleMessage(Localizer["Error.Job.Load"], MessageType.Error); AddModuleMessage(Localizer["Error.Job.Load"], MessageType.Error);
} }
} }
private async Task SaveJob() private async Task SaveJob()
{ {
validated = true; validated = true;
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
if (await interop.FormValid(form)) if (await interop.FormValid(form))
{ {
var job = await JobService.GetJobAsync(_jobId); var job = await JobService.GetJobAsync(_jobId);
job.Name = _name; job.Name = _name;
job.JobType = _jobType; job.JobType = _jobType;
job.IsEnabled = Boolean.Parse(_isEnabled); job.IsEnabled = Boolean.Parse(_isEnabled);
job.Frequency = _frequency; job.Frequency = _frequency;
job.Interval = int.Parse(_interval); if (job.Frequency == "O") // once
{
job.Interval = 1;
}
else
{
job.Interval = int.Parse(_interval);
}
job.StartDate = _startDate; job.StartDate = _startDate;
if (job.StartDate != null) if (job.StartDate != null)
{ {

View File

@ -36,75 +36,75 @@ else
<td>@context.NextExecution</td> <td>@context.NextExecution</td>
<td> <td>
@if (context.IsStarted) @if (context.IsStarted)
{ {
<button type="button" class="btn btn-danger" @onclick="(async () => await StopJob(context.JobId))">@Localizer["Stop"]</button> <button type="button" class="btn btn-danger" @onclick="(async () => await StopJob(context.JobId))">@Localizer["Stop"]</button>
} }
else else
{ {
<button type="button" class="btn btn-success" @onclick="(async () => await StartJob(context.JobId))">@Localizer["Start"]</button> <button type="button" class="btn btn-success" @onclick="(async () => await StartJob(context.JobId))">@Localizer["Start"]</button>
} }
</td> </td>
</Row> </Row>
</Pager> </Pager>
} }
@code { @code {
private List<Job> _jobs; private List<Job> _jobs;
public override SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.Host; } } public override SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.Host; } }
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
_jobs = await JobService.GetJobsAsync(); _jobs = await JobService.GetJobsAsync();
} }
private string DisplayStatus(bool isEnabled, bool isExecuting) private string DisplayStatus(bool isEnabled, bool isExecuting)
{ {
var status = string.Empty; var status = string.Empty;
if (!isEnabled) if (!isEnabled)
{ {
status = Localizer["Disabled"]; status = Localizer["Disabled"];
} }
else else
{ {
if (isExecuting) if (isExecuting)
{ {
status = Localizer["Executing"]; status = Localizer["Executing"];
} }
else else
{ {
status = Localizer["Idle"]; status = Localizer["Idle"];
} }
} }
return status; return status;
} }
private string DisplayFrequency(int interval, string frequency) private string DisplayFrequency(int interval, string frequency)
{ {
var result = $"{Localizer["Every"]} {interval.ToString()} "; var result = "";
switch (frequency) switch (frequency)
{ {
case "m": case "m":
result += Localizer["Minute"]; result = $"{Localizer["Every"]} {interval.ToString()} " + Localizer["Minute"];
break; break;
case "H": case "H":
result += Localizer["Hour"]; result = $"{Localizer["Every"]} {interval.ToString()} " + Localizer["Hour"];
break; break;
case "d": case "d":
result += Localizer["Day"]; result = $"{Localizer["Every"]} {interval.ToString()} " + Localizer["Day"];
break; break;
case "M": case "w":
result += Localizer["Month"]; result = $"{Localizer["Every"]} {interval.ToString()} " + Localizer["Week"];
break; break;
} case "M":
result = $"{Localizer["Every"]} {interval.ToString()} " + Localizer["Month"];
if (interval > 1) break;
{ case "O":
result += Localizer["s"]; result = Localizer["Once"];
} break;
}
return result; return result;
} }
@ -114,6 +114,7 @@ else
{ {
await JobService.DeleteJobAsync(job.JobId); await JobService.DeleteJobAsync(job.JobId);
await logger.LogInformation("Job Deleted {Job}", job); await logger.LogInformation("Job Deleted {Job}", job);
_jobs = await JobService.GetJobsAsync();
StateHasChanged(); StateHasChanged();
} }
catch (Exception ex) catch (Exception ex)
@ -125,12 +126,36 @@ else
private async Task StartJob(int jobId) private async Task StartJob(int jobId)
{ {
await JobService.StartJobAsync(jobId); try
{
await JobService.StartJobAsync(jobId);
await logger.LogInformation("Job Started {JobId}", jobId);
AddModuleMessage(Localizer["Message.Job.Start"], MessageType.Success);
_jobs = await JobService.GetJobsAsync();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Starting Job {JobId} {Error}", jobId, ex.Message);
AddModuleMessage(Localizer["Error.Job.Start"], MessageType.Error);
}
} }
private async Task StopJob(int jobId) private async Task StopJob(int jobId)
{ {
await JobService.StopJobAsync(jobId); try
{
await JobService.StopJobAsync(jobId);
await logger.LogInformation("Job Stopped {JobId}", jobId);
AddModuleMessage(Localizer["Message.Job.Stop"], MessageType.Success);
_jobs = await JobService.GetJobsAsync();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Stopping Job {JobId} {Error}", jobId, ex.Message);
AddModuleMessage(Localizer["Error.Job.Stop"], MessageType.Error);
}
} }
private async Task Refresh() private async Task Refresh()

View File

@ -177,17 +177,17 @@
{ {
await UserService.ForgotPasswordAsync(user); await UserService.ForgotPasswordAsync(user);
await logger.LogInformation(LogFunction.Security, "Password Reset Notification Sent For Username {Username}", _username); await logger.LogInformation(LogFunction.Security, "Password Reset Notification Sent For Username {Username}", _username);
_message = "Please Check The Email Address Associated To Your User Account For A Password Reset Notification"; _message = Localizer["Message.ForgotUser"];
} }
else else
{ {
_message = "User Does Not Exist"; _message = Localizer["Message.UserDoesNotExist"];
_type = MessageType.Warning; _type = MessageType.Warning;
} }
} }
else else
{ {
_message = "Please Enter The Username Related To Your Account And Then Select The Forgot Password Option Again"; _message = Localizer["Message.ForgotPassword"];
} }
StateHasChanged(); StateHasChanged();

View File

@ -9,86 +9,88 @@
@inject IStringLocalizer<Detail> Localizer @inject IStringLocalizer<Detail> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="container"> @if (_initialized)
<div class="row mb-1 align-items-center"> {
<Label Class="col-sm-3" For="dateTime" HelpText="The date and time of this log" ResourceKey="DateTime">Date/Time: </Label> <div class="container">
<div class="col-sm-9"> <div class="row mb-1 align-items-center">
<input id="dateTime" class="form-control" @bind="@_logDate" readonly /> <Label Class="col-sm-3" For="dateTime" HelpText="The date and time of this log" ResourceKey="DateTime">Date/Time: </Label>
</div> <div class="col-sm-9">
</div> <input id="dateTime" class="form-control" @bind="@_logDate" readonly />
<div class="row mb-1 align-items-center"> </div>
<Label Class="col-sm-3" For="level" HelpText="The level of this log" ResourceKey="Level">Level: </Label> </div>
<div class="col-sm-9"> <div class="row mb-1 align-items-center">
<input id="level" class="form-control" @bind="@_level" readonly /> <Label Class="col-sm-3" For="level" HelpText="The level of this log" ResourceKey="Level">Level: </Label>
</div> <div class="col-sm-9">
</div> <input id="level" class="form-control" @bind="@_level" readonly />
<div class="row mb-1 align-items-center"> </div>
<Label Class="col-sm-3" For="feature" HelpText="The feature that was affected" ResourceKey="Feature">Feature: </Label> </div>
<div class="col-sm-9"> <div class="row mb-1 align-items-center">
<input id="feature" class="form-control" @bind="@_feature" readonly /> <Label Class="col-sm-3" For="feature" HelpText="The feature that was affected" ResourceKey="Feature">Feature: </Label>
</div> <div class="col-sm-9">
</div> <input id="feature" class="form-control" @bind="@_feature" readonly />
<div class="row mb-1 align-items-center"> </div>
<Label Class="col-sm-3" For="function" HelpText="The function that was performed" ResourceKey="Function">Function: </Label> </div>
<div class="col-sm-9"> <div class="row mb-1 align-items-center">
<input id="function" class="form-control" @bind="@_function" readonly /> <Label Class="col-sm-3" For="function" HelpText="The function that was performed" ResourceKey="Function">Function: </Label>
</div> <div class="col-sm-9">
</div> <input id="function" class="form-control" @bind="@_function" readonly />
<div class="row mb-1 align-items-center"> </div>
<Label Class="col-sm-3" For="category" HelpText="The categories that were affected" ResourceKey="Category">Category: </Label> </div>
<div class="col-sm-9"> <div class="row mb-1 align-items-center">
<input id="category" class="form-control" @bind="@_category" readonly /> <Label Class="col-sm-3" For="category" HelpText="The categories that were affected" ResourceKey="Category">Category: </Label>
</div> <div class="col-sm-9">
</div> <input id="category" class="form-control" @bind="@_category" readonly />
@if (_pageName != string.Empty) </div>
{ </div>
<div class="row mb-1 align-items-center"> @if (_pageName != string.Empty)
<Label Class="col-sm-3" For="page" HelpText="The page that was affected" ResourceKey="Page">Page: </Label> {
<div class="col-sm-9"> <div class="row mb-1 align-items-center">
<input id="page" class="form-control" @bind="@_pageName" readonly /> <Label Class="col-sm-3" For="page" HelpText="The page that was affected" ResourceKey="Page">Page: </Label>
</div> <div class="col-sm-9">
</div> <input id="page" class="form-control" @bind="@_pageName" readonly />
} </div>
@if (_moduleTitle != string.Empty) </div>
{ }
<div class="row mb-1 align-items-center"> @if (_moduleTitle != string.Empty)
<Label Class="col-sm-3" For="module" HelpText="The module that was affected" ResourceKey="Module">Module: </Label> {
<div class="col-sm-9"> <div class="row mb-1 align-items-center">
<input id="module" class="form-control" @bind="@_moduleTitle" readonly /> <Label Class="col-sm-3" For="module" HelpText="The module that was affected" ResourceKey="Module">Module: </Label>
</div> <div class="col-sm-9">
</div> <input id="module" class="form-control" @bind="@_moduleTitle" readonly />
} </div>
@if (_username != string.Empty) </div>
{ }
<div class="row mb-1 align-items-center"> @if (_username != string.Empty)
<Label Class="col-sm-3" For="user" HelpText="The user that caused this log" ResourceKey="User">User: </Label> {
<div class="col-sm-9"> <div class="row mb-1 align-items-center">
<input id="user" class="form-control" @bind="@_username" readonly /> <Label Class="col-sm-3" For="user" HelpText="The user that caused this log" ResourceKey="User">User: </Label>
</div> <div class="col-sm-9">
</div> <input id="user" class="form-control" @bind="@_username" readonly />
} </div>
<div class="row mb-1 align-items-center"> </div>
<Label Class="col-sm-3" For="url" HelpText="The url the log comes from" ResourceKey="Url">Url: </Label> }
<div class="col-sm-9"> <div class="row mb-1 align-items-center">
<input id="url" class="form-control" @bind="@_url" readonly /> <Label Class="col-sm-3" For="url" HelpText="The url the log comes from" ResourceKey="Url">Url: </Label>
</div> <div class="col-sm-9">
</div> <input id="url" class="form-control" @bind="@_url" readonly />
<div class="row mb-1 align-items-center"> </div>
<Label Class="col-sm-3" For="template" HelpText="What the log is about" ResourceKey="Template">Template: </Label> </div>
<div class="col-sm-9"> <div class="row mb-1 align-items-center">
<input id="template" class="form-control" @bind="@_template" readonly /> <Label Class="col-sm-3" For="template" HelpText="What the log is about" ResourceKey="Template">Template: </Label>
</div> <div class="col-sm-9">
</div> <input id="template" class="form-control" @bind="@_template" readonly />
<div class="row mb-1 align-items-center"> </div>
<Label Class="col-sm-3" For="message" HelpText="The message that the system generated" ResourceKey="Message">Message: </Label> </div>
<div class="col-sm-9"> <div class="row mb-1 align-items-center">
<textarea id="message" class="form-control" @bind="@_message" rows="5" readonly></textarea> <Label Class="col-sm-3" For="message" HelpText="The message that the system generated" ResourceKey="Message">Message: </Label>
</div> <div class="col-sm-9">
</div> <textarea id="message" class="form-control" @bind="@_message" rows="5" readonly></textarea>
@if (!string.IsNullOrEmpty(_exception)) </div>
{ </div>
<div class="row mb-1 align-items-center"> @if (!string.IsNullOrEmpty(_exception))
<Label Class="col-sm-3" For="exception" HelpText="The exceptions generated by the system" ResourceKey="Exception">Exception: </Label> {
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="exception" HelpText="The exceptions generated by the system" ResourceKey="Exception">Exception: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<textarea id="exception" class="form-control" @bind="@_exception" rows="5" readonly></textarea> <textarea id="exception" class="form-control" @bind="@_exception" rows="5" readonly></textarea>
</div> </div>
@ -107,81 +109,95 @@
</div> </div>
</div> </div>
</div> </div>
}
<NavLink class="btn btn-secondary" href="@CloseUrl()">@SharedLocalizer["Cancel"]</NavLink>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> @code {
private bool _initialized = false;
private int _logId;
private string _logDate = string.Empty;
private string _level = string.Empty;
private string _feature = string.Empty;
private string _function = string.Empty;
private string _category = string.Empty;
private string _pageName = string.Empty;
private string _moduleTitle = string.Empty;
private string _username = string.Empty;
private string _url = string.Empty;
private string _template = string.Empty;
private string _message = string.Empty;
private string _exception = string.Empty;
private string _properties = string.Empty;
private string _server = string.Empty;
@code { public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
private int _logId;
private string _logDate = string.Empty;
private string _level = string.Empty;
private string _feature = string.Empty;
private string _function = string.Empty;
private string _category = string.Empty;
private string _pageName = string.Empty;
private string _moduleTitle = string.Empty;
private string _username = string.Empty;
private string _url = string.Empty;
private string _template = string.Empty;
private string _message = string.Empty;
private string _exception = string.Empty;
private string _properties = string.Empty;
private string _server = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; protected override async Task OnInitializedAsync()
{
protected override async Task OnInitializedAsync() try
{ {
try _logId = Int32.Parse(PageState.QueryString["id"]);
var log = await LogService.GetLogAsync(_logId);
if (log != null)
{ {
_logId = Int32.Parse(PageState.QueryString["id"]); _logDate = log.LogDate.ToString(CultureInfo.CurrentCulture);
var log = await LogService.GetLogAsync(_logId); _level = log.Level;
if (log != null) _feature = log.Feature;
_function = log.Function;
_category = log.Category;
if (log.PageId != null)
{ {
_logDate = log.LogDate.ToString(CultureInfo.CurrentCulture); var page = await PageService.GetPageAsync(log.PageId.Value);
_level = log.Level; if (page != null)
_feature = log.Feature;
_function = log.Function;
_category = log.Category;
if (log.PageId != null)
{ {
var page = await PageService.GetPageAsync(log.PageId.Value); _pageName = page.Name;
if (page != null)
{
_pageName = page.Name;
}
} }
if (log.PageId != null && log.ModuleId != null)
{
var pagemodule = await PageModuleService.GetPageModuleAsync(log.PageId.Value, log.ModuleId.Value);
if (pagemodule != null)
{
_moduleTitle = pagemodule.Title;
}
}
if (log.UserId != null)
{
var user = await UserService.GetUserAsync(log.UserId.Value, PageState.Site.SiteId);
if (user != null)
{
_username = user.Username;
}
}
_url = log.Url;
_template = log.MessageTemplate;
_message = log.Message;
_exception = log.Exception;
_properties = log.Properties;
_server = log.Server;
} }
}
catch (Exception ex) if (log.PageId != null && log.ModuleId != null)
{ {
await logger.LogError(ex, "Error Loading Log {LogId} {Error}", _logId, ex.Message); var pagemodule = await PageModuleService.GetPageModuleAsync(log.PageId.Value, log.ModuleId.Value);
AddModuleMessage(Localizer["Error.Log.Load"], MessageType.Error); if (pagemodule != null)
{
_moduleTitle = pagemodule.Title;
}
}
if (log.UserId != null)
{
var user = await UserService.GetUserAsync(log.UserId.Value, PageState.Site.SiteId);
if (user != null)
{
_username = user.Username;
}
}
_url = log.Url;
_template = log.MessageTemplate;
_message = log.Message;
_exception = log.Exception;
_properties = log.Properties;
_server = log.Server;
_initialized = true;
} }
} }
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Log {LogId} {Error}", _logId, ex.Message);
AddModuleMessage(Localizer["Error.Log.Load"], MessageType.Error);
}
} }
private string CloseUrl()
{
if (!PageState.QueryString.ContainsKey("level"))
{
return NavigateUrl();
}
else
{
return NavigateUrl(PageState.Page.Path, "level=" + PageState.QueryString["level"] + "&function=" + PageState.QueryString["function"] + "&rows=" + PageState.QueryString["rows"] + "&page=" + PageState.QueryString["page"]);
}
}
}

View File

@ -1,6 +1,7 @@
@namespace Oqtane.Modules.Admin.Logs @namespace Oqtane.Modules.Admin.Logs
@inherits ModuleBase @inherits ModuleBase
@inject ILogService LogService @inject ILogService LogService
@inject ISettingService SettingService
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
@ -10,73 +11,92 @@
} }
else else
{ {
<div class="container g-0"> <TabStrip>
<div class="row mb-1 align-items-center"> <TabPanel Name="Events" Heading="Events" ResourceKey="Events">
<div class="col-sm-4"> <div class="container g-0">
<Label For="level" HelpText="Select the log level for event log items" ResourceKey="Level">Level: </Label><br /><br /> <div class="row mb-1 align-items-center">
<select id="level" class="form-select" @onchange="(e => LevelChanged(e))"> <div class="col-sm-4">
<option value="-">&lt;@Localizer["AllLevels"]&gt;</option> <Label For="level" HelpText="Select the log level for event log items" ResourceKey="Level">Level: </Label><br /><br />
<option value="Trace">@Localizer["Trace"]</option> <select id="level" class="form-select" value="@_level" @onchange="(e => LevelChanged(e))">
<option value="Debug">@Localizer["Debug"]</option> <option value="-">&lt;@Localizer["AllLevels"]&gt;</option>
<option value="Information">@Localizer["Information"]</option> <option value="Trace">@Localizer["Trace"]</option>
<option value="Warning">@Localizer["Warning"]</option> <option value="Debug">@Localizer["Debug"]</option>
<option value="Error">@Localizer["Error"]</option> <option value="Information">@Localizer["Information"]</option>
<option value="Critical">@Localizer["Critical"]</option> <option value="Warning">@Localizer["Warning"]</option>
</select> <option value="Error">@Localizer["Error"]</option>
</div> <option value="Critical">@Localizer["Critical"]</option>
<div class="col-sm-4"> </select>
<Label For="function" HelpText="Select the function for event log items" ResourceKey="Function">Function: </Label><br /><br /> </div>
<select id="function" class="form-select" @onchange="(e => FunctionChanged(e))"> <div class="col-sm-4">
<option value="-">&lt;@Localizer["AllFunctions"]&gt;</option> <Label For="function" HelpText="Select the function for event log items" ResourceKey="Function">Function: </Label><br /><br />
<option value="Create">@Localizer["Create"]</option> <select id="function" class="form-select" value="@_function" @onchange="(e => FunctionChanged(e))">
<option value="Read">@Localizer["Read"]</option> <option value="-">&lt;@Localizer["AllFunctions"]&gt;</option>
<option value="Update">@SharedLocalizer["Update"]</option> <option value="Create">@Localizer["Create"]</option>
<option value="Delete">@SharedLocalizer["Delete"]</option> <option value="Read">@Localizer["Read"]</option>
<option value="Security">@Localizer["Security"]</option> <option value="Update">@SharedLocalizer["Update"]</option>
<option value="Other">@Localizer["Other"]</option> <option value="Delete">@SharedLocalizer["Delete"]</option>
</select> <option value="Security">@Localizer["Security"]</option>
</div> <option value="Other">@Localizer["Other"]</option>
<div class="col-sm-4"> </select>
<Label For="rows" HelpText="Select the maximum number of event log items to review. Please note that if you choose more than 10 items the information will be split into pages." ResourceKey="Rows">Maximum Items: </Label><br /><br /> </div>
<select id="rows" class="form-select" @onchange="(e => RowsChanged(e))"> <div class="col-sm-4">
<option value="10">10</option> <Label For="rows" HelpText="Select the maximum number of event log items to review. Please note that if you choose more than 10 items the information will be split into pages." ResourceKey="Rows">Maximum Items: </Label><br /><br />
<option value="50">50</option> <select id="rows" class="form-select" value="@_rows" @onchange="(e => RowsChanged(e))">
<option value="100">100</option> <option value="10">10</option>
</select> <option value="50">50</option>
</div> <option value="100">100</option>
</div> </select>
</div> </div>
</div>
</div>
<br />
@if (_logs.Any()) @if (_logs.Any())
{ {
<Pager Items="@_logs"> <Pager Items="@_logs" CurrentPage="@_page.ToString()" OnPageChange="OnPageChange">
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Date"]</th> <th>@Localizer["Date"]</th>
<th>@Localizer["Level"]</th> <th>@Localizer["Level"]</th>
<th>@Localizer["Feature"]</th> <th>@Localizer["Feature"]</th>
<th>@Localizer["Function"]</th> <th>@Localizer["Function"]</th>
</Header> </Header>
<Row> <Row>
<td class="@GetClass(context.Function)"><ActionLink Action="Detail" Parameters="@($"id=" + context.LogId.ToString())" ResourceKey="LogDetails" /></td> <td class="@GetClass(context.Function)"><ActionLink Action="Detail" Parameters="@($"id=" + context.LogId.ToString() + "&level=" + _level + "&function=" + _function + "&rows=" + _rows + "&page=" + _page.ToString())" ResourceKey="LogDetails" /></td>
<td class="@GetClass(context.Function)">@context.LogDate</td> <td class="@GetClass(context.Function)">@context.LogDate</td>
<td class="@GetClass(context.Function)">@context.Level</td> <td class="@GetClass(context.Function)">@context.Level</td>
<td class="@GetClass(context.Function)">@context.Feature</td> <td class="@GetClass(context.Function)">@context.Feature</td>
<td class="@GetClass(context.Function)">@context.Function</td> <td class="@GetClass(context.Function)">@context.Function</td>
</Row> </Row>
</Pager> </Pager>
} }
else else
{ {
<p><em>@Localizer["NoLogs"]</em></p> <p><em>@Localizer["NoLogs"]</em></p>
} }
</TabPanel>
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="retention" HelpText="Number of days of events to retain" ResourceKey="Retention">Retention (Days): </Label>
<div class="col-sm-9">
<input id="retention" class="form-control" @bind="@_retention" />
</div>
</div>
</div>
<br />
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
</TabPanel>
</TabStrip>
} }
@code { @code {
private string _level = "-"; private string _level = "-";
private string _function = "-"; private string _function = "-";
private string _rows = "10"; private string _rows = "10";
private int _page = 1;
private List<Log> _logs; private List<Log> _logs;
private string _retention = "";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
@ -84,7 +104,27 @@ else
{ {
try try
{ {
if (PageState.QueryString.ContainsKey("level"))
{
_level = PageState.QueryString["level"];
}
if (PageState.QueryString.ContainsKey("function"))
{
_function = PageState.QueryString["function"];
}
if (PageState.QueryString.ContainsKey("rows"))
{
_rows = PageState.QueryString["rows"];
}
if (PageState.QueryString.ContainsKey("page") && int.TryParse(PageState.QueryString["page"], out int page))
{
_page = page;
}
await GetLogs(); await GetLogs();
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
_retention = SettingService.GetSetting(settings, "LogRetention", "30");
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -170,4 +210,27 @@ else
} }
return classname; return classname;
} }
private async Task SaveSiteSettings()
{
try
{
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
settings = SettingService.SetSetting(settings, "LogRetention", _retention, true);
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message);
AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error);
}
}
private void OnPageChange(int page)
{
_page = page;
}
} }

View File

@ -128,7 +128,7 @@ else
var moduleDefinition = new ModuleDefinition { Owner = _owner, Name = _module, Description = _description, Template = _template, Version = _reference }; var moduleDefinition = new ModuleDefinition { Owner = _owner, Name = _module, Description = _description, Template = _template, Version = _reference };
moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition); moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition);
var settings = ModuleState.Settings; var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
SettingService.SetSetting(settings, "ModuleDefinitionName", moduleDefinition.ModuleDefinitionName); SettingService.SetSetting(settings, "ModuleDefinitionName", moduleDefinition.ModuleDefinitionName);
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId); await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);

View File

@ -37,7 +37,7 @@ else
<td>@context.Name</td> <td>@context.Name</td>
<td>@context.Version</td> <td>@context.Version</td>
<td> <td>
@if(context.AssemblyName == "Oqtane.Client" || PageState.Modules.Where(m => m.ModuleDefinition.ModuleDefinitionId == context.ModuleDefinitionId).Count() > 0) @if(context.AssemblyName == "Oqtane.Client" || PageState.Modules.Where(m => m.ModuleDefinition?.ModuleDefinitionId == context.ModuleDefinitionId).FirstOrDefault() != null)
{ {
<span>@SharedLocalizer["Yes"]</span> <span>@SharedLocalizer["Yes"]</span>
} }

View File

@ -95,6 +95,12 @@
<input id="title" class="form-control" @bind="@_title" /> <input id="title" class="form-control" @bind="@_title" />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="meta" HelpText="Optionally enter meta tags (in exactly the form you want them to be included in the page output)." ResourceKey="Meta">Meta: </Label>
<div class="col-sm-9">
<textarea id="meta" class="form-control" @bind="@_meta" rows="3"></textarea>
</div>
</div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label> <Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
@ -156,220 +162,222 @@
</form> </form>
@code { @code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
private List<Theme> _themeList; private List<Theme> _themeList;
private List<ThemeControl> _themes = new List<ThemeControl>(); private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>(); private List<ThemeControl> _containers = new List<ThemeControl>();
private List<Page> _pageList; private List<Page> _pageList;
private string _name; private string _name;
private string _title; private string _title;
private string _path = string.Empty; private string _meta;
private string _parentid = "-1"; private string _path = string.Empty;
private string _insert = ">>"; private string _parentid = "-1";
private List<Page> _children; private string _insert = ">>";
private int _childid = -1; private List<Page> _children;
private string _isnavigation = "True"; private int _childid = -1;
private string _isclickable = "True"; private string _isnavigation = "True";
private string _url; private string _isclickable = "True";
private string _ispersonalizable = "False"; private string _url;
private string _themetype = string.Empty; private string _ispersonalizable = "False";
private string _containertype = string.Empty; private string _themetype = string.Empty;
private string _icon = string.Empty; private string _containertype = string.Empty;
private string _permissions = string.Empty; private string _icon = string.Empty;
private PermissionGrid _permissionGrid; private string _permissions = string.Empty;
private Type _themeSettingsType; private PermissionGrid _permissionGrid;
private object _themeSettings; private Type _themeSettingsType;
private RenderFragment ThemeSettingsComponent { get; set; } private object _themeSettings;
private bool _refresh = false; private RenderFragment ThemeSettingsComponent { get; set; }
private ElementReference form; private bool _refresh = false;
private bool validated = false; private ElementReference form;
private bool validated = false;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
try try
{ {
_themeList = await ThemeService.GetThemesAsync(); _themeList = await ThemeService.GetThemesAsync();
_themes = ThemeService.GetThemeControls(_themeList); _themes = ThemeService.GetThemeControls(_themeList);
_themetype = PageState.Site.DefaultThemeType; _themetype = PageState.Site.DefaultThemeType;
_containers = ThemeService.GetContainerControls(_themeList, _themetype); _containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containertype = PageState.Site.DefaultContainerType; _containertype = PageState.Site.DefaultContainerType;
_pageList = PageState.Pages; _pageList = PageState.Pages;
_children = PageState.Pages.Where(item => item.ParentId == null).ToList(); _children = PageState.Pages.Where(item => item.ParentId == null).ToList();
_permissions = string.Empty; _permissions = string.Empty;
ThemeSettings(); ThemeSettings();
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Initializing Page {Error}", ex.Message); await logger.LogError(ex, "Error Initializing Page {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Page.Initialize"], MessageType.Error); AddModuleMessage(Localizer["Error.Page.Initialize"], MessageType.Error);
} }
} }
private async void ParentChanged(ChangeEventArgs e) private async void ParentChanged(ChangeEventArgs e)
{ {
try try
{ {
_parentid = (string)e.Value; _parentid = (string)e.Value;
_children = new List<Page>(); _children = new List<Page>();
if (_parentid == "-1") if (_parentid == "-1")
{ {
foreach (Page p in PageState.Pages.Where(item => item.ParentId == null)) foreach (Page p in PageState.Pages.Where(item => item.ParentId == null))
{ {
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.Permissions)) if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.Permissions))
{ {
_children.Add(p); _children.Add(p);
} }
} }
} }
else else
{ {
foreach (Page p in PageState.Pages.Where(item => item.ParentId == int.Parse(_parentid))) foreach (Page p in PageState.Pages.Where(item => item.ParentId == int.Parse(_parentid)))
{ {
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.Permissions)) if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.Permissions))
{ {
_children.Add(p); _children.Add(p);
} }
} }
} }
StateHasChanged(); StateHasChanged();
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Child Pages For Parent {PageId} {Error}", _parentid, ex.Message); await logger.LogError(ex, "Error Loading Child Pages For Parent {PageId} {Error}", _parentid, ex.Message);
AddModuleMessage(Localizer["Error.ChildPage.Load"], MessageType.Error); AddModuleMessage(Localizer["Error.ChildPage.Load"], MessageType.Error);
} }
} }
private async void ThemeChanged(ChangeEventArgs e) private async void ThemeChanged(ChangeEventArgs e)
{ {
try try
{ {
_themetype = (string)e.Value; _themetype = (string)e.Value;
_containers = ThemeService.GetContainerControls(_themeList, _themetype); _containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containertype = "-"; _containertype = "-";
ThemeSettings(); ThemeSettings();
StateHasChanged(); StateHasChanged();
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Pane Layouts For Theme {ThemeType} {Error}", _themetype, ex.Message); await logger.LogError(ex, "Error Loading Pane Layouts For Theme {ThemeType} {Error}", _themetype, ex.Message);
AddModuleMessage(Localizer["Error.Pane.Load"], MessageType.Error); AddModuleMessage(Localizer["Error.Pane.Load"], MessageType.Error);
} }
} }
private void ThemeSettings() private void ThemeSettings()
{ {
_themeSettingsType = null; _themeSettingsType = null;
var theme = _themeList.FirstOrDefault(item => item.Themes.Any(themecontrol => themecontrol.TypeName.Equals(_themetype))); var theme = _themeList.FirstOrDefault(item => item.Themes.Any(themecontrol => themecontrol.TypeName.Equals(_themetype)));
if (theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType)) if (theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType))
{ {
_themeSettingsType = Type.GetType(theme.ThemeSettingsType); _themeSettingsType = Type.GetType(theme.ThemeSettingsType);
if (_themeSettingsType != null) if (_themeSettingsType != null)
{ {
ThemeSettingsComponent = builder => ThemeSettingsComponent = builder =>
{ {
builder.OpenComponent(0, _themeSettingsType); builder.OpenComponent(0, _themeSettingsType);
builder.AddComponentReferenceCapture(1, inst => { _themeSettings = Convert.ChangeType(inst, _themeSettingsType); }); builder.AddComponentReferenceCapture(1, inst => { _themeSettings = Convert.ChangeType(inst, _themeSettingsType); });
builder.CloseComponent(); builder.CloseComponent();
}; };
} }
_refresh = true; _refresh = true;
} }
} }
private async Task SavePage() private async Task SavePage()
{ {
validated = true; validated = true;
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
if (await interop.FormValid(form)) if (await interop.FormValid(form))
{ {
Page page = null; Page page = null;
try try
{ {
if (!string.IsNullOrEmpty(_themetype) && _containertype != "-") if (!string.IsNullOrEmpty(_themetype) && _containertype != "-")
{ {
page = new Page(); page = new Page();
page.SiteId = PageState.Page.SiteId; page.SiteId = PageState.Page.SiteId;
page.Name = _name; page.Name = _name;
page.Title = _title; page.Title = _title;
if (string.IsNullOrEmpty(_path)) if (string.IsNullOrEmpty(_path))
{ {
_path = _name; _path = _name;
} }
if (_path.Contains("/")) if (_path.Contains("/"))
{ {
_path = _path.Substring(_path.LastIndexOf("/") + 1); _path = _path.Substring(_path.LastIndexOf("/") + 1);
} }
if (_parentid == "-1") if (_parentid == "-1")
{ {
page.ParentId = null; page.ParentId = null;
page.Path = Utilities.GetFriendlyUrl(_path); page.Path = Utilities.GetFriendlyUrl(_path);
} }
else else
{ {
page.ParentId = Int32.Parse(_parentid); page.ParentId = Int32.Parse(_parentid);
var parent = PageState.Pages.Where(item => item.PageId == page.ParentId).FirstOrDefault(); var parent = PageState.Pages.Where(item => item.PageId == page.ParentId).FirstOrDefault();
if (parent.Path == string.Empty) if (parent.Path == string.Empty)
{ {
page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path); page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
} }
else else
{ {
page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path); page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path);
} }
} }
if(PagePathIsDeleted(page.Path, page.SiteId, _pageList)) if(PagePathIsDeleted(page.Path, page.SiteId, _pageList))
{ {
AddModuleMessage(string.Format(Localizer["Message.Page.Deleted"], _path), MessageType.Warning); AddModuleMessage(string.Format(Localizer["Message.Page.Deleted"], _path), MessageType.Warning);
return; return;
} }
if (!PagePathIsUnique(page.Path, page.SiteId, _pageList)) if (!PagePathIsUnique(page.Path, page.SiteId, _pageList))
{ {
AddModuleMessage(string.Format(Localizer["Message.Page.Exists"], _path), MessageType.Warning); AddModuleMessage(string.Format(Localizer["Message.Page.Exists"], _path), MessageType.Warning);
return; return;
} }
Page child; Page child;
switch (_insert) switch (_insert)
{ {
case "<<": case "<<":
page.Order = 0; page.Order = 0;
break; break;
case "<": case "<":
child = PageState.Pages.Where(item => item.PageId == _childid).FirstOrDefault(); child = PageState.Pages.Where(item => item.PageId == _childid).FirstOrDefault();
page.Order = child.Order - 1; page.Order = child.Order - 1;
break; break;
case ">": case ">":
child = PageState.Pages.Where(item => item.PageId == _childid).FirstOrDefault(); child = PageState.Pages.Where(item => item.PageId == _childid).FirstOrDefault();
page.Order = child.Order + 1; page.Order = child.Order + 1;
break; break;
case ">>": case ">>":
page.Order = int.MaxValue; page.Order = int.MaxValue;
break; break;
} }
page.IsNavigation = (_isnavigation == null ? true : Boolean.Parse(_isnavigation)); page.IsNavigation = (_isnavigation == null ? true : Boolean.Parse(_isnavigation));
page.IsClickable = (_isclickable == null ? true : Boolean.Parse(_isclickable)); page.IsClickable = (_isclickable == null ? true : Boolean.Parse(_isclickable));
page.Url = _url; page.Url = _url;
page.ThemeType = (_themetype != "-") ? _themetype : string.Empty; page.ThemeType = (_themetype != "-") ? _themetype : string.Empty;
if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType) if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType)
{ {
page.ThemeType = string.Empty; page.ThemeType = string.Empty;
} }
page.DefaultContainerType = (_containertype != "-") ? _containertype : string.Empty; page.DefaultContainerType = (_containertype != "-") ? _containertype : string.Empty;
if (!string.IsNullOrEmpty(page.DefaultContainerType) && page.DefaultContainerType == PageState.Site.DefaultContainerType) if (!string.IsNullOrEmpty(page.DefaultContainerType) && page.DefaultContainerType == PageState.Site.DefaultContainerType)
{ {
page.DefaultContainerType = string.Empty; page.DefaultContainerType = string.Empty;
} }
page.Icon = (_icon == null ? string.Empty : _icon); page.Icon = (_icon == null ? string.Empty : _icon);
page.Permissions = _permissionGrid.GetPermissions(); page.Permissions = _permissionGrid.GetPermissions();
page.IsPersonalizable = (_ispersonalizable == null ? false : Boolean.Parse(_ispersonalizable)); page.IsPersonalizable = (_ispersonalizable == null ? false : Boolean.Parse(_ispersonalizable));
page.UserId = null; page.UserId = null;
page.Meta = _meta;
page = await PageService.AddPageAsync(page); page = await PageService.AddPageAsync(page);
await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, page.ParentId); await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, page.ParentId);

View File

@ -102,6 +102,12 @@
<input id="title" class="form-control" @bind="@_title" maxlength="200"/> <input id="title" class="form-control" @bind="@_title" maxlength="200"/>
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="meta" HelpText="Optionally enter meta tags (in exactly the form you want them to be included in the page output)." ResourceKey="Meta">Meta: </Label>
<div class="col-sm-9">
<textarea id="meta" class="form-control" @bind="@_meta" rows="3"></textarea>
</div>
</div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label> <Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
@ -200,6 +206,7 @@
private int _pageId; private int _pageId;
private string _name; private string _name;
private string _title; private string _title;
private string _meta;
private string _path; private string _path;
private string _currentparentid; private string _currentparentid;
private string _parentid = "-1"; private string _parentid = "-1";
@ -243,6 +250,7 @@
{ {
_name = page.Name; _name = page.Name;
_title = page.Title; _title = page.Title;
_meta = page.Meta;
_path = page.Path; _path = page.Path;
_pageModules = PageState.Modules.Where(m => m.PageId == page.PageId && m.IsDeleted == false).ToList(); _pageModules = PageState.Modules.Where(m => m.PageId == page.PageId && m.IsDeleted == false).ToList();
@ -313,183 +321,187 @@
_pageModules.RemoveAll(item => item.PageModuleId == pagemodule.PageModuleId); _pageModules.RemoveAll(item => item.PageModuleId == pagemodule.PageModuleId);
StateHasChanged(); StateHasChanged();
NavigationManager.NavigateTo(NavigationManager.Uri + "&tab=PageModules"); NavigationManager.NavigateTo(NavigationManager.Uri + "&tab=PageModules");
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Deleting Module {Title} {Error}", module.Title, ex.Message); await logger.LogError(ex, "Error Deleting Module {Title} {Error}", module.Title, ex.Message);
AddModuleMessage(Localizer["Error.Module.Delete"], MessageType.Error); AddModuleMessage(Localizer["Error.Module.Delete"], MessageType.Error);
} }
} }
private async void ParentChanged(ChangeEventArgs e) private async void ParentChanged(ChangeEventArgs e)
{ {
try try
{ {
_parentid = (string)e.Value; _parentid = (string)e.Value;
_children = new List<Page>(); _children = new List<Page>();
if (_parentid == "-1") if (_parentid == "-1")
{ {
foreach (Page p in PageState.Pages.Where(item => item.ParentId == null)) foreach (Page p in PageState.Pages.Where(item => item.ParentId == null))
{ {
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.Permissions)) if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.Permissions))
{ {
_children.Add(p); _children.Add(p);
} }
} }
} }
else else
{ {
foreach (Page p in PageState.Pages.Where(item => item.ParentId == int.Parse(_parentid))) foreach (Page p in PageState.Pages.Where(item => item.ParentId == int.Parse(_parentid)))
{ {
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.Permissions)) if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.Permissions))
{ {
_children.Add(p); _children.Add(p);
} }
} }
} }
if (_parentid == _currentparentid) if (_parentid == _currentparentid)
{ {
_insert = "="; _insert = "=";
} }
else else
{ {
_insert = ">>"; _insert = ">>";
} }
StateHasChanged(); StateHasChanged();
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Child Pages For Parent {PageId} {Error}", _parentid, ex.Message); await logger.LogError(ex, "Error Loading Child Pages For Parent {PageId} {Error}", _parentid, ex.Message);
AddModuleMessage(Localizer["Error.ChildPage.Load"], MessageType.Error); AddModuleMessage(Localizer["Error.ChildPage.Load"], MessageType.Error);
} }
} }
private async void ThemeChanged(ChangeEventArgs e) private async void ThemeChanged(ChangeEventArgs e)
{ {
try try
{ {
_themetype = (string)e.Value; _themetype = (string)e.Value;
_containers = ThemeService.GetContainerControls(_themeList, _themetype); _containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containertype = "-"; _containertype = "-";
ThemeSettings(); ThemeSettings();
StateHasChanged(); StateHasChanged();
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Pane Layouts For Theme {ThemeType} {Error}", _themetype, ex.Message); await logger.LogError(ex, "Error Loading Pane Layouts For Theme {ThemeType} {Error}", _themetype, ex.Message);
AddModuleMessage(Localizer["Error.Pane.Load"], MessageType.Error); AddModuleMessage(Localizer["Error.Pane.Load"], MessageType.Error);
} }
} }
private void ThemeSettings() private void ThemeSettings()
{ {
_themeSettingsType = null; _themeSettingsType = null;
var theme = _themeList.FirstOrDefault(item => item.Themes.Any(themecontrol => themecontrol.TypeName.Equals(_themetype))); if (PageState.QueryString.ContainsKey("cp")) // can only be displayed if invoked from Control Panel
if (theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType)) {
{ var theme = _themeList.FirstOrDefault(item => item.Themes.Any(themecontrol => themecontrol.TypeName.Equals(_themetype)));
_themeSettingsType = Type.GetType(theme.ThemeSettingsType); if (theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType))
if (_themeSettingsType != null) {
{ _themeSettingsType = Type.GetType(theme.ThemeSettingsType);
ThemeSettingsComponent = builder => if (_themeSettingsType != null)
{ {
builder.OpenComponent(0, _themeSettingsType); ThemeSettingsComponent = builder =>
builder.AddComponentReferenceCapture(1, inst => { _themeSettings = Convert.ChangeType(inst, _themeSettingsType); }); {
builder.CloseComponent(); builder.OpenComponent(0, _themeSettingsType);
}; builder.AddComponentReferenceCapture(1, inst => { _themeSettings = Convert.ChangeType(inst, _themeSettingsType); });
} builder.CloseComponent();
_refresh = true; };
} }
} _refresh = true;
}
}
}
private async Task SavePage() private async Task SavePage()
{ {
validated = true; validated = true;
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
if (await interop.FormValid(form)) if (await interop.FormValid(form))
{ {
Page page = null; Page page = null;
try try
{ {
if (!string.IsNullOrEmpty(_themetype) && _containertype != "-") if (!string.IsNullOrEmpty(_themetype) && _containertype != "-")
{ {
page = PageState.Pages.FirstOrDefault(item => item.PageId == _pageId); page = PageState.Pages.FirstOrDefault(item => item.PageId == _pageId);
string currentPath = page.Path; string currentPath = page.Path;
page.Name = _name; page.Name = _name;
page.Title = _title; page.Title = _title;
if (string.IsNullOrEmpty(_path)) if (string.IsNullOrEmpty(_path))
{ {
_path = _name; _path = _name;
} }
if (_path.Contains("/")) if (_path.Contains("/"))
{ {
_path = _path.Substring(_path.LastIndexOf("/") + 1); _path = _path.Substring(_path.LastIndexOf("/") + 1);
} }
if (_parentid == "-1") if (_parentid == "-1")
{ {
page.ParentId = null; page.ParentId = null;
page.Path = Utilities.GetFriendlyUrl(_path); page.Path = Utilities.GetFriendlyUrl(_path);
} }
else else
{ {
page.ParentId = Int32.Parse(_parentid); page.ParentId = Int32.Parse(_parentid);
Page parent = PageState.Pages.FirstOrDefault(item => item.PageId == page.ParentId); Page parent = PageState.Pages.FirstOrDefault(item => item.PageId == page.ParentId);
if (parent.Path == string.Empty) if (parent.Path == string.Empty)
{ {
page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path); page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
} }
else else
{ {
page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path); page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path);
} }
} }
if (!PagePathIsUnique(page.Path, page.SiteId, page.PageId, _pageList)) if (!PagePathIsUnique(page.Path, page.SiteId, page.PageId, _pageList))
{ {
AddModuleMessage(string.Format(Localizer["Mesage.Page.PathExists"], _path), MessageType.Warning); AddModuleMessage(string.Format(Localizer["Mesage.Page.PathExists"], _path), MessageType.Warning);
return; return;
} }
if (_insert != "=") if (_insert != "=")
{ {
Page child; Page child;
switch (_insert) switch (_insert)
{ {
case "<<": case "<<":
page.Order = 0; page.Order = 0;
break; break;
case "<": case "<":
child = PageState.Pages.FirstOrDefault(item => item.PageId == _childid); child = PageState.Pages.FirstOrDefault(item => item.PageId == _childid);
if (child != null) page.Order = child.Order - 1; if (child != null) page.Order = child.Order - 1;
break; break;
case ">": case ">":
child = PageState.Pages.FirstOrDefault(item => item.PageId == _childid); child = PageState.Pages.FirstOrDefault(item => item.PageId == _childid);
if (child != null) page.Order = child.Order + 1; if (child != null) page.Order = child.Order + 1;
break; break;
case ">>": case ">>":
page.Order = int.MaxValue; page.Order = int.MaxValue;
break; break;
} }
} }
page.IsNavigation = (_isnavigation == null || Boolean.Parse(_isnavigation)); page.IsNavigation = (_isnavigation == null || Boolean.Parse(_isnavigation));
page.IsClickable = (_isclickable == null ? true : Boolean.Parse(_isclickable)); page.IsClickable = (_isclickable == null ? true : Boolean.Parse(_isclickable));
page.Url = _url; page.Url = _url;
page.ThemeType = (_themetype != "-") ? _themetype : string.Empty; page.ThemeType = (_themetype != "-") ? _themetype : string.Empty;
if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType) if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType)
{ {
page.ThemeType = string.Empty; page.ThemeType = string.Empty;
} }
page.DefaultContainerType = (_containertype != "-") ? _containertype : string.Empty; page.DefaultContainerType = (_containertype != "-") ? _containertype : string.Empty;
if (!string.IsNullOrEmpty(page.DefaultContainerType) && page.DefaultContainerType == PageState.Site.DefaultContainerType) if (!string.IsNullOrEmpty(page.DefaultContainerType) && page.DefaultContainerType == PageState.Site.DefaultContainerType)
{ {
page.DefaultContainerType = string.Empty; page.DefaultContainerType = string.Empty;
} }
page.Icon = _icon ?? string.Empty; page.Icon = _icon ?? string.Empty;
page.Permissions = _permissionGrid.GetPermissions(); page.Permissions = _permissionGrid.GetPermissions();
page.IsPersonalizable = (_ispersonalizable != null && Boolean.Parse(_ispersonalizable)); page.IsPersonalizable = (_ispersonalizable != null && Boolean.Parse(_ispersonalizable));
page.UserId = null; page.UserId = null;
page.Meta = _meta;
page = await PageService.UpdatePageAsync(page); page = await PageService.UpdatePageAsync(page);
await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, page.ParentId); await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, page.ParentId);

View File

@ -25,7 +25,7 @@
<th>@Localizer["DeletedOn"]</th> <th>@Localizer["DeletedOn"]</th>
</Header> </Header>
<Row> <Row>
<td><button type="button" @onclick="@(() => RestorePage(context))" class="btn btn-info" title="Restore">Restore</button></td> <td><button type="button" @onclick="@(() => RestorePage(context))" class="btn btn-success" title="Restore">Restore</button></td>
<td><ActionDialog Header="Delete Page" Message="@string.Format(Localizer["Confirm.Page.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td> <td><ActionDialog Header="Delete Page" Message="@string.Format(Localizer["Confirm.Page.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
<td>@context.Name</td> <td>@context.Name</td>
<td>@context.DeletedBy</td> <td>@context.DeletedBy</td>
@ -56,7 +56,7 @@
<th>@Localizer["DeletedOn"]</th> <th>@Localizer["DeletedOn"]</th>
</Header> </Header>
<Row> <Row>
<td><button type="button" @onclick="@(() => RestoreModule(context))" class="btn btn-info" title="Restore">@Localizer["Restore"]</button></td> <td><button type="button" @onclick="@(() => RestoreModule(context))" class="btn btn-success" title="Restore">@Localizer["Restore"]</button></td>
<td><ActionDialog Header="Delete Module" Message="@string.Format(Localizer["Confirm.Module.Delete"], context.Title)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" /></td> <td><ActionDialog Header="Delete Module" Message="@string.Format(Localizer["Confirm.Module.Delete"], context.Title)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" /></td>
<td>@PageState.Pages.Find(item => item.PageId == context.PageId).Name</td> <td>@PageState.Pages.Find(item => item.PageId == context.PageId).Name</td>
<td>@context.Title</td> <td>@context.Title</td>

View File

@ -7,41 +7,49 @@
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container"> <div class="container">
<div class="form-group"> <div class="row mb-1 align-items-center">
<label for="Username" class="control-label">@SharedLocalizer["Username"] </label> <Label Class="col-sm-3" For="username" HelpText="Your username will be populated from the link you received in the password reset notification" ResourceKey="Username">Username: </Label>
<input type="text" class="form-control" placeholder="Username" @bind="@_username" readonly id="Username" /> <div class="col-sm-9">
<input id="username" type="text" class="form-control" @bind="@_username" readonly />
</div>
</div> </div>
<div class="form-group"> <div class="row mb-1 align-items-center">
<label for="Password" class="control-label">@SharedLocalizer["Password"] </label> <Label Class="col-sm-3" For="password" HelpText="The new password. It must satisfy complexity rules for the site." ResourceKey="Password">Password: </Label>
<input type="password" class="form-control" placeholder="Password" @bind="@_password" id="Password" required /> <div class="col-sm-9">
<input id="password" type="password" class="form-control" @bind="@_password" required />
</div>
</div> </div>
<div class="form-group"> <div class="row mb-1 align-items-center">
<label for="Confirm" class="control-label">@Localizer["Password.Confirm"] </label> <Label Class="col-sm-3" For="confirm" HelpText="Enter the password again. It must exactly match the password entered above." ResourceKey="Confirm">Confirm: </Label>
<input type="password" class="form-control" placeholder="Password" @bind="@_confirm" id="Confirm" required /> <div class="col-sm-9">
<input id="confirm" type="password" class="form-control" @bind="@_confirm" required />
</div>
</div> </div>
<button type="button" class="btn btn-primary" @onclick="Reset">@Localizer["Password.Reset"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</div> </div>
<br />
<button type="button" class="btn btn-primary" @onclick="Reset">@Localizer["Password.Reset"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</form> </form>
@code { @code {
private ElementReference form; private ElementReference form;
private bool validated = false; private bool validated = false;
private string _username = string.Empty; private string _username = string.Empty;
private string _password = string.Empty; private string _password = string.Empty;
private string _confirm = string.Empty; private string _confirm = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
protected override void OnInitialized() protected override async Task OnInitializedAsync()
{ {
if (PageState.QueryString.ContainsKey("name") && PageState.QueryString.ContainsKey("token")) if (PageState.QueryString.ContainsKey("name") && PageState.QueryString.ContainsKey("token"))
{ {
_username = PageState.QueryString["name"]; _username = PageState.QueryString["name"];
} }
else else
{ {
NavigationManager.NavigateTo(NavigateUrl(string.Empty)); await logger.LogError(LogFunction.Security, "Invalid Attempt To Access User Password Reset");
NavigationManager.NavigateTo(NavigateUrl("")); // home page
} }
} }

View File

@ -53,12 +53,14 @@ else
<Pager Items="@userroles"> <Pager Items="@userroles">
<Header> <Header>
<th>@Localizer["Users"]</th> <th>@Localizer["Users"]</th>
<th>@SharedLocalizer["Username"]</th>
<th>@Localizer["Effective"]</th> <th>@Localizer["Effective"]</th>
<th>@Localizer["Expiry"]</th> <th>@Localizer["Expiry"]</th>
<th>&nbsp;</th> <th>&nbsp;</th>
</Header> </Header>
<Row> <Row>
<td>@context.User.DisplayName</td> <td>@context.User.DisplayName</td>
<td>@context.User.Username</td>
<td>@context.EffectiveDate</td> <td>@context.EffectiveDate</td>
<td>@context.ExpiryDate</td> <td>@context.ExpiryDate</td>
<td> <td>

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Admin.Site @namespace Oqtane.Modules.Admin.Site
@inherits ModuleBase @inherits ModuleBase
@using System.Text.RegularExpressions
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject ISiteService SiteService @inject ISiteService SiteService
@inject ITenantService TenantService @inject ITenantService TenantService
@ -22,81 +23,64 @@
</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="alias" HelpText="The aliases for the site. An alias can be a domain name (www.site.com) or a virtual folder (ie. www.site.com/folder). If a site has multiple aliases they should be separated by commas." ResourceKey="Aliases">Aliases: </Label> <Label Class="col-sm-3" For="logo" HelpText="Specify a logo for the site" ResourceKey="Logo">Logo: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) <FileManager FileId="@_logofileid" Filter="@Constants.ImageFiles" @ref="_logofilemanager" />
{
<textarea id="alias" class="form-control" @bind="@_urls" rows="3" required></textarea>
}
else
{
<textarea id="alias" class="form-control" @bind="@_urls" rows="3" readonly></textarea>
}
</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="isDeleted" HelpText="Is this site deleted?" ResourceKey="IsDeleted">Is Deleted? </Label> <Label Class="col-sm-3" For="favicon" HelpText="Specify a Favicon" ResourceKey="FavoriteIcon">Favicon: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="isDeleted" class="form-select" @bind="@_isdeleted" required> <FileManager FileId="@_faviconfileid" Filter="ico" @ref="_faviconfilemanager" />
<option value="True">@SharedLocalizer["Yes"]</option> </div>
<option value="False">@SharedLocalizer["No"]</option> </div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultTheme" HelpText="Select the sites default theme" ResourceKey="DefaultTheme">Default Theme: </Label>
<div class="col-sm-9">
<select id="defaultTheme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
<option value="-">&lt;@Localizer["Theme.Select"]&gt;</option>
@foreach (var theme in _themes)
{
<option value="@theme.TypeName">@theme.Name</option>
}
</select> </select>
</div> </div>
</div> </div>
</div> <div class="row mb-1 align-items-center">
<Section Name="Appearance" Heading="Appearance" ResourceKey="Appearance"> <Label Class="col-sm-3" For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label>
<div class="container"> <div class="col-sm-9">
<div class="row mb-1 align-items-center"> <select id="defaultContainer" class="form-select" @bind="@_containertype" required>
<Label Class="col-sm-3" For="logo" HelpText="Specify a logo for the site" ResourceKey="Logo">Logo: </Label> <option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
<div class="col-sm-9"> @foreach (var container in _containers)
<FileManager FileId="@_logofileid" Filter="@Constants.ImageFiles" @ref="_logofilemanager" /> {
</div> <option value="@container.TypeName">@container.Name</option>
</div> }
<div class="row mb-1 align-items-center"> </select>
<Label Class="col-sm-3" For="favicon" HelpText="Specify a Favicon" ResourceKey="FavoriteIcon">Favicon: </Label>
<div class="col-sm-9">
<FileManager FileId="@_faviconfileid" Filter="ico" @ref="_faviconfilemanager" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultTheme" HelpText="Select the sites default theme" ResourceKey="DefaultTheme">Default Theme: </Label>
<div class="col-sm-9">
<select id="defaultTheme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
<option value="-">&lt;@Localizer["Theme.Select"]&gt;</option>
@foreach (var theme in _themes)
{
<option value="@theme.TypeName">@theme.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label>
<div class="col-sm-9">
<select id="defaultContainer" class="form-select" @bind="@_containertype" required>
<option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultAdminContainer" HelpText="Select the default admin container for the site" ResourceKey="DefaultAdminContainer">Default Admin Container: </Label>
<div class="col-sm-9">
<select id="defaultAdminContainer" class="form-select" @bind="@_admincontainertype" required>
<option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
<option value="@Constants.DefaultAdminContainer">&lt;@Localizer["DefaultAdminContainer"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</div>
</div> </div>
</div> </div>
</Section> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultAdminContainer" HelpText="Select the default admin container for the site" ResourceKey="DefaultAdminContainer">Default Admin Container: </Label>
<div class="col-sm-9">
<select id="defaultAdminContainer" class="form-select" @bind="@_admincontainertype" required>
<option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
<option value="@Constants.DefaultAdminContainer">&lt;@Localizer["DefaultAdminContainer"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="isDeleted" HelpText="Is this site deleted?" ResourceKey="IsDeleted">Deleted? </Label>
<div class="col-sm-9">
<select id="isDeleted" class="form-select" @bind="@_isdeleted" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
<Section Name="SMTP" Heading="SMTP Settings" ResourceKey="SMTPSettings"> <Section Name="SMTP" Heading="SMTP Settings" ResourceKey="SMTPSettings">
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
@ -174,8 +158,29 @@
</div> </div>
</div> </div>
</Section> </Section>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) @if (_aliases != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{ {
<Section Name="Aliases" Heading="Aliases" ResourceKey="Aliases">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="alias" HelpText="The aliases for the site. An alias can be a domain name (www.site.com) or a virtual folder (ie. www.site.com/folder). If a site has multiple aliases they should be separated by commas." ResourceKey="Aliases">Aliases: </Label>
<div class="col-sm-9">
<textarea id="alias" class="form-control" @bind="@_urls" rows="3" required></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultalias" HelpText="The default alias for the site. Requests for non-default aliases will be redirected to the default alias." ResourceKey="DefaultAlias">Default Alias: </Label>
<div class="col-sm-9">
<select id="defaultalias" class="form-select" @bind="@_defaultalias" required>
@foreach (string name in _urls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(sValue => sValue.Trim()).ToArray())
{
<option value="@name">@name</option>
}
</select>
</div>
</div>
</div>
</Section>
<Section Name="Hosting" Heading="Hosting Model" ResourceKey="Hosting"> <Section Name="Hosting" Heading="Hosting Model" ResourceKey="Hosting">
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
@ -238,7 +243,8 @@
private List<ThemeControl> _themes = new List<ThemeControl>(); private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>(); private List<ThemeControl> _containers = new List<ThemeControl>();
private string _name = string.Empty; private string _name = string.Empty;
private List<Alias> _aliasList; private List<Alias> _aliases;
private string _defaultalias = string.Empty;
private string _urls = string.Empty; private string _urls = string.Empty;
private string _runtime = ""; private string _runtime = "";
private string _prerender = ""; private string _prerender = "";
@ -288,13 +294,7 @@
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{ {
_aliasList = await AliasService.GetAliasesAsync(); await GetAliases();
foreach (Alias alias in _aliasList.Where(item => item.SiteId == site.SiteId && item.TenantId == site.TenantId).ToList())
{
_urls += alias.Name + ",";
}
_urls = _urls.Substring(0, _urls.Length - 1);
} }
if (site.LogoFileId != null) if (site.LogoFileId != null)
@ -322,7 +322,7 @@
{ {
_pwasplashiconfileid = site.PwaSplashIconFileId.Value; _pwasplashiconfileid = site.PwaSplashIconFileId.Value;
} }
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId); var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
_smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty); _smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty);
_smtpport = SettingService.GetSetting(settings, "SMTPPort", string.Empty); _smtpport = SettingService.GetSetting(settings, "SMTPPort", string.Empty);
@ -398,13 +398,17 @@
var unique = true; var unique = true;
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{ {
foreach (string name in _urls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) _urls = Regex.Replace(_urls, @"\r\n?|\n", ","); // convert line breaks to commas
var aliases = await AliasService.GetAliasesAsync();
foreach (string name in _urls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(sValue => sValue.Trim()).ToArray())
{ {
if (_aliasList.Exists(item => item.Name == name && item.SiteId != PageState.Alias.SiteId && item.TenantId != PageState.Alias.TenantId)) var alias = aliases.Where(item => item.Name == name).FirstOrDefault();
if (alias != null && unique)
{ {
unique = false; unique = (alias.TenantId == PageState.Site.TenantId && alias.SiteId == PageState.Site.SiteId);
} }
} }
if (unique && string.IsNullOrEmpty(_defaultalias)) unique = false;
} }
if (unique) if (unique)
@ -422,7 +426,6 @@
{ {
site.Runtime = _runtime; site.Runtime = _runtime;
site.RenderMode = _runtime + _prerender; site.RenderMode = _runtime + _prerender;
refresh = true;
reload = true; // needs to be reloaded on server reload = true; // needs to be reloaded on server
} }
} }
@ -461,145 +464,173 @@
int? pwaappiconfileid = _pwaappiconfilemanager.GetFileId(); int? pwaappiconfileid = _pwaappiconfilemanager.GetFileId();
if (pwaappiconfileid == -1) pwaappiconfileid = null; if (pwaappiconfileid == -1) pwaappiconfileid = null;
if (site.PwaAppIconFileId != pwaappiconfileid) if (site.PwaAppIconFileId != pwaappiconfileid)
{ {
site.PwaAppIconFileId = pwaappiconfileid; site.PwaAppIconFileId = pwaappiconfileid;
reload = true; // needs to be reloaded on server reload = true; // needs to be reloaded on server
} }
int? pwasplashiconfileid = _pwasplashiconfilemanager.GetFileId(); int? pwasplashiconfileid = _pwasplashiconfilemanager.GetFileId();
if (pwasplashiconfileid == -1) pwasplashiconfileid = null; if (pwasplashiconfileid == -1) pwasplashiconfileid = null;
if (site.PwaSplashIconFileId != pwasplashiconfileid) if (site.PwaSplashIconFileId != pwasplashiconfileid)
{ {
site.PwaSplashIconFileId = pwasplashiconfileid; site.PwaSplashIconFileId = pwasplashiconfileid;
reload = true; // needs to be reloaded on server reload = true; // needs to be reloaded on server
} }
site = await SiteService.UpdateSiteAsync(site); site = await SiteService.UpdateSiteAsync(site);
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId); var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
SettingService.SetSetting(settings, "SMTPHost", _smtphost); SettingService.SetSetting(settings, "SMTPHost", _smtphost, true);
SettingService.SetSetting(settings, "SMTPPort", _smtpport); SettingService.SetSetting(settings, "SMTPPort", _smtpport, true);
SettingService.SetSetting(settings, "SMTPSSL", _smtpssl); SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true);
SettingService.SetSetting(settings, "SMTPUsername", _smtpusername); SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true);
SettingService.SetSetting(settings, "SMTPPassword", _smtppassword); SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true);
SettingService.SetSetting(settings, "SMTPSender", _smtpsender); SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId); await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{ {
var names = _urls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); var names = _urls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
foreach (Alias alias in _aliasList.Where(item => item.SiteId == site.SiteId && item.TenantId == site.TenantId).ToList()) .Select(sValue => sValue.Trim()).ToArray();
{ foreach (Alias alias in _aliases)
if (!names.Contains(alias.Name)) {
{ if (!names.Contains(alias.Name.Trim()))
await AliasService.DeleteAliasAsync(alias.AliasId); {
} await AliasService.DeleteAliasAsync(alias.AliasId);
} }
}
foreach (string name in names) foreach (string name in names)
{ {
if (!_aliasList.Exists(item => item.Name == name)) var alias = _aliases.Find(item => item.Name.Trim() == name);
{ if (alias == null)
Alias alias = new Alias(); {
alias.Name = name; alias = new Alias();
alias.TenantId = site.TenantId; alias.Name = name;
alias.SiteId = site.SiteId; alias.TenantId = site.TenantId;
await AliasService.AddAliasAsync(alias); alias.SiteId = site.SiteId;
} alias.IsDefault = (name == _defaultalias);
} await AliasService.AddAliasAsync(alias);
} }
else
{
if (alias.Name != name || alias.IsDefault != (alias.Name.Trim() == _defaultalias))
{
alias.Name = name;
alias.IsDefault = (name == _defaultalias);
await AliasService.UpdateAliasAsync(alias);
}
}
}
await GetAliases();
}
await logger.LogInformation("Site Settings Saved {Site}", site); await logger.LogInformation("Site Settings Saved {Site}", site);
if (refresh) if (refresh || reload)
{ {
NavigationManager.NavigateTo(NavigateUrl(true), reload); // refresh/reload NavigationManager.NavigateTo(NavigateUrl(true), reload); // refresh/reload
} }
else else
{ {
AddModuleMessage(Localizer["Success.Settings.SaveSite"], MessageType.Success); AddModuleMessage(Localizer["Success.Settings.SaveSite"], MessageType.Success);
} await interop.ScrollTo(0, 0, "smooth");
} }
} }
else }
{ else // deuplicate alias or default alias not specified
AddModuleMessage(Localizer["Message.Aliases.Taken"], MessageType.Warning); {
} AddModuleMessage(Localizer["Message.Aliases.Taken"], MessageType.Warning);
} }
else }
{ else
AddModuleMessage(Localizer["Message.Required.SiteName"], MessageType.Warning); {
} AddModuleMessage(Localizer["Message.Required.SiteName"], MessageType.Warning);
} }
catch (Exception ex) }
{ catch (Exception ex)
await logger.LogError(ex, "Error Saving Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message); {
AddModuleMessage(Localizer["Error.SaveSite"], MessageType.Error); await logger.LogError(ex, "Error Saving Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message);
} AddModuleMessage(Localizer["Error.SaveSite"], MessageType.Error);
} }
else }
{ else
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning); {
} AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
} }
}
private async Task DeleteSite() private async Task DeleteSite()
{ {
try try
{ {
var sites = await SiteService.GetSitesAsync(); var sites = await SiteService.GetSitesAsync();
if (sites.Count > 1) if (sites.Count > 1)
{ {
await SiteService.DeleteSiteAsync(PageState.Site.SiteId); await SiteService.DeleteSiteAsync(PageState.Site.SiteId);
await logger.LogInformation("Site Deleted {SiteId}", PageState.Site.SiteId); await logger.LogInformation("Site Deleted {SiteId}", PageState.Site.SiteId);
var aliases = await AliasService.GetAliasesAsync(); var aliases = await AliasService.GetAliasesAsync();
foreach (Alias a in aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Site.TenantId)) foreach (Alias a in aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Site.TenantId))
{ {
await AliasService.DeleteAliasAsync(a.AliasId); await AliasService.DeleteAliasAsync(a.AliasId);
} }
NavigationManager.NavigateTo(NavigateUrl("admin/sites")); NavigationManager.NavigateTo(NavigateUrl("admin/sites"));
} }
else else
{ {
AddModuleMessage(Localizer["Message.FailAuth.DeleteSite"], MessageType.Warning); AddModuleMessage(Localizer["Message.FailAuth.DeleteSite"], MessageType.Warning);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Deleting Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message); await logger.LogError(ex, "Error Deleting Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message);
AddModuleMessage(Localizer["Error.DeleteSite"], MessageType.Error); AddModuleMessage(Localizer["Error.DeleteSite"], MessageType.Error);
} }
} }
private async Task SendEmail() private async Task SendEmail()
{ {
if (_smtphost != "" && _smtpport != "" && _smtpsender != "") if (_smtphost != "" && _smtpport != "" && _smtpsender != "")
{ {
try try
{ {
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
SettingService.SetSetting(settings, "SMTPHost", _smtphost); SettingService.SetSetting(settings, "SMTPHost", _smtphost, true);
SettingService.SetSetting(settings, "SMTPPort", _smtpport); SettingService.SetSetting(settings, "SMTPPort", _smtpport, true);
SettingService.SetSetting(settings, "SMTPSSL", _smtpssl); SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true);
SettingService.SetSetting(settings, "SMTPUsername", _smtpusername); SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true);
SettingService.SetSetting(settings, "SMTPPassword", _smtppassword); SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true);
SettingService.SetSetting(settings, "SMTPSender", _smtpsender); SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId); await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
await logger.LogInformation("Site SMTP Settings Saved"); await logger.LogInformation("Site SMTP Settings Saved");
await NotificationService.AddNotificationAsync(new Notification(PageState.Site.SiteId, PageState.User.DisplayName, PageState.User.Email, PageState.User.DisplayName, PageState.User.Email, PageState.Site.Name + " SMTP Configuration Test", "SMTP Server Is Configured Correctly.")); await NotificationService.AddNotificationAsync(new Notification(PageState.Site.SiteId, PageState.User.DisplayName, PageState.User.Email, PageState.User.DisplayName, PageState.User.Email, PageState.Site.Name + " SMTP Configuration Test", "SMTP Server Is Configured Correctly."));
AddModuleMessage(Localizer["Info.Smtp.SaveSettings"], MessageType.Info); AddModuleMessage(Localizer["Info.Smtp.SaveSettings"], MessageType.Info);
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Testing SMTP Configuration"); await logger.LogError(ex, "Error Testing SMTP Configuration");
AddModuleMessage(Localizer["Error.Smtp.TestConfig"], MessageType.Error); AddModuleMessage(Localizer["Error.Smtp.TestConfig"], MessageType.Error);
} }
} }
else else
{ {
AddModuleMessage(Localizer["Message.required.Smtp"], MessageType.Warning); AddModuleMessage(Localizer["Message.required.Smtp"], MessageType.Warning);
} }
} }
private async Task GetAliases()
{
_urls = string.Empty;
_defaultalias = string.Empty;
_aliases = await AliasService.GetAliasesAsync();
_aliases = _aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Site.TenantId).OrderBy(item => item.AliasId).ToList();
foreach (Alias alias in _aliases)
{
_urls += (_urls == string.Empty) ? alias.Name.Trim() : ", " + alias.Name.Trim();
if (alias.IsDefault && string.IsNullOrEmpty(_defaultalias)) _defaultalias = alias.Name.Trim();
}
if (string.IsNullOrEmpty(_defaultalias)) _defaultalias = _aliases.First().Name.Trim();
}
} }

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Admin.Sites @namespace Oqtane.Modules.Admin.Sites
@using Oqtane.Interfaces @using Oqtane.Interfaces
@using System.Text.RegularExpressions
@inherits ModuleBase @inherits ModuleBase
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject ITenantService TenantService @inject ITenantService TenantService
@ -282,6 +283,7 @@ else
{ {
if (_tenantid != "-" && _name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-" && _sitetemplatetype != "-") if (_tenantid != "-" && _name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-" && _sitetemplatetype != "-")
{ {
_urls = Regex.Replace(_urls, @"\r\n?|\n", ",");
var duplicates = new List<string>(); var duplicates = new List<string>();
var aliases = await AliasService.GetAliasesAsync(); var aliases = await AliasService.GetAliasesAsync();
foreach (string name in _urls.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) foreach (string name in _urls.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
@ -370,8 +372,7 @@ else
if (installation.Success) if (installation.Success)
{ {
var aliasname = config.Aliases.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)[0]; var aliasname = config.Aliases.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)[0];
var uri = new Uri(NavigationManager.Uri); NavigationManager.NavigateTo(PageState.Uri.Scheme + "://" + aliasname, true);
NavigationManager.NavigateTo(uri.Scheme + "://" + aliasname, true);
} }
else else
{ {

View File

@ -30,20 +30,16 @@ else
@code { @code {
private List<Alias> _sites; private List<Alias> _sites;
private string _scheme;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
var uri = new Uri(NavigationManager.Uri);
_scheme = uri.Scheme + "://";
var aliases = await AliasService.GetAliasesAsync(); var aliases = await AliasService.GetAliasesAsync();
_sites = new List<Alias>(); _sites = new List<Alias>();
foreach (Alias alias in aliases) foreach (Alias alias in aliases)
{ {
if (!_sites.Exists(item => item.TenantId == alias.TenantId && item.SiteId == alias.SiteId)) if (alias.IsDefault && !_sites.Exists(item => item.TenantId == alias.TenantId && item.SiteId == alias.SiteId))
{ {
_sites.Add(alias); _sites.Add(alias);
} }
@ -52,16 +48,11 @@ else
private void Edit(string name) private void Edit(string name)
{ {
if (name.Equals("*")) NavigationManager.NavigateTo(PageState.Uri.Scheme + "://" + name + "/admin/site", true);
{
var uri = new Uri(NavigationManager.Uri);
name = uri.Authority;
}
NavigationManager.NavigateTo(_scheme + name + "/admin/site/?reload");
} }
private void Browse(string name) private void Browse(string name)
{ {
NavigationManager.NavigateTo(_scheme + name + "/?reload"); NavigationManager.NavigateTo(PageState.Uri.Scheme + "://" + name, true);
} }
} }

View File

@ -57,22 +57,20 @@ else
{ {
@if (_results.Count > 0) @if (_results.Count > 0)
{ {
<div class="table-responsive"> <Pager Class="table table-bordered" Items="@_results">
<Pager Class="table table-bordered" Items="@_results"> <Header>
<Header> @foreach (KeyValuePair<string, string> kvp in _results.First())
@foreach (KeyValuePair<string, string> kvp in _results.First()) {
{ <th>@kvp.Key</th>
<th>@kvp.Key</th> }
} </Header>
</Header> <Row>
<Row> @foreach (KeyValuePair<string, string> kvp in context)
@foreach (KeyValuePair<string, string> kvp in context) {
{ <td>@kvp.Value</td>
<td>@kvp.Value</td> }
} </Row>
</Row> </Pager>
</Pager>
</div>
} }
else else
{ {

View File

@ -33,7 +33,7 @@
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="servertime" HelpText="Server Time" ResourceKey="ServerTime">Server Time: </Label> <Label Class="col-sm-3" For="servertime" HelpText="Server Date/Time (in UTC)" ResourceKey="ServerTime">Server Date/Time: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="servertime" class="form-control" @bind="@_servertime" readonly /> <input id="servertime" class="form-control" @bind="@_servertime" readonly />
</div> </div>
@ -99,32 +99,32 @@
</TabStrip> </TabStrip>
@code { @code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
private string _version = string.Empty; private string _version = string.Empty;
private string _clrversion = string.Empty; private string _clrversion = string.Empty;
private string _osversion = string.Empty; private string _osversion = string.Empty;
private string _serverpath = string.Empty; private string _serverpath = string.Empty;
private string _servertime = string.Empty; private string _servertime = string.Empty;
private string _installationid = string.Empty; private string _installationid = string.Empty;
private string _detailederrors = string.Empty; private string _detailederrors = string.Empty;
private string _logginglevel = string.Empty; private string _logginglevel = string.Empty;
private string _swagger = string.Empty; private string _swagger = string.Empty;
private string _packageservice = string.Empty; private string _packageservice = string.Empty;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
_version = Constants.Version; _version = Constants.Version;
Dictionary<string, string> systeminfo = await SystemService.GetSystemInfoAsync(); Dictionary<string, string> systeminfo = await SystemService.GetSystemInfoAsync();
if (systeminfo != null) if (systeminfo != null)
{ {
_clrversion = systeminfo["clrversion"]; _clrversion = systeminfo["clrversion"];
_osversion = systeminfo["osversion"]; _osversion = systeminfo["osversion"];
_serverpath = systeminfo["serverpath"]; _serverpath = systeminfo["serverpath"];
_servertime = systeminfo["servertime"]; _servertime = systeminfo["servertime"] + " UTC";
_installationid = systeminfo["installationid"]; _installationid = systeminfo["installationid"];
_detailederrors = systeminfo["detailederrors"]; _detailederrors = systeminfo["detailederrors"];
_logginglevel = systeminfo["logginglevel"]; _logginglevel = systeminfo["logginglevel"];

View File

@ -40,28 +40,46 @@
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
if (await interop.FormValid(form)) if (await interop.FormValid(form))
{ {
var route = new Route(_url, PageState.Alias.Path); if (_url != _mappedurl)
var url = route.SiteUrl + "/" + route.PagePath; {
var url = PageState.Uri.Scheme + "://" + PageState.Uri.Authority + "/";
url = url + (!string.IsNullOrEmpty(PageState.Alias.Path) ? PageState.Alias.Path + "/" : "");
var urlmapping = new UrlMapping(); _url = (_url.StartsWith("/")) ? _url.Substring(1) : _url;
urlmapping.SiteId = PageState.Site.SiteId; _url = (!_url.StartsWith("http")) ? url + _url : _url;
urlmapping.Url = url;
urlmapping.MappedUrl = _mappedurl;
urlmapping.Requests = 0;
urlmapping.CreatedOn = DateTime.UtcNow;
urlmapping.RequestedOn = DateTime.UtcNow;
try if (_url.StartsWith(url))
{ {
urlmapping = await UrlMappingService.AddUrlMappingAsync(urlmapping); var urlmapping = new UrlMapping();
await logger.LogInformation("UrlMapping Saved {UrlMapping}", urlmapping); urlmapping.SiteId = PageState.Site.SiteId;
NavigationManager.NavigateTo(NavigateUrl()); var route = new Route(_url, PageState.Alias.Path);
} urlmapping.Url = route.PagePath;
catch (Exception ex) urlmapping.MappedUrl = _mappedurl.Replace(url, "");
{ urlmapping.Requests = 0;
await logger.LogError(ex, "Error Saving UrlMapping {UrlMapping} {Error}", urlmapping, ex.Message); urlmapping.CreatedOn = DateTime.UtcNow;
AddModuleMessage(Localizer["Error.SaveUrlMapping"], MessageType.Error); urlmapping.RequestedOn = DateTime.UtcNow;
}
try
{
urlmapping = await UrlMappingService.AddUrlMappingAsync(urlmapping);
await logger.LogInformation("UrlMapping Saved {UrlMapping}", urlmapping);
NavigationManager.NavigateTo(NavigateUrl());
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving UrlMapping {UrlMapping} {Error}", urlmapping, ex.Message);
AddModuleMessage(Localizer["Error.SaveUrlMapping"], MessageType.Error);
}
}
else
{
AddModuleMessage(Localizer["Message.SaveUrlMapping"], MessageType.Warning);
}
}
else
{
AddModuleMessage(Localizer["Message.DuplicateUrlMapping"], MessageType.Warning);
}
} }
else else
{ {

View File

@ -26,55 +26,64 @@
</form> </form>
@code { @code {
private ElementReference form; private ElementReference form;
private bool validated = false; private bool validated = false;
private int _urlmappingid; private int _urlmappingid;
private string _url = string.Empty; private string _url = string.Empty;
private string _mappedurl = string.Empty; private string _mappedurl = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
try try
{ {
_urlmappingid = Int32.Parse(PageState.QueryString["id"]); _urlmappingid = Int32.Parse(PageState.QueryString["id"]);
var urlmapping = await UrlMappingService.GetUrlMappingAsync(_urlmappingid); var urlmapping = await UrlMappingService.GetUrlMappingAsync(_urlmappingid);
if (urlmapping != null) if (urlmapping != null)
{ {
_url = urlmapping.Url; _url = urlmapping.Url;
_mappedurl = urlmapping.MappedUrl; _mappedurl = urlmapping.MappedUrl;
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading UrlMapping {UrlMappingId} {Error}", _urlmappingid, ex.Message); await logger.LogError(ex, "Error Loading UrlMapping {UrlMappingId} {Error}", _urlmappingid, ex.Message);
AddModuleMessage(Localizer["Error.LoadUrlMapping"], MessageType.Error); AddModuleMessage(Localizer["Error.LoadUrlMapping"], MessageType.Error);
} }
} }
private async Task SaveUrlMapping() private async Task SaveUrlMapping()
{ {
validated = true; validated = true;
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
if (await interop.FormValid(form)) if (await interop.FormValid(form))
{ {
var urlmapping = await UrlMappingService.GetUrlMappingAsync(_urlmappingid); if (_url != _mappedurl)
urlmapping.MappedUrl = _mappedurl; {
try
{
var url = PageState.Uri.Scheme + "://" + PageState.Uri.Authority + "/";
url = url + (!string.IsNullOrEmpty(PageState.Alias.Path) ? PageState.Alias.Path + "/" : "");
try var urlmapping = await UrlMappingService.GetUrlMappingAsync(_urlmappingid);
{ urlmapping.MappedUrl = _mappedurl.Replace(url, "");
urlmapping = await UrlMappingService.UpdateUrlMappingAsync(urlmapping); urlmapping = await UrlMappingService.UpdateUrlMappingAsync(urlmapping);
await logger.LogInformation("UrlMapping Saved {UrlMapping}", urlmapping); await logger.LogInformation("UrlMapping Saved {UrlMapping}", urlmapping);
NavigationManager.NavigateTo(NavigateUrl()); NavigationManager.NavigateTo(NavigateUrl());
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Saving UrlMapping {UrlMapping} {Error}", urlmapping, ex.Message); await logger.LogError(ex, "Error Saving UrlMapping {UrlMappingId} {Error}", _urlmappingid, ex.Message);
AddModuleMessage(Localizer["Error.SaveUrlMapping"], MessageType.Error); AddModuleMessage(Localizer["Error.SaveUrlMapping"], MessageType.Error);
} }
} }
else
{
AddModuleMessage(Localizer["Message.DuplicateUrlMapping"], MessageType.Warning);
}
}
else else
{ {
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning); AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);

View File

@ -40,10 +40,10 @@ else
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.UrlMappingId.ToString())" ResourceKey="Edit" /></td> <td><ActionLink Action="Edit" Parameters="@($"id=" + context.UrlMappingId.ToString())" ResourceKey="Edit" /></td>
<td><ActionDialog Header="Delete Url Mapping" Message="@string.Format(Localizer["Confirm.DeleteUrlMapping"], context.Url)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteUrlMapping(context))" ResourceKey="DeleteUrlMapping" /></td> <td><ActionDialog Header="Delete Url Mapping" Message="@string.Format(Localizer["Confirm.DeleteUrlMapping"], context.Url)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteUrlMapping(context))" ResourceKey="DeleteUrlMapping" /></td>
<td> <td>
<a href="" onclick="@(() => BrowseUrl(context.Url))">@context.Url</a> <a href="@Utilities.TenantUrl(PageState.Alias, context.Url)">@context.Url</a>
@if (_mapped) @if (_mapped)
{ {
@((MarkupString)"<br />&gt;&gt;&nbsp;")<a href="" onclick="@(() => BrowseUrl(context.MappedUrl))">@context.MappedUrl</a> @((MarkupString)"<br />&gt;&gt;&nbsp;")<a href="@((context.MappedUrl.StartsWith("http") ? context.MappedUrl : Utilities.TenantUrl(PageState.Alias, context.MappedUrl)))">@context.MappedUrl</a>
} }
</td> </td>
<td>@context.Requests</td> <td>@context.Requests</td>
@ -96,11 +96,6 @@ else
} }
} }
private void BrowseUrl(string url)
{
NavigationManager.NavigateTo(url, true);
}
private async Task DeleteUrlMapping(UrlMapping urlMapping) private async Task DeleteUrlMapping(UrlMapping urlMapping)
{ {
try try

View File

@ -13,7 +13,7 @@
<Label Class="col-sm-3" For="to" HelpText="Enter the username you wish to send a message to" ResourceKey="To">To: </Label> <Label Class="col-sm-3" For="to" HelpText="Enter the username you wish to send a message to" ResourceKey="To">To: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="to" class="form-control" @bind="@username" /> <input id="to" class="form-control" @bind="@username" />
</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="subject" HelpText="Enter the subject of the message" ResourceKey="Subject">Subject: </Label> <Label Class="col-sm-3" For="subject" HelpText="Enter the subject of the message" ResourceKey="Subject">Subject: </Label>

View File

@ -12,7 +12,7 @@
@if (PageState.User != null && photo != null) @if (PageState.User != null && photo != null)
{ {
<img src="@ImageUrl(photofileid, 400, 400, "crop")" alt="@displayname" style="max-width: 400px" class="rounded-circle mx-auto d-block"> <img src="@ImageUrl(photofileid, 400, 400)" alt="@displayname" style="max-width: 400px" class="rounded-circle mx-auto d-block">
} }
else else
{ {

View File

@ -36,6 +36,7 @@ else
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>
<th>@SharedLocalizer["Username"]</th>
</Header> </Header>
<Row> <Row>
<td> <td>
@ -48,6 +49,7 @@ else
<ActionLink Action="Roles" Parameters="@($"id=" + context.UserId.ToString())" ResourceKey="Roles" /> <ActionLink Action="Roles" Parameters="@($"id=" + context.UserId.ToString())" ResourceKey="Roles" />
</td> </td>
<td>@context.User.DisplayName</td> <td>@context.User.DisplayName</td>
<td>@context.User.Username</td>
</Row> </Row>
</Pager> </Pager>
</TabPanel> </TabPanel>
@ -89,7 +91,7 @@ else
{ {
var results = allroles.Where(item => item.Role.Name == RoleNames.Registered || (item.Role.Name == RoleNames.Host && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))); var results = allroles.Where(item => item.Role.Name == RoleNames.Registered || (item.Role.Name == RoleNames.Host && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)));
if (string.IsNullOrEmpty(_search)) if (!string.IsNullOrEmpty(_search))
{ {
results = results.Where(item => results = results.Where(item =>
( (

View File

@ -7,69 +7,73 @@
@inject IStringLocalizer<Detail> Localizer @inject IStringLocalizer<Detail> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="container"> @if (_initialized)
<div class="row mb-1 align-items-center"> {
<Label Class="col-sm-3" For="ip" HelpText="The last recorded IP address for this visitor" ResourceKey="IP">IP Address: </Label> <div class="container">
<div class="col-sm-9"> <div class="row mb-1 align-items-center">
<input id="ip" class="form-control" @bind="@_ip" readonly /> <Label Class="col-sm-3" For="ip" HelpText="The last recorded IP address for this visitor" ResourceKey="IP">IP Address: </Label>
</div> <div class="col-sm-9">
</div> <input id="ip" class="form-control" @bind="@_ip" readonly />
<div class="row mb-1 align-items-center"> </div>
<Label Class="col-sm-3" For="language" HelpText="The last recorded language for this visitor" ResourceKey="Language">Language: </Label> </div>
<div class="col-sm-9"> <div class="row mb-1 align-items-center">
<input id="language" class="form-control" @bind="@_language" readonly /> <Label Class="col-sm-3" For="language" HelpText="The last recorded language for this visitor" ResourceKey="Language">Language: </Label>
</div> <div class="col-sm-9">
</div> <input id="language" class="form-control" @bind="@_language" readonly />
<div class="row mb-1 align-items-center"> </div>
<Label Class="col-sm-3" For="useragent" HelpText="The last recorded user agent for this visitor" ResourceKey="UserAgent">User Agent: </Label> </div>
<div class="col-sm-9"> <div class="row mb-1 align-items-center">
<input id="useragent" class="form-control" @bind="@_useragent" readonly /> <Label Class="col-sm-3" For="useragent" HelpText="The last recorded user agent for this visitor" ResourceKey="UserAgent">User Agent: </Label>
</div> <div class="col-sm-9">
</div> <input id="useragent" class="form-control" @bind="@_useragent" readonly />
<div class="row mb-1 align-items-center"> </div>
<Label Class="col-sm-3" For="url" HelpText="The last recorded url for this visitor" ResourceKey="Url">Url: </Label> </div>
<div class="col-sm-9"> <div class="row mb-1 align-items-center">
<input id="url" class="form-control" @bind="@_url" readonly /> <Label Class="col-sm-3" For="url" HelpText="The last recorded url for this visitor" ResourceKey="Url">Url: </Label>
</div> <div class="col-sm-9">
</div> <input id="url" class="form-control" @bind="@_url" readonly />
<div class="row mb-1 align-items-center"> </div>
<Label Class="col-sm-3" For="referrer" HelpText="The last recorded referrer for this visitor" ResourceKey="Referrer">Referrer: </Label> </div>
<div class="col-sm-9"> <div class="row mb-1 align-items-center">
<input id="referrer" class="form-control" @bind="@_referrer" readonly /> <Label Class="col-sm-3" For="referrer" HelpText="The last recorded referrer for this visitor" ResourceKey="Referrer">Referrer: </Label>
</div> <div class="col-sm-9">
</div> <input id="referrer" class="form-control" @bind="@_referrer" readonly />
@if (_user != string.Empty) </div>
{ </div>
<div class="row mb-1 align-items-center"> @if (_user != string.Empty)
<Label Class="col-sm-3" For="user" HelpText="The last recorded user associated with this visitor" ResourceKey="User">User: </Label> {
<div class="col-sm-9"> <div class="row mb-1 align-items-center">
<input id="user" class="form-control" @bind="@_user" readonly /> <Label Class="col-sm-3" For="user" HelpText="The last recorded user associated with this visitor" ResourceKey="User">User: </Label>
</div> <div class="col-sm-9">
</div> <input id="user" class="form-control" @bind="@_user" readonly />
} </div>
<div class="row mb-1 align-items-center"> </div>
<Label Class="col-sm-3" For="visits" HelpText="The total number of visits by this visitor all time" ResourceKey="Visits">Visits: </Label> }
<div class="col-sm-9"> <div class="row mb-1 align-items-center">
<input id="visits" class="form-control" @bind="@_visits" readonly /> <Label Class="col-sm-3" For="visits" HelpText="The total number of visits by this visitor all time" ResourceKey="Visits">Visits: </Label>
</div> <div class="col-sm-9">
</div> <input id="visits" class="form-control" @bind="@_visits" readonly />
<div class="row mb-1 align-items-center"> </div>
<Label Class="col-sm-3" For="visited" HelpText="The last recorded date/time when the visitor visited the site" ResourceKey="Visited">Visited: </Label> </div>
<div class="col-sm-9"> <div class="row mb-1 align-items-center">
<input id="visited" class="form-control" @bind="@_visited" readonly /> <Label Class="col-sm-3" For="visited" HelpText="The last recorded date/time when the visitor visited the site" ResourceKey="Visited">Visited: </Label>
</div> <div class="col-sm-9">
</div> <input id="visited" class="form-control" @bind="@_visited" readonly />
<div class="row mb-1 align-items-center"> </div>
<Label Class="col-sm-3" For="created" HelpText="The first recorded date/time when this visitor visited the site" ResourceKey="Created">Created: </Label> </div>
<div class="col-sm-9"> <div class="row mb-1 align-items-center">
<input id="created" class="form-control" @bind="@_created" readonly /> <Label Class="col-sm-3" For="created" HelpText="The first recorded date/time when this visitor visited the site" ResourceKey="Created">Created: </Label>
</div> <div class="col-sm-9">
</div> <input id="created" class="form-control" @bind="@_created" readonly />
</div>
</div>
</div> </div>
}
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@CloseUrl()">@SharedLocalizer["Cancel"]</NavLink>
@code { @code {
private bool _initialized = false;
private int _visitorId; private int _visitorId;
private string _ip = string.Empty; private string _ip = string.Empty;
private string _language = string.Empty; private string _language = string.Empty;
@ -108,6 +112,7 @@
_user = user.DisplayName; _user = user.DisplayName;
} }
} }
_initialized = true;
} }
else else
{ {
@ -120,4 +125,16 @@
AddModuleMessage(Localizer["Error.LoadVisitor"], MessageType.Error); AddModuleMessage(Localizer["Error.LoadVisitor"], MessageType.Error);
} }
} }
}
private string CloseUrl()
{
if (!PageState.QueryString.ContainsKey("type"))
{
return NavigateUrl();
}
else
{
return NavigateUrl(PageState.Page.Path, "type=" + PageState.QueryString["type"] + "&days=" + PageState.QueryString["days"] + "&page=" + PageState.QueryString["page"]);
}
}
}

View File

@ -2,6 +2,7 @@
@inherits ModuleBase @inherits ModuleBase
@inject IVisitorService VisitorService @inject IVisitorService VisitorService
@inject ISiteService SiteService @inject ISiteService SiteService
@inject ISettingService SettingService
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
@ -16,13 +17,13 @@ else
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<div class="col-sm-6"> <div class="col-sm-6">
<select id="type" class="form-select custom-select" @onchange="(e => TypeChanged(e))"> <select id="type" class="form-select custom-select" value="@_type" @onchange="(e => TypeChanged(e))">
<option value="false">@Localizer["AllVisitors"]</option> <option value="visitors">@Localizer["AllVisitors"]</option>
<option value="true">@Localizer["UsersOnly"]</option> <option value="users">@Localizer["UsersOnly"]</option>
</select> </select>
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
<select id="type" class="form-select custom-select" @onchange="(e => DateChanged(e))"> <select id="days" class="form-select custom-select" value="@_days" @onchange="(e => DaysChanged(e))">
<option value="1">@Localizer["PastDay"]</option> <option value="1">@Localizer["PastDay"]</option>
<option value="7">@Localizer["PastWeek"]</option> <option value="7">@Localizer["PastWeek"]</option>
<option value="30">@Localizer["PastMonth"]</option> <option value="30">@Localizer["PastMonth"]</option>
@ -31,7 +32,7 @@ else
</div> </div>
</div> </div>
<br/> <br/>
<Pager Items="@_visitors"> <Pager Items="@_visitors" CurrentPage="@_page.ToString()" OnPageChange="OnPageChange">
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th>@Localizer["IP"]</th> <th>@Localizer["IP"]</th>
@ -42,7 +43,7 @@ else
<th>@Localizer["Created"]</th> <th>@Localizer["Created"]</th>
</Header> </Header>
<Row> <Row>
<td><ActionLink Action="Detail" Parameters="@($"id=" + context.VisitorId.ToString())" ResourceKey="Details" /></td> <td><ActionLink Action="Detail" Parameters="@($"id=" + context.VisitorId.ToString() + "&type=" + _type.ToString() + "&days=" + _days.ToString() + "&page=" + _page.ToString())" ResourceKey="Details" /></td>
<td>@context.IPAddress</td> <td>@context.IPAddress</td>
<td> <td>
@if (context.UserId != null) @if (context.UserId != null)
@ -56,18 +57,39 @@ else
<td>@context.CreatedOn</td> <td>@context.CreatedOn</td>
</Row> </Row>
</Pager> </Pager>
</TabPanel> </TabPanel>
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings"> <TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="visitortracking" HelpText="Specify if visitor tracking is enabled" ResourceKey="VisitorTracking">Visitor Tracking Enabled? </Label> <Label Class="col-sm-3" For="tracking" HelpText="Specify if visitor tracking is enabled" ResourceKey="Tracking">Tracking Enabled? </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="visitortracking" class="form-select" @bind="@_visitortracking" > <select id="tracking" class="form-select" @bind="@_tracking" >
<option value="True">@SharedLocalizer["Yes"]</option> <option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option> <option value="False">@SharedLocalizer["No"]</option>
</select> </select>
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="filter" HelpText="Comma delimited list of terms which may exist in IP addresses, user agents, or languages identifying visitors which should not be tracked" ResourceKey="Filter">Filter: </Label>
<div class="col-sm-9">
<textarea id="filter" class="form-control" @bind="@_filter" rows="3"></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="retention" HelpText="Number of days of visitor activity to retain" ResourceKey="Retention">Retention (Days): </Label>
<div class="col-sm-9">
<input id="retention" class="form-control" @bind="@_retention" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="correlation" HelpText="Indicate if new visitors to this site should be correlated based on their IP Address" ResourceKey="Correlation">Correlate Visitors? </Label>
<div class="col-sm-9">
<select id="correlation" class="form-select" @bind="@_correlation">
<option value="true">@SharedLocalizer["True"]</option>
<option value="false">@SharedLocalizer["False"]</option>
</select>
</div>
</div>
</div> </div>
<br /> <br />
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
@ -76,24 +98,46 @@ else
} }
@code { @code {
private bool _users = false; private string _type = "visitors";
private int _days = 1; private int _days = 1;
private int _page = 1;
private List<Visitor> _visitors; private List<Visitor> _visitors;
private string _visitortracking; private string _tracking;
private string _filter = "";
private string _retention = "";
private string _correlation = "true";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
if (PageState.QueryString.ContainsKey("type"))
{
_type = PageState.QueryString["type"];
}
if (PageState.QueryString.ContainsKey("days") && int.TryParse(PageState.QueryString["days"], out int days))
{
_days = days;
}
if (PageState.QueryString.ContainsKey("page") && int.TryParse(PageState.QueryString["page"], out int page))
{
_page = page;
}
await GetVisitors(); await GetVisitors();
_visitortracking = PageState.Site.VisitorTracking.ToString();
_tracking = PageState.Site.VisitorTracking.ToString();
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
_filter = SettingService.GetSetting(settings, "VisitorFilter", Constants.DefaultVisitorFilter);
_retention = SettingService.GetSetting(settings, "VisitorRetention", "30");
_correlation = SettingService.GetSetting(settings, "VisitorCorrelation", "true");
} }
private async void TypeChanged(ChangeEventArgs e) private async void TypeChanged(ChangeEventArgs e)
{ {
try try
{ {
_users = bool.Parse(e.Value.ToString()); _type = e.Value.ToString();
await GetVisitors(); await GetVisitors();
StateHasChanged(); StateHasChanged();
} }
@ -103,7 +147,7 @@ else
} }
} }
private async void DateChanged(ChangeEventArgs e) private async void DaysChanged(ChangeEventArgs e)
{ {
try try
{ {
@ -120,19 +164,26 @@ else
private async Task GetVisitors() private async Task GetVisitors()
{ {
_visitors = await VisitorService.GetVisitorsAsync(PageState.Site.SiteId, DateTime.UtcNow.AddDays(-_days)); _visitors = await VisitorService.GetVisitorsAsync(PageState.Site.SiteId, DateTime.UtcNow.AddDays(-_days));
if (_users) if (_type == "users")
{ {
_visitors = _visitors.Where(item => item.UserId != null).ToList(); _visitors = _visitors.Where(item => item.UserId != null).ToList();
} }
} }
private async Task SaveSiteSettings() private async Task SaveSiteSettings()
{ {
try try
{ {
var site = PageState.Site; var site = PageState.Site;
site.VisitorTracking = bool.Parse(_visitortracking); site.VisitorTracking = bool.Parse(_tracking);
await SiteService.UpdateSiteAsync(site); await SiteService.UpdateSiteAsync(site);
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
settings = SettingService.SetSetting(settings, "VisitorFilter", _filter, true);
settings = SettingService.SetSetting(settings, "VisitorRetention", _retention, true);
settings = SettingService.SetSetting(settings, "VisitorCorrelation", _correlation, true);
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success); AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
} }
catch (Exception ex) catch (Exception ex)
@ -141,4 +192,9 @@ else
AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error); AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error);
} }
} }
private void OnPageChange(int page)
{
_page = page;
}
} }

View File

@ -25,8 +25,8 @@ else
[Parameter] [Parameter]
public string HelpText { get; set; } // optional - tooltip for this label public string HelpText { get; set; } // optional - tooltip for this label
private string _spanclass = "app-tooltip"; private string _spanclass;
private string _labelclass = "form-label"; private string _labelclass;
private string _helptext = string.Empty; private string _helptext = string.Empty;
protected override void OnParametersSet() protected override void OnParametersSet()
@ -36,11 +36,15 @@ else
if (!string.IsNullOrEmpty(HelpText)) if (!string.IsNullOrEmpty(HelpText))
{ {
_helptext = Localize(nameof(HelpText), HelpText); _helptext = Localize(nameof(HelpText), HelpText);
_spanclass += (!string.IsNullOrEmpty(Class)) ? " " + Class : ""; _labelclass = "form-label";
var spanclass = (!string.IsNullOrEmpty(Class)) ? " " + Class : "";
_spanclass = "app-tooltip" + spanclass;
} }
else else
{ {
_labelclass += (!string.IsNullOrEmpty(Class)) ? " " + Class : ""; var labelclass = (!string.IsNullOrEmpty(Class)) ? " " + Class : "";
_labelclass = "form-label" + labelclass;
} }
var text = Localize("Text", String.Empty); var text = Localize("Text", String.Empty);

View File

@ -13,6 +13,9 @@ namespace Oqtane.Modules.Controls
[Parameter] [Parameter]
public string ResourceKey { get; set; } public string ResourceKey { get; set; }
[Parameter]
public string ResourceType { get; set; }
protected bool IsLocalizable { get; private set; } protected bool IsLocalizable { get; private set; }
protected string Localize(string name) => _localizer?[name] ?? name; protected string Localize(string name) => _localizer?[name] ?? name;
@ -50,9 +53,14 @@ namespace Oqtane.Modules.Controls
{ {
IsLocalizable = false; IsLocalizable = false;
if (!String.IsNullOrEmpty(ResourceKey) && ModuleState?.ModuleType != null) if (string.IsNullOrEmpty(ResourceType))
{ {
var moduleType = Type.GetType(ModuleState.ModuleType); ResourceType = ModuleState?.ModuleType;
}
if (!String.IsNullOrEmpty(ResourceKey) && !string.IsNullOrEmpty(ResourceType))
{
var moduleType = Type.GetType(ResourceType);
if (moduleType != null) if (moduleType != null)
{ {
using (var scope = ServiceActivator.GetScope()) using (var scope = ServiceActivator.GetScope())

View File

@ -54,47 +54,58 @@
} }
@if (Format == "Table" && Row != null) @if (Format == "Table" && Row != null)
{ {
<table class="@Class"> <div class="table-responsive">
<thead> <table class="@Class">
<tr>@Header</tr> <thead>
</thead> <tr class="@RowClass">@Header</tr>
<tbody> </thead>
@foreach (var item in ItemList) <tbody>
{ @foreach (var item in ItemList)
<tr>@Row(item)</tr> {
@if (Detail != null) <tr class="@RowClass">@Row(item)</tr>
{ @if (Detail != null)
<tr>@Detail(item)</tr> {
} <tr>@Detail(item)</tr>
} }
</tbody> }
</table> </tbody>
</table>
</div>
} }
@if (Format == "Grid" && Row != null) @if (Format == "Grid" && Row != null)
{ {
int count = 0; int count = 0;
if (ItemList != null) int rows = 0;
int cols = 0;
if (ItemList != null)
{ {
count = (int)Math.Ceiling(ItemList.Count() / (decimal)_columns) * _columns; if (_columns == 0)
{
count = ItemList.Count();
rows = 1;
cols = count;
}
else
{
count = (int)Math.Ceiling(ItemList.Count() / (decimal)_columns) * _columns;
rows = count / _columns;
cols = _columns;
}
} }
<div class="@Class"> <div class="@Class">
@if (Header != null) @for (int row = 0; row < rows; row++)
{ {
<div class="row"><div class="col">@Header</div></div> <div class="@RowClass">
} @for (int col = 0; col < cols; col++)
@for (int row = 0; row < (count / _columns); row++)
{
<div class="row">
@for (int col = 0; col < _columns; col++)
{ {
int index = (row * _columns) + col; int index = (row * _columns) + col;
if (index < ItemList.Count()) if (index < ItemList.Count())
{ {
<div class="col">@Row(ItemList.ElementAt(index))</div> <div class="@ColumnClass">@Row(ItemList.ElementAt(index))</div>
} }
else else
{ {
<div class="col">&nbsp;</div> <div>&nbsp;</div>
} }
} }
</div> </div>
@ -107,8 +118,8 @@
<li class="page-item@((_page > 1) ? "" : " disabled")"> <li class="page-item@((_page > 1) ? "" : " disabled")">
<a class="page-link" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a> <a class="page-link" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
</li> </li>
@if (_pages > _displayPages) @if (_pages > _displayPages && _displayPages > 1)
{ {
<li class="page-item@((_page > _displayPages) ? "" : " disabled")"> <li class="page-item@((_page > _displayPages) ? "" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a> <a class="page-link" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
</li> </li>
@ -135,7 +146,7 @@
<li class="page-item@((_page < _pages) ? "" : " disabled")"> <li class="page-item@((_page < _pages) ? "" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a> <a class="page-link" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
</li> </li>
@if (_pages > _displayPages) @if (_pages > _displayPages && _displayPages > 1)
{ {
<li class="page-item@((_endPage < _pages) ? "" : " disabled")"> <li class="page-item@((_endPage < _pages) ? "" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a> <a class="page-link" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
@ -145,7 +156,7 @@
<a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a> <a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
</li> </li>
<li class="page-item disabled"> <li class="page-item disabled">
<a class="page-link">Page @_page of @_pages</a> <a class="page-link" style="white-space: nowrap;">Page @_page of @_pages</a>
</li> </li>
</ul> </ul>
} }
@ -158,7 +169,7 @@
private int _displayPages = 5; private int _displayPages = 5;
private int _startPage = 0; private int _startPage = 0;
private int _endPage = 0; private int _endPage = 0;
private int _columns = 1; private int _columns = 0;
[Parameter] [Parameter]
public string Format { get; set; } // Table or Grid public string Format { get; set; } // Table or Grid
@ -167,10 +178,10 @@
public string Toolbar { get; set; } // Top, Bottom or Both public string Toolbar { get; set; } // Top, Bottom or Both
[Parameter] [Parameter]
public RenderFragment Header { get; set; } = null; public RenderFragment Header { get; set; } = null; // only applicable to Table layouts
[Parameter] [Parameter]
public RenderFragment<TableItem> Row { get; set; } = null; public RenderFragment<TableItem> Row { get; set; } = null; // required
[Parameter] [Parameter]
public RenderFragment<TableItem> Detail { get; set; } = null; // only applicable to Table layouts public RenderFragment<TableItem> Detail { get; set; } = null; // only applicable to Table layouts
@ -182,16 +193,25 @@
public string PageSize { get; set; } // number of items to display on a page public string PageSize { get; set; } // number of items to display on a page
[Parameter] [Parameter]
public string Columns { get; set; } // only applicable to Grid layouts public string Columns { get; set; } // only applicable to Grid layouts - default is zero indicating use responsive behavior
[Parameter] [Parameter]
public string CurrentPage { get; set; } // optional property to set the initial page to display public string CurrentPage { get; set; } // sets the initial page to display
[Parameter] [Parameter]
public string DisplayPages { get; set; } // maximum number of page numbers to display for user selection public string DisplayPages { get; set; } // maximum number of page numbers to display for user selection
[Parameter] [Parameter]
public string Class { get; set; } public string Class { get; set; } // class for the containing element - ie. <table> for Table or <div> for Grid
[Parameter]
public string RowClass { get; set; } // class for row element - ie. <tr> for Table or <div> for Grid
[Parameter]
public string ColumnClass { get; set; } // class for column element - only applicable to Grid format
[Parameter]
public Action<int> OnPageChange { get; set; } // a method to be executed in the calling component when the page changes
private IEnumerable<TableItem> ItemList { get; set; } private IEnumerable<TableItem> ItemList { get; set; }
@ -215,11 +235,35 @@
} }
else else
{ {
Class = "container-fluid px-0"; Class = "container-fluid";
} }
} }
if (!string.IsNullOrEmpty(PageSize)) if (string.IsNullOrEmpty(RowClass))
{
if (Format == "Table")
{
RowClass = "";
}
else
{
RowClass = "row";
}
}
if (string.IsNullOrEmpty(ColumnClass))
{
if (Format == "Table")
{
ColumnClass = "";
}
else
{
ColumnClass = "col";
}
}
if (!string.IsNullOrEmpty(PageSize))
{ {
_maxItems = int.Parse(PageSize); _maxItems = int.Parse(PageSize);
} }
@ -266,6 +310,7 @@
{ {
_endPage = _pages; _endPage = _pages;
} }
OnPageChange?.Invoke(_page);
StateHasChanged(); StateHasChanged();
} }

View File

@ -100,160 +100,172 @@
} }
@code { @code {
private string _permissionnames = string.Empty; private string _permissionnames = string.Empty;
private List<Role> _roles; private List<Role> _roles;
private List<PermissionString> _permissions; private List<PermissionString> _permissions;
private List<User> _users = new List<User>(); private List<User> _users = new List<User>();
private string _username = string.Empty; private string _username = string.Empty;
private string _message = string.Empty; private string _message = string.Empty;
[Parameter] [Parameter]
public string EntityName { get; set; } public string EntityName { get; set; }
[Parameter] [Parameter]
public string PermissionNames { get; set; } public string PermissionNames { get; set; }
[Parameter] [Parameter]
public string Permissions { get; set; } public string Permissions { get; set; }
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
if (string.IsNullOrEmpty(PermissionNames)) if (string.IsNullOrEmpty(PermissionNames))
{ {
_permissionnames = Shared.PermissionNames.View + "," + Shared.PermissionNames.Edit; _permissionnames = Shared.PermissionNames.View + "," + Shared.PermissionNames.Edit;
} }
else else
{ {
_permissionnames = PermissionNames; _permissionnames = PermissionNames;
} }
_roles = await RoleService.GetRolesAsync(ModuleState.SiteId); _roles = await RoleService.GetRolesAsync(ModuleState.SiteId);
_roles.Insert(0, new Role { Name = RoleNames.Everyone }); _roles.Insert(0, new Role { Name = RoleNames.Everyone });
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_roles.Add(new Role { Name = RoleNames.Host });
}
_permissions = new List<PermissionString>(); _permissions = new List<PermissionString>();
foreach (string permissionname in _permissionnames.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) foreach (string permissionname in _permissionnames.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{ {
// initialize with admin role // initialize with admin role
_permissions.Add(new PermissionString { PermissionName = permissionname, Permissions = RoleNames.Admin }); _permissions.Add(new PermissionString { PermissionName = permissionname, Permissions = RoleNames.Admin });
} }
if (!string.IsNullOrEmpty(Permissions)) if (!string.IsNullOrEmpty(Permissions))
{ {
// populate permissions // populate permissions
foreach (PermissionString permissionstring in UserSecurity.GetPermissionStrings(Permissions)) foreach (PermissionString permissionstring in UserSecurity.GetPermissionStrings(Permissions))
{ {
if (_permissions.Find(item => item.PermissionName == permissionstring.PermissionName) != null) if (_permissions.Find(item => item.PermissionName == permissionstring.PermissionName) != null)
{ {
_permissions[_permissions.FindIndex(item => item.PermissionName == permissionstring.PermissionName)].Permissions = permissionstring.Permissions; _permissions[_permissions.FindIndex(item => item.PermissionName == permissionstring.PermissionName)].Permissions = permissionstring.Permissions;
} }
if (permissionstring.Permissions.Contains("[")) if (permissionstring.Permissions.Contains("["))
{ {
foreach (string user in permissionstring.Permissions.Split(new char[] { '[' }, StringSplitOptions.RemoveEmptyEntries)) foreach (string user in permissionstring.Permissions.Split(new char[] { '[' }, StringSplitOptions.RemoveEmptyEntries))
{ {
if (user.Contains("]")) if (user.Contains("]"))
{ {
var userid = int.Parse(user.Substring(0, user.IndexOf("]"))); var userid = int.Parse(user.Substring(0, user.IndexOf("]")));
if (_users.Where(item => item.UserId == userid).FirstOrDefault() == null) if (_users.Where(item => item.UserId == userid).FirstOrDefault() == null)
{ {
_users.Add(await UserService.GetUserAsync(userid, ModuleState.SiteId)); _users.Add(await UserService.GetUserAsync(userid, ModuleState.SiteId));
} }
} }
} }
} }
} }
} }
} }
private bool? GetPermissionValue(string permissions, string securityKey) private bool? GetPermissionValue(string permissions, string securityKey)
{ {
if ((";" + permissions + ";").Contains(";" + "!" + securityKey + ";")) if ((";" + permissions + ";").Contains(";" + "!" + securityKey + ";"))
{ {
return false; // deny permission return false; // deny permission
} }
else else
{ {
if ((";" + permissions + ";").Contains(";" + securityKey + ";")) if ((";" + permissions + ";").Contains(";" + securityKey + ";"))
{ {
return true; // grant permission return true; // grant permission
} }
else else
{ {
return null; // not specified return null; // not specified
} }
} }
} }
private bool GetPermissionDisabled(string roleName) private bool GetPermissionDisabled(string roleName)
=> roleName == RoleNames.Admin => (roleName == RoleNames.Admin && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) ? true : false;
? true
: false;
private async Task AddUser() private async Task AddUser()
{ {
if (_users.Where(item => item.Username == _username).FirstOrDefault() == null) if (_users.Where(item => item.Username == _username).FirstOrDefault() == null)
{ {
try try
{ {
var user = await UserService.GetUserAsync(_username, ModuleState.SiteId); var user = await UserService.GetUserAsync(_username, ModuleState.SiteId);
if (user != null) if (user != null)
{ {
_users.Add(user); _users.Add(user);
} }
} }
catch catch
{ {
_message = Localizer["Message.Username.DontExist"]; _message = Localizer["Message.Username.DontExist"];
} }
} }
_username = string.Empty; _username = string.Empty;
} }
private void PermissionChanged(bool? value, string permissionName, string securityId) private void PermissionChanged(bool? value, string permissionName, string securityId)
{ {
var selected = value; var selected = value;
var permission = _permissions.Find(item => item.PermissionName == permissionName); var permission = _permissions.Find(item => item.PermissionName == permissionName);
if (permission != null) if (permission != null)
{ {
var ids = permission.Permissions.Split(';').ToList(); var ids = permission.Permissions.Split(';').ToList();
ids.Remove(securityId); // remove grant permission ids.Remove(securityId); // remove grant permission
ids.Remove("!" + securityId); // remove deny permission ids.Remove("!" + securityId); // remove deny permission
switch (selected) switch (selected)
{ {
case true: case true:
ids.Add(securityId); // add grant permission ids.Add(securityId); // add grant permission
break; break;
case false: case false:
ids.Add("!" + securityId); // add deny permission ids.Add("!" + securityId); // add deny permission
break; break;
case null: case null:
break; // permission not specified break; // permission not specified
} }
_permissions[_permissions.FindIndex(item => item.PermissionName == permissionName)].Permissions = string.Join(";", ids.ToArray()); _permissions[_permissions.FindIndex(item => item.PermissionName == permissionName)].Permissions = string.Join(";", ids.ToArray());
} }
} }
public string GetPermissions() public string GetPermissions()
{ {
ValidatePermissions(); ValidatePermissions();
return UserSecurity.SetPermissionStrings(_permissions); return UserSecurity.SetPermissionStrings(_permissions);
} }
private void ValidatePermissions() private void ValidatePermissions()
{ {
PermissionString permission; PermissionString permission;
for (int i = 0; i < _permissions.Count; i++) for (int i = 0; i < _permissions.Count; i++)
{ {
permission = _permissions[i]; permission = _permissions[i];
List<string> ids = permission.Permissions.Split(';').ToList(); List<string> ids = permission.Permissions.Split(';').ToList();
ids.Remove("!" + RoleNames.Everyone); // remove deny all users ids.Remove("!" + RoleNames.Everyone); // remove deny all users
ids.Remove("!" + RoleNames.Registered); // remove deny registered users ids.Remove("!" + RoleNames.Registered); // remove deny registered users
permission.Permissions = string.Join(";", ids.ToArray()); if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
ids.Remove("!" + RoleNames.Admin); // remove deny administrators
ids.Remove("!" + RoleNames.Host); // remove deny host users
if (!ids.Contains(RoleNames.Host) && !ids.Contains(RoleNames.Admin))
{
// add administrators role if host user role is not assigned
ids.Add(RoleNames.Admin);
}
}
permission.Permissions = string.Join(";", ids.ToArray());
_permissions[i] = permission; _permissions[i] = permission;
} }
} }

View File

@ -71,11 +71,11 @@
</div> </div>
@if (ReadOnly) @if (ReadOnly)
{ {
<textarea class="form-control" placeholder="@Placeholder" @bind="@_content" rows="10" readonly></textarea> <textarea class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10" readonly></textarea>
} }
else else
{ {
<textarea class="form-control" placeholder="@Placeholder" @bind="@_content" rows="10"></textarea> <textarea class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10"></textarea>
} }
</TabPanel> </TabPanel>
</TabStrip> </TabStrip>
@ -83,110 +83,121 @@
</div> </div>
@code { @code {
private ElementReference _editorElement; private ElementReference _editorElement;
private ElementReference _toolBar; private ElementReference _toolBar;
private bool _filemanagervisible = false; private bool _filemanagervisible = false;
private FileManager _fileManager; private FileManager _fileManager;
private string _content = string.Empty; private string _richhtml = string.Empty;
private string _original = string.Empty; private string _originalrichhtml = string.Empty;
private string _message = string.Empty; private string _rawhtml = string.Empty;
private string _originalrawhtml = string.Empty;
private string _message = string.Empty;
[Parameter] [Parameter]
public string Content { get; set; } public string Content { get; set; }
[Parameter] [Parameter]
public bool ReadOnly { get; set; } = false; public bool ReadOnly { get; set; } = false;
[Parameter] [Parameter]
public string Placeholder { get; set; } = "Enter Your Content..."; public string Placeholder { get; set; } = "Enter Your Content...";
// parameters only applicable to rich text editor // parameters only applicable to rich text editor
[Parameter] [Parameter]
public RenderFragment ToolbarContent { get; set; } public RenderFragment ToolbarContent { get; set; }
[Parameter] [Parameter]
public string Theme { get; set; } = "snow"; public string Theme { get; set; } = "snow";
[Parameter] [Parameter]
public string DebugLevel { get; set; } = "info"; public string DebugLevel { get; set; } = "info";
[Parameter] [Parameter]
public bool AllowFileManagement { get; set; } = true; public bool AllowFileManagement { get; set; } = true;
public override List<Resource> Resources => new List<Resource>() public override List<Resource> Resources => new List<Resource>()
{ {
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill1.3.7.min.js" }, new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill.min.js" },
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-blot-formatter.min.js" }, new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-blot-formatter.min.js" },
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-interop.js" } new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-interop.js" }
}; };
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
_content = Content; // raw HTML _richhtml = Content;
} _rawhtml = Content;
_originalrawhtml = _rawhtml; // preserve for comparison later
}
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
var interop = new RichTextEditorInterop(JSRuntime); await base.OnAfterRenderAsync(firstRender);
if (firstRender)
{
await base.OnAfterRenderAsync(firstRender);
await interop.CreateEditor( var interop = new RichTextEditorInterop(JSRuntime);
_editorElement,
_toolBar,
ReadOnly,
Placeholder,
Theme,
DebugLevel);
}
await interop.LoadEditorContent(_editorElement, Content); if (firstRender)
{
await interop.CreateEditor(
_editorElement,
_toolBar,
ReadOnly,
Placeholder,
Theme,
DebugLevel);
_content = Content; // raw HTML await interop.LoadEditorContent(_editorElement, _richhtml);
// preserve a copy of the rich text content ( Quill sanitizes content so we need to retrieve it from the editor ) // preserve a copy of the rich text content (Quill sanitizes content so we need to retrieve it from the editor)
_original = await interop.GetHtml(_editorElement); _originalrichhtml = await interop.GetHtml(_editorElement);
}
else
{
await interop.LoadEditorContent(_editorElement, _richhtml);
}
}
} public void CloseFileManager()
{
_filemanagervisible = false;
_message = string.Empty;
StateHasChanged();
}
public void CloseFileManager() public void RefreshRichText()
{ {
_filemanagervisible = false; _richhtml = _rawhtml;
_message = string.Empty; StateHasChanged();
StateHasChanged(); }
}
public async Task RefreshRichText() public async Task RefreshRawHtml()
{ {
var interop = new RichTextEditorInterop(JSRuntime); var interop = new RichTextEditorInterop(JSRuntime);
await interop.LoadEditorContent(_editorElement, _content); _rawhtml = await interop.GetHtml(_editorElement);
} StateHasChanged();
}
public async Task RefreshRawHtml() public async Task<string> GetHtml()
{ {
var interop = new RichTextEditorInterop(JSRuntime); // evaluate raw html content as first priority
_content = await interop.GetHtml(_editorElement); if (_rawhtml != _originalrawhtml)
StateHasChanged(); {
} return _rawhtml;
}
public async Task<string> GetHtml() else
{ {
// get rich text content // return rich text content if it has changed
var interop = new RichTextEditorInterop(JSRuntime); var interop = new RichTextEditorInterop(JSRuntime);
string content = await interop.GetHtml(_editorElement); var richhtml = await interop.GetHtml(_editorElement);
if (richhtml != _originalrichhtml)
if (_original != content) {
{ return richhtml;
// rich text content has changed - return it }
return content; else
} {
else // return original raw html content
{ return _originalrawhtml;
// return raw html content }
return _content; }
}
} }
public async Task InsertImage() public async Task InsertImage()
@ -212,23 +223,4 @@
} }
StateHasChanged(); StateHasChanged();
} }
// other rich text editor methods which can be used by developers
public async Task<string> GetText()
{
var interop = new RichTextEditorInterop(JSRuntime);
return await interop.GetText(_editorElement);
}
public async Task<string> GetContent()
{
var interop = new RichTextEditorInterop(JSRuntime);
return await interop.GetContent(_editorElement);
}
public async Task EnableEditor(bool mode)
{
var interop = new RichTextEditorInterop(JSRuntime);
await interop.EnableEditor(_editorElement, mode);
}
} }

View File

@ -9,95 +9,186 @@
@inject IStringLocalizer<Edit> Localizer @inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_content != null) <TabStrip>
{ <TabPanel Name="Edit" Heading="Edit" ResourceKey="Edit">
<RichTextEditor Content="@_content" AllowFileManagement="@_allowfilemanagement" @ref="@RichTextEditorHtml"></RichTextEditor> @if (_content != null)
<button type="button" class="btn btn-success" @onclick="SaveContent">@SharedLocalizer["Save"]</button> {
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <RichTextEditor Content="@_content" AllowFileManagement="@_allowfilemanagement" @ref="@RichTextEditorHtml"></RichTextEditor>
@if (!string.IsNullOrEmpty(_content)) <br />
{ <button type="button" class="btn btn-success" @onclick="SaveContent">@SharedLocalizer["Save"]</button>
<br /> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<br /> @if (!string.IsNullOrEmpty(_content))
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo> {
} <br />
} <br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
}
}
</TabPanel>
<TabPanel Name="Versions" Heading="Versions" ResourceKey="Versions">
<Pager Items="@_htmltexts">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["CreatedOn"]</th>
<th>@SharedLocalizer["CreatedBy"]</th>
</Header>
<Row>
<td><ActionLink Action="View" Security="SecurityAccessLevel.Edit" OnClick="@(async () => await View(context))" ResourceKey="View" /></td>
<td><ActionDialog Header="Restore Version" Message="@string.Format(Localizer["Confirm.Restore"], context.CreatedOn)" Action="Restore" Security="SecurityAccessLevel.Edit" Class="btn btn-success" OnClick="@(async () => await Restore(context))" ResourceKey="Restore" /></td>
<td><ActionDialog Header="Delete Version" Message="@string.Format(Localizer["Confirm.Delete"], context.CreatedOn)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" ResourceKey="Delete" /></td>
<td>@context.CreatedOn</td>
<td>@context.CreatedBy</td>
</Row>
</Pager>
@((MarkupString)_view)
</TabPanel>
</TabStrip>
@code { @code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Title => "Edit Html/Text"; public override string Title => "Edit Html/Text";
public override List<Resource> Resources => new List<Resource>() public override List<Resource> Resources => new List<Resource>()
{ {
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }, new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill1.3.7.bubble.css" }, new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill.bubble.css" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill1.3.7.snow.css" } new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill.snow.css" }
}; };
private RichTextEditor RichTextEditorHtml; private RichTextEditor RichTextEditorHtml;
private bool _allowfilemanagement; private bool _allowfilemanagement;
private string _content = null; private string _content = null;
private string _createdby; private string _createdby;
private DateTime _createdon; private DateTime _createdon;
private string _modifiedby; private string _modifiedby;
private DateTime _modifiedon; private DateTime _modifiedon;
private List<Models.HtmlText> _htmltexts;
private string _view = "";
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
try try
{ {
_allowfilemanagement = bool.Parse(SettingService.GetSetting(ModuleState.Settings, "AllowFileManagement", "true")); _allowfilemanagement = bool.Parse(SettingService.GetSetting(ModuleState.Settings, "AllowFileManagement", "true"));
await LoadContent();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Content {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Content.Load"], MessageType.Error);
}
}
var htmltext = await HtmlTextService.GetHtmlTextAsync(ModuleState.ModuleId); private async Task LoadContent()
if (htmltext != null) {
{ var htmltext = await HtmlTextService.GetHtmlTextAsync(ModuleState.ModuleId);
_content = htmltext.Content; if (htmltext != null)
_content = Utilities.FormatContent(_content, PageState.Alias, "render"); {
_createdby = htmltext.CreatedBy; _content = htmltext.Content;
_createdon = htmltext.CreatedOn; _content = Utilities.FormatContent(_content, PageState.Alias, "render");
_modifiedby = htmltext.ModifiedBy; _createdby = htmltext.CreatedBy;
_modifiedon = htmltext.ModifiedOn; _createdon = htmltext.CreatedOn;
} _modifiedby = htmltext.ModifiedBy;
else _modifiedon = htmltext.ModifiedOn;
{ }
_content = string.Empty; else
} {
} _content = string.Empty;
catch (Exception ex) }
{
await logger.LogError(ex, "Error Loading Content {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Content.Load"], MessageType.Error);
}
}
private async Task SaveContent() _htmltexts = await HtmlTextService.GetHtmlTextsAsync(ModuleState.ModuleId);
{ _htmltexts = _htmltexts.OrderByDescending(item => item.CreatedOn).ToList();
string content = await RichTextEditorHtml.GetHtml();
content = Utilities.FormatContent(content, PageState.Alias, "save");
try _view = "";
{ }
var htmltext = await HtmlTextService.GetHtmlTextAsync(ModuleState.ModuleId);
if (htmltext != null)
{
htmltext.Content = content;
await HtmlTextService.UpdateHtmlTextAsync(htmltext);
}
else
{
htmltext = new HtmlText();
htmltext.ModuleId = ModuleState.ModuleId;
htmltext.Content = content;
await HtmlTextService.AddHtmlTextAsync(htmltext);
}
await logger.LogInformation("Content Saved {HtmlText}", htmltext); private async Task SaveContent()
NavigationManager.NavigateTo(NavigateUrl()); {
} string content = await RichTextEditorHtml.GetHtml();
catch (Exception ex) content = Utilities.FormatContent(content, PageState.Alias, "save");
{
await logger.LogError(ex, "Error Saving Content {Error}", ex.Message); try
AddModuleMessage(Localizer["Error.Content.Save"], MessageType.Error); {
} var htmltext = new HtmlText();
} htmltext.ModuleId = ModuleState.ModuleId;
htmltext.Content = content;
await HtmlTextService.AddHtmlTextAsync(htmltext);
await logger.LogInformation("Content Saved {HtmlText}", htmltext);
NavigationManager.NavigateTo(NavigateUrl());
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Content {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Content.Save"], MessageType.Error);
}
}
private async Task View(Models.HtmlText htmltext)
{
try
{
htmltext = await HtmlTextService.GetHtmlTextAsync(htmltext.HtmlTextId, htmltext.ModuleId);
if (htmltext != null)
{
_view = htmltext.Content;
_view = Utilities.FormatContent(_view, PageState.Alias, "render");
StateHasChanged();
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Viewing Content {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Content.View"], MessageType.Error);
}
}
private async Task Restore(Models.HtmlText htmltext)
{
try
{
htmltext = await HtmlTextService.GetHtmlTextAsync(htmltext.HtmlTextId, ModuleState.ModuleId);
if (htmltext != null)
{
var content = htmltext.Content;
htmltext = new HtmlText();
htmltext.ModuleId = ModuleState.ModuleId;
htmltext.Content = content;
await HtmlTextService.AddHtmlTextAsync(htmltext);
await logger.LogInformation("Content Restored {HtmlText}", htmltext);
AddModuleMessage(Localizer["Message.Content.Restored"], MessageType.Success);
await LoadContent();
StateHasChanged();
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Restoring Content {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Content.Restore"], MessageType.Error);
}
}
private async Task Delete(Models.HtmlText htmltext)
{
try
{
htmltext = await HtmlTextService.GetHtmlTextAsync(htmltext.HtmlTextId, ModuleState.ModuleId);
if (htmltext != null)
{
await HtmlTextService.DeleteHtmlTextAsync(htmltext.HtmlTextId, htmltext.ModuleId);
await logger.LogInformation("Content Deleted {HtmlText}", htmltext);
AddModuleMessage(Localizer["Message.Content.Deleted"], MessageType.Success);
await LoadContent();
StateHasChanged();
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting Content {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Content.Delete"], MessageType.Error);
}
}
} }

View File

@ -1,3 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Oqtane.Documentation; using Oqtane.Documentation;
@ -13,24 +15,29 @@ namespace Oqtane.Modules.HtmlText.Services
private string ApiUrl => CreateApiUrl("HtmlText"); private string ApiUrl => CreateApiUrl("HtmlText");
public async Task<List<Models.HtmlText>> GetHtmlTextsAsync(int moduleId)
{
return await GetJsonAsync<List<Models.HtmlText>>(CreateAuthorizationPolicyUrl($"{ApiUrl}?moduleid={moduleId}", EntityNames.Module, moduleId));
}
public async Task<Models.HtmlText> GetHtmlTextAsync(int moduleId) public async Task<Models.HtmlText> GetHtmlTextAsync(int moduleId)
{ {
return await GetJsonAsync<Models.HtmlText>(CreateAuthorizationPolicyUrl($"{ApiUrl}/{moduleId}", EntityNames.Module, moduleId)); return await GetJsonAsync<Models.HtmlText>(CreateAuthorizationPolicyUrl($"{ApiUrl}/{moduleId}", EntityNames.Module, moduleId));
} }
public async Task<Models.HtmlText> GetHtmlTextAsync(int htmlTextId, int moduleId)
{
return await GetJsonAsync<Models.HtmlText>(CreateAuthorizationPolicyUrl($"{ApiUrl}/{htmlTextId}/{moduleId}", EntityNames.Module, moduleId));
}
public async Task AddHtmlTextAsync(Models.HtmlText htmlText) public async Task AddHtmlTextAsync(Models.HtmlText htmlText)
{ {
await PostJsonAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}", EntityNames.Module, htmlText.ModuleId), htmlText); await PostJsonAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}", EntityNames.Module, htmlText.ModuleId), htmlText);
} }
public async Task UpdateHtmlTextAsync(Models.HtmlText htmlText) public async Task DeleteHtmlTextAsync(int htmlTextId, int moduleId)
{ {
await PutJsonAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}/{htmlText.HtmlTextId}", EntityNames.Module, htmlText.ModuleId), htmlText); await DeleteAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}/{htmlTextId}/{moduleId}", EntityNames.Module, moduleId));
}
public async Task DeleteHtmlTextAsync(int moduleId)
{
await DeleteAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}/{moduleId}", EntityNames.Module, moduleId));
} }
} }
} }

View File

@ -1,19 +1,20 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Oqtane.Documentation; using Oqtane.Documentation;
using Oqtane.Modules.HtmlText.Models;
namespace Oqtane.Modules.HtmlText.Services namespace Oqtane.Modules.HtmlText.Services
{ {
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")] [PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
public interface IHtmlTextService public interface IHtmlTextService
{ {
Task<Models.HtmlText> GetHtmlTextAsync(int ModuleId); Task<List<Models.HtmlText>> GetHtmlTextsAsync(int moduleId);
Task<Models.HtmlText> GetHtmlTextAsync(int moduleId);
Task<Models.HtmlText> GetHtmlTextAsync(int htmlTextId, int moduleId);
Task AddHtmlTextAsync(Models.HtmlText htmltext); Task AddHtmlTextAsync(Models.HtmlText htmltext);
Task UpdateHtmlTextAsync(Models.HtmlText htmltext); Task DeleteHtmlTextAsync(int htmlTextId, int moduleId);
Task DeleteHtmlTextAsync(int ModuleId);
} }
} }

View File

@ -5,44 +5,45 @@
@inject IStringLocalizer<Settings> Localizer @inject IStringLocalizer<Settings> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="files" ResourceKey="Allow File Management" HelpText="Specify If Editors Can Upload and Select Files">Allow File Management: </Label> <Label Class="col-sm-3" For="files" ResourceKey="AllowFileManagement" ResourceType="@resourceType" HelpText="Specify If Editors Can Upload and Select Files">Allow File Management: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="files" class="form-select" @bind="@_allowfilemanagement"> <select id="files" class="form-select" @bind="@_allowfilemanagement">
<option value="true">@SharedLocalizer["Yes"]</option> <option value="true">@SharedLocalizer["Yes"]</option>
<option value="false">@SharedLocalizer["No"]</option> <option value="false">@SharedLocalizer["No"]</option>
</select> </select>
</div>
</div> </div>
</div> </div>
</div>
@code { @code {
private string _allowfilemanagement; private string resourceType = "Oqtane.Modules.HtmlText.Settings, Oqtane.Client"; // for localization
private string _allowfilemanagement;
protected override void OnInitialized() protected override void OnInitialized()
{
try
{ {
try _allowfilemanagement = SettingService.GetSetting(ModuleState.Settings, "AllowFileManagement", "true");
{
_allowfilemanagement = SettingService.GetSetting(ModuleState.Settings, "AllowFileManagement", "true");
}
catch (Exception ex)
{
ModuleInstance.AddModuleMessage(ex.Message, MessageType.Error);
}
} }
catch (Exception ex)
public async Task UpdateSettings()
{ {
try ModuleInstance.AddModuleMessage(ex.Message, MessageType.Error);
{
var settings = ModuleState.Settings;
settings = SettingService.SetSetting(settings, "AllowFileManagement", _allowfilemanagement);
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
}
catch (Exception ex)
{
ModuleInstance.AddModuleMessage(ex.Message, MessageType.Error);
}
} }
} }
public async Task UpdateSettings()
{
try
{
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
settings = SettingService.SetSetting(settings, "AllowFileManagement", _allowfilemanagement);
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
}
catch (Exception ex)
{
ModuleInstance.AddModuleMessage(ex.Message, MessageType.Error);
}
}
}

View File

@ -134,14 +134,19 @@ namespace Oqtane.Modules
return Utilities.ContentUrl(PageState.Alias, fileid, asAttachment); return Utilities.ContentUrl(PageState.Alias, fileid, asAttachment);
} }
public string ImageUrl(int fileid, int width, int height, string mode) public string ImageUrl(int fileid, int width, int height)
{ {
return ImageUrl(fileid, width, height, mode, 0); return ImageUrl(fileid, width, height, "");
} }
public string ImageUrl(int fileid, int width, int height, string mode, int rotate) public string ImageUrl(int fileid, int width, int height, string mode)
{ {
return Utilities.ImageUrl(PageState.Alias, fileid, width, height, mode, rotate); return ImageUrl(fileid, width, height, mode, "", "", 0, false);
}
public string ImageUrl(int fileid, int width, int height, string mode, string position, string background, int rotate, bool recreate)
{
return Utilities.ImageUrl(PageState.Alias, fileid, width, height, mode, position, background, rotate, recreate);
} }
public virtual Dictionary<string, string> GetUrlParameters(string parametersTemplate = "") public virtual Dictionary<string, string> GetUrlParameters(string parametersTemplate = "")

View File

@ -5,7 +5,7 @@
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<RazorLangVersion>3.0</RazorLangVersion> <RazorLangVersion>3.0</RazorLangVersion>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Version>3.0.1</Version> <Version>3.0.3</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -13,7 +13,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/v3.0.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.3</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

@ -186,4 +186,10 @@
<data name="NextExecution.Text" xml:space="preserve"> <data name="NextExecution.Text" xml:space="preserve">
<value>Next Execution: </value> <value>Next Execution: </value>
</data> </data>
<data name="Week(s)" xml:space="preserve">
<value>Week(s)</value>
</data>
<data name="Once" xml:space="preserve">
<value>Execute Once</value>
</data>
</root> </root>

View File

@ -133,19 +133,16 @@
<value>Every</value> <value>Every</value>
</data> </data>
<data name="Minute" xml:space="preserve"> <data name="Minute" xml:space="preserve">
<value>Minute</value> <value>Minute(s)</value>
</data> </data>
<data name="Hour" xml:space="preserve"> <data name="Hour" xml:space="preserve">
<value>Hour</value> <value>Hour(s)</value>
</data> </data>
<data name="Day" xml:space="preserve"> <data name="Day" xml:space="preserve">
<value>Day</value> <value>Day(s)</value>
</data> </data>
<data name="Month" xml:space="preserve"> <data name="Month" xml:space="preserve">
<value>Month</value> <value>Month(s)</value>
</data>
<data name="s" xml:space="preserve">
<value>s</value>
</data> </data>
<data name="Error.Job.Delete" xml:space="preserve"> <data name="Error.Job.Delete" xml:space="preserve">
<value>Error Deleting Job</value> <value>Error Deleting Job</value>
@ -177,4 +174,22 @@
<data name="JobLog.Text" xml:space="preserve"> <data name="JobLog.Text" xml:space="preserve">
<value>Log</value> <value>Log</value>
</data> </data>
<data name="Error.Job.Start" xml:space="preserve">
<value>An error occurred when starting the job</value>
</data>
<data name="Error.Job.Stop" xml:space="preserve">
<value>An error occurred when stopping the job</value>
</data>
<data name="Message.Job.Start" xml:space="preserve">
<value>The process responsible for executing this job has been started. The next execution will be based on the schedule criteria for the job.</value>
</data>
<data name="Message.Job.Stop" xml:space="preserve">
<value>The process responsible for executing this job has been stopped. In order to restart the process you will need to use the Start button or restart the application.</value>
</data>
<data name="Week" xml:space="preserve">
<value>Week(s)</value>
</data>
<data name="Once" xml:space="preserve">
<value>Execute Once</value>
</data>
</root> </root>

View File

@ -138,4 +138,13 @@
<data name="Info.SignedIn" xml:space="preserve"> <data name="Info.SignedIn" xml:space="preserve">
<value>You Are Already Signed In</value> <value>You Are Already Signed In</value>
</data> </data>
<data name="Message.ForgotPassword" xml:space="preserve">
<value>Please Enter The Username Related To Your Account And Then Select The Forgot Password Option Again</value>
</data>
<data name="Message.ForgotUser" xml:space="preserve">
<value>Please Check The Email Address Associated To Your User Account For A Password Reset Notification</value>
</data>
<data name="Message.UserDoesNotExist" xml:space="preserve">
<value>User Does Not Exist</value>
</data>
</root> </root>

View File

@ -192,4 +192,22 @@
<data name="LogDetails.Text" xml:space="preserve"> <data name="LogDetails.Text" xml:space="preserve">
<value>Details</value> <value>Details</value>
</data> </data>
<data name="Error.SaveSiteSettings" xml:space="preserve">
<value>Error Saving Settings</value>
</data>
<data name="Events.Heading" xml:space="preserve">
<value>Events</value>
</data>
<data name="Retention.HelpText" xml:space="preserve">
<value>Number of days of events to retain</value>
</data>
<data name="Retention.Text" xml:space="preserve">
<value>Retention (Days):</value>
</data>
<data name="Settings.Heading" xml:space="preserve">
<value>Settings</value>
</data>
<data name="Success.SaveSiteSettings" xml:space="preserve">
<value>Settings Saved Successfully</value>
</data>
</root> </root>

View File

@ -231,4 +231,10 @@
<data name="Message.Page.Deleted" xml:space="preserve"> <data name="Message.Page.Deleted" xml:space="preserve">
<value>A page with path {0} already exists for the selected parent page in the Recycle Bin. Either recover the page or remove from the Recycle Bin and create it again.</value> <value>A page with path {0} already exists for the selected parent page in the Recycle Bin. Either recover the page or remove from the Recycle Bin and create it again.</value>
</data> </data>
<data name="Meta.HelpText" xml:space="preserve">
<value>Optionally enter meta tags (in exactly the form you want them to be included in the page output).</value>
</data>
<data name="Meta.Text" xml:space="preserve">
<value>Meta:</value>
</data>
</root> </root>

View File

@ -258,13 +258,16 @@
<data name="ThemeSettings.Heading" xml:space="preserve"> <data name="ThemeSettings.Heading" xml:space="preserve">
<value>Theme Settings</value> <value>Theme Settings</value>
</data> </data>
<data name="Appearance.Hea" xml:space="preserve">
<value />
</data>
<data name="Clickable.HelpText" xml:space="preserve"> <data name="Clickable.HelpText" xml:space="preserve">
<value>Select whether the link in the site navigation is enabled or disabled</value> <value>Select whether the link in the site navigation is enabled or disabled</value>
</data> </data>
<data name="Clickable.Text" xml:space="preserve"> <data name="Clickable.Text" xml:space="preserve">
<value>Clickable?</value> <value>Clickable?</value>
</data> </data>
<data name="Meta.HelpText" xml:space="preserve">
<value>Optionally enter meta tags (in exactly the form you want them to be included in the page output).</value>
</data>
<data name="Meta.Text" xml:space="preserve">
<value>Meta:</value>
</data>
</root> </root>

View File

@ -117,22 +117,37 @@
<resheader name="writer"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="Password.Confirm" xml:space="preserve">
<value>Confirm Password:</value>
</data>
<data name="Message.Password.NoMatch" xml:space="preserve"> <data name="Message.Password.NoMatch" xml:space="preserve">
<value>Passwords Entered Do Not Match</value> <value>Passwords Entered Do Not Match</value>
</data> </data>
<data name="Message.Required.UserInfo" xml:space="preserve"> <data name="Message.Required.UserInfo" xml:space="preserve">
<value>You Must Provide A Username, Password, and Email Address</value> <value>You Must Provide The Password And Confirmation</value>
</data> </data>
<data name="Password.Reset" xml:space="preserve"> <data name="Password.Reset" xml:space="preserve">
<value>Reset Password</value> <value>Reset Password</value>
</data> </data>
<data name="Error.Password.ResetInfo" xml:space="preserve"> <data name="Error.Password.ResetInfo" xml:space="preserve">
<value>Error Resetting User Password. Please Ensure Password Meets Complexity Requirements.</value> <value>Error Resetting User Password. Please Ensure The Request To Reset Your Password Was Made Within The Past 24 Hours And The New Password Meets The Complexity Requirements.</value>
</data> </data>
<data name="Error.Password.Reset" xml:space="preserve"> <data name="Error.Password.Reset" xml:space="preserve">
<value>Error Resetting User Password</value> <value>Error Resetting User Password</value>
</data> </data>
<data name="Confirm.Text" xml:space="preserve">
<value>Confirm:</value>
</data>
<data name="Conform.HelpText" xml:space="preserve">
<value>Enter the password again. It must exactly match the password entered above.</value>
</data>
<data name="Password.HelpText" xml:space="preserve">
<value>The new password. It must satisfy complexity rules for the site.</value>
</data>
<data name="Password.Text" xml:space="preserve">
<value>Password:</value>
</data>
<data name="Username.HelpText" xml:space="preserve">
<value>Your username will be populated from the link you received in the password reset notification</value>
</data>
<data name="Username.Text" xml:space="preserve">
<value>Username:</value>
</data>
</root> </root>

View File

@ -129,9 +129,6 @@
<data name="DefaultContainer.Text" xml:space="preserve"> <data name="DefaultContainer.Text" xml:space="preserve">
<value>Default Container: </value> <value>Default Container: </value>
</data> </data>
<data name="Appearance.Heading" xml:space="preserve">
<value>Appearance</value>
</data>
<data name="DefaultAdminContainer" xml:space="preserve"> <data name="DefaultAdminContainer" xml:space="preserve">
<value>Default Admin Container</value> <value>Default Admin Container</value>
</data> </data>
@ -145,7 +142,7 @@
<value>Site Settings Saved</value> <value>Site Settings Saved</value>
</data> </data>
<data name="Message.Aliases.Taken" xml:space="preserve"> <data name="Message.Aliases.Taken" xml:space="preserve">
<value>An Alias Specified Has Already Been Used For Another Site</value> <value>The Default Alias Has Not Been Specified Or An Alias Was Specified That Has Already Been Used For Another Site</value>
</data> </data>
<data name="Message.Required.SiteName" xml:space="preserve"> <data name="Message.Required.SiteName" xml:space="preserve">
<value>You Must Provide A Site Name, Alias, And Default Theme/Container</value> <value>You Must Provide A Site Name, Alias, And Default Theme/Container</value>
@ -223,7 +220,7 @@
<value>Aliases: </value> <value>Aliases: </value>
</data> </data>
<data name="IsDeleted.Text" xml:space="preserve"> <data name="IsDeleted.Text" xml:space="preserve">
<value>Is Deleted? </value> <value>Deleted? </value>
</data> </data>
<data name="Logo.Text" xml:space="preserve"> <data name="Logo.Text" xml:space="preserve">
<value>Logo: </value> <value>Logo: </value>
@ -318,4 +315,13 @@
<data name="DeleteSite.Text" xml:space="preserve"> <data name="DeleteSite.Text" xml:space="preserve">
<value>Delete Site</value> <value>Delete Site</value>
</data> </data>
<data name="DefaultAlias.HelpText" xml:space="preserve">
<value>The default alias for the site. Requests for non-default aliases will be redirected to the default alias.</value>
</data>
<data name="DefaultAlias.Text" xml:space="preserve">
<value>Default Alias: </value>
</data>
<data name="Aliases.Heading" xml:space="preserve">
<value>Aliases</value>
</data>
</root> </root>

View File

@ -133,7 +133,7 @@
<value>Server Path</value> <value>Server Path</value>
</data> </data>
<data name="ServerTime.HelpText" xml:space="preserve"> <data name="ServerTime.HelpText" xml:space="preserve">
<value>Server Time</value> <value>Server Date/Time (in UTC)</value>
</data> </data>
<data name="FrameworkVersion.Text" xml:space="preserve"> <data name="FrameworkVersion.Text" xml:space="preserve">
<value>Framework Version: </value> <value>Framework Version: </value>
@ -148,7 +148,7 @@
<value>Server Path: </value> <value>Server Path: </value>
</data> </data>
<data name="ServerTime.Text" xml:space="preserve"> <data name="ServerTime.Text" xml:space="preserve">
<value>Server Time: </value> <value>Server Date/Time: </value>
</data> </data>
<data name="RestartApplication.Header" xml:space="preserve"> <data name="RestartApplication.Header" xml:space="preserve">
<value>Restart Application</value> <value>Restart Application</value>

View File

@ -121,10 +121,10 @@
<value>Redirect To:</value> <value>Redirect To:</value>
</data> </data>
<data name="MappedUrl.HelpText" xml:space="preserve"> <data name="MappedUrl.HelpText" xml:space="preserve">
<value>A fully qualified Url where the user will be redirected</value> <value>A relative or absolute Url where the user will be redirected</value>
</data> </data>
<data name="Url.HelpText" xml:space="preserve"> <data name="Url.HelpText" xml:space="preserve">
<value>A fully qualified Url for this site</value> <value>An absolute Url identifying a path to a specific page in the site</value>
</data> </data>
<data name="Url.Text" xml:space="preserve"> <data name="Url.Text" xml:space="preserve">
<value>Url:</value> <value>Url:</value>
@ -135,4 +135,10 @@
<data name="Message.InfoRequired" xml:space="preserve"> <data name="Message.InfoRequired" xml:space="preserve">
<value>Please Provide All Required Information</value> <value>Please Provide All Required Information</value>
</data> </data>
<data name="Message.DuplicateUrlMapping" xml:space="preserve">
<value>The Url and Redirect To cannot be the same</value>
</data>
<data name="Message.SaveUrlMapping" xml:space="preserve">
<value>The Url must belong to the current site</value>
</data>
</root> </root>

View File

@ -121,10 +121,10 @@
<value>Redirect To:</value> <value>Redirect To:</value>
</data> </data>
<data name="MappedUrl.HelpText" xml:space="preserve"> <data name="MappedUrl.HelpText" xml:space="preserve">
<value>A fully qualified Url where the user will be redirected</value> <value>A relative or absolute Url where the user will be redirected</value>
</data> </data>
<data name="Url.HelpText" xml:space="preserve"> <data name="Url.HelpText" xml:space="preserve">
<value>A fully qualified Url for this site</value> <value>A relative Url identifying a path to a specific page in the site</value>
</data> </data>
<data name="Url.Text" xml:space="preserve"> <data name="Url.Text" xml:space="preserve">
<value>Url:</value> <value>Url:</value>
@ -138,4 +138,7 @@
<data name="Message.InfoRequired" xml:space="preserve"> <data name="Message.InfoRequired" xml:space="preserve">
<value>Please Provide All Required Information</value> <value>Please Provide All Required Information</value>
</data> </data>
<data name="Message.DuplicateUrlMapping" xml:space="preserve">
<value>The Url and Redirect To cannot be the same</value>
</data>
</root> </root>

View File

@ -156,11 +156,11 @@
<data name="Visitors.Heading" xml:space="preserve"> <data name="Visitors.Heading" xml:space="preserve">
<value>Visitors</value> <value>Visitors</value>
</data> </data>
<data name="VisitorTracking.HelpText" xml:space="preserve"> <data name="Tracking.HelpText" xml:space="preserve">
<value>Specify if visitor tracking is enabled</value> <value>Specify if visitor tracking is enabled</value>
</data> </data>
<data name="VisitorTracking.Text" xml:space="preserve"> <data name="Tracking.Text" xml:space="preserve">
<value>Visitor Tracking Enabled?</value> <value>Tracking Enabled?</value>
</data> </data>
<data name="IP" xml:space="preserve"> <data name="IP" xml:space="preserve">
<value>IP</value> <value>IP</value>
@ -171,4 +171,25 @@
<data name="Created" xml:space="preserve"> <data name="Created" xml:space="preserve">
<value>Created</value> <value>Created</value>
</data> </data>
<data name="Details.Text" xml:space="preserve">
<value>Details</value>
</data>
<data name="Filter.HelpText" xml:space="preserve">
<value>Comma delimited list of terms which may exist in IP addresses, user agents, or languages identifying visitors which should not be tracked</value>
</data>
<data name="Filter.Text" xml:space="preserve">
<value>Filter:</value>
</data>
<data name="Retention.HelpText" xml:space="preserve">
<value>Number of days of visitor activity to retain</value>
</data>
<data name="Retention.Text" xml:space="preserve">
<value>Retention (Days):</value>
</data>
<data name="Correlation.HelpText" xml:space="preserve">
<value>Indicate if new visitors to this site should be correlated based on their IP Address</value>
</data>
<data name="Correlation.Text" xml:space="preserve">
<value>Correlate Visitors?</value>
</data>
</root> </root>

View File

@ -117,10 +117,52 @@
<resheader name="writer"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="Confirm.Delete" xml:space="preserve">
<value>Are You Sure You Wish To Delete The {0} Version?</value>
</data>
<data name="Confirm.Restore" xml:space="preserve">
<value>Are You Sure You Wish To Restore The {0} Version?</value>
</data>
<data name="CreatedBy" xml:space="preserve">
<value>Created By</value>
</data>
<data name="CreatedOn" xml:space="preserve">
<value>Created On</value>
</data>
<data name="Delete.Header" xml:space="preserve">
<value>Delete Version</value>
</data>
<data name="Delete.Text" xml:space="preserve">
<value>Delete</value>
</data>
<data name="Error.Content.Delete" xml:space="preserve">
<value>Error Deleting Version</value>
</data>
<data name="Error.Content.Load" xml:space="preserve"> <data name="Error.Content.Load" xml:space="preserve">
<value>An Error Occurred Loading Content</value> <value>An Error Occurred Loading Content</value>
</data> </data>
<data name="Error.Content.Restore" xml:space="preserve">
<value>Error Restoring Version</value>
</data>
<data name="Error.Content.Save" xml:space="preserve"> <data name="Error.Content.Save" xml:space="preserve">
<value>An Error Occurred Saving Content</value> <value>An Error Occurred Saving Content</value>
</data> </data>
<data name="Error.Content.View" xml:space="preserve">
<value>Error Viewing Version</value>
</data>
<data name="Message.Content.Deleted" xml:space="preserve">
<value>Version Deleted</value>
</data>
<data name="Message.Content.Restored" xml:space="preserve">
<value>Version Restored</value>
</data>
<data name="Restore.Header" xml:space="preserve">
<value>Restore Version</value>
</data>
<data name="Restore.Text" xml:space="preserve">
<value>Restore</value>
</data>
<data name="View.Text" xml:space="preserve">
<value>View</value>
</data>
</root> </root>

View File

@ -259,7 +259,7 @@
<value>Upgrade</value> <value>Upgrade</value>
</data> </data>
<data name="Username" xml:space="preserve"> <data name="Username" xml:space="preserve">
<value>Username:</value> <value>Username</value>
</data> </data>
<data name="Version" xml:space="preserve"> <data name="Version" xml:space="preserve">
<value>Version</value> <value>Version</value>

View File

@ -177,4 +177,13 @@
<data name="System.Update" xml:space="preserve"> <data name="System.Update" xml:space="preserve">
<value>Check For System Updates</value> <value>Check For System Updates</value>
</data> </data>
<data name="Visibility" xml:space="preserve">
<value>Visibility:</value>
</data>
<data name="VisibilityEdit" xml:space="preserve">
<value>Page Editors Only</value>
</data>
<data name="VisibilityView" xml:space="preserve">
<value>Same As Page</value>
</data>
</root> </root>

View File

@ -1,65 +1,65 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!-- <!--
Microsoft ResX Schema Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
Version 2.0 mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
The primary goals of this format is to allow a simple XML format : using a System.ComponentModel.TypeConverter
that is mostly human readable. The generation and parsing of the : and then encoded with base64 encoding.
various data types are done through the TypeConverter classes -->
associated with the data types. <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true"> <xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType> <xsd:complexType>

View File

@ -121,7 +121,7 @@
<value>Specify if a Footer pane should always be displayed in a fixed location at the bottom of the page.</value> <value>Specify if a Footer pane should always be displayed in a fixed location at the bottom of the page.</value>
</data> </data>
<data name="Footer.Text" xml:space="preserve"> <data name="Footer.Text" xml:space="preserve">
<value>Display Footer?</value> <value>Display Fixed Footer?</value>
</data> </data>
<data name="Login.HelpText" xml:space="preserve"> <data name="Login.HelpText" xml:space="preserve">
<value>Specify if a Login option should be displayed, Note that this option does not prevent the login page from being accessible via a direct url.</value> <value>Specify if a Login option should be displayed, Note that this option does not prevent the login page from being accessible via a direct url.</value>

View File

@ -206,7 +206,7 @@ namespace Oqtane.Services
/// <returns></returns> /// <returns></returns>
Dictionary<string, string> SetSetting(Dictionary<string, string> settings, string settingName, string settingValue); Dictionary<string, string> SetSetting(Dictionary<string, string> settings, string settingName, string settingValue);
Dictionary<string, string> SetSetting(Dictionary<string, string> settings, string settingName, string settingValue, bool isPublic); Dictionary<string, string> SetSetting(Dictionary<string, string> settings, string settingName, string settingValue, bool isPrivate);
Dictionary<string, string> MergeSettings(Dictionary<string, string> settings1, Dictionary<string, string> settings2); Dictionary<string, string> MergeSettings(Dictionary<string, string> settings1, Dictionary<string, string> settings2);

View File

@ -24,6 +24,14 @@ namespace Oqtane.Services
/// <returns></returns> /// <returns></returns>
Task<UrlMapping> GetUrlMappingAsync(int urlMappingId); Task<UrlMapping> GetUrlMappingAsync(int urlMappingId);
/// <summary>
/// Get one specific <see cref="UrlMapping"/>
/// </summary>
/// <param name="siteId">ID-reference of a <see cref="Site"/></param>
/// <param name="url">A url</param>
/// <returns></returns>
Task<UrlMapping> GetUrlMappingAsync(int siteId, string url);
/// <summary> /// <summary>
/// Add / save a new <see cref="UrlMapping"/> to the database. /// Add / save a new <see cref="UrlMapping"/> to the database.
/// </summary> /// </summary>

View File

@ -131,14 +131,21 @@ namespace Oqtane.Services
foreach (KeyValuePair<string, string> kvp in settings) foreach (KeyValuePair<string, string> kvp in settings)
{ {
string value = kvp.Value; string value = kvp.Value;
bool ispublic = false; bool modified = false;
bool isprivate = false;
// manage settings modified with SetSetting method
if (value.StartsWith("[Private]"))
{
modified = true;
isprivate = true;
value = value.Substring(9);
}
if (value.StartsWith("[Public]")) if (value.StartsWith("[Public]"))
{ {
if (entityName == EntityNames.Site) modified = true;
{ isprivate = false;
ispublic = true; value = value.Substring(8);
}
value = value.Substring(8); // remove [Public]
} }
Setting setting = settingsList.FirstOrDefault(item => item.SettingName.Equals(kvp.Key, StringComparison.OrdinalIgnoreCase)); Setting setting = settingsList.FirstOrDefault(item => item.SettingName.Equals(kvp.Key, StringComparison.OrdinalIgnoreCase));
@ -149,22 +156,21 @@ namespace Oqtane.Services
setting.EntityId = entityId; setting.EntityId = entityId;
setting.SettingName = kvp.Key; setting.SettingName = kvp.Key;
setting.SettingValue = value; setting.SettingValue = value;
setting.IsPublic = ispublic; setting.IsPrivate = isprivate;
setting = await AddSettingAsync(setting); setting = await AddSettingAsync(setting);
} }
else else
{ {
if (setting.SettingValue != kvp.Value) if (setting.SettingValue != value || (modified && setting.IsPrivate != isprivate))
{ {
setting.SettingValue = value; setting.SettingValue = value;
setting.IsPublic = ispublic; setting.IsPrivate = isprivate;
setting = await UpdateSettingAsync(setting); setting = await UpdateSettingAsync(setting);
} }
} }
} }
} }
public async Task<Setting> GetSettingAsync(string entityName, int settingId) public async Task<Setting> GetSettingAsync(string entityName, int settingId)
{ {
return await GetJsonAsync<Setting>($"{Apiurl}/{settingId}/{entityName}"); return await GetJsonAsync<Setting>($"{Apiurl}/{settingId}/{entityName}");
@ -201,13 +207,13 @@ namespace Oqtane.Services
return SetSetting(settings, settingName, settingValue, false); return SetSetting(settings, settingName, settingValue, false);
} }
public Dictionary<string, string> SetSetting(Dictionary<string, string> settings, string settingName, string settingValue, bool isPublic) public Dictionary<string, string> SetSetting(Dictionary<string, string> settings, string settingName, string settingValue, bool isPrivate)
{ {
if (settings == null) if (settings == null)
{ {
settings = new Dictionary<string, string>(); settings = new Dictionary<string, string>();
} }
settingValue = (isPublic) ? "[Public]" + settingValue : settingValue; settingValue = (isPrivate) ? "[Private]" + settingValue : "[Public]" + settingValue;
if (settings.ContainsKey(settingName)) if (settings.ContainsKey(settingName))
{ {
settings[settingName] = settingValue; settings[settingName] = settingValue;

View File

@ -5,6 +5,7 @@ using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using Oqtane.Documentation; using Oqtane.Documentation;
using Oqtane.Shared; using Oqtane.Shared;
using System.Net;
namespace Oqtane.Services namespace Oqtane.Services
{ {
@ -33,6 +34,11 @@ namespace Oqtane.Services
return await GetJsonAsync<UrlMapping>($"{Apiurl}/{urlMappingId}"); return await GetJsonAsync<UrlMapping>($"{Apiurl}/{urlMappingId}");
} }
public async Task<UrlMapping> GetUrlMappingAsync(int siteId, string url)
{
return await GetJsonAsync<UrlMapping>($"{Apiurl}/url/{siteId}?url={WebUtility.UrlEncode(url)}");
}
public async Task<UrlMapping> AddUrlMappingAsync(UrlMapping role) public async Task<UrlMapping> AddUrlMappingAsync(UrlMapping role)
{ {
return await PostJsonAsync<UrlMapping>(Apiurl, role); return await PostJsonAsync<UrlMapping>(Apiurl, role);

View File

@ -1,13 +1,20 @@
@namespace Oqtane.Themes.BlazorTheme @namespace Oqtane.Themes.BlazorTheme
@inherits ContainerBase @inherits ContainerBase
<div class="container"> <div class="container">
<div class="row p-4"> @if (ModuleState.Title != "-")
<div class="d-flex flex-nowrap"> {
<ModuleActions /><h2><ModuleTitle /></h2> <div class="row p-4">
</div> <div class="d-flex flex-nowrap">
<hr class="app-rule" /> <ModuleActions /><h2><ModuleTitle /></h2>
</div> </div>
<div class="row px-4"> <hr class="app-rule" />
</div>
}
else
{
<ModuleActions />
}
<div class="row px-4">
<div class="container"> <div class="container">
<ModuleInstance /> <ModuleInstance />
</div> </div>

View File

@ -31,7 +31,7 @@
public override List<Resource> Resources => new List<Resource>() public override List<Resource> Resources => new List<Resource>()
{ {
// obtained from https://cdnjs.com/libraries // obtained from https://cdnjs.com/libraries
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.0.2/css/bootstrap.min.css", Integrity = "sha512-usVBAd66/NpVNfBge19gws2j6JZinnca12rAe2l+d+QkLU9fiG02O1X8Q6hepIpr/EYKZvKx/I9WsnujJuOmBA==", CrossOrigin = "anonymous" }, new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/css/bootstrap.min.css", Integrity = "sha512-GQGU0fMMi238uA+a/bdWJfpUGKUkBdgfFdgBm72SUQ6BeyWjoY/ton0tEjH+OSH9iP4Dfh+7HM0I9f5eR0L/4w==", CrossOrigin = "anonymous" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "Theme.css" }, new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "Theme.css" },
new Resource { ResourceType = ResourceType.Script, Bundle = "Bootstrap", Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/js/bootstrap.bundle.min.js", Integrity = "sha512-pax4MlgXjHEPfCwcJLQhigY7+N8rt6bVvWLFyUMuxShv170X53TRzGPmPkZmGBhk+jikR8WBM4yl7A9WMHHqvg==", CrossOrigin = "anonymous" } new Resource { ResourceType = ResourceType.Script, Bundle = "Bootstrap", Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/js/bootstrap.bundle.min.js", Integrity = "sha512-pax4MlgXjHEPfCwcJLQhigY7+N8rt6bVvWLFyUMuxShv170X53TRzGPmPkZmGBhk+jikR8WBM4yl7A9WMHHqvg==", CrossOrigin = "anonymous" }
}; };

View File

@ -3,7 +3,9 @@
@attribute [OqtaneIgnore] @attribute [OqtaneIgnore]
<span class="app-moduletitle"> <span class="app-moduletitle">
@((MarkupString)title) <a id="@ModuleState.PageModuleId.ToString()">
@((MarkupString)title)
</a>
</span> </span>
@code { @code {

View File

@ -46,360 +46,366 @@
</div> </div>
<div class="@BodyClass"> <div class="@BodyClass">
<div class="container-fluid"> <div class="container-fluid">
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{ {
<div class="row d-flex"> <div class="row d-flex">
<div class="col"> <div class="col">
<button type="button" data-bs-dismiss="offcanvas" class="btn btn-primary col-12" @onclick=@(async () => Navigate("Admin"))>@Localizer["AdminDash"]</button> <button type="button" data-bs-dismiss="offcanvas" class="btn btn-primary col-12" @onclick=@(async () => Navigate("Admin"))>@Localizer["AdminDash"]</button>
</div>
</div> </div>
</div>
<hr class="app-rule" /> <hr class="app-rule" />
<div class="row"> <div class="row">
<div class="col text-center"> <div class="col text-center">
<label class="control-label">@Localizer["Page.Manage"] </label> <label class="control-label">@Localizer["Page.Manage"] </label>
</div>
</div> </div>
</div> <div class="row d-flex mb-2">
<div class="row d-flex"> <div class="col d-flex justify-content-between">
<div class="col d-flex justify-content-between"> <button type="button" class="btn btn-secondary col me-1" data-bs-dismiss="offcanvas" @onclick=@(async () => Navigate("Add"))>@SharedLocalizer["Add"]</button>
<button type="button" class="btn btn-secondary col me-1" data-bs-dismiss="offcanvas" @onclick=@(async () => Navigate("Add"))>@SharedLocalizer["Add"]</button> <button type="button" class="btn btn-secondary col" data-bs-dismiss="offcanvas" @onclick=@(async () => Navigate("Edit"))>@SharedLocalizer["Edit"]</button>
<button type="button" class="btn btn-secondary col" data-bs-dismiss="offcanvas" @onclick=@(async () => Navigate("Edit"))>@SharedLocalizer["Edit"]</button> <button type="button" class="btn btn-danger col ms-1" @onclick="ConfirmDelete">@SharedLocalizer["Delete"]</button>
<button type="button" class="btn btn-danger col ms-1" @onclick="ConfirmDelete">@SharedLocalizer["Delete"]</button> </div>
</div> </div>
</div> <div class="row d-flex">
<br /> <div class="col">
<div class="row d-flex"> @if (UserSecurity.GetPermissionStrings(PageState.Page.Permissions).FirstOrDefault(item => item.PermissionName == PermissionNames.View).Permissions.Split(';').Contains(RoleNames.Everyone))
<div class="col"> {
@if (UserSecurity.GetPermissionStrings(PageState.Page.Permissions).FirstOrDefault(item => item.PermissionName == PermissionNames.View).Permissions.Split(';').Contains(RoleNames.Everyone)) <button type="button" class="btn btn-secondary col-12" @onclick=@(async () => Publish("unpublish"))>@Localizer["Page.Unpublish"]</button>
{ }
<button type="button" class="btn btn-secondary col-12" @onclick=@(async () => Publish("unpublish"))>@Localizer["Page.Unpublish"]</button> else
} {
else <button type="button" class="btn btn-secondary col-12" @onclick=@(async () => Publish("publish"))>@Localizer["Page.Publish"]</button>
{ }
<button type="button" class="btn btn-secondary col-12" @onclick=@(async () => Publish("publish"))>@Localizer["Page.Publish"]</button> </div>
}
</div> </div>
</div> }
}
@if (_deleteConfirmation) @if (_deleteConfirmation)
{ {
<div class="app-admin-modal"> <div class="app-admin-modal">
<div class="modal" tabindex="-1" role="dialog"> <div class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">@Localizer["Page.Delete"]</h5> <h5 class="modal-title">@Localizer["Page.Delete"]</h5>
<button type="button" class="btn-close" aria-label="Close" @onclick="ConfirmDelete"></button> <button type="button" class="btn-close" aria-label="Close" @onclick="ConfirmDelete"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>Are You Sure You Want To Delete This Page?</p> <p>Are You Sure You Want To Delete This Page?</p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-danger" @onclick="DeletePage">@SharedLocalizer["Delete"]</button> <button type="button" class="btn btn-danger" @onclick="DeletePage">@SharedLocalizer["Delete"]</button>
<button type="button" class="btn btn-secondary" @onclick="ConfirmDelete">@SharedLocalizer["Cancel"]</button> <button type="button" class="btn btn-secondary" @onclick="ConfirmDelete">@SharedLocalizer["Cancel"]</button>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> }
} <hr class="app-rule" />
<hr class="app-rule" /> <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> </select>
</select> @if (ModuleType == "new")
@if (ModuleType == "new")
{
@if (_moduleDefinitions != null)
{ {
<select class="form-select" @onchange="(e => CategoryChanged(e))"> @if (_moduleDefinitions != null)
@foreach (var category in _categories) {
{ <select class="form-select" @onchange="(e => CategoryChanged(e))">
if (category == Category) @foreach (var category in _categories)
{ {
<option value="@category" selected>@category @Localizer["Modules"]</option> if (category == Category)
{
<option value="@category" selected>@category @Localizer["Modules"]</option>
}
else
{
<option value="@category">@category @Localizer["Modules"]</option>
}
}
</select>
<select class="form-select" @onchange="(e => ModuleChanged(e))">
@if (ModuleDefinitionName == "-")
{
<option value="-" selected>&lt;@Localizer["Module.Select"]&gt;</option>
} }
else else
{ {
<option value="@category">@category @Localizer["Modules"]</option> <option value="-">&lt;@Localizer["Module.Select"]&gt;</option>
} }
} @foreach (var moduledefinition in _moduleDefinitions)
</select>
<select class="form-select" @onchange="(e => ModuleChanged(e))">
@if (ModuleDefinitionName == "-")
{
<option value="-" selected>&lt;@Localizer["Module.Select"]&gt;</option>
}
else
{
<option value="-">&lt;@Localizer["Module.Select"]&gt;</option>
}
@foreach (var moduledefinition in _moduleDefinitions)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Utilize, moduledefinition.Permissions))
{ {
if (moduledefinition.Runtimes == "" || moduledefinition.Runtimes.Contains(PageState.Runtime.ToString())) if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Utilize, moduledefinition.Permissions))
{ {
<option value="@moduledefinition.ModuleDefinitionName">@moduledefinition.Name</option> if (moduledefinition.Runtimes == "" || moduledefinition.Runtimes.Contains(PageState.Runtime.ToString()))
{
<option value="@moduledefinition.ModuleDefinitionName">@moduledefinition.Name</option>
}
} }
} }
</select>
}
}
else
{
<select class="form-select" @onchange="(e => PageChanged(e))">
<option value="-">&lt;@Localizer["Page.Select"]&gt;</option>
@foreach (Page p in _pages)
{
<option value="@p.PageId">@p.Name</option>
}
</select>
<select class="form-select" @bind="@ModuleId">
<option value="-">&lt;@Localizer["Module.Select"]&gt;</option>
@foreach (Module module in _modules)
{
<option value="@module.ModuleId">@module.Title</option>
} }
</select> </select>
@((MarkupString) Description)
} }
} </div>
else
{
<select class="form-select" @onchange="(e => PageChanged(e))">
<option value="-">&lt;@Localizer["Page.Select"]&gt;</option>
@foreach (Page p in _pages)
{
<option value="@p.PageId">@p.Name</option>
}
</select>
<select class="form-select" @bind="@ModuleId">
<option value="-">&lt;@Localizer["Module.Select"]&gt;</option>
@foreach (Module module in _modules)
{
<option value="@module.ModuleId">@module.Title</option>
}
</select>
}
</div> </div>
</div>
<div class="row">
<div class="col text-center">
<label for="Title" class="control-label">@Localizer["Title"] </label>
<input type="text" name="Title" class="form-control" @bind="@Title" />
</div>
</div>
@if (_pane.Length > 1)
{
<div class="row"> <div class="row">
<div class="col text-center"> <div class="col text-center">
<label for="Pane" class="control-label">@Localizer["Pane"] </label> <label for="Title" class="control-label">@Localizer["Title"] </label>
<select class="form-select" @bind="@Pane"> <input type="text" name="Title" class="form-control" @bind="@Title" />
@foreach (string pane in PageState.Page.Panes) </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="col text-center">
<label for="Container" class="control-label">@Localizer["Container"] </label>
<select class="form-select" @bind="@ContainerType">
@foreach (var container in _containers)
{ {
<option value="@pane">@pane Pane</option> <option value="@container.TypeName">@container.Name</option>
} }
</select> </select>
</div> </div>
</div> </div>
} <div class="row">
<div class="row"> <div class="col text-center">
<div class="col text-center"> <label for="visibility" class="control-label">@Localizer["Visibility"]</label>
<label for="Container" class="control-label">@Localizer["Container"] </label> <select class="form-select" @bind="@Visibility">
<select class="form-select" @bind="@ContainerType"> <option value="edit" selected>@Localizer["VisibilityEdit"]</option>
@foreach (var container in _containers) <option value="view">@Localizer["VisibilityView"]</option>
{ </select>
<option value="@container.TypeName">@container.Name</option> </div>
}
</select>
</div> </div>
</div> <button type="button" class="btn btn-primary col-12 mt-4" @onclick="@AddModule">@Localizer["Page.Module.Add"]</button>
<br /> @((MarkupString) Message)
<button type="button" class="btn btn-primary col-12" @onclick="@AddModule">@Localizer["Page.Module.Add"]</button>
@((MarkupString) Message)
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
<hr class="app-rule" />
<NavLink class="btn btn-info col-12" data-bs-dismiss="offcanvas" href="@NavigateUrl("admin/update")">@Localizer["System.Update"]</NavLink>
}
</div> </div>
</div> </div>
</div> </div>
} }
@code{ @code{
private bool _deleteConfirmation = false;
private List<string> _categories = new List<string>();
private List<ModuleDefinition> _allModuleDefinitions;
private List<ModuleDefinition> _moduleDefinitions;
private List<Page> _pages = new List<Page>();
private List<Module> _modules = new List<Module>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private string _category = "Common";
private bool _deleteConfirmation = false; protected string PageId { get; private set; } = "-";
private List<string> _categories = new List<string>(); protected string ModuleId { get; private set; } = "-";
private List<ModuleDefinition> _allModuleDefinitions; protected string ModuleType { get; private set; } = "new";
private List<ModuleDefinition> _moduleDefinitions; protected string ModuleDefinitionName { get; private set; } = "-";
private List<Page> _pages = new List<Page>();
private List<Module> _modules = new List<Module>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private string _category = "Common";
protected string PageId { get; private set; } = "-"; protected string Category
protected string ModuleId { get; private set; } = "-"; {
protected string ModuleType { get; private set; } = "new"; get => _category;
protected string ModuleDefinitionName { get; private set; } = "-"; private set
{
if (_category != value)
{
_category = value;
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(Category)).ToList();
ModuleDefinitionName = "-";
Message = "";
StateHasChanged();
_ = UpdateSettingsAsync();
}
}
}
protected string Category protected string Pane
{ {
get => _category; get => _pane;
private set private set
{ {
if (_category != value) if (_pane != value)
{ {
_category = value; _pane = value;
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(Category)).ToList(); _ = UpdateSettingsAsync();
ModuleDefinitionName = "-"; }
Description = ""; }
StateHasChanged(); }
_ = UpdateSettingsAsync();
}
}
}
protected string Pane protected string Title { get; private set; } = "";
{ protected string ContainerType { get; private set; } = "";
get => _pane; protected string Visibility { get; private set; } = "edit";
private set protected string Message { get; private set; } = "";
{
if (_pane != value) [Parameter]
{ public string ButtonClass { get; set; } = "btn-outline-secondary";
_pane = value;
_ = UpdateSettingsAsync(); [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 string Description { get; private set; } = ""; protected override async Task OnParametersSetAsync()
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions))
{
_pages?.Clear();
protected string Title { get; private set; } = ""; foreach (Page p in PageState.Pages)
protected string ContainerType { get; private set; } = ""; {
protected string Message { get; private set; } = ""; if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.Permissions))
{
_pages.Add(p);
}
}
await LoadSettingsAsync();
[Parameter] var themes = await ThemeService.GetThemesAsync();
public string ButtonClass { get; set; } = "btn-outline-secondary"; _containers = ThemeService.GetContainerControls(themes, PageState.Page.ThemeType);
ContainerType = PageState.Site.DefaultContainerType;
_allModuleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(Category)).ToList();
_categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',')).Distinct().ToList();
}
}
[Parameter] private void CategoryChanged(ChangeEventArgs e)
public string ContainerClass { get; set; } = "offcanvas offcanvas-end"; {
Category = (string)e.Value;
}
[Parameter] private void ModuleChanged(ChangeEventArgs e)
public string HeaderClass { get; set; } = "offcanvas-header"; {
ModuleDefinitionName = (string)e.Value;
if (ModuleDefinitionName != "-")
{
var moduleDefinition = _moduleDefinitions.FirstOrDefault(item => item.ModuleDefinitionName == ModuleDefinitionName);
Message = "<div class=\"alert alert-info mt-2 text-center\" role=\"alert\">" + moduleDefinition.Description + "</div>";
}
else
{
Message = "";
}
StateHasChanged();
}
[Parameter] private void PageChanged(ChangeEventArgs e)
public string BodyClass { get; set; } = "offcanvas-body overflow-auto"; {
PageId = (string)e.Value;
if (PageId != "-")
{
_modules = PageState.Modules
.Where(module => module.PageId == int.Parse(PageId)
&& !module.IsDeleted
&& UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, module.Permissions))
.ToList();
}
ModuleId = "-";
StateHasChanged();
}
[Parameter] private async Task AddModule()
public bool ShowLanguageSwitcher { get; set; } = true; {
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions))
{
if ((ModuleType == "new" && ModuleDefinitionName != "-") || (ModuleType != "new" && ModuleId != "-"))
{
if (ModuleType == "new")
{
Module module = new Module();
module.SiteId = PageState.Site.SiteId;
module.PageId = PageState.Page.PageId;
module.ModuleDefinitionName = ModuleDefinitionName;
module.AllPages = false;
List<PermissionString> permissions = UserSecurity.GetPermissionStrings(PageState.Page.Permissions);
if (Visibility == "view")
{
// set module view permissions to page view permissions
permissions.Find(p => p.PermissionName == PermissionNames.View).Permissions = permissions.Find(p => p.PermissionName == PermissionNames.View).Permissions;
}
else
{
// set module view permissions to page edit permissions
permissions.Find(p => p.PermissionName == PermissionNames.View).Permissions = permissions.Find(p => p.PermissionName == PermissionNames.Edit).Permissions;
}
module.Permissions = UserSecurity.SetPermissionStrings(permissions);
protected override async Task OnParametersSetAsync() module = await ModuleService.AddModuleAsync(module);
{ ModuleId = module.ModuleId.ToString();
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions)) }
{
_pages?.Clear();
foreach (Page p in PageState.Pages) var pageModule = new PageModule
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.Permissions))
{
_pages.Add(p);
}
}
await LoadSettingsAsync();
var themes = await ThemeService.GetThemesAsync();
_containers = ThemeService.GetContainerControls(themes, PageState.Page.ThemeType);
ContainerType = PageState.Site.DefaultContainerType;
_allModuleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(Category)).ToList();
_categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',')).Distinct().ToList();
}
}
private void CategoryChanged(ChangeEventArgs e)
{
Category = (string)e.Value;
}
private void ModuleChanged(ChangeEventArgs e)
{
ModuleDefinitionName = (string)e.Value;
if (ModuleDefinitionName != "-")
{
var moduleDefinition = _moduleDefinitions.FirstOrDefault(item => item.ModuleDefinitionName == ModuleDefinitionName);
Description = "<br /><div class=\"alert alert-info\" role=\"alert\">" + moduleDefinition.Description + "</div>";
}
else
{
Description = "";
}
StateHasChanged();
}
private void PageChanged(ChangeEventArgs e)
{
PageId = (string)e.Value;
if (PageId != "-")
{
_modules = PageState.Modules
.Where(module => module.PageId == int.Parse(PageId)
&& !module.IsDeleted
&& UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, module.Permissions))
.ToList();
}
ModuleId = "-";
StateHasChanged();
}
private async Task AddModule()
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions))
{
if ((ModuleType == "new" && ModuleDefinitionName != "-") || (ModuleType != "new" && ModuleId != "-"))
{
if (ModuleType == "new")
{
Module module = new Module();
module.SiteId = PageState.Site.SiteId;
module.PageId = PageState.Page.PageId;
module.ModuleDefinitionName = ModuleDefinitionName;
module.AllPages = false;
// set module view permissions to page edit permissions
List<PermissionString> permissions = UserSecurity.GetPermissionStrings(PageState.Page.Permissions);
permissions.Find(p => p.PermissionName == PermissionNames.View).Permissions = permissions.Find(p => p.PermissionName == PermissionNames.Edit).Permissions;
module.Permissions = UserSecurity.SetPermissionStrings(permissions);
module = await ModuleService.AddModuleAsync(module);
ModuleId = module.ModuleId.ToString();
}
var pageModule = new PageModule
{ {
PageId = PageState.Page.PageId, PageId = PageState.Page.PageId,
ModuleId = int.Parse(ModuleId), ModuleId = int.Parse(ModuleId),
Title = Title Title = Title
}; };
if (pageModule.Title == "") if (pageModule.Title == "")
{ {
if (ModuleType == "new") if (ModuleType == "new")
{ {
pageModule.Title = _moduleDefinitions.FirstOrDefault(item => item.ModuleDefinitionName == ModuleDefinitionName)?.Name; pageModule.Title = _moduleDefinitions.FirstOrDefault(item => item.ModuleDefinitionName == ModuleDefinitionName)?.Name;
} }
else else
{ {
pageModule.Title = _modules.FirstOrDefault(item => item.ModuleId == int.Parse(ModuleId))?.Title; pageModule.Title = _modules.FirstOrDefault(item => item.ModuleId == int.Parse(ModuleId))?.Title;
} }
} }
pageModule.Pane = Pane; pageModule.Pane = Pane;
pageModule.Order = int.MaxValue; pageModule.Order = int.MaxValue;
pageModule.ContainerType = ContainerType; pageModule.ContainerType = ContainerType;
if (pageModule.ContainerType == PageState.Site.DefaultContainerType) if (pageModule.ContainerType == PageState.Site.DefaultContainerType)
{ {
pageModule.ContainerType = ""; pageModule.ContainerType = "";
} }
await PageModuleService.AddPageModuleAsync(pageModule); await PageModuleService.AddPageModuleAsync(pageModule);
await PageModuleService.UpdatePageModuleOrderAsync(pageModule.PageId, pageModule.Pane); await PageModuleService.UpdatePageModuleOrderAsync(pageModule.PageId, pageModule.Pane);
Message = $"<div class=\"alert alert-success mt-2 text-center\" role=\"alert\">{Localizer["Success.Page.ModuleAdd"]}</div>"; Message = $"<div class=\"alert alert-success mt-2 text-center\" role=\"alert\">{Localizer["Success.Page.ModuleAdd"]}</div>";
NavigationManager.NavigateTo(NavigateUrl()); Title = "";
NavigationManager.NavigateTo(NavigateUrl());
} }
else else
{ {

View File

@ -2,6 +2,7 @@ using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop; using Microsoft.JSInterop;
using Oqtane.Enums;
using Oqtane.Providers; using Oqtane.Providers;
using Oqtane.Security; using Oqtane.Security;
using Oqtane.Services; using Oqtane.Services;
@ -17,6 +18,7 @@ namespace Oqtane.Themes.Controls
[Inject] public IJSRuntime jsRuntime { get; set; } [Inject] public IJSRuntime jsRuntime { get; set; }
[Inject] public IServiceProvider ServiceProvider { get; set; } [Inject] public IServiceProvider ServiceProvider { get; set; }
[Inject] public SiteState SiteState { get; set; } [Inject] public SiteState SiteState { get; set; }
[Inject] public ILogService LoggingService { get; set; }
protected void LoginUser() protected void LoginUser()
{ {
@ -31,6 +33,8 @@ namespace Oqtane.Themes.Controls
protected async Task LogoutUser() protected async Task LogoutUser()
{ {
await UserService.LogoutUserAsync(PageState.User); await UserService.LogoutUserAsync(PageState.User);
await LoggingService.Log(PageState.Alias, PageState.Page.PageId, PageState.ModuleId, PageState.User.UserId, GetType().AssemblyQualifiedName, "Logout", LogFunction.Security, LogLevel.Information, null, "User Logout For Username {Username}", PageState.User.Username);
PageState.User = null; PageState.User = null;
bool authorizedtoviewpage = UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, PageState.Page.Permissions); bool authorizedtoviewpage = UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, PageState.Page.Permissions);

View File

@ -3,7 +3,7 @@
@inject ISettingService SettingService @inject ISettingService SettingService
<div class="@_classes"> <div class="@_classes">
@if (_title) @if (_title && ModuleState.Title != "-")
{ {
<div class="row px-4"> <div class="row px-4">
<div class="d-flex flex-nowrap"> <div class="d-flex flex-nowrap">

View File

@ -6,7 +6,7 @@
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="title" ResourceKey="Title" HelpText="Specify If The Module Title Should Be Displayed">Display Title?</Label> <Label Class="col-sm-3" For="title" ResourceKey="Title" ResourceType="@resourceType" HelpText="Specify If The Module Title Should Be Displayed">Display Title?</Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="title" class="form-select" @bind="@_title"> <select id="title" class="form-select" @bind="@_title">
<option value="true">Yes</option> <option value="true">Yes</option>
@ -15,7 +15,7 @@
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="background\" ResourceKey="Background" HelpText="Optionally Specify A Background Color For The Container">Background Color:</Label> <Label Class="col-sm-3" For="background\" ResourceKey="Background" ResourceType="@resourceType" HelpText="Optionally Specify A Background Color For The Container">Background Color:</Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="background" class="form-select" @bind="@_background"> <select id="background" class="form-select" @bind="@_background">
<option value="">None</option> <option value="">None</option>
@ -31,7 +31,7 @@
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="text" ResourceKey="Text" HelpText="Optionally Specify A Text Color For The Container">Text Color:</Label> <Label Class="col-sm-3" For="text" ResourceKey="Text" ResourceType="@resourceType" HelpText="Optionally Specify A Text Color For The Container">Text Color:</Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="text" class="form-select" @bind="@_text"> <select id="text" class="form-select" @bind="@_text">
<option value="">None</option> <option value="">None</option>
@ -47,7 +47,7 @@
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="border" ResourceKey="Border" HelpText="Optionally Specify A Border For The Container">Border Color:</Label> <Label Class="col-sm-3" For="border" ResourceKey="Border" ResourceType="@resourceType" HelpText="Optionally Specify A Border For The Container">Border Color:</Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="border" class="form-select" @bind="@_border"> <select id="border" class="form-select" @bind="@_border">
<option value="">None</option> <option value="">None</option>
@ -65,41 +65,42 @@
</div> </div>
</div> </div>
@code { @code {
private string _title = "true"; private string resourceType = "Oqtane.Themes.OqtaneTheme.ContainerSettings, Oqtane.Client"; // for localization
private string _background = ""; private string _title = "true";
private string _text = ""; private string _background = "";
private string _border = ""; private string _text = "";
private string _border = "";
protected override void OnInitialized() protected override void OnInitialized()
{
try
{ {
try _title = SettingService.GetSetting(ModuleState.Settings, GetType().Namespace + ":Title", "true");
{ _background = SettingService.GetSetting(ModuleState.Settings, GetType().Namespace + ":Background", "");
_title = SettingService.GetSetting(ModuleState.Settings, GetType().Namespace + ":Title", "true"); _text = SettingService.GetSetting(ModuleState.Settings, GetType().Namespace + ":Text", "");
_background = SettingService.GetSetting(ModuleState.Settings, GetType().Namespace + ":Background", ""); _border = SettingService.GetSetting(ModuleState.Settings, GetType().Namespace + ":Border", "");
_text = SettingService.GetSetting(ModuleState.Settings, GetType().Namespace + ":Text", "");
_border = SettingService.GetSetting(ModuleState.Settings, GetType().Namespace + ":Border", "");
}
catch (Exception ex)
{
ModuleInstance.AddModuleMessage(ex.Message, MessageType.Error);
}
} }
catch (Exception ex)
public async Task UpdateSettings()
{ {
try ModuleInstance.AddModuleMessage(ex.Message, MessageType.Error);
{
var settings = ModuleState.Settings;
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Title", _title);
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Background", _background);
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Text", _text);
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Border", _border);
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
}
catch (Exception ex)
{
ModuleInstance.AddModuleMessage(ex.Message, MessageType.Error);
}
} }
} }
public async Task UpdateSettings()
{
try
{
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Title", _title);
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Background", _background);
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Text", _text);
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Border", _border);
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
}
catch (Exception ex)
{
ModuleInstance.AddModuleMessage(ex.Message, MessageType.Error);
}
}
}

View File

@ -8,7 +8,7 @@
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="scope" ResourceKey="Scope" HelpText="Specify if the settings are applicable to this page or the entire site.">Setting Scope:</Label> <Label Class="col-sm-3" For="scope" ResourceKey="Scope" ResourceType="@resourceType" HelpText="Specify if the settings are applicable to this page or the entire site.">Setting Scope:</Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="scope" class="form-select" value="@_scope" @onchange="(e => ScopeChanged(e))"> <select id="scope" class="form-select" value="@_scope" @onchange="(e => ScopeChanged(e))">
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
@ -20,7 +20,7 @@
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="login" ResourceKey="Login" HelpText="Specify if a Login option should be displayed. Note that this option does not prevent the login page from being accessible via a direct url.">Show Login?</Label> <Label Class="col-sm-3" For="login" ResourceKey="Login" ResourceType="@resourceType" HelpText="Specify if a Login option should be displayed. Note that this option does not prevent the login page from being accessible via a direct url.">Show Login?</Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="login" class="form-select" @bind="@_login"> <select id="login" class="form-select" @bind="@_login">
<option value="-">&lt;@SharedLocalizer["Not Specified"]&gt;</option> <option value="-">&lt;@SharedLocalizer["Not Specified"]&gt;</option>
@ -30,7 +30,7 @@
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="register" ResourceKey="Register" HelpText="Specify if a Register option should be displayed. Note that this option is also dependent on the Allow Registration option in Site Settings.">Show Register?</Label> <Label Class="col-sm-3" For="register" ResourceKey="Register" ResourceType="@resourceType" HelpText="Specify if a Register option should be displayed. Note that this option is also dependent on the Allow Registration option in Site Settings.">Show Register?</Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="register" class="form-select" @bind="@_register"> <select id="register" class="form-select" @bind="@_register">
<option value="-">&lt;@SharedLocalizer["Not Specified"]&gt;</option> <option value="-">&lt;@SharedLocalizer["Not Specified"]&gt;</option>
@ -40,7 +40,7 @@
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="footer" ResourceKey="Footer" HelpText="Specify if a Footer pane should always be displayed in a fixed location at the bottom of the page">Display Fixed Footer?</Label> <Label Class="col-sm-3" For="footer" ResourceKey="Footer" ResourceType="@resourceType" HelpText="Specify if a Footer pane should always be displayed in a fixed location at the bottom of the page">Display Fixed Footer?</Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="footer" class="form-select" @bind="@_footer"> <select id="footer" class="form-select" @bind="@_footer">
<option value="-">&lt;@SharedLocalizer["Not Specified"]&gt;</option> <option value="-">&lt;@SharedLocalizer["Not Specified"]&gt;</option>
@ -52,16 +52,17 @@
</div> </div>
@code { @code {
private string _scope = "page"; private string resourceType = "Oqtane.Themes.OqtaneTheme.ThemeSettings, Oqtane.Client"; // for localization
private string _login = "-"; private string _scope = "page";
private string _register = "-"; private string _login = "-";
private string _footer = "-"; private string _register = "-";
private string _footer = "-";
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
try try
{ {
await LoadSettings(); await LoadSettings();
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -110,7 +111,7 @@
{ {
if (_scope == "site") if (_scope == "site")
{ {
var settings = PageState.Site.Settings; var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
if (_login != "-") if (_login != "-")
{ {
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Login", _login, true); settings = SettingService.SetSetting(settings, GetType().Namespace + ":Login", _login, true);
@ -127,7 +128,7 @@
} }
else else
{ {
var settings = PageState.Page.Settings; var settings = await SettingService.GetPageSettingsAsync(PageState.Page.PageId);
if (_login != "-") if (_login != "-")
{ {
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Login", _login); settings = SettingService.SetSetting(settings, GetType().Namespace + ":Login", _login);

View File

@ -104,14 +104,19 @@ namespace Oqtane.Themes
return Utilities.ContentUrl(PageState.Alias, fileid, asAttachment); return Utilities.ContentUrl(PageState.Alias, fileid, asAttachment);
} }
public string ImageUrl(int fileid, int width, int height, string mode) public string ImageUrl(int fileid, int width, int height)
{ {
return ImageUrl(fileid, width, height, mode, 0); return ImageUrl(fileid, width, height, "");
} }
public string ImageUrl(int fileid, int width, int height, string mode, int rotate) public string ImageUrl(int fileid, int width, int height, string mode)
{ {
return Utilities.ImageUrl(PageState.Alias, fileid, width, height, mode, rotate); return ImageUrl(fileid, width, height, mode, "", "", 0, false);
}
public string ImageUrl(int fileid, int width, int height, string mode, string position, string background, int rotate, bool recreate)
{
return Utilities.ImageUrl(PageState.Alias, fileid, width, height, mode, position, background, rotate, recreate);
} }
} }
} }

View File

@ -262,5 +262,36 @@ namespace Oqtane.UI
return Task.CompletedTask; return Task.CompletedTask;
} }
} }
public Task ScrollTo(int top, int left, string behavior)
{
try
{
if (string.IsNullOrEmpty(behavior)) behavior = "smooth";
_jsRuntime.InvokeVoidAsync(
"Oqtane.Interop.scrollTo",
top, left, behavior);
return Task.CompletedTask;
}
catch
{
return Task.CompletedTask;
}
}
public Task ScrollToId(string id)
{
try
{
_jsRuntime.InvokeVoidAsync(
"Oqtane.Interop.scrollToId",
id);
return Task.CompletedTask;
}
catch
{
return Task.CompletedTask;
}
}
} }
} }

View File

@ -21,5 +21,6 @@ namespace Oqtane.UI
public DateTime LastSyncDate { get; set; } public DateTime LastSyncDate { get; set; }
public Oqtane.Shared.Runtime Runtime { get; set; } public Oqtane.Shared.Runtime Runtime { get; set; }
public int VisitorId { get; set; } public int VisitorId { get; set; }
public string RemoteIPAddress { get; set; }
} }
} }

View File

@ -9,7 +9,9 @@
@inject IPageService PageService @inject IPageService PageService
@inject IUserService UserService @inject IUserService UserService
@inject IModuleService ModuleService @inject IModuleService ModuleService
@inject IUrlMappingService UrlMappingService
@inject ILogService LogService @inject ILogService LogService
@inject IJSRuntime JSRuntime
@implements IHandleAfterRender @implements IHandleAfterRender
@DynamicComponent @DynamicComponent
@ -82,134 +84,137 @@
var action = (!string.IsNullOrEmpty(route.Action)) ? route.Action : Constants.DefaultAction; var action = (!string.IsNullOrEmpty(route.Action)) ? route.Action : Constants.DefaultAction;
var querystring = ParseQueryString(route.Query); var querystring = ParseQueryString(route.Query);
// reload the client application if there is a forced reload or the user navigated to a site with a different alias // reload the client application if there is a forced reload or the user navigated to a site with a different alias
if (querystring.ContainsKey("reload") || (!route.AbsolutePath.Substring(1).ToLower().StartsWith(SiteState.Alias.Path.ToLower()) && !string.IsNullOrEmpty(SiteState.Alias.Path))) if (querystring.ContainsKey("reload") || (!route.AbsolutePath.Substring(1).ToLower().StartsWith(SiteState.Alias.Path.ToLower()) && !string.IsNullOrEmpty(SiteState.Alias.Path)))
{ {
NavigationManager.NavigateTo(_absoluteUri.Replace("?reload", ""), true); NavigationManager.NavigateTo(_absoluteUri.Replace("?reload", ""), true);
return; return;
} }
else else
{ {
// the refresh parameter is used to refresh the PageState // the refresh parameter is used to refresh the PageState
if (querystring.ContainsKey("refresh")) if (querystring.ContainsKey("refresh"))
{ {
refresh = UI.Refresh.Site; refresh = UI.Refresh.Site;
} }
} }
if (PageState != null) if (PageState != null)
{ {
editmode = PageState.EditMode; editmode = PageState.EditMode;
lastsyncdate = PageState.LastSyncDate; lastsyncdate = PageState.LastSyncDate;
} }
// process any sync events // process any sync events
var sync = await SyncService.GetSyncAsync(lastsyncdate); var sync = await SyncService.GetSyncAsync(lastsyncdate);
lastsyncdate = sync.SyncDate; lastsyncdate = sync.SyncDate;
if (sync.SyncEvents.Any()) if (sync.SyncEvents.Any())
{ {
// reload client application if server was restarted or site runtime/rendermode was modified // reload client application if server was restarted or site runtime/rendermode was modified
if (PageState != null && sync.SyncEvents.Exists(item => (item.TenantId == -1 || item.EntityName == EntityNames.Site && item.EntityId == SiteState.Alias.SiteId) && item.Reload)) if (PageState != null && sync.SyncEvents.Exists(item => (item.TenantId == -1 || item.EntityName == EntityNames.Site && item.EntityId == SiteState.Alias.SiteId) && item.Reload))
{ {
NavigationManager.NavigateTo(_absoluteUri, true); NavigationManager.NavigateTo(_absoluteUri, true);
return; return;
} }
if (sync.SyncEvents.Exists(item => item.EntityName == EntityNames.Site && item.EntityId == SiteState.Alias.SiteId)) if (sync.SyncEvents.Exists(item => item.EntityName == EntityNames.Site && item.EntityId == SiteState.Alias.SiteId))
{ {
refresh = UI.Refresh.Site; refresh = UI.Refresh.Site;
} }
} }
if (refresh == UI.Refresh.Site || PageState == null || PageState.Alias.SiteId != SiteState.Alias.SiteId) if (refresh == UI.Refresh.Site || PageState == null || PageState.Alias.SiteId != SiteState.Alias.SiteId)
{ {
site = await SiteService.GetSiteAsync(SiteState.Alias.SiteId); site = await SiteService.GetSiteAsync(SiteState.Alias.SiteId);
refresh = UI.Refresh.Site; refresh = UI.Refresh.Site;
} }
else else
{ {
site = PageState.Site; site = PageState.Site;
} }
if (site != null) if (site != null)
{ {
if (PageState == null || refresh == UI.Refresh.Site) if (PageState == null || refresh == UI.Refresh.Site)
{ {
// get user // get user
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
if (authState.User.Identity.IsAuthenticated) if (authState.User.Identity.IsAuthenticated)
{ {
user = await UserService.GetUserAsync(authState.User.Identity.Name, site.SiteId); user = await UserService.GetUserAsync(authState.User.Identity.Name, site.SiteId);
user.IsAuthenticated = authState.User.Identity.IsAuthenticated; if (user != null)
} {
} user.IsAuthenticated = authState.User.Identity.IsAuthenticated;
else }
{ }
user = PageState.User; }
} else
{
user = PageState.User;
}
// process any sync events for user // process any sync events for user
if (refresh != UI.Refresh.Site && user != null && sync.SyncEvents.Any()) if (refresh != UI.Refresh.Site && user != null && sync.SyncEvents.Any())
{ {
if (sync.SyncEvents.Exists(item => item.EntityName == EntityNames.User && item.EntityId == user.UserId)) if (sync.SyncEvents.Exists(item => item.EntityName == EntityNames.User && item.EntityId == user.UserId))
{ {
refresh = UI.Refresh.Site; refresh = UI.Refresh.Site;
} }
} }
if (PageState == null || refresh == UI.Refresh.Site) if (PageState == null || refresh == UI.Refresh.Site)
{ {
pages = await PageService.GetPagesAsync(site.SiteId); pages = await PageService.GetPagesAsync(site.SiteId);
} }
else else
{ {
pages = PageState.Pages; pages = PageState.Pages;
} }
if (PageState == null || refresh == UI.Refresh.Site) if (PageState == null || refresh == UI.Refresh.Site)
{ {
page = pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase)); page = pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase));
} }
else else
{ {
page = PageState.Page; page = PageState.Page;
} }
// get the page if the path has changed // get the page if the path has changed
if (page == null || page.Path != route.PagePath) if (page == null || page.Path != route.PagePath)
{ {
page = pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase)); page = pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase));
// if the home page path does not exist then use the first page in the collection (a future enhancement would allow the admin to specify the home page) // if the home page path does not exist then use the first page in the collection (a future enhancement would allow the admin to specify the home page)
if (page == null && route.PagePath == "") if (page == null && route.PagePath == "")
{ {
page = pages.FirstOrDefault(); page = pages.FirstOrDefault();
} }
editmode = false; editmode = false;
} }
if (page != null) if (page != null)
{ {
if (PageState == null) if (PageState == null)
{ {
editmode = false; editmode = false;
} }
// check if user is authorized to view page // check if user is authorized to view page
if (UserSecurity.IsAuthorized(user, PermissionNames.View, page.Permissions)) if (UserSecurity.IsAuthorized(user, PermissionNames.View, page.Permissions))
{ {
page = await ProcessPage(page, site, user); page = await ProcessPage(page, site, user);
if (PageState == null || refresh == UI.Refresh.Site) if (PageState == null || refresh == UI.Refresh.Site)
{ {
modules = await ModuleService.GetModulesAsync(site.SiteId); modules = await ModuleService.GetModulesAsync(site.SiteId);
} }
else else
{ {
modules = PageState.Modules; modules = PageState.Modules;
} }
(page, modules) = ProcessModules(page, modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType); (page, modules) = ProcessModules(page, modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType);
_pagestate = new PageState _pagestate = new PageState
{ {
Alias = SiteState.Alias, Alias = SiteState.Alias,
Site = site, Site = site,
@ -225,260 +230,293 @@
EditMode = editmode, EditMode = editmode,
LastSyncDate = lastsyncdate, LastSyncDate = lastsyncdate,
Runtime = runtime, Runtime = runtime,
VisitorId = VisitorId VisitorId = VisitorId,
RemoteIPAddress = SiteState.RemoteIPAddress
}; };
OnStateChange?.Invoke(_pagestate); OnStateChange?.Invoke(_pagestate);
} await ScrollToFragment(_pagestate.Uri);
} }
else }
{ else // page not found
if (user == null) {
{ // look for url mapping
// redirect to login page var urlMapping = await UrlMappingService.GetUrlMappingAsync(site.SiteId, route.PagePath);
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "login", "?returnurl=" + route.AbsolutePath)); if (urlMapping != null && !string.IsNullOrEmpty(urlMapping.MappedUrl))
} {
else var url = (urlMapping.MappedUrl.StartsWith("http")) ? urlMapping.MappedUrl : route.SiteUrl + "/" + urlMapping.MappedUrl;
{ NavigationManager.NavigateTo(url, false);
await LogService.Log(null, null, user.UserId, GetType().AssemblyQualifiedName, Utilities.GetTypeNameLastSegment(GetType().AssemblyQualifiedName, 1), LogFunction.Security, LogLevel.Error, null, "Page Does Not Exist Or User Is Not Authorized To View Page {Path}", route.PagePath); }
if (route.PagePath != "") else // not mapped
{ {
// redirect to home page if (user == null)
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "", "")); {
} // redirect to login page if user not logged in as they may need to be authenticated
} NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "login", "?returnurl=" + route.AbsolutePath));
} }
else
{
await LogService.Log(null, null, user.UserId, GetType().AssemblyQualifiedName, Utilities.GetTypeNameLastSegment(GetType().AssemblyQualifiedName, 1), LogFunction.Security, LogLevel.Error, null, "Page Does Not Exist Or User Is Not Authorized To View Page {Path}", route.PagePath);
if (route.PagePath != "")
{
// redirect to home page
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "", ""));
}
}
}
}
}
else
{
// site does not exist
}
}
private async void LocationChanged(object sender, LocationChangedEventArgs args)
{
_absoluteUri = args.Location;
await Refresh();
}
Task IHandleAfterRender.OnAfterRenderAsync()
{
if (!_navigationInterceptionEnabled)
{
_navigationInterceptionEnabled = true;
return NavigationInterception.EnableNavigationInterceptionAsync();
}
return Task.CompletedTask;
}
private Dictionary<string, string> ParseQueryString(string query)
{
Dictionary<string, string> querystring = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); // case insensistive keys
if (!string.IsNullOrEmpty(query))
{
if (query.StartsWith("?"))
{
query = query.Substring(1); // ignore "?"
}
foreach (string kvp in query.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries))
{
if (kvp != "")
{
if (kvp.Contains("="))
{
string[] pair = kvp.Split('=');
querystring.Add(pair[0], pair[1]);
}
else
{
querystring.Add(kvp, "true"); // default parameter when no value is provided
}
}
}
}
return querystring;
}
private async Task<Page> ProcessPage(Page page, Site site, User user)
{
try
{
if (page.IsPersonalizable && user != null)
{
// load the personalized page
page = await PageService.GetPageAsync(page.PageId, user.UserId);
}
if (string.IsNullOrEmpty(page.ThemeType))
{
page.ThemeType = site.DefaultThemeType;
}
page.Panes = new List<string>();
page.Resources = new List<Resource>();
string panes = PaneNames.Admin;
Type themetype = Type.GetType(page.ThemeType);
if (themetype == null)
{
// fallback
page.ThemeType = Constants.DefaultTheme;
themetype = Type.GetType(Constants.DefaultTheme);
}
if (themetype != null)
{
var themeobject = Activator.CreateInstance(themetype) as IThemeControl;
if (themeobject != null)
{
if (!string.IsNullOrEmpty(themeobject.Panes))
{
panes = themeobject.Panes;
}
page.Resources = ManagePageResources(page.Resources, themeobject.Resources);
}
}
page.Panes = panes.Replace(";", ",").Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();
}
catch
{
// error loading theme or layout
}
return page;
}
private (Page Page, List<Module> Modules) ProcessModules(Page page, List<Module> modules, int moduleid, string action, string defaultcontainertype)
{
var paneindex = new Dictionary<string, int>();
foreach (Module module in modules)
{
// initialize module control properties
module.SecurityAccessLevel = SecurityAccessLevel.Host;
module.ControlTitle = "";
module.Actions = "";
module.UseAdminContainer = false;
module.PaneModuleIndex = -1;
module.PaneModuleCount = 0;
if ((module.PageId == page.PageId || module.ModuleId == moduleid))
{
var typename = Constants.ErrorModule;
if (module.ModuleDefinition != null && (module.ModuleDefinition.Runtimes == "" || module.ModuleDefinition.Runtimes.Contains(Runtime)))
{
typename = module.ModuleDefinition.ControlTypeTemplate;
// handle default action
if (action == Constants.DefaultAction && !string.IsNullOrEmpty(module.ModuleDefinition.DefaultAction))
{
action = module.ModuleDefinition.DefaultAction;
}
// check if the module defines custom action routes
if (module.ModuleDefinition.ControlTypeRoutes != "")
{
foreach (string route in module.ModuleDefinition.ControlTypeRoutes.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
{
if (route.StartsWith(action + "="))
{
typename = route.Replace(action + "=", "");
}
}
}
}
// ensure component exists and implements IModuleControl
module.ModuleType = "";
if (Constants.DefaultModuleActions.Contains(action, StringComparer.OrdinalIgnoreCase))
{
typename = Constants.DefaultModuleActionsTemplate.Replace(Constants.ActionToken, action);
}
else
{
typename = typename.Replace(Constants.ActionToken, action);
}
Type moduletype = Type.GetType(typename, false, true); // case insensitive
if (moduletype != null && moduletype.GetInterfaces().Contains(typeof(IModuleControl)))
{
module.ModuleType = Utilities.GetFullTypeName(moduletype.AssemblyQualifiedName); // get actual type name
}
// get additional metadata from IModuleControl interface
if (moduletype != null && module.ModuleType != "")
{
// retrieve module component resources
var moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources);
if (action.ToLower() == "settings" && module.ModuleDefinition != null)
{
// settings components are embedded within a framework settings module
moduletype = Type.GetType(module.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, action), false, true);
if (moduletype != null)
{
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources);
}
}
// additional metadata needed for admin components
if (module.ModuleId == moduleid && action != "")
{
module.SecurityAccessLevel = moduleobject.SecurityAccessLevel;
module.ControlTitle = moduleobject.Title;
module.Actions = moduleobject.Actions;
module.UseAdminContainer = moduleobject.UseAdminContainer;
}
}
// ensure module's pane exists in current page and if not, assign it to the Admin pane
if (page.Panes == null || page.Panes.FindIndex(item => item.Equals(module.Pane, StringComparison.OrdinalIgnoreCase)) == -1)
{
module.Pane = PaneNames.Admin;
}
// calculate module position within pane
if (paneindex.ContainsKey(module.Pane.ToLower()))
{
paneindex[module.Pane.ToLower()] += 1;
}
else
{
paneindex.Add(module.Pane.ToLower(), 0);
}
module.PaneModuleIndex = paneindex[module.Pane.ToLower()];
// container fallback
if (string.IsNullOrEmpty(module.ContainerType))
{
module.ContainerType = defaultcontainertype;
}
}
}
foreach (Module module in modules.Where(item => item.PageId == page.PageId))
{
if (paneindex.ContainsKey(module.Pane.ToLower()))
{
module.PaneModuleCount = paneindex[module.Pane.ToLower()] + 1;
}
}
return (page, modules);
}
private List<Resource> ManagePageResources(List<Resource> pageresources, List<Resource> resources)
{
if (resources != null)
{
foreach (var resource in resources)
{
// ensure resource does not exist already
if (pageresources.Find(item => item.Url == resource.Url) == null)
{
pageresources.Add(resource);
}
}
}
return pageresources;
}
private async Task ScrollToFragment(Uri uri)
{
var fragment = uri.Fragment;
if (fragment.StartsWith('#'))
{
// handle text fragment (https://example.org/#test:~:text=foo)
var id = fragment.Substring(1);
var index = id.IndexOf(":~:", StringComparison.Ordinal);
if (index > 0)
{
id = id.Substring(0, index);
}
if (!string.IsNullOrEmpty(id))
{
var interop = new Interop(JSRuntime);
await interop.ScrollToId(id);
}
} }
else
{
// site does not exist
}
}
private async void LocationChanged(object sender, LocationChangedEventArgs args)
{
_absoluteUri = args.Location;
await Refresh();
}
Task IHandleAfterRender.OnAfterRenderAsync()
{
if (!_navigationInterceptionEnabled)
{
_navigationInterceptionEnabled = true;
return NavigationInterception.EnableNavigationInterceptionAsync();
}
return Task.CompletedTask;
}
private Dictionary<string, string> ParseQueryString(string query)
{
Dictionary<string, string> querystring = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); // case insensistive keys
if (!string.IsNullOrEmpty(query))
{
if (query.StartsWith("?"))
{
query = query.Substring(1); // ignore "?"
}
foreach (string kvp in query.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries))
{
if (kvp != "")
{
if (kvp.Contains("="))
{
string[] pair = kvp.Split('=');
querystring.Add(pair[0], pair[1]);
}
else
{
querystring.Add(kvp, "true"); // default parameter when no value is provided
}
}
}
}
return querystring;
}
private async Task<Page> ProcessPage(Page page, Site site, User user)
{
try
{
if (page.IsPersonalizable && user != null)
{
// load the personalized page
page = await PageService.GetPageAsync(page.PageId, user.UserId);
}
if (string.IsNullOrEmpty(page.ThemeType))
{
page.ThemeType = site.DefaultThemeType;
}
page.Panes = new List<string>();
page.Resources = new List<Resource>();
string panes = PaneNames.Admin;
Type themetype = Type.GetType(page.ThemeType);
if (themetype == null)
{
// fallback
page.ThemeType = Constants.DefaultTheme;
themetype = Type.GetType(Constants.DefaultTheme);
}
if (themetype != null)
{
var themeobject = Activator.CreateInstance(themetype) as IThemeControl;
if (themeobject != null)
{
if (!string.IsNullOrEmpty(themeobject.Panes))
{
panes = themeobject.Panes;
}
page.Resources = ManagePageResources(page.Resources, themeobject.Resources);
}
}
page.Panes = panes.Replace(";", ",").Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();
}
catch
{
// error loading theme or layout
}
return page;
}
private (Page Page, List<Module> Modules) ProcessModules(Page page, List<Module> modules, int moduleid, string action, string defaultcontainertype)
{
var paneindex = new Dictionary<string, int>();
foreach (Module module in modules)
{
// initialize module control properties
module.SecurityAccessLevel = SecurityAccessLevel.Host;
module.ControlTitle = "";
module.Actions = "";
module.UseAdminContainer = false;
module.PaneModuleIndex = -1;
module.PaneModuleCount = 0;
if ((module.PageId == page.PageId || module.ModuleId == moduleid))
{
var typename = Constants.ErrorModule;
if (module.ModuleDefinition != null && (module.ModuleDefinition.Runtimes == "" || module.ModuleDefinition.Runtimes.Contains(Runtime)))
{
typename = module.ModuleDefinition.ControlTypeTemplate;
// handle default action
if (action == Constants.DefaultAction && !string.IsNullOrEmpty(module.ModuleDefinition.DefaultAction))
{
action = module.ModuleDefinition.DefaultAction;
}
// check if the module defines custom action routes
if (module.ModuleDefinition.ControlTypeRoutes != "")
{
foreach (string route in module.ModuleDefinition.ControlTypeRoutes.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
{
if (route.StartsWith(action + "="))
{
typename = route.Replace(action + "=", "");
}
}
}
}
// ensure component exists and implements IModuleControl
module.ModuleType = "";
if (Constants.DefaultModuleActions.Contains(action, StringComparer.OrdinalIgnoreCase))
{
typename = Constants.DefaultModuleActionsTemplate.Replace(Constants.ActionToken, action);
}
else
{
typename = typename.Replace(Constants.ActionToken, action);
}
Type moduletype = Type.GetType(typename, false, true); // case insensitive
if (moduletype != null && moduletype.GetInterfaces().Contains(typeof(IModuleControl)))
{
module.ModuleType = Utilities.GetFullTypeName(moduletype.AssemblyQualifiedName); // get actual type name
}
// get additional metadata from IModuleControl interface
if (moduletype != null && module.ModuleType != "")
{
// retrieve module component resources
var moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources);
if (action.ToLower() == "settings" && module.ModuleDefinition != null)
{
// settings components are embedded within a framework settings module
moduletype = Type.GetType(module.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, action), false, true);
if (moduletype != null)
{
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources);
}
}
// additional metadata needed for admin components
if (module.ModuleId == moduleid && action != "")
{
module.SecurityAccessLevel = moduleobject.SecurityAccessLevel;
module.ControlTitle = moduleobject.Title;
module.Actions = moduleobject.Actions;
module.UseAdminContainer = moduleobject.UseAdminContainer;
}
}
// ensure module's pane exists in current page and if not, assign it to the Admin pane
if (page.Panes == null || page.Panes.FindIndex(item => item.Equals(module.Pane, StringComparison.OrdinalIgnoreCase)) == -1)
{
module.Pane = PaneNames.Admin;
}
// calculate module position within pane
if (paneindex.ContainsKey(module.Pane.ToLower()))
{
paneindex[module.Pane.ToLower()] += 1;
}
else
{
paneindex.Add(module.Pane.ToLower(), 0);
}
module.PaneModuleIndex = paneindex[module.Pane.ToLower()];
// container fallback
if (string.IsNullOrEmpty(module.ContainerType))
{
module.ContainerType = defaultcontainertype;
}
}
}
foreach (Module module in modules.Where(item => item.PageId == page.PageId))
{
if (paneindex.ContainsKey(module.Pane.ToLower()))
{
module.PaneModuleCount = paneindex[module.Pane.ToLower()] + 1;
}
}
return (page, modules);
}
private List<Resource> ManagePageResources(List<Resource> pageresources, List<Resource> resources)
{
if (resources != null)
{
foreach (var resource in resources)
{
// ensure resource does not exist already
if (pageresources.Find(item => item.Url == resource.Url) == null)
{
pageresources.Add(resource);
}
}
}
return pageresources;
} }
} }

View File

@ -28,32 +28,29 @@
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
if (!firstRender) var interop = new Interop(JsRuntime);
// manage stylesheets for this page
string batch = DateTime.UtcNow.ToString("yyyyMMddHHmmssfff");
var links = new List<object>();
foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet && item.Declaration != ResourceDeclaration.Global))
{
links.Add(new { id = "app-stylesheet-" + batch + "-" + (links.Count + 1).ToString("00"), rel = "stylesheet", href = resource.Url, type = "text/css", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", key = "" });
}
if (links.Any())
{
await interop.IncludeLinks(links.ToArray());
}
await interop.RemoveElementsById("app-stylesheet", "", "app-stylesheet-" + batch + "-00");
// set page title
if (!string.IsNullOrEmpty(PageState.Page.Title))
{ {
var interop = new Interop(JsRuntime); await interop.UpdateTitle(PageState.Page.Title);
}
// set page title else
if (!string.IsNullOrEmpty(PageState.Page.Title)) {
{ await interop.UpdateTitle(PageState.Site.Name + " - " + PageState.Page.Name);
await interop.UpdateTitle(PageState.Page.Title);
}
else
{
await interop.UpdateTitle(PageState.Site.Name + " - " + PageState.Page.Name);
}
// manage stylesheets for this page
string batch = DateTime.Now.ToString("yyyyMMddHHmmssfff");
var links = new List<object>();
foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet && item.Declaration != ResourceDeclaration.Global))
{
links.Add(new { id = "app-stylesheet-" + batch + "-" + (links.Count + 1).ToString("00"), rel = "stylesheet", href = resource.Url, type = "text/css", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", key = "" });
}
if (links.Any())
{
await interop.IncludeLinks(links.ToArray());
}
await interop.RemoveElementsById("app-stylesheet", "", "app-stylesheet-" + batch + "-00");
} }
} }
} }

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Version>3.0.1</Version> <Version>3.0.3</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/v3.0.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.3</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>3.0.1</version> <version>3.0.3</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/v3.0.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.3</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>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Version>3.0.1</Version> <Version>3.0.3</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/v3.0.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.3</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>3.0.1</version> <version>3.0.3</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/v3.0.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.3</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>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Version>3.0.1</Version> <Version>3.0.3</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/v3.0.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.3</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>3.0.1</version> <version>3.0.3</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/v3.0.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.3</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>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Version>3.0.1</Version> <Version>3.0.3</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/v3.0.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.3</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>3.0.1</version> <version>3.0.3</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/v3.0.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.3</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>

View File

@ -30,6 +30,11 @@ namespace Oqtane.Database.Sqlite
return table.Column<int>(name: name, nullable: false).Annotation("Sqlite:Autoincrement", true); return table.Column<int>(name: name, nullable: false).Annotation("Sqlite:Autoincrement", true);
} }
public override void DropColumn(MigrationBuilder builder, string name, string table)
{
// not implemented as SQLite does not support dropping columns
}
public override string ConcatenateSql(params string[] values) public override string ConcatenateSql(params string[] values)
{ {
var returnValue = String.Empty; var returnValue = String.Empty;

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>3.0.1</version> <version>3.0.3</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/v3.0.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.3</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>3.0.1</version> <version>3.0.3</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/v3.0.1/Oqtane.Framework.3.0.1.Upgrade.zip</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v3.0.3/Oqtane.Framework.3.0.3.Upgrade.zip</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.3</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>3.0.1</version> <version>3.0.3</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/v3.0.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.3</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>3.0.1</version> <version>3.0.3</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/v3.0.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.3</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>3.0.1</version> <version>3.0.3</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/v3.0.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.3</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\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.0.1.Install.zip" -Force Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.0.3.Install.zip" -Force

View File

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

View File

@ -19,7 +19,6 @@ using Oqtane.Extensions;
using SixLabors.ImageSharp; using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using System.Net.Http; using System.Net.Http;
// ReSharper disable StringIndexOfIsCultureSpecific.1 // ReSharper disable StringIndexOfIsCultureSpecific.1
@ -508,8 +507,8 @@ namespace Oqtane.Controllers
return System.IO.File.Exists(errorPath) ? PhysicalFile(errorPath, MimeUtilities.GetMimeType(errorPath)) : null; return System.IO.File.Exists(errorPath) ? PhysicalFile(errorPath, MimeUtilities.GetMimeType(errorPath)) : null;
} }
[HttpGet("image/{id}/{width}/{height}/{mode?}/{rotate?}")] [HttpGet("image/{id}/{width}/{height}/{mode}/{position}/{background}/{rotate}/{recreate}")]
public IActionResult GetImage(int id, int width, int height, string mode, string rotate) public IActionResult GetImage(int id, int width, int height, string mode, string position, string background, string rotate, string recreate)
{ {
var file = _files.GetFile(id); var file = _files.GetFile(id);
if (file != null && file.Folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.View, file.Folder.Permissions)) if (file != null && file.Folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.View, file.Folder.Permissions))
@ -519,21 +518,25 @@ namespace Oqtane.Controllers
var filepath = _files.GetFilePath(file); var filepath = _files.GetFilePath(file);
if (System.IO.File.Exists(filepath)) if (System.IO.File.Exists(filepath))
{ {
mode = (string.IsNullOrEmpty(mode)) ? "crop" : mode; // validation
rotate = (string.IsNullOrEmpty(rotate)) ? "0" : rotate; if (!Enum.TryParse(mode, true, out ResizeMode _)) mode = "crop";
if (!Enum.TryParse(position, true, out AnchorPositionMode _)) position = "center";
if (!Color.TryParseHex("#" + background, out _)) background = "000000";
if (!int.TryParse(rotate, out _)) rotate = "0";
rotate = (int.Parse(rotate) < 0 || int.Parse(rotate) > 360) ? "0" : rotate;
if (!bool.TryParse(recreate, out _)) recreate = "false";
string imagepath = filepath.Replace(Path.GetExtension(filepath), "." + width.ToString() + "x" + height.ToString() + "." + mode.ToLower() + ".png"); string imagepath = filepath.Replace(Path.GetExtension(filepath), "." + width.ToString() + "x" + height.ToString() + ".png");
if (!System.IO.File.Exists(imagepath)) if (!System.IO.File.Exists(imagepath) || bool.Parse(recreate))
{ {
if ((_userPermissions.IsAuthorized(User, PermissionNames.Edit, file.Folder.Permissions) || if ((_userPermissions.IsAuthorized(User, PermissionNames.Edit, file.Folder.Permissions) ||
!string.IsNullOrEmpty(file.Folder.ImageSizes) && file.Folder.ImageSizes.ToLower().Split(",").Contains(width.ToString() + "x" + height.ToString())) !string.IsNullOrEmpty(file.Folder.ImageSizes) && file.Folder.ImageSizes.ToLower().Split(",").Contains(width.ToString() + "x" + height.ToString())))
&& Enum.TryParse(mode, true, out ResizeMode resizemode))
{ {
imagepath = CreateImage(filepath, width, height, resizemode.ToString(), rotate, imagepath); imagepath = CreateImage(filepath, width, height, mode, position, background, rotate, imagepath);
} }
else else
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Invalid Image Size For Folder Or Invalid Mode Specification {Folder} {Width} {Height} {Mode}", file.Folder, width, height, mode); _logger.Log(LogLevel.Error, this, LogFunction.Security, "Invalid Image Size For Folder {Folder} {Width} {Height}", file.Folder, width, height);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
} }
} }
@ -569,35 +572,37 @@ namespace Oqtane.Controllers
return System.IO.File.Exists(errorPath) ? PhysicalFile(errorPath, MimeUtilities.GetMimeType(errorPath)) : null; return System.IO.File.Exists(errorPath) ? PhysicalFile(errorPath, MimeUtilities.GetMimeType(errorPath)) : null;
} }
private string CreateImage(string filepath, int width, int height, string mode, string rotate, string imagepath) private string CreateImage(string filepath, int width, int height, string mode, string position, string background, string rotate, string imagepath)
{ {
try try
{ {
FileStream stream = new FileStream(filepath, FileMode.Open, FileAccess.Read); using (var stream = new FileStream(filepath, FileMode.Open, FileAccess.Read))
using (Image image = Image.Load(stream))
{ {
Enum.TryParse(mode, true, out ResizeMode resizemode); stream.Position = 0;
using (var image = Image.Load(stream))
image.Mutate(x =>
x.Resize(new ResizeOptions
{
Size = new Size(width, height),
Mode = resizemode
})
.BackgroundColor(new Rgba32(255, 255, 255, 0)));
if (rotate != "0" && int.TryParse(rotate, out int angle))
{ {
image.Mutate(x => x.Rotate(angle)); int.TryParse(rotate, out int angle);
} Enum.TryParse(mode, true, out ResizeMode resizemode);
Enum.TryParse(position, true, out AnchorPositionMode anchorpositionmode);
image.Save(imagepath, new PngEncoder()); image.Mutate(x => x
.AutoOrient() // auto orient the image
.Rotate(angle)
.Resize(new ResizeOptions
{
Mode = resizemode,
Position = anchorpositionmode,
Size = new Size(width, height)
})
.BackgroundColor(Color.ParseHex("#" + background)));
image.Save(imagepath, new PngEncoder());
}
} }
stream.Close();
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Error Creating Image For File {FilePath} {Width} {Height} {Mode} {Error}", filepath, width, height, mode, ex.Message); _logger.Log(LogLevel.Error, this, LogFunction.Security, "Error Creating Image For File {FilePath} {Width} {Height} {Mode} {Rotate} {Error}", filepath, width, height, mode, rotate, ex.Message);
imagepath = ""; imagepath = "";
} }

View File

@ -55,7 +55,7 @@ namespace Oqtane.Controllers
} }
else else
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Job Post Attempt {Alias}", job); _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Job Post Attempt {Job}", job);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
job = null; job = null;
} }
@ -74,7 +74,7 @@ namespace Oqtane.Controllers
} }
else else
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Job Put Attempt {Alias}", job); _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Job Put Attempt {Job}", job);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
job = null; job = null;
} }

View File

@ -75,6 +75,7 @@ namespace Oqtane.Controllers
module.ModuleDefinition = moduledefinitions.Find(item => item.ModuleDefinitionName == module.ModuleDefinitionName); module.ModuleDefinition = moduledefinitions.Find(item => item.ModuleDefinitionName == module.ModuleDefinitionName);
module.Settings = settings.Where(item => item.EntityId == pagemodule.ModuleId) module.Settings = settings.Where(item => item.EntityId == pagemodule.ModuleId)
.Where(item => !item.IsPrivate || _userPermissions.IsAuthorized(User, PermissionNames.Edit, pagemodule.Module.Permissions))
.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue); .ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
modules.Add(module); modules.Add(module);
@ -101,7 +102,8 @@ namespace Oqtane.Controllers
List<ModuleDefinition> moduledefinitions = _moduleDefinitions.GetModuleDefinitions(module.SiteId).ToList(); List<ModuleDefinition> moduledefinitions = _moduleDefinitions.GetModuleDefinitions(module.SiteId).ToList();
module.ModuleDefinition = moduledefinitions.Find(item => item.ModuleDefinitionName == module.ModuleDefinitionName); module.ModuleDefinition = moduledefinitions.Find(item => item.ModuleDefinitionName == module.ModuleDefinitionName);
module.Settings = _settings.GetSettings(EntityNames.Module, id) module.Settings = _settings.GetSettings(EntityNames.Module, id)
.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue); .Where(item => !item.IsPrivate || _userPermissions.IsAuthorized(User, PermissionNames.Edit, module.Permissions))
.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
return module; return module;
} }
else else
@ -171,7 +173,7 @@ namespace Oqtane.Controllers
public void Delete(int id) public void Delete(int id)
{ {
var module = _modules.GetModule(id); var module = _modules.GetModule(id);
if (module != null && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Page, module.ModuleId, PermissionNames.Edit)) if (module != null && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Module, module.ModuleId, PermissionNames.Edit))
{ {
_modules.DeleteModule(id); _modules.DeleteModule(id);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId); _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId);

View File

@ -57,6 +57,7 @@ namespace Oqtane.Controllers
if (_userPermissions.IsAuthorized(User, PermissionNames.View, page.Permissions)) if (_userPermissions.IsAuthorized(User, PermissionNames.View, page.Permissions))
{ {
page.Settings = settings.Where(item => item.EntityId == page.PageId) page.Settings = settings.Where(item => item.EntityId == page.PageId)
.Where(item => !item.IsPrivate || _userPermissions.IsAuthorized(User, PermissionNames.Edit, page.Permissions))
.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue); .ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
pages.Add(page); pages.Add(page);
} }
@ -85,15 +86,16 @@ namespace Oqtane.Controllers
{ {
page = _pages.GetPage(id, int.Parse(userid)); page = _pages.GetPage(id, int.Parse(userid));
} }
if (_userPermissions.IsAuthorized(User,PermissionNames.View, page.Permissions)) if (page != null && page.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User,PermissionNames.View, page.Permissions))
{ {
page.Settings = _settings.GetSettings(EntityNames.Page, page.PageId) page.Settings = _settings.GetSettings(EntityNames.Page, page.PageId)
.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue); .Where(item => !item.IsPrivate || _userPermissions.IsAuthorized(User, PermissionNames.Edit, page.Permissions))
.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
return page; return page;
} }
else else
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Page Get Attempt {Page}", page); _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Page Get Attempt {PageId} {UserId}", id, userid);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null; return null;
} }
@ -104,24 +106,16 @@ namespace Oqtane.Controllers
public Page Get(string path, int siteid) public Page Get(string path, int siteid)
{ {
Page page = _pages.GetPage(WebUtility.UrlDecode(path), siteid); Page page = _pages.GetPage(WebUtility.UrlDecode(path), siteid);
if (page != null && page.SiteId == _alias.SiteId) if (page != null && page.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.View, page.Permissions))
{ {
if (_userPermissions.IsAuthorized(User,PermissionNames.View, page.Permissions)) page.Settings = _settings.GetSettings(EntityNames.Page, page.PageId)
{ .Where(item => !item.IsPrivate || _userPermissions.IsAuthorized(User, PermissionNames.Edit, page.Permissions))
page.Settings = _settings.GetSettings(EntityNames.Page, page.PageId) .ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue); return page;
return page;
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Page Get Attempt {Page}", page);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null;
}
} }
else else
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Page Get Attempt {Path} for Site {SiteId}", path, siteid); _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Page Get Attempt {SiteId} {Path}", siteid, path);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null; return null;
} }
@ -267,11 +261,10 @@ namespace Oqtane.Controllers
// save url mapping if page path changed // save url mapping if page path changed
if (currentPage.Path != page.Path) if (currentPage.Path != page.Path)
{ {
var url = HttpContext.Request.Scheme + "://" + _alias.Name + "/";
var urlMapping = new UrlMapping(); var urlMapping = new UrlMapping();
urlMapping.SiteId = page.SiteId; urlMapping.SiteId = page.SiteId;
urlMapping.Url = url + currentPage.Path; urlMapping.Url = currentPage.Path;
urlMapping.MappedUrl = url + page.Path; urlMapping.MappedUrl = page.Path;
urlMapping.Requests = 0; urlMapping.Requests = 0;
urlMapping.CreatedOn = System.DateTime.UtcNow; urlMapping.CreatedOn = System.DateTime.UtcNow;
urlMapping.RequestedOn = System.DateTime.UtcNow; urlMapping.RequestedOn = System.DateTime.UtcNow;

View File

@ -20,6 +20,7 @@ namespace Oqtane.Controllers
private readonly ISyncManager _syncManager; private readonly ISyncManager _syncManager;
private readonly ILogManager _logger; private readonly ILogManager _logger;
private readonly Alias _alias; private readonly Alias _alias;
private readonly string _visitorCookie;
public SettingController(ISettingRepository settings, IPageModuleRepository pageModules, IUserPermissions userPermissions, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger) public SettingController(ISettingRepository settings, IPageModuleRepository pageModules, IUserPermissions userPermissions, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger)
{ {
@ -29,39 +30,25 @@ namespace Oqtane.Controllers
_syncManager = syncManager; _syncManager = syncManager;
_logger = logger; _logger = logger;
_alias = tenantManager.GetAlias(); _alias = tenantManager.GetAlias();
_visitorCookie = "APP_VISITOR_" + _alias.SiteId.ToString();
} }
// GET: api/<controller> // GET: api/<controller>
[HttpGet] [HttpGet]
public IEnumerable<Setting> Get(string entityName, int entityid) public IEnumerable<Setting> Get(string entityName, int entityId)
{ {
List<Setting> settings = new List<Setting>(); List<Setting> settings = new List<Setting>();
if (IsAuthorized(entityName, entityid, PermissionNames.View)) if (IsAuthorized(entityName, entityId, PermissionNames.View))
{ {
settings = _settings.GetSettings(entityName, entityid).ToList(); settings = _settings.GetSettings(entityName, entityId).ToList();
if (FilterPrivate(entityName, entityId))
// ispublic filter
switch (entityName)
{ {
case EntityNames.Tenant: settings = settings.Where(item => !item.IsPrivate).ToList();
case EntityNames.ModuleDefinition:
case EntityNames.Host:
if (!User.IsInRole(RoleNames.Host))
{
settings = settings.Where(item => item.IsPublic).ToList();
}
break;
case EntityNames.Site:
if (!User.IsInRole(RoleNames.Admin))
{
settings = settings.Where(item => item.IsPublic).ToList();
}
break;
} }
} }
else else
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Access Settings {EntityName} {EntityId}", entityName, entityid); _logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Access Settings {EntityName} {EntityId}", entityName, entityId);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
} }
return settings; return settings;
@ -74,30 +61,15 @@ namespace Oqtane.Controllers
Setting setting = _settings.GetSetting(entityName, id); Setting setting = _settings.GetSetting(entityName, id);
if (IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.View)) if (IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.View))
{ {
// ispublic filter if (FilterPrivate(entityName, id) && setting.IsPrivate)
switch (entityName)
{ {
case EntityNames.Tenant: setting = null;
case EntityNames.ModuleDefinition:
case EntityNames.Host:
if (!User.IsInRole(RoleNames.Host) && !setting.IsPublic)
{
setting = null;
}
break;
case EntityNames.Site:
if (!User.IsInRole(RoleNames.Admin) && !setting.IsPublic)
{
setting = null;
}
break;
} }
return setting; return setting;
} }
else else
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Access Setting {Setting}", setting); _logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Access Setting {EntityName} {SettingId}", entityName, id);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null; return null;
} }
@ -204,20 +176,67 @@ namespace Oqtane.Controllers
} }
break; break;
case EntityNames.Visitor: case EntityNames.Visitor:
var visitorCookie = "APP_VISITOR_" + _alias.SiteId.ToString(); authorized = User.IsInRole(RoleNames.Admin);
if (int.TryParse(Request.Cookies[visitorCookie], out int visitorId)) if (!authorized)
{ {
authorized = (visitorId == entityId); if (int.TryParse(Request.Cookies[_visitorCookie], out int visitorId))
{
authorized = (visitorId == entityId);
}
}
break;
default: // custom entity
if (permissionName == PermissionNames.Edit)
{
authorized = User.IsInRole(RoleNames.Admin) || _userPermissions.IsAuthorized(User, entityName, entityId, permissionName);
} }
else else
{ {
authorized = User.IsInRole(RoleNames.Admin); authorized = true;
} }
break; break;
} }
return authorized; return authorized;
} }
private bool FilterPrivate(string entityName, int entityId)
{
bool filter = false;
switch (entityName)
{
case EntityNames.Tenant:
case EntityNames.ModuleDefinition:
case EntityNames.Host:
filter = !User.IsInRole(RoleNames.Host);
break;
case EntityNames.Site:
filter = !User.IsInRole(RoleNames.Admin);
break;
case EntityNames.Page:
case EntityNames.Module:
case EntityNames.Folder:
filter = !_userPermissions.IsAuthorized(User, entityName, entityId, PermissionNames.Edit);
break;
case EntityNames.User:
filter = !User.IsInRole(RoleNames.Admin) && _userPermissions.GetUser(User).UserId != entityId;
break;
case EntityNames.Visitor:
if (!User.IsInRole(RoleNames.Admin))
{
filter = true;
if (int.TryParse(Request.Cookies[_visitorCookie], out int visitorId))
{
filter = (visitorId != entityId);
}
}
break;
default: // custom entity
filter = !User.IsInRole(RoleNames.Admin) && !_userPermissions.IsAuthorized(User, entityName, entityId, PermissionNames.Edit);
break;
}
return filter;
}
private void AddSyncEvent(string EntityName) private void AddSyncEvent(string EntityName)
{ {
switch (EntityName) switch (EntityName)

View File

@ -44,12 +44,9 @@ namespace Oqtane.Controllers
var site = _sites.GetSite(id); var site = _sites.GetSite(id);
if (site.SiteId == _alias.SiteId) if (site.SiteId == _alias.SiteId)
{ {
var settings = _settings.GetSettings(EntityNames.Site, site.SiteId); site.Settings = _settings.GetSettings(EntityNames.Site, site.SiteId)
if (!User.IsInRole(RoleNames.Admin)) .Where(item => !item.IsPrivate || User.IsInRole(RoleNames.Admin))
{ .ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
settings = settings.Where(item => item.IsPublic);
}
site.Settings = settings.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
return site; return site;
} }
else else

View File

@ -31,7 +31,7 @@ namespace Oqtane.Controllers
systeminfo.Add("osversion", Environment.OSVersion.ToString()); systeminfo.Add("osversion", Environment.OSVersion.ToString());
systeminfo.Add("machinename", Environment.MachineName); systeminfo.Add("machinename", Environment.MachineName);
systeminfo.Add("serverpath", _environment.ContentRootPath); systeminfo.Add("serverpath", _environment.ContentRootPath);
systeminfo.Add("servertime", DateTime.Now.ToString()); systeminfo.Add("servertime", DateTime.UtcNow.ToString());
systeminfo.Add("installationid", _configManager.GetInstallationId()); systeminfo.Add("installationid", _configManager.GetInstallationId());
systeminfo.Add("runtime", _configManager.GetSetting("Runtime", "Server")); systeminfo.Add("runtime", _configManager.GetSetting("Runtime", "Server"));

View File

@ -48,7 +48,7 @@ namespace Oqtane.Controllers
public UrlMapping Get(int id) public UrlMapping Get(int id)
{ {
var urlMapping = _urlMappings.GetUrlMapping(id); var urlMapping = _urlMappings.GetUrlMapping(id);
if (urlMapping != null && (urlMapping.SiteId == _alias.SiteId)) if (urlMapping != null && urlMapping.SiteId == _alias.SiteId)
{ {
return urlMapping; return urlMapping;
} }
@ -60,6 +60,23 @@ namespace Oqtane.Controllers
} }
} }
// GET api/<controller>/url/x?url=y
[HttpGet("url/{siteid}")]
public UrlMapping Get(int siteid, string url)
{
var urlMapping = _urlMappings.GetUrlMapping(siteid, WebUtility.UrlDecode(url));
if (urlMapping != null && urlMapping.SiteId == _alias.SiteId)
{
return urlMapping;
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized UrlMapping Get Attempt {SiteId} {Url}", siteid, url);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null;
}
}
// POST api/<controller> // POST api/<controller>
[HttpPost] [HttpPost]
[Authorize(Roles = RoleNames.Admin)] [Authorize(Roles = RoleNames.Admin)]

View File

@ -389,7 +389,7 @@ namespace Oqtane.Controllers
if (ModelState.IsValid) if (ModelState.IsValid)
{ {
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username); IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username);
if (identityuser != null) if (identityuser != null && !string.IsNullOrEmpty(token))
{ {
var result = await _identityUserManager.ConfirmEmailAsync(identityuser, token); var result = await _identityUserManager.ConfirmEmailAsync(identityuser, token);
if (result.Succeeded) if (result.Succeeded)
@ -398,13 +398,13 @@ namespace Oqtane.Controllers
} }
else else
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Email Verification Failed For {Username}", user.Username); _logger.Log(LogLevel.Error, this, LogFunction.Security, "Email Verification Failed For {Username} - Error {Error}", user.Username, result.Errors.ToString());
user = null; user = null;
} }
} }
else else
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Email Verification Failed For {Username}", user.Username); _logger.Log(LogLevel.Error, this, LogFunction.Security, "Email Verification Failed For {Username}And Token {Token}", user.Username, token);
user = null; user = null;
} }
} }
@ -420,9 +420,14 @@ namespace Oqtane.Controllers
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username); IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username);
if (identityuser != null) if (identityuser != null)
{ {
user = _users.GetUser(user.Username);
string token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityuser); string token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityuser);
string url = HttpContext.Request.Scheme + "://" + _alias.Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token); string url = HttpContext.Request.Scheme + "://" + _alias.Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
string body = "Dear " + user.DisplayName + ",\n\nPlease Click The Link Displayed Below To Reset Your Password:\n\n" + url + "\n\nThank You!"; string body = "Dear " + user.DisplayName + ",\n\nYou recently requested to reset your password. Please use the link below to complete the process:\n\n" + url +
"\n\nPlease note that the link is only valid for 24 hours so if you are unable to take action within that time period, you should initiate another password reset on the site." +
"\n\nIf you did not request to reset your password you can safely ignore this message." +
"\n\nThank You!";
var notification = new Notification(user.SiteId, null, user, "User Password Reset", body, null); var notification = new Notification(user.SiteId, null, user, "User Password Reset", body, null);
_notifications.AddNotification(notification); _notifications.AddNotification(notification);
_logger.Log(LogLevel.Information, this, LogFunction.Security, "Password Reset Notification Sent For {Username}", user.Username); _logger.Log(LogLevel.Information, this, LogFunction.Security, "Password Reset Notification Sent For {Username}", user.Username);
@ -451,13 +456,13 @@ namespace Oqtane.Controllers
} }
else else
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Password Reset Failed For {Username}", user.Username); _logger.Log(LogLevel.Error, this, LogFunction.Security, "Password Reset Failed For {Username} - Error {Error}", user.Username, result.Errors.ToString());
user = null; user = null;
} }
} }
else else
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Password Reset Failed For {Username}", user.Username); _logger.Log(LogLevel.Error, this, LogFunction.Security, "Password Reset Failed For {Username} And Token {Token}", user.Username, token);
user = null; user = null;
} }
} }

View File

@ -47,15 +47,14 @@ namespace Oqtane.Controllers
[HttpGet("{id}")] [HttpGet("{id}")]
public Visitor Get(int id) public Visitor Get(int id)
{ {
bool authorized; bool authorized = User.IsInRole(RoleNames.Admin);
var visitorCookie = "APP_VISITOR_" + _alias.SiteId.ToString(); if (!authorized)
if (int.TryParse(Request.Cookies[visitorCookie], out int visitorId))
{ {
authorized = (visitorId == id); var visitorCookie = "APP_VISITOR_" + _alias.SiteId.ToString();
} if (int.TryParse(Request.Cookies[visitorCookie], out int visitorId))
else {
{ authorized = (visitorId == id);
authorized = User.IsInRole(RoleNames.Admin); }
} }
var visitor = _visitors.GetVisitor(id); var visitor = _visitors.GetVisitor(id);

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Data; using System.Data;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Operations; using Microsoft.EntityFrameworkCore.Migrations.Operations;
using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders;
using Oqtane.Databases.Interfaces; using Oqtane.Databases.Interfaces;
@ -75,6 +76,11 @@ namespace Oqtane.Databases
} }
public virtual void DropColumn(MigrationBuilder builder, string name, string table)
{
builder.DropColumn(name, table);
}
public abstract DbContextOptionsBuilder UseDatabase(DbContextOptionsBuilder optionsBuilder, string connectionString); public abstract DbContextOptionsBuilder UseDatabase(DbContextOptionsBuilder optionsBuilder, string connectionString);
} }
} }

Some files were not shown because too many files have changed in this diff Show More