Compare commits

..

286 Commits

Author SHA1 Message Date
802ee8a1ff Merge pull request #2419 from oqtane/master
Merge pull request #2418 from oqtane/dev
2022-09-13 09:18:35 -04:00
e312970212 Merge pull request #2418 from oqtane/dev
3.2.0 release
2022-09-13 09:18:16 -04:00
c0f4069a9b Merge pull request #2417 from sbwalker/dev
refactor IndexedDB interop functions
2022-09-13 07:44:10 -04:00
654352827e refactor IndexedDB interop functions 2022-09-13 07:42:27 -04:00
7dd210976d Merge pull request #2416 from sbwalker/dev
optimize assembly list retrieval
2022-09-12 16:21:11 -04:00
5302be8bc1 optimize assembly list retrieval 2022-09-12 16:19:32 -04:00
9ed7181e28 Merge pull request #2415 from sbwalker/dev
remove unnecessary using statements
2022-09-12 14:56:35 -04:00
23ae4b01cb remove unnecessary using statements 2022-09-12 14:54:31 -04:00
59764d3378 Merge pull request #2414 from sbwalker/dev
cache assemblies in IndexedDB on WebAssembly
2022-09-12 14:48:17 -04:00
b8e2c729c1 cache assemblies in IndexedDB on WebAssembly 2022-09-12 14:46:46 -04:00
530d80a011 Merge pull request #2412 from sbwalker/dev
optimize assembly loading for MAUI to use client storage
2022-09-11 10:50:20 -04:00
2d306e8fda optimize assembly loading for MAUI to use client storage 2022-09-11 10:48:40 -04:00
b3d9a70fd1 Merge pull request #2410 from sbwalker/dev
remove Oqtane.Server from Oqtane.Maui solution
2022-09-09 11:40:06 -04:00
b880207f61 remove Oqtane.Server from Oqtane.Maui solution 2022-09-09 11:37:33 -04:00
ee76d02999 Merge pull request #2409 from sbwalker/dev
improvements to run in Android Emulator
2022-09-09 10:27:45 -04:00
804c33a375 improvements to run in Android Emulator 2022-09-09 10:26:13 -04:00
de784714d9 Merge pull request #2408 from sbwalker/dev
fix issue in upgrade logic for making folder paths cross platform
2022-09-08 15:44:34 -04:00
2404e26b61 fix issue in upgrade logic for making folder paths cross platform 2022-09-08 15:43:03 -04:00
b191fdda2c Merge pull request #2407 from sbwalker/dev
prepare for 3.2.0
2022-09-08 15:30:05 -04:00
e8adfd45d2 prepare for 3.2.0 2022-09-08 15:28:25 -04:00
7158595801 Merge pull request #2406 from orionlaw/dev
Make sure Job date times are stored in the database as UTC.
2022-09-08 09:14:31 -04:00
ba97f63338 Make sure Job date times are stored in the database as UTC. This is required if using Postgres or you will get an exception with a message of “Cannot write DateTime with Kind=Unspecified to PostgreSQL type 'timestamp with time zone', only UTC is supported.”. 2022-09-07 12:46:24 -06:00
62eca2aedc Merge pull request #2401 from chlupac/BackslashFix
Backslash fix.
2022-09-06 10:53:48 -04:00
b15f6b1fa7 Merge pull request #2402 from chlupac/GitignoreUpdate
Gitignore update
2022-09-06 10:53:05 -04:00
5b22de589c Merge pull request #2403 from sbwalker/dev
Fix #2399 - page paths not being validated for deleted pages
2022-09-06 10:52:34 -04:00
d1f50f12af Fix #2399 - page paths not being validated for deleted pages 2022-09-06 10:50:53 -04:00
d40c1d9b31 Backslash fix. 2022-09-06 09:14:58 +02:00
b69041d4af Gitignore update 2022-09-06 09:14:42 +02:00
64c5d9a09f Merge pull request #2400 from sbwalker/dev
more changes to support Default pane
2022-09-05 15:51:22 -04:00
dd170bb41a more changes to support Default pane 2022-09-05 15:49:38 -04:00
1e6e4033f8 Merge pull request #2398 from sbwalker/dev
changed UrlParameterTemplate name for consistency
2022-09-04 09:48:48 -04:00
01fabc8d9e changed UrlParameterTemplate name for consistency 2022-09-04 09:47:03 -04:00
2ca2539b53 Merge pull request #2397 from sbwalker/dev
fix #2366 - populate new UrlParameters property
2022-09-04 09:37:09 -04:00
51e2e2966f fix #2366 - populate new UrlParameters property 2022-09-04 09:35:18 -04:00
55d02d2db5 Merge pull request #2396 from sbwalker/dev
Fix #2382 - Admin pane improvements
2022-09-02 18:11:51 -04:00
282a0b0c44 Fix #2382 - Admin pane improvements 2022-09-02 18:10:13 -04:00
8432779b23 Merge pull request #2395 from sbwalker/dev
added public Refresh method to FileManager
2022-09-02 09:12:41 -04:00
13b9982461 added public Refresh method to FileManager 2022-09-02 09:11:00 -04:00
d76b8cebdc Merge pull request #2389 from sbwalker/dev
Changes for .NET MAUI on Android
2022-08-31 16:35:53 -04:00
80315ae6d4 Changes for .NET MAUI on Android 2022-08-31 16:34:14 -04:00
95e7344286 Merge pull request #2385 from sbwalker/dev
moved hierarchical ordering logic to server for pages and folders
2022-08-30 07:33:46 -04:00
28f73727b5 moved hierarchical ordering logic to server for pages and folders 2022-08-30 07:31:56 -04:00
68f5bf5759 Merge pull request #2384 from sbwalker/dev
made folder paths cross platform, introduced file handler for abstracting the serving of files, enabled url mapping for broken file links, resolved public folder deletion issue
2022-08-30 07:23:50 -04:00
075748d697 made folder paths cross platform, introduced file handler for abstracting the serving of files, enabled url mapping for broken file links, resolved public folder deletion issue 2022-08-30 07:21:52 -04:00
e8d86f94f2 Merge pull request #2376 from sbwalker/dev
fix rootnamespace
2022-08-19 16:12:32 -04:00
d6bb802892 fix rootnamespace 2022-08-19 16:10:27 -04:00
52680e9002 Merge pull request #2375 from sbwalker/dev
prepare for 3.2.0
2022-08-19 15:59:35 -04:00
d6385d82ae prepare for 3.2.0 2022-08-19 15:57:31 -04:00
d058de067c Merge pull request #2374 from sbwalker/dev
Prepare for 3.2.0 release
2022-08-19 15:56:39 -04:00
32d6d143dd Prepare for 3.2.0 release 2022-08-19 15:54:33 -04:00
b49432802b Merge pull request #2373 from sbwalker/dev
Improvements to richtexteditor to allow file management in raw html editor. Also allow disabling of raw html editor which can be utilized via new setting in Html/Text module.
2022-08-19 15:34:43 -04:00
99d4d75d8e Improvements to richtexteditor to allow file management in raw html editor. Also allow disabling of raw html editor which can be utilized via new setting in Html/Text module. 2022-08-19 15:32:30 -04:00
1f584d57ac Merge pull request #2372 from sbwalker/dev
optimize Url Parameters and implement in Event Log
2022-08-18 16:06:34 -04:00
2c1543aa82 optimize Url Parameters and implement in Event Log 2022-08-18 16:04:30 -04:00
4390cbbfae Update README.md 2022-08-18 08:35:49 -04:00
bbf9e5717e Merge pull request #2370 from sbwalker/dev
improve support for module content editors
2022-08-16 17:27:54 -04:00
c7edc28bd9 improve support for module content editors 2022-08-16 17:25:46 -04:00
6e0de6f7bf Merge pull request #2369 from sbwalker/dev
check for existence of appsettings.json on Maui
2022-08-16 09:42:14 -04:00
3659422165 check for existence of appsettings.json on Maui 2022-08-16 09:40:03 -04:00
1af30da44e Merge pull request #2368 from sbwalker/dev
trim list of pages allowed to be Home Page
2022-08-16 08:44:53 -04:00
56c082cb26 trim list of pages allowed to be Home Page 2022-08-16 08:42:47 -04:00
e8eca582de Merge pull request #2363 from sbwalker/dev
added ability to specify a site home page, updated default template content to include .NET MAUI
2022-08-15 17:03:34 -04:00
4084b352de added ability to specify a site home page, updated default template content to include .NET MAUI 2022-08-15 17:01:20 -04:00
633e4acf0e Merge pull request #2362 from sbwalker/dev
add Site option for specifying a Hosting Model of Blazor Hybrid
2022-08-15 09:32:42 -04:00
468df15d80 add Site option for specifying a Hosting Model of Blazor Hybrid 2022-08-15 09:30:36 -04:00
f4537b4fcb Merge pull request #2361 from sbwalker/dev
optimize site router
2022-08-14 11:24:43 -04:00
8bca345b45 optimize site router 2022-08-14 11:22:39 -04:00
ee80712c77 Merge pull request #2360 from sbwalker/dev
resolve issue with deleted pages and modules caused by refactoring
2022-08-12 18:04:51 -04:00
3cf7153f44 resolve issue with deleted pages and modules caused by refactoring 2022-08-12 18:02:45 -04:00
8e2fc75e48 Update README.md 2022-08-12 17:09:15 -04:00
4aa51c8583 Merge pull request #2359 from sbwalker/dev
performance improvements to reduce http and database interactions
2022-08-12 16:49:59 -04:00
3c6ebd7742 performance improvements to reduce http and database interactions 2022-08-12 16:47:51 -04:00
b85539dc17 Merge pull request #2358 from sbwalker/dev
add ability to dynamically set module title and visible from components
2022-08-12 13:08:14 -04:00
4cae3f02ed add ability to dynamically set module title and visible from components 2022-08-12 13:05:48 -04:00
469b436f10 Merge pull request #2356 from dkoeder/dev
Some methods failing in BaseEntityBuilder if Schema is not null.
2022-08-12 10:45:14 -04:00
66e3e6729b Merge pull request #2357 from sbwalker/dev
add support for preserving state when loading admin components
2022-08-12 10:45:05 -04:00
fc6a794714 add support for preserving state when loading admin components 2022-08-12 10:43:00 -04:00
d75ed3d5ac Update BaseEntityBuilder.cs
Some methods failing in BaseEntityBuilder if Schema is not null.
2022-08-11 16:15:09 -06:00
bd0a218214 Merge pull request #2355 from sbwalker/dev
Blazor Hybrid / .NET MAUI support
2022-08-11 17:11:45 -04:00
f96129fa37 Blazor Hybrid / .NET MAUI support 2022-08-11 17:09:32 -04:00
920418618a Update README.md 2022-08-10 08:30:38 -04:00
29247481a6 Merge pull request #2351 from ajahangard/patch-1
#Bug in passing Lifetime property to GenerateToken
2022-08-09 10:47:28 -04:00
773710aeef #Bug in passing Lifetime property to GenerateToken
Audience is passed to GenerateToken instead of Lifetime.
2022-08-09 15:32:52 +04:30
d0c8ee57e6 Merge pull request #2348 from sbwalker/dev
Fix satellite assembly loading issue when running on WebAssembly
2022-08-08 10:49:42 -04:00
cf2adc7f6a Fix satellite assembly loading issue when running on WebAssembly 2022-08-08 10:47:33 -04:00
b621f24540 Merge pull request #2342 from sbwalker/dev
Fix #2336 - error.png path incorrect
2022-08-06 16:27:45 -04:00
99be638525 Fix #2336 - error.png path incorrect 2022-08-06 16:27:24 -04:00
d35c204e07 Merge pull request #2341 from sbwalker/dev
Fix #2339 - refactor module upgrade logic to  remove requirement on ServerManagerType for modules which have no backend
2022-08-06 16:13:53 -04:00
d8b4267668 Fix #2339 - refactor module upgrade logic to remove requirement on ServerManagerType for modules which have no backend 2022-08-06 16:13:28 -04:00
3c2f3be451 Merge pull request #2338 from chlupac/TruncateAgent
Truncate UserAgent for save to Visitors table #2337
2022-08-05 09:16:54 -04:00
e846cf8672 Truncate UserAgent for save to Visitors table 2022-08-04 16:14:39 +02:00
8804bce6c0 Merge pull request #2331 from sbwalker/dev
add proper translation keys for ActionLink and ActionDialog into RESX for Module Creator template
2022-08-03 08:50:12 -04:00
83acda6d05 add proper translation keys for ActionLink and ActionDialog into RESX for Module Creator template 2022-08-03 08:49:47 -04:00
063719532f Merge pull request #2328 from sbwalker/dev
include ResourceType attribute in Settings component for external module template
2022-08-02 07:55:38 -04:00
7b1b061355 include ResourceType attribute in Settings component for external module template 2022-08-02 07:55:11 -04:00
ed4540887e Merge pull request #2326 from leigh-pointer/Bootstrap5.2
Formating issues with Bootstrap 5.2
2022-08-02 07:51:33 -04:00
6968476ed0 Merge pull request #2327 from leigh-pointer/ScreenProgess
Added Progress Indicator
2022-08-02 07:50:59 -04:00
5d2c7c3058 Added Progress Indicator
When deleting large blocks of Pages, Modules or Notifications there was currently no visual feedback so added the ModuleInstance.ShowProgressIndicator() and ModuleInstance.HideProgressIndicator() calls to these processes.
2022-08-02 10:37:04 +02:00
e6cb90e545 Formating issues with Bootstrap 5.2 2022-08-02 08:55:42 +02:00
ec73f4dbea Merge pull request #2322 from leigh-pointer/Bootstrap5.2
Updated Bootstrap to 5.2
2022-08-01 17:36:10 -04:00
4f41a52ee7 Merge pull request #2325 from sbwalker/dev
fix upgrade issue for framework translations, improvements for managing module translations
2022-08-01 17:05:57 -04:00
c097956fcb fix upgrade issue for framework translations, improvements for managing module translations 2022-08-01 17:05:18 -04:00
8cbc17ed98 Theme Creator updated to Bootstrap 5.2.0 2022-07-28 20:59:52 +02:00
50d89d0f13 Updated Bootstrap to 5.2
Replaced Bootstrap cloudflare versions and Integrity keys to match 5.2.0
2022-07-28 20:52:11 +02:00
7b4d13b73e Merge pull request #2318 from oqtane/master
Merge pull request #2317 from oqtane/dev
2022-07-27 16:16:01 -04:00
2909aa1656 Merge pull request #2317 from oqtane/dev
3.1.4 release
2022-07-27 16:15:43 -04:00
64131b6764 Update README.md 2022-07-27 08:54:48 -04:00
0b78d75a21 Merge pull request #2314 from sbwalker/dev
prepare for 3.1.4 release
2022-07-26 17:22:27 -04:00
b35c342960 prepare for 3.1.4 release 2022-07-26 17:22:06 -04:00
24c858d379 Merge pull request #2313 from sbwalker/dev
support for module translation download/install
2022-07-26 14:44:25 -04:00
b8a31a8be9 support for module translation download/install 2022-07-26 14:44:06 -04:00
02c30d6454 Merge pull request #2312 from sbwalker/dev
add ability to supply connection string in Add Site
2022-07-26 10:13:14 -04:00
985f003e6d add ability to supply connection string in Add Site 2022-07-26 10:12:54 -04:00
98045e1e2e Merge pull request #2311 from sbwalker/dev
introduce ITransientService interface for auto registration of transient services (for DBContexts and Repositories)
2022-07-26 09:42:12 -04:00
5762ce58a4 introduce ITransientService interface for auto registration of transient services (for DBContexts and Repositories) 2022-07-26 09:41:42 -04:00
2787ee71fc Merge pull request #2310 from sbwalker/dev
Allow for entry of raw connection string during installation
2022-07-26 07:48:29 -04:00
e61a6df4d7 Allow for entry of raw connection string during installation 2022-07-26 07:48:04 -04:00
0a5c2ecbf5 Merge pull request #2304 from sbwalker/dev
optimize satellite assembly loading based on the new model where all cultures are available
2022-07-21 16:02:48 -04:00
6bfab696ad optimize satellite assembly loading based on the new model where all cultures are available 2022-07-21 16:02:23 -04:00
928f2dd496 Merge pull request #2302 from chlupac/FileServiceFix
FileService fix
2022-07-20 08:23:41 -04:00
bcf75892f7 FileService fix 2022-07-20 10:39:13 +02:00
594761385f Merge pull request #2301 from sbwalker/dev
add Environment to System Info
2022-07-19 14:34:10 -04:00
d05fba06ec add Environment to System Info 2022-07-19 14:33:51 -04:00
25155b1b38 Merge pull request #2295 from chlupac/AppSettingsFix
Fixed loading of alternative appsettings "appsettings.{env.EnvironmentName}.json"
2022-07-19 14:20:08 -04:00
ded6c9c199 Merge pull request #2299 from chlupac/InstallManFix
Fix - InstallationManager crash when package folders are missing
2022-07-19 13:12:43 -04:00
c62e6c0045 Merge pull request #2300 from sbwalker/dev
performance optimization for permissions
2022-07-19 10:49:52 -04:00
b3feda9fd1 performance optimization for permissions 2022-07-19 10:49:33 -04:00
7ef8e2c8b8 Fix - InstallationManager crash when package folders are missing 2022-07-19 09:42:12 +02:00
d5ff211871 Fixed loading of alternative appsettings "appsettings.{env.EnvironmentName}.json" 2022-07-18 21:34:59 +02:00
51c23e3842 Merge pull request #2294 from sbwalker/dev
use package name as a convention for identifying satellite assemblies
2022-07-18 13:14:53 -04:00
557b30815e use package name as a convention for identifying satellite assemblies 2022-07-18 13:14:34 -04:00
145459bfc3 Merge pull request #2292 from sbwalker/dev
Added version to Language Management, improved framework performance by loading languages into PageState, include all supported cultures and allow Administrator to add any language to a site regardless of translation availability, fix translation upgrade issue
2022-07-16 10:00:20 -04:00
f97a6a2bee Added version to Language Management, improved framework performance by loading languages into PageState, include all supported cultures and allow Administrator to add any language to a site regardless of translation availability, fix translation upgrade issue 2022-07-16 09:59:47 -04:00
1134422891 Merge pull request #2291 from sbwalker/dev
Fix #2282 - dynamically determine framework path when scaffolding project references
2022-07-15 16:00:23 -04:00
6012275c7b Fix #2282 - dynamically determine framework path when scaffolding project references 2022-07-15 15:59:55 -04:00
2f07063375 Merge pull request #2288 from sbwalker/dev
Fix #2285 - handle scenario where the module definition associated to a module instance does not exist
2022-07-14 16:58:43 -04:00
310d1ed485 Fix #2285 - handle scenario where the module definition associated to a module instance does not exist 2022-07-14 16:58:16 -04:00
a48edbb16e Merge pull request #2287 from sbwalker/dev
fixed issue in default site template where MIT License module was being created in invalid pane
2022-07-14 09:11:16 -04:00
d6258409fc fixed issue in default site template where MIT License module was being created in invalid pane 2022-07-14 09:10:51 -04:00
1b94b1247b Merge pull request #2284 from sbwalker/dev
Fix #2280 - add 404 page on upgrade, Fix #2279 add message indicating a restart is required to activate scheduled jobs after installation, add Package Name to Module and Theme management
2022-07-13 15:19:07 -04:00
9ef63ae60e Fix #2280 - add 404 page on upgrade, Fix #2279 add message indicating a restart is required to activate scheduled jobs after installation, add Package Name to Module and Theme management 2022-07-13 15:18:41 -04:00
f99de4be48 Merge pull request #2272 from sbwalker/dev
Support for module editors by exposing Edit Mode in the Control Panel
2022-07-06 17:25:43 -04:00
80fd1820c2 Support for module editors by exposing Edit Mode in the Control Panel 2022-07-06 17:25:08 -04:00
0a4a983d20 Merge pull request #2269 from leigh-pointer/Resx
Added Missing Resource from Oqtane Theme Settings
2022-07-06 08:37:25 -04:00
c2cc830691 Added Missing Resource from Oqtane Theme Settings
Missing Resource string added
2022-07-04 21:37:42 +02:00
4d0490d1c6 Merge pull request #2260 from sbwalker/dev
FIx issue with redirect after site delete and remove tenant if it is empty
2022-06-28 08:17:39 -04:00
02d1838547 FIx issue with redirect after site delete and remove tenant if it is empty 2022-06-28 08:17:06 -04:00
5c6edff778 Update README.md 2022-06-27 16:35:50 -04:00
7cbca32ddd Update README.md 2022-06-27 16:16:58 -04:00
8950e315f8 Merge pull request #2258 from oqtane/master
Merge pull request #2257 from oqtane/dev
2022-06-27 16:12:27 -04:00
a703df40c0 Merge pull request #2257 from oqtane/dev
3.1.3 release
2022-06-27 16:12:05 -04:00
225fce8810 Merge pull request #2256 from sbwalker/dev
3.1.3 data provider packages
2022-06-27 15:43:42 -04:00
bdc0f0fcdd 3.1.3 data provider packages 2022-06-27 15:43:24 -04:00
5bbb8c4858 Merge pull request #2255 from sbwalker/dev
Add schema support to BaseEntityBuilder
2022-06-27 13:55:08 -04:00
35b9551bfb Add schema support to BaseEntityBuilder 2022-06-27 13:54:52 -04:00
c8c5a05b39 Merge pull request #2254 from sbwalker/dev
Fix #2249 Fix #2250 - issues with site deletion
2022-06-27 13:46:02 -04:00
2771f0301a Fix #2249 Fix #2250 - issues with site deletion 2022-06-27 13:45:42 -04:00
c4f04edc59 Merge pull request #2253 from sbwalker/dev
Fix #2252 - unable to insert images into rich text editor
2022-06-27 12:24:16 -04:00
5530422846 Fix #2252 - unable to insert images into rich text editor 2022-06-27 12:23:55 -04:00
ce77c81fb5 Merge pull request #2248 from sbwalker/dev
prepare for 3.1.3
2022-06-21 09:25:33 -04:00
bc488d4ac2 prepare for 3.1.3 2022-06-21 09:25:14 -04:00
fe72a10346 Merge pull request #2247 from sbwalker/dev
fix #2239 - email notification encoding to support all cultures
2022-06-20 19:42:50 -04:00
0da88398b4 fix #2239 - email notification encoding to support all cultures 2022-06-20 19:42:32 -04:00
c42de3da20 Merge pull request #2246 from sbwalker/dev
fix #2245 - default database type not set correctly when adding new site for any DB other than LocalDB, added Source: info to all extension installation scenarios now that the Registry supports both Nuget and GitHub locations
2022-06-20 17:45:10 -04:00
4bf9f36baa fix #2245 - default database type not set correctly when adding new site for any DB other than LocalDB, added Source: info to all extension installation scenarios now that the Registry supports both Nuget and GitHub locations 2022-06-20 17:44:49 -04:00
380cb192c7 Merge pull request #2244 from sbwalker/dev
allow multiple aliases to be defined as default
2022-06-18 09:18:47 -04:00
8882e19ec5 allow multiple aliases to be defined as default 2022-06-18 09:18:23 -04:00
7f52059b98 Merge pull request #2243 from sbwalker/dev
added extension method for creating a LocalizerFactory using a type name, refactored Pager and LocalizableComponent to use LocalizerFactory
2022-06-15 16:19:51 -04:00
1ce3cc4d7c added extension method for creating a LocalizerFactory using a type name, refactored Pager and LocalizableComponent to use LocalizerFactory 2022-06-15 16:19:22 -04:00
657c71e94d Merge pull request #2242 from leigh-pointer/Issue#2234
Fix for Recycle bin not showing Deleted Date (Issue #2234)
2022-06-14 10:26:50 -04:00
c8cfb3c7b7 Fix for Recycle bin not showing Deleted Date (Issue #2234)
Added the Deleted data to Module from the PageModule
2022-06-14 09:22:12 +02:00
6e7e90acf4 Merge pull request #2241 from sbwalker/dev
additional changes for #2228
2022-06-13 09:10:20 -04:00
6d3a556d34 additional changes for #2228 2022-06-13 09:10:01 -04:00
7d9188b659 Merge pull request #2238 from chlupac/LogExceptions
Exception is not saved to log
2022-06-09 15:13:10 -04:00
4f0a805c79 Exception is not saved to log 2022-06-09 10:24:13 +02:00
53f3320492 Merge pull request #2228 from chlupac/Log_notification_improvement
Log notification improvement
2022-06-08 15:48:17 -04:00
f9ce51b4a5 Merge pull request #2222 from hishamco/pager
Pager should inherits from LocalizableComponent
2022-06-08 15:47:25 -04:00
f8bf432c0d Merge pull request #2237 from sbwalker/dev
Improvements for #2229 - relax userrole restrictions
2022-06-08 15:47:12 -04:00
a822482172 Improvements for #2229 - relax userrole restrictions 2022-06-08 15:46:36 -04:00
b22f8a0b02 Remove generic type suffix properly 2022-06-08 00:12:10 +03:00
227331bf24 Merge branch 'dev' into pager 2022-06-07 23:37:08 +03:00
744688cbe1 Merge pull request #2235 from sbwalker/dev
Fix #2230 - add support for an Unauthenticated User global role
2022-06-07 15:26:05 -04:00
79c8126c4a Fix #2230 - add support for an Unauthenticated User global role 2022-06-07 15:25:44 -04:00
0b7c8e4ef7 Merge pull request #2232 from ijaz-saeed/dev
missing translation keys for module names
2022-06-07 15:23:08 -04:00
35e00f61d8 Update README.md 2022-06-07 09:38:42 -04:00
aba3d58df8 missing translation keys for module names 2022-06-06 10:12:00 +05:00
45984a8166 Merge pull request #2231 from sbwalker/dev
Improvements for #2221 - validate file extensions client-side before initiating upload, validate file extension server-side before writing part to disk, optimize part cleanup logic, add error handling to JavaScript XMLHttpRequest, ensure FileInput gets initialized after upload
2022-06-04 15:41:38 -04:00
ea5655ae42 Improvements for #2221 - validate file extensions client-side before initiating upload, valid file extension server-side before writing part to disk, optimize cleanup logic, add error handling to JavaScript XMLHttpRequest, ensure FileInput gets initialized after upload 2022-06-04 15:40:26 -04:00
f06cb0dfbb Log notification improvement 2022-05-31 11:33:42 +02:00
1abae55976 Remove magic string 2022-05-31 11:59:17 +03:00
a83ed40ec4 Avoid breaking changes 2022-05-31 10:40:42 +03:00
75ecae4672 Merge pull request #2223 from hishamco/toggle-password
Avoid toggle password & confirm password as same time when one button is clicked
2022-05-28 21:59:40 -04:00
16a6f942c5 Avoid toggle password & confirm password as same time when one button is clicked 2022-05-27 16:20:35 +03:00
aa98508e57 Remove the entry from SharedResources 2022-05-27 15:37:13 +03:00
583383aee1 Pager should inherits from LocalizableComponent 2022-05-27 15:33:48 +03:00
13f69f81d7 Merge pull request #2219 from sbwalker/dev
fix #2213 - disabling show on all pages
2022-05-26 01:19:35 -04:00
43c34fcd64 fix #2213 - disabling show on all pages 2022-05-26 01:19:14 -04:00
c272238539 Merge pull request #2217 from leigh-pointer/MissingRes2215
Fix for missing Resx entries #2215
2022-05-25 07:23:01 -04:00
6e0d2706a8 Fix for missing Resx entries #2215
Updated the Resx File with the missing entries.  

However the UserProfile\Add cant be Localized at present as the IStringLocalizer is not loaded when that property assignment is executed.
2022-05-25 11:13:38 +02:00
25a7289ce8 Merge pull request #2210 from leigh-pointer/PageOf
Fix for #2209 Localization Pager component (Page @_page of @_pages)
2022-05-24 21:47:12 -04:00
1ba7d045e4 Merge pull request #2211 from elgransan/dev
* Collapse menu after click on a page (mobile version)
2022-05-24 21:46:11 -04:00
a4d75befe7 * Collapse menu after click on a page (mobile version) 2022-05-22 20:05:01 -03:00
88377529bc Fix for #2209 Localization Pager component (Page @_page of @_pages)
Added fixe for issue.  Added the resource "PageOfPages" to SharedResources as trying to inject IStringLocalizer<Pager<TableItem>> and adding Pager.resx the resource failed to load.
2022-05-22 20:23:17 +02:00
91b9a0280f Merge pull request #2207 from sbwalker/dev
Add filtering by Category to Module Management - default to Common
2022-05-21 10:11:24 -04:00
25173ae85c Ddd filtering by Category to Module Management - default to Common 2022-05-21 10:10:57 -04:00
ad3350705e Update README.md 2022-05-19 11:44:38 -04:00
a16bc5db28 Merge pull request #2200 from oqtane/master
Merge pull request #2199 from oqtane/dev
2022-05-14 09:49:24 -04:00
51657338f5 Merge pull request #2199 from oqtane/dev
3.1.2 release
2022-05-14 09:48:59 -04:00
0fe3ea25af Merge pull request #2198 from sbwalker/dev
remove columns from main user management view  and migrate them to edit view
2022-05-13 17:00:32 -04:00
806daaf7c9 remove columns from main user management view and migrate them to edit view 2022-05-13 17:00:10 -04:00
21ff4a83b5 Merge pull request #2197 from sbwalker/dev
resolve login issue related to 'LoginOptions:TwoFactor' and order list of files alphabetically
2022-05-13 12:03:57 -04:00
ecc9aa40d7 resolve login issue related to 'LoginOptions:TwoFactor' and order list of files alphabetically 2022-05-13 12:03:34 -04:00
c34ca2a59b Merge pull request #2196 from sbwalker/dev
prepare for 3.1.2
2022-05-12 20:55:26 -04:00
dde7094fe3 prepare for 3.1.2 2022-05-12 20:55:11 -04:00
105afdfefc Merge pull request #2195 from sbwalker/dev
fix #2192 - Adding a new site fails
2022-05-12 20:42:20 -04:00
4c254a8686 fix #2192 - Adding a new site fails 2022-05-12 20:42:05 -04:00
33ca203e57 Merge pull request #2194 from sbwalker/dev
updated resource file
2022-05-12 13:56:01 -04:00
2ff4133cd4 updated resource file 2022-05-12 13:55:47 -04:00
49ad85713e Merge pull request #2193 from sbwalker/dev
add support for external login parameters and improve diagnostic messages related to claims
2022-05-12 13:52:06 -04:00
1978bf151f add support for external login parameters and improve diagnostic messages related to claims 2022-05-12 13:51:46 -04:00
506378de82 Merge pull request #2191 from sbwalker/dev
fix #2185 - alias auto registration including trailing slash
2022-05-10 08:03:55 -04:00
53ead7a03f fix #2185 - alias auto registration including trailing slash 2022-05-10 08:03:38 -04:00
ab979fd63c Merge pull request #2189 from sbwalker/dev
fix #2180 - Error in Module Creator if the template is not set
2022-05-09 11:35:17 -04:00
1e84a2238b fix #2180 - Error in Module Creator if the template is not set 2022-05-09 11:35:01 -04:00
5618adf86c Merge pull request #2187 from sbwalker/dev
fix #2182 - modifications to address MySQL compatibility issues
2022-05-08 22:14:54 -04:00
345b0bc95f fix #2182 - modifications to address MySQL compatibility issues 2022-05-08 22:14:26 -04:00
b1d6c35e99 Merge pull request #2183 from leigh-pointer/UserEmail
Args not in sink
2022-05-06 11:43:59 -04:00
d767f1a101 Args not in sink
The Display name and email address  not is the correct order!
2022-05-06 12:43:40 +02:00
c15f2b9a12 Merge pull request #2178 from leigh-pointer/UserEmail
Added the User Email field to the List
2022-05-05 17:14:08 -04:00
a21a53662b Update for real-estate
Removed Name
Removed Seconds from DateTime fields
Added Name to the Email link
2022-05-05 16:35:43 +02:00
ebb5340019 Merge pull request #2177 from leigh-pointer/NotIficationDateFormat
Updated the CreatedOn date format
2022-05-05 10:13:24 -04:00
6108bd214e Merge pull request #2179 from sbwalker/dev
fix #2176 - update LastIPAddress correctly during login
2022-05-05 09:57:26 -04:00
eed27e101a fix #2176 - update LastIPAddress correctly during login 2022-05-05 09:57:09 -04:00
2767680bed Added the User Email field to the List
Added the formatted email address of the user to the list view.
2022-05-05 13:25:35 +02:00
4080e30b6f Updated the CreatedOn date format
Updated the format to a more readable format of dd-MMM-yyyy
2022-05-05 13:07:09 +02:00
e89257be62 Merge pull request #2174 from sbwalker/dev
fix #2172 - File Upload issue caused by JS Interop not passing AntiForgery token in POST method
2022-05-04 17:15:10 -04:00
d3c40a7e8b fix #2172 - File Upload issue caused by JS Interop not passing AntiForgery token in POST methid 2022-05-04 17:14:45 -04:00
60657d5d25 Update README.md 2022-05-03 08:13:34 -04:00
71d5ae0e68 Merge pull request #2169 from oqtane/master
Merge pull request #2168 from oqtane/dev
2022-05-03 08:08:51 -04:00
46b8d202c7 Merge pull request #2168 from oqtane/dev
3.1.1 release
2022-05-03 08:08:34 -04:00
bc193a6470 Merge pull request #2167 from sbwalker/dev
remove custom module assets not part of framework
2022-05-03 07:54:30 -04:00
577528fa0a remove custom module assets not part of framework 2022-05-03 07:54:14 -04:00
35e78ea633 Merge pull request #2166 from sbwalker/dev
3.1.1 database providers, default module creator version to local install version
2022-05-02 17:08:56 -04:00
d5d4f85003 3.1.1 database providers, default module creator version to local install version 2022-05-02 17:08:29 -04:00
c6a49a6f5d Merge pull request #2164 from sbwalker/dev
enhance UserRole service with filtering and moved workload to server for better performance, improve error message details during installation
2022-04-29 21:39:36 -04:00
a3ff9373a2 enhance UserRole service with filtering and moved workload to server for better performance, improve error message details during installation 2022-04-29 21:39:11 -04:00
e6d2e74b17 Merge pull request #2162 from sbwalker/dev
refactor module upgrade logic, implement for themes and translations
2022-04-27 19:29:49 -04:00
e8464206e7 refactor module upgrade logic, implement for themes and translations 2022-04-27 19:29:29 -04:00
b6c4934123 Merge pull request #2161 from 2sic-forks/dev
Fix #2160 - upgrade module option
2022-04-27 15:03:46 -04:00
8ee83f738b fix# https://github.com/oqtane/oqtane.framework/issues/2160 2022-04-27 17:44:54 +02:00
c359300375 Merge pull request #2158 from sbwalker/dev
fix path on app-stylesheets
2022-04-26 16:34:08 -04:00
eb3361fa07 fix path on app-stylesheets 2022-04-26 16:33:50 -04:00
1b53da6749 Merge pull request #2155 from sbwalker/dev
external login improvements
2022-04-25 20:05:03 -04:00
c701895e29 external login improvements 2022-04-25 20:04:43 -04:00
e81222821e Merge pull request #2153 from sbwalker/dev
prepare for 3.1.1 release
2022-04-24 20:20:02 -04:00
cbca8c9e93 prepare for 3.1.1 release 2022-04-24 20:19:44 -04:00
92c4edacf2 Merge pull request #2152 from sbwalker/dev
completed antiforgery implementation, improved external login claim mapping, principal construction, and user experience
2022-04-22 17:54:49 -04:00
e4c648ee92 completed antiforgery implementation, improved external login claim mapping, principal construction, and user experience 2022-04-22 17:54:20 -04:00
69f6586aa9 Merge pull request #2149 from sbwalker/dev
Fix #2144 - install issue, Fix #2146 - move file issue, require verification of external login account linkage
2022-04-20 16:01:21 -04:00
391713b84d Fix #2144 - install issue, Fix #2146 - move file issue, require verification of external login account linkage 2022-04-20 16:00:58 -04:00
8c6a25e4b4 Merge pull request #2140 from sbwalker/dev
remove web.release.config as it causes installation issues in pure .net core environments (see #1957)
2022-04-15 09:23:14 -04:00
250701aff0 remove web.release.config as it causes installation issues in pure .net core environments (see #1957) 2022-04-15 09:22:51 -04:00
2dfd1bd5a2 Merge pull request #2139 from sbwalker/dev
removed method-level [ValidateAntiForgeryToken] attribute as it is now handled by global AutoValidateAntiforgeryTokenFilter, adjusted gitignore to improve filtering of Module and Theme folders in wwwroot and exclude all files in Oqtane.Server/Data
2022-04-15 08:02:04 -04:00
1c7380d4cf removed method-level [ValidateAntiForgeryToken] attribute as it is now handled by global AutoValidateAntiforgeryTokenFilter, adjusted gitignore to improve filtering of Module and Theme folders in wwwroot and exclude all files in Oqtane.Server/Data 2022-04-15 08:01:32 -04:00
68d9ac88b3 Merge pull request #2138 from sbwalker/dev
create separate API methods for tokens (short-lived) and personal access tokens (long-lived), include global antiforgery filter to mitigate XSRF when using cookie auth (ignored when using Jwt)
2022-04-14 19:42:24 -04:00
f6b3874668 create separate API methods for tokens (short-lived) and personal access tokens (long-lived), include global antiforgery filter to mitigate XSRF when using cookie auth (ignored when using Jwt) 2022-04-14 19:41:43 -04:00
c616878a64 Merge pull request #2134 from leigh-pointer/TogglePassword
User Areas to use the Toggle Password method
2022-04-14 08:58:59 -04:00
ad485a68ce Merge pull request #2136 from leigh-pointer/UsersCreatedOn
Added CreatedOn class "align-middle" to RowClass
2022-04-14 08:58:49 -04:00
2ebba3b8e7 Added CreatedOn class "align-middle" to RowClass 2022-04-14 14:52:34 +02:00
4117e6e1c5 Merge pull request #2135 from sbwalker/dev
Fix #2128 - site settings validation issue when logged in as Administrator (not Host)
2022-04-14 08:31:29 -04:00
423ee04879 Fix #2128 - site settings validation issue when logged in as Administrator (not Host) 2022-04-14 08:30:55 -04:00
1625e3ba6c User Areas to use the Toggle Password method
Updated the Components where the Password is required to allow toggle show / hide
2022-04-14 13:47:27 +02:00
5a71ab3c20 Merge pull request #2130 from leigh-pointer/LastLoggedIn
Update to User Management
2022-04-13 19:31:10 -04:00
b5833bf556 Merge pull request #2129 from leigh-pointer/NotificationDelAll
Allow the deletion of all Notifications
2022-04-13 19:28:37 -04:00
6c31765965 Merge pull request #2132 from sbwalker/dev
fix #2125 - cannot login using WebAssembly, remove granular 404 logging as it is already managed by url mapping, make IModule ReleaseVersions optional when using EF Core migrations
2022-04-13 19:27:35 -04:00
6dc1d42d90 fix #2125 - cannot login using WebAssembly, remove granular 404 logging as it is already managed by url mapping, make IModule ReleaseVersions optional when using EF Core migrations 2022-04-13 19:27:12 -04:00
e273a954e6 Update to User Management
Updated user management to display more of the User ; Last Login Last IP and Is Authenticated.
2022-04-13 15:24:28 +02:00
a602a942c4 Allow the deletion of all Notifications
Added button to delete all the notifications for the selected filter.
2022-04-13 14:33:30 +02:00
dd32b33621 Merge pull request #2124 from sbwalker/dev
minor improvements to security features, use ActivatorUtilities.CreateInstance with SiteMigration to enable simpler DI
2022-04-12 07:47:10 -04:00
355d0405f4 minor improvements to security features, use ActivatorUtilities.CreateInstance with SiteMigration to enable simpler DI 2022-04-12 07:46:43 -04:00
c1e1595d58 Update README.md 2022-04-11 08:30:24 -04:00
ef476ac9b3 Update README.md 2022-04-11 07:56:10 -04:00
a7aaa7b18b Merge pull request #2116 from sbwalker/dev
Fix #2111 - Adding user to Host role removes all other users roles
2022-04-05 17:11:29 -04:00
3abfbab5d1 Fix #2111 - Adding user to Host role removes all other users roles 2022-04-05 17:11:13 -04:00
b158474e21 Merge pull request #2113 from oqtane/master
Merge pull request #2112 from oqtane/dev
2022-04-05 08:51:40 -04:00
cfbcc41543 Merge pull request #2112 from oqtane/dev
3.1.0 release
2022-04-05 08:51:10 -04:00
309 changed files with 8472 additions and 3688 deletions

13
.gitignore vendored
View File

@ -10,11 +10,11 @@ msbuild.binlog
*.zip
*.idea
_ReSharper.Caches
.DS_Store
Oqtane.Server/appsettings.json
Oqtane.Server/Data/*.mdf
Oqtane.Server/Data/*.ldf
Oqtane.Server/Data/*.db
Oqtane.Server/Data
/Oqtane.Server/Properties/PublishProfiles/FolderProfile.pubxml
Oqtane.Server/Content
@ -22,3 +22,10 @@ Oqtane.Server/Packages
Oqtane.Server/wwwroot/Content
Oqtane.Server/wwwroot/Packages/*.log
Oqtane.Server/wwwroot/Modules
!Oqtane.Server/wwwroot/Modules/Oqtane.Modules.*
!Oqtane.Server/wwwroot/Modules/Templates
Oqtane.Server/wwwroot/Themes
!Oqtane.Server/wwwroot/Themes/Oqtane.Themes.*
!Oqtane.Server/wwwroot/Themes/Templates

View File

@ -1,3 +1,5 @@
using System;
namespace Microsoft.Extensions.Localization
{
public static class OqtaneLocalizationExtensions
@ -18,5 +20,42 @@ namespace Microsoft.Extensions.Localization
}
return localizedValue;
}
/// <summary>
/// Creates an IStringLocalizer based on a type name. This extension method is useful in scenarios where the default IStringLocalizer is unable to locate the resources.
/// </summary>
/// <param name="localizerFactory"></param>
/// <param name="fullTypeName">the full type name ie. GetType().FullName</param>
/// <returns></returns>
public static IStringLocalizer Create(this IStringLocalizerFactory localizerFactory, string fullTypeName)
{
var typename = fullTypeName;
// handle generic types
var type = Type.GetType(fullTypeName);
if (type.IsGenericType)
{
typename = type.GetGenericTypeDefinition().FullName;
typename = typename.Substring(0, typename.IndexOf("`")); // remove generic type info
}
// format typename
if (typename.Contains(","))
{
typename = typename.Substring(0, typename.IndexOf(",")); // remove assembly info
}
// remove rootnamespace
var rootnamespace = "";
var attributes = type.Assembly.GetCustomAttributes(typeof(RootNamespaceAttribute), false);
if (attributes.Length > 0)
{
rootnamespace = ((RootNamespaceAttribute)attributes[0]).RootNamespace;
}
typename = typename.Replace(rootnamespace + ".", "");
// create IStringLocalizer using factory
return localizerFactory.Create(typename, type.Assembly.GetName().Name);
}
}
}

View File

@ -16,7 +16,7 @@ namespace Microsoft.Extensions.DependencyInjection
return services;
}
internal static IServiceCollection AddOqtaneScopedServices(this IServiceCollection services)
public static IServiceCollection AddOqtaneScopedServices(this IServiceCollection services)
{
services.AddScoped<SiteState>();
services.AddScoped<IInstallationService, InstallationService>();

View File

@ -28,7 +28,7 @@
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="pwd" HelpText="Enter the password to use for the database" ResourceKey="Pwd">Password:</Label>
<div class="col-sm-9">
<input id="pwd" type="password" class="form-control" @bind="@_pwd" />
<input id="pwd" type="password" class="form-control" @bind="@_pwd" autocomplete="new-password" />
</div>
</div>

View File

@ -40,7 +40,7 @@
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="pwd" HelpText="Enter the password to use for the database" ResourceKey="Pwd">Password:</Label>
<div class="col-sm-9">
<input id="pwd" type="password" class="form-control" @bind="@_pwd" />
<input id="pwd" type="password" class="form-control" @bind="@_pwd" autocomplete="new-password" />
</div>
</div>
}

View File

@ -4,7 +4,7 @@
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="server" HelpText="Enter the database server" ResourceKey="Server">Server:</Label>
<Label Class="col-sm-3" For="server" HelpText="Enter the database server name. This might include a port number as well if you are using a cloud service (ie. servername.database.windows.net,1433) " ResourceKey="Server">Server:</Label>
<div class="col-sm-9">
<input id="server" type="text" class="form-control" @bind="@_server" />
</div>
@ -35,7 +35,7 @@
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="pwd" HelpText="Enter the password to use for the database" ResourceKey="Pwd">Password:</Label>
<div class="col-sm-9">
<input id="pwd" type="password" class="form-control" @bind="@_pwd" />
<input id="pwd" type="password" class="form-control" @bind="@_pwd" autocomplete="new-password" />
</div>
</div>
}
@ -51,7 +51,7 @@
@if (_encryption == "true")
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="trustservercertificate" HelpText="Specify the type of certificate you are using for encryption" ResourceKey="TrustServerCertificate">Certificate:</Label>
<Label Class="col-sm-3" For="trustservercertificate" HelpText="Specify the type of certificate you are using for encryption. Verifiable is equivalent to False. Self Signed is equivalent to True." ResourceKey="TrustServerCertificate">Certificate:</Label>
<div class="col-sm-9">
<select id="encryption" class="form-select custom-select" @bind="@_trustservercertificate">
<option value="true">@Localizer["Self Signed"]</option>

View File

@ -26,28 +26,41 @@
<div class="col-sm-9">
@if (_databases != null)
{
<select id="databasetype" class="form-select custom-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))">
<div class="input-group">
<select id="databaseType" class="form-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))" required>
@foreach (var database in _databases)
{
if (database.IsDefault)
{
<option value="@database.Name" selected>@Localizer[@database.Name]</option>
}
else
{
<option value="@database.Name">@Localizer[@database.Name]</option>
}
}
</select>
@if (!_showConnectionString)
{
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionString"]</button>
}
else
{
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionParameters"]</button>
}
</div>
}
</div>
</div>
@{
@if (!_showConnectionString)
{
if (_databaseConfigType != null)
{
@DatabaseConfigComponent;
@DatabaseConfigComponent
}
}
else
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="connectionstring" HelpText="Enter a complete connection string including all parameters and delimiters" ResourceKey="ConnectionString">String:</Label>
<div class="col-sm-9">
<textarea id="connectionstring" class="form-control" @bind="@_connectionString" rows="3"></textarea>
</div>
</div>
}
</div>
</div>
<div class="col text-center">
@ -62,13 +75,19 @@
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="password" HelpText="Provide a password for the primary user account" ResourceKey="Password">Password:</Label>
<div class="col-sm-9">
<input id="password" type="password" class="form-control" @bind="@_hostPassword" />
<div class="input-group">
<input id="password" type="@_passwordType" class="form-control" @bind="@_hostPassword" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglePassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="confirm" HelpText="Please confirm the password entered above by entering it again" ResourceKey="Confirm">Confirm:</Label>
<div class="col-sm-9">
<input id="confirm" type="password" class="form-control" @bind="@_confirmPassword" />
<div class="input-group">
<input id="confirm" type="@_confirmPasswordType" class="form-control" @bind="@_confirmPassword" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@ToggleConfirmPassword">@_toggleConfirmPassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
@ -101,9 +120,15 @@
private Type _databaseConfigType;
private object _databaseConfig;
private RenderFragment DatabaseConfigComponent { get; set; }
private bool _showConnectionString = false;
private string _connectionString = string.Empty;
private string _hostUsername = string.Empty;
private string _hostPassword = string.Empty;
private string _passwordType = "password";
private string _confirmPasswordType = "password";
private string _togglePassword = string.Empty;
private string _toggleConfirmPassword = string.Empty;
private string _confirmPassword = string.Empty;
private string _hostEmail = string.Empty;
private bool _register = true;
@ -112,6 +137,9 @@
protected override async Task OnInitializedAsync()
{
_togglePassword = SharedLocalizer["ShowPassword"];
_toggleConfirmPassword = SharedLocalizer["ShowPassword"];
_databases = await DatabaseService.GetDatabasesAsync();
if (_databases.Exists(item => item.IsDefault))
{
@ -129,7 +157,7 @@
try
{
_databaseName = (string)eventArgs.Value;
_showConnectionString = false;
LoadDatabaseConfigComponent();
}
catch
@ -158,18 +186,25 @@
if (firstRender)
{
var interop = new Interop(JSRuntime);
await interop.IncludeLink("", "stylesheet", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/css/bootstrap.min.css", "text/css", "sha512-GQGU0fMMi238uA+a/bdWJfpUGKUkBdgfFdgBm72SUQ6BeyWjoY/ton0tEjH+OSH9iP4Dfh+7HM0I9f5eR0L/4w==", "anonymous", "");
await interop.IncludeScript("", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/js/bootstrap.bundle.min.js", "sha512-pax4MlgXjHEPfCwcJLQhigY7+N8rt6bVvWLFyUMuxShv170X53TRzGPmPkZmGBhk+jikR8WBM4yl7A9WMHHqvg==", "anonymous", "", "head");
await interop.IncludeLink("", "stylesheet", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.0/css/bootstrap.min.css", "text/css", "sha512-XWTTruHZEYJsxV3W/lSXG1n3Q39YIWOstqvmFsdNEEQfHoZ6vm6E9GK2OrF6DSJSpIbRbi+Nn0WDPID9O7xB2Q==", "anonymous", "");
await interop.IncludeScript("", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.0/js/bootstrap.bundle.min.js", "sha512-9GacT4119eY3AcosfWtHMsT5JyZudrexyEVzTBWV3viP/YfB9e2pEy3N7WXL3SV6ASXpTU0vzzSxsbfsuUH4sQ==", "anonymous", "", "head");
}
}
private async Task Install()
{
var connectionString = String.Empty;
if (_showConnectionString)
{
connectionString = _connectionString;
}
else
{
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
{
connectionString = databaseConfigControl.GetConnectionString();
}
}
if (connectionString != "" && !string.IsNullOrEmpty(_hostUsername) && !string.IsNullOrEmpty(_hostPassword) && _hostPassword == _confirmPassword && !string.IsNullOrEmpty(_hostEmail) && _hostEmail.Contains("@"))
{
@ -218,4 +253,42 @@
_message = Localizer["Message.Require.DbInfo"];
}
}
private void TogglePassword()
{
if (_passwordType == "password")
{
_passwordType = "text";
_togglePassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordType = "password";
_togglePassword = SharedLocalizer["ShowPassword"];
}
}
private void ToggleConfirmPassword()
{
if (_confirmPasswordType == "password")
{
_confirmPasswordType = "text";
_toggleConfirmPassword = SharedLocalizer["HidePassword"];
}
else
{
_confirmPasswordType = "password";
_toggleConfirmPassword = SharedLocalizer["ShowPassword"];
}
}
private void ToggleConnectionString()
{
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
{
_connectionString = databaseConfigControl.GetConnectionString();
}
_showConnectionString = !_showConnectionString;
}
}

View File

@ -10,8 +10,8 @@
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.Permissions))
{
string url = NavigateUrl(p.Path);
<div class="col-md-2 mx-auto text-center">
<NavLink class="nav-link" href="@url" Match="NavLinkMatch.All">
<div class="col-md-2 mx-auto text-center mb-3">
<NavLink class="nav-link text-primary" href="@url" Match="NavLinkMatch.All">
<h2><span class="@p.Icon" aria-hidden="true"></span></h2>@SharedLocalizer[p.Name]
</NavLink>
</div>

View File

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

View File

@ -132,22 +132,10 @@
_isEnabled = job.IsEnabled.ToString();
_interval = job.Interval.ToString();
_frequency = job.Frequency;
_startDate = job.StartDate;
if (job.StartDate != null && job.StartDate.Value.TimeOfDay.TotalSeconds != 0)
{
_startTime = job.StartDate.Value.ToString("HH:mm");
}
_endDate = job.EndDate;
if (job.EndDate != null && job.EndDate.Value.TimeOfDay.TotalSeconds != 0)
{
_endTime = job.EndDate.Value.ToString("HH:mm");
}
(_startDate, _startTime) = Utilities.UtcAsLocalDateAndTime(job.StartDate);
(_endDate, _endTime) = Utilities.UtcAsLocalDateAndTime(job.EndDate);
_retentionHistory = job.RetentionHistory.ToString();
_nextDate = job.NextExecution;
if (job.NextExecution != null && job.NextExecution.Value.TimeOfDay.TotalSeconds != 0)
{
_nextTime = job.NextExecution.Value.ToString("HH:mm");
}
(_nextDate, _nextTime) = Utilities.UtcAsLocalDateAndTime(job.NextExecution);
createdby = job.CreatedBy;
createdon = job.CreatedOn;
modifiedby = job.ModifiedBy;
@ -180,34 +168,10 @@
{
job.Interval = int.Parse(_interval);
}
job.StartDate = _startDate;
if (job.StartDate != null)
{
job.StartDate = job.StartDate.Value.Date;
if (!string.IsNullOrEmpty(_startTime))
{
job.StartDate = DateTime.Parse(job.StartDate.Value.ToShortDateString() + " " + _startTime);
}
}
job.EndDate = _endDate;
if (job.EndDate != null)
{
job.EndDate = job.EndDate.Value.Date;
if (!string.IsNullOrEmpty(_endTime))
{
job.EndDate = DateTime.Parse(job.EndDate.Value.ToShortDateString() + " " + _endTime);
}
}
job.StartDate = Utilities.LocalDateAndTimeAsUtc(_startDate, _startTime);
job.EndDate = Utilities.LocalDateAndTimeAsUtc(_endDate, _endTime);
job.RetentionHistory = int.Parse(_retentionHistory);
job.NextExecution = _nextDate;
if (job.NextExecution != null)
{
job.NextExecution = job.NextExecution.Value.Date;
if (!string.IsNullOrEmpty(_nextTime))
{
job.NextExecution = DateTime.Parse(job.NextExecution.Value.ToShortDateString() + " " + _nextTime);
}
}
job.NextExecution = Utilities.LocalDateAndTimeAsUtc(_nextDate, _nextTime);
try
{
@ -226,4 +190,5 @@
AddModuleMessage(Localizer["Message.Required.JobInfo"], MessageType.Warning);
}
}
}

View File

@ -33,7 +33,7 @@ else
<td>@context.Name</td>
<td>@DisplayStatus(context.IsEnabled, context.IsExecuting)</td>
<td>@DisplayFrequency(context.Interval, context.Frequency)</td>
<td>@context.NextExecution</td>
<td>@context.NextExecution?.ToLocalTime()</td>
<td>
@if (context.IsStarted)
{
@ -56,6 +56,10 @@ else
protected override async Task OnParametersSetAsync()
{
_jobs = await JobService.GetJobsAsync();
if (_jobs.Count == 0)
{
AddModuleMessage(string.Format(Localizer["Message.NoJobs"], NavigateUrl("admin/system")), MessageType.Warning);
}
}
private string DisplayStatus(bool isEnabled, bool isExecuting)

View File

@ -20,6 +20,7 @@ else
@if (_availableCultures.Count() == 0)
{
<ModuleMessage Type="MessageType.Info" Message="@_message"></ModuleMessage>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
}
else
{
@ -31,7 +32,7 @@ else
<select id="_code" class="form-select" @bind="@_code" required>
@foreach (var culture in _availableCultures)
{
<option value="@culture.Name">@culture.DisplayName</option>
<option value="@culture.Name">@(culture.DisplayName + " (" + culture.Name + ")")</option>
}
</select>
</div>
@ -47,11 +48,11 @@ else
</div>
</div>
<button type="button" class="btn btn-success" @onclick="SaveLanguage">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</form>
}
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</TabPanel>
<TabPanel Name="Download" ResourceKey="Download" Security="SecurityAccessLevel.Host">
<TabPanel Name="Translations" Heading="Translations" ResourceKey="Download" Security="SecurityAccessLevel.Host">
<div class="row justify-content-center mb-3">
<div class="col-sm-6">
<div class="input-group">
@ -78,6 +79,7 @@ else
<strong>@(String.Format("{0:n0}", context.Downloads))</strong> @SharedLocalizer["Search.Downloads"]&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Released"]: <strong>@context.ReleaseDate.ToString("MMM dd, yyyy")</strong>&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Version"]: <strong>@context.Version</strong>
@((MarkupString)(!string.IsNullOrEmpty(context.PackageUrl) ? "&nbsp;&nbsp;|&nbsp;&nbsp;" + SharedLocalizer["Search.Source"] + ": <strong>" + new Uri(context.PackageUrl).Host + "</strong>" : ""))
@((MarkupString)(context.TrialPeriod > 0 ? "&nbsp;&nbsp;|&nbsp;&nbsp;<strong>" + context.TrialPeriod + " " + @SharedLocalizer["Trial"] + "</strong>" : ""))
</td>
<td style="width: 1px; vertical-align: middle;">
@ -108,12 +110,16 @@ else
}
<button type="button" class="btn btn-success" @onclick="InstallLanguages">@SharedLocalizer["Install"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<br />
<br />
<ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" />
}
</TabPanel>
<TabPanel Name="Upload" ResourceKey="Upload" Security="SecurityAccessLevel.Host">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" HelpText="Upload one or more translations. Once they are uploaded click Install to complete the installation." ResourceKey="LanguageUpload">Language: </Label>
<Label Class="col-sm-3" HelpText="Upload one or more translations. Once they are uploaded click Install to complete the installation." ResourceKey="LanguageUpload">Translation: </Label>
<div class="col-sm-9">
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" />
</div>
@ -183,8 +189,7 @@ else
var languagesCodes = languages.Select(l => l.Code).ToList();
_supportedCultures = await LocalizationService.GetCulturesAsync();
_availableCultures = _supportedCultures
.Where(c => !c.Name.Equals(Constants.DefaultCulture) && !languagesCodes.Contains(c.Name));
_availableCultures = _supportedCultures.Where(c => !c.Name.Equals(Constants.DefaultCulture) && !languagesCodes.Contains(c.Name));
await LoadTranslations();
if (_supportedCultures.Count() == 1)

View File

@ -19,6 +19,7 @@ else
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th>
<th>@Localizer["Code"]</th>
<th>@Localizer["Translation"]</th>
<th>@Localizer["Default"]</th>
<th style="width: 1px;">&nbsp;</th>
</Header>
@ -26,9 +27,10 @@ else
<td><ActionDialog Header="Delete Language" Message="@string.Format(Localizer["Confirm.Language.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteLanguage(context))" Disabled="@((context.IsDefault && _languages.Count > 2) || context.Code == Constants.DefaultCulture)" ResourceKey="DeleteLanguage" /></td>
<td>@context.Name</td>
<td>@context.Code</td>
<td>@context.Version</td>
<td><TriStateCheckBox Value="@(context.IsDefault)" Disabled="true"></TriStateCheckBox></td>
<td>
@if (UpgradeAvailable(context.Code))
@if (UpgradeAvailable(context.Code, context.Version))
{
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadLanguage(context.Code))>@SharedLocalizer["Upgrade"]</button>
}
@ -45,14 +47,11 @@ else
protected override async Task OnParametersSetAsync()
{
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId, Constants.PackageId);
var cultures = await LocalizationService.GetCulturesAsync();
var culture = cultures.First(c => c.Name.Equals(Constants.DefaultCulture));
// Adds English as default language
_languages.Insert(0, new Language { Name = culture.DisplayName, Code = culture.Name, IsDefault = !_languages.Any(l => l.IsDefault) });
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_packages = await PackageService.GetPackagesAsync("translation");
@ -76,15 +75,16 @@ else
}
}
private bool UpgradeAvailable(string code)
private bool UpgradeAvailable(string code, string version)
{
var upgradeavailable = false;
if (_packages != null)
{
var package = _packages.Where(item => item.PackageId == (Constants.PackageId + ".Client." + code)).FirstOrDefault();
var package = _packages.Where(item => item.PackageId == (Constants.PackageId + "." + code)).FirstOrDefault();
if (package != null)
{
upgradeavailable = (Version.Parse(package.Version).CompareTo(Version.Parse(Constants.Version)) > 0);
upgradeavailable = (Version.Parse(package.Version).CompareTo(Version.Parse(Constants.Version)) == 0) &&
(Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0);
}
}
@ -97,7 +97,7 @@ else
{
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
await PackageService.DownloadPackageAsync(Constants.PackageId + ".Client." + code, Constants.Version, Constants.PackagesFolder);
await PackageService.DownloadPackageAsync(Constants.PackageId + "." + code, Constants.Version, Constants.PackagesFolder);
await logger.LogInformation("Translation Downloaded {Code} {Version}", code, Constants.Version);
await PackageService.InstallPackagesAsync();
AddModuleMessage(string.Format(Localizer["Success.Language.Install"], NavigateUrl("admin/system")), MessageType.Success);

View File

@ -3,7 +3,6 @@
@inject NavigationManager NavigationManager
@inject IUserService UserService
@inject IServiceProvider ServiceProvider
@inject SiteState SiteState
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@ -95,7 +94,7 @@
{
try
{
_togglepassword = Localizer["ShowPassword"];
_togglepassword = SharedLocalizer["ShowPassword"];
if (PageState.Site.Settings.ContainsKey("LoginOptions:AllowSiteLogin") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:AllowSiteLogin"]))
{
@ -117,13 +116,30 @@
_username = PageState.QueryString["name"];
}
if (PageState.QueryString.ContainsKey("token"))
if (PageState.QueryString.ContainsKey("token") && !string.IsNullOrEmpty(_username))
{
var user = new User();
user.SiteId = PageState.Site.SiteId;
user.Username = _username;
user = await UserService.VerifyEmailAsync(user, PageState.QueryString["token"]);
if (PageState.QueryString.ContainsKey("key"))
{
user = await UserService.LinkUserAsync(user, PageState.QueryString["token"], PageState.Site.Settings["ExternalLogin:ProviderType"], PageState.QueryString["key"], PageState.Site.Settings["ExternalLogin:ProviderName"]);
if (user != null)
{
await logger.LogInformation(LogFunction.Security, "External Login Linkage Successful For Username {Username}", _username);
AddModuleMessage(Localizer["Success.Account.Linked"], MessageType.Info);
}
else
{
await logger.LogError(LogFunction.Security, "External Login Linkage Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Message.Account.NotLinked"], MessageType.Warning);
}
_username = "";
}
else
{
user = await UserService.VerifyEmailAsync(user, PageState.QueryString["token"]);
if (user != null)
{
await logger.LogInformation(LogFunction.Security, "Email Verified For For Username {Username}", _username);
@ -132,7 +148,15 @@
else
{
await logger.LogError(LogFunction.Security, "Email Verification Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Message.Account.NotVerfied"], MessageType.Warning);
AddModuleMessage(Localizer["Message.Account.NotVerified"], MessageType.Warning);
}
}
}
else
{
if (PageState.QueryString.ContainsKey("status"))
{
AddModuleMessage(Localizer["ExternalLoginStatus." + PageState.QueryString["status"]], MessageType.Info);
}
}
}
@ -159,11 +183,12 @@
var interop = new Interop(JSRuntime);
if (await interop.FormValid(login))
{
var user = new User { SiteId = PageState.Site.SiteId, Username = _username, Password = _password};
var hybrid = (PageState.Runtime == Shared.Runtime.Hybrid);
var user = new User { SiteId = PageState.Site.SiteId, Username = _username, Password = _password, LastIPAddress = SiteState.RemoteIPAddress};
if (!twofactor)
{
user = await UserService.LoginUserAsync(user, false, false);
user = await UserService.LoginUserAsync(user, hybrid, _remember);
}
else
{
@ -174,23 +199,25 @@
{
await logger.LogInformation(LogFunction.Security, "Login Successful For Username {Username}", _username);
if (PageState.Runtime == Oqtane.Shared.Runtime.Server)
if (hybrid)
{
// server-side Blazor needs to post to the Login page so that the cookies are set correctly
// hybrid apps utilize an interactive login
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider
.GetService(typeof(IdentityAuthenticationStateProvider));
authstateprovider.NotifyAuthenticationChanged();
NavigationManager.NavigateTo(NavigateUrl(_returnUrl, true));
}
else
{
// post back to the Login page so that the cookies are set correctly
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = _returnUrl };
string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/");
await interop.SubmitForm(url, fields);
}
else
{
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
authstateprovider.NotifyAuthenticationChanged();
NavigationManager.NavigateTo(NavigateUrl(_returnUrl, true));
}
}
else
{
if (user.TwoFactorRequired)
if ((PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && PageState.Site.Settings["LoginOptions:TwoFactor"] == "required") || user.TwoFactorRequired)
{
twofactor = true;
validated = false;
@ -282,12 +309,12 @@
if (_passwordtype == "password")
{
_passwordtype = "text";
_togglepassword = Localizer["HidePassword"];
_togglepassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordtype = "password";
_togglepassword = Localizer["ShowPassword"];
_togglepassword = SharedLocalizer["ShowPassword"];
}
}

View File

@ -130,13 +130,14 @@
private string _properties = string.Empty;
private string _server = string.Empty;
public override string UrlParametersTemplate => "/{id}";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync()
{
try
{
_logId = Int32.Parse(PageState.QueryString["id"]);
_logId = Int32.Parse(UrlParameters["id"]);
var log = await LogService.GetLogAsync(_logId);
if (log != null)
{
@ -191,13 +192,6 @@
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"]);
}
return (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : NavigateUrl();
}
}

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Admin.Logs
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject ILogService LogService
@inject ISettingService SettingService
@inject IStringLocalizer<Index> Localizer
@ -62,7 +63,7 @@ else
<th>@Localizer["Function"]</th>
</Header>
<Row>
<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)"><ActionLink Action="Detail" Parameters="@($"/{context.LogId}")" ReturnUrl="@(NavigateUrl(PageState.Page.Path, AddUrlParameters(_level, _function, _rows, _page)))" ResourceKey="LogDetails" /></td>
<td class="@GetClass(context.Function)">@context.LogDate</td>
<td class="@GetClass(context.Function)">@context.Level</td>
<td class="@GetClass(context.Function)">@context.Feature</td>
@ -98,25 +99,32 @@ else
private List<Log> _logs;
private string _retention = "";
public override string UrlParametersTemplate => "/{level}/{function}/{rows}/{page}";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync()
protected override async Task OnParametersSetAsync()
{
try
{
if (PageState.QueryString.ContainsKey("level"))
// external link to log item will display Details component
if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int id))
{
_level = PageState.QueryString["level"];
NavigationManager.NavigateTo(EditUrl(PageState.Page.Path, ModuleState.ModuleId, "Detail", $"id={id}"));
}
if (PageState.QueryString.ContainsKey("function"))
if (UrlParameters.ContainsKey("level"))
{
_function = PageState.QueryString["function"];
_level = UrlParameters["level"];
}
if (PageState.QueryString.ContainsKey("rows"))
if (UrlParameters.ContainsKey("function"))
{
_rows = PageState.QueryString["rows"];
_function = UrlParameters["function"];
}
if (PageState.QueryString.ContainsKey("page") && int.TryParse(PageState.QueryString["page"], out int page))
if (UrlParameters.ContainsKey("rows"))
{
_rows = UrlParameters["rows"];
}
if (UrlParameters.ContainsKey("page") && int.TryParse(UrlParameters["page"], out int page))
{
_page = page;
}

View File

@ -85,7 +85,7 @@ else
private List<Template> _templates;
private string _template = "-";
private string[] _versions;
private string _reference = Constants.Version;
private string _reference = "local";
private string _minversion = "2.0.0";
private string _location = string.Empty;
@ -124,6 +124,8 @@ else
if (await interop.FormValid(form))
{
try
{
if (IsValid(_owner) && IsValid(_module) && _owner != _module && _template != "-")
{
var moduleDefinition = new ModuleDefinition { Owner = _owner, Name = _module, Description = _description, Template = _template, Version = _reference };
moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition);
@ -135,6 +137,11 @@ else
GetLocation();
AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success);
}
else
{
AddModuleMessage(Localizer["Message.Require.ValidName"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Creating Module");

View File

@ -35,6 +35,7 @@
<strong>@(String.Format("{0:n0}", context.Downloads))</strong> @SharedLocalizer["Search.Downloads"]&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Released"]: <strong>@context.ReleaseDate.ToString("MMM dd, yyyy")</strong>&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Version"]: <strong>@context.Version</strong>
@((MarkupString)(!string.IsNullOrEmpty(context.PackageUrl) ? "&nbsp;&nbsp;|&nbsp;&nbsp;" + SharedLocalizer["Search.Source"] + ": <strong>" + new Uri(context.PackageUrl).Host + "</strong>" : ""))
@((MarkupString)(context.TrialPeriod > 0 ? "&nbsp;&nbsp;|&nbsp;&nbsp;<strong>" + context.TrialPeriod + " " + @SharedLocalizer["Trial"] + "</strong>" : ""))
</td>
<td style="width: 1px; vertical-align: middle;">
@ -90,9 +91,9 @@
<div class="modal-body">
<p style="height: 200px; overflow-y: scroll;">
<h3>@_productname</h3>
@if (!string.IsNullOrEmpty(_license))
@if (!string.IsNullOrEmpty(_packagelicense))
{
@((MarkupString)_license)
@((MarkupString)_packagelicense)
}
else
{
@ -113,14 +114,18 @@
<button type="button" class="btn btn-success" @onclick="InstallModules">@SharedLocalizer["Install"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<br />
<br />
<ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" />
@code {
private List<Package> _packages;
private string _price = "free";
private string _search = "";
private string _productname = "";
private string _license = "";
private string _packageid = "";
private string _version = "";
private string _packagelicense = "";
private string _packageversion = "";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
@ -197,7 +202,7 @@
private void HideModal()
{
_productname = "";
_license = "";
_packagelicense = "";
StateHasChanged();
}
@ -209,12 +214,12 @@
if (package != null)
{
_productname = package.Name;
_packageid = package.PackageId;
if (!string.IsNullOrEmpty(package.License))
{
_license = package.License.Replace("\n", "<br />");
_packagelicense = package.License.Replace("\n", "<br />");
}
_packageid = package.PackageId;
_version = package.Version;
_packageversion = package.Version;
}
StateHasChanged();
}
@ -229,16 +234,16 @@
{
try
{
await PackageService.DownloadPackageAsync(_packageid, _version, Constants.PackagesFolder);
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _packageid, _version);
await PackageService.DownloadPackageAsync(_packageid, _packageversion, Constants.PackagesFolder);
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _packageid, _packageversion);
AddModuleMessage(Localizer["Success.Module.Download"], MessageType.Success);
_productname = "";
_license = "";
_packagelicense = "";
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packageid, _version);
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packageid, _packageversion);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
}
}
@ -252,7 +257,7 @@
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Installing Module");
await logger.LogError(ex, "Error Installing Modules");
}
}
}

View File

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

View File

@ -1,6 +1,10 @@
@namespace Oqtane.Modules.Admin.ModuleDefinitions
@inherits ModuleBase
@using System.Globalization
@using Microsoft.AspNetCore.Localization
@inject IModuleDefinitionService ModuleDefinitionService
@inject IPackageService PackageService
@inject ILanguageService LanguageService
@inject NavigationManager NavigationManager
@inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@ -42,6 +46,12 @@
<div class="col-sm-9">
<input id="version" class="form-control" @bind="@_version" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this module was installed" ResourceKey="PackageName">Package Name: </Label>
<div class="col-sm-9">
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="owner" HelpText="The owner or creator of the module" ResourceKey="Owner">Owner: </Label>
@ -75,6 +85,12 @@
</div>
</div>
</Section>
<br />
<button type="button" class="btn btn-success" @onclick="SaveModuleDefinition">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<br />
<br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
</TabPanel>
<TabPanel Name="Permissions" ResourceKey="Permissions">
<div class="container">
@ -82,23 +98,97 @@
<PermissionGrid EntityName="@EntityNames.ModuleDefinition" PermissionNames="@PermissionNames.Utilize" Permissions="@_permissions" @ref="_permissionGrid" />
</div>
</div>
</TabPanel>
</TabStrip>
<button type="button" class="btn btn-success" @onclick="SaveModuleDefinition">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</TabPanel>
<TabPanel Name="Translations" ResourceKey="Translations">
@if (_languages != null)
{
@if (_languages.Count > 0)
{
<Pager Items="@_languages">
<Header>
<th>@SharedLocalizer["Name"]</th>
<th>@Localizer["Code"]</th>
<th>@Localizer["Version"]</th>
<th style="width: 1px;">&nbsp;</th>
</Header>
<Row>
<td>@context.Name</td>
<td>@context.Code</td>
<td>@context.Version</td>
<td>
@if (context.IsDefault)
{
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(_packagename + "." + context.Code))>@SharedLocalizer["Download"]</button>
}
else
{
if (UpgradeAvailable(_packagename + "." + context.Code, context.Version))
{
<button type="button" class="btn btn-primary" @onclick=@(async () => await DownloadPackage(_packagename + "." + context.Code))>@SharedLocalizer["Upgrade"]</button>
}
}
</td>
</Row>
</Pager>
<button type="button" class="btn btn-success" @onclick="InstallTranslations">@SharedLocalizer["Install"]</button>
}
else
{
<br />
<div class="mx-auto text-center">
@Localizer["Search.NoResults"]
</div>
<br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
}
}
</TabPanel>
</TabStrip>
@if (_productname != "")
{
<div class="app-actiondialog">
<div class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@SharedLocalizer["Review License Terms"]</h5>
<button type="button" class="btn-close" aria-label="Close" @onclick="HideModal"></button>
</div>
<div class="modal-body">
<p style="height: 200px; overflow-y: scroll;">
<h3>@_productname</h3>
@if (!string.IsNullOrEmpty(_packagelicense))
{
@((MarkupString)_packagelicense)
}
else
{
@SharedLocalizer["License Not Specified"]
}
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadPackage(_packageid))>@SharedLocalizer["Accept"]</button>
<button type="button" class="btn btn-secondary" @onclick="HideModal">@SharedLocalizer["Cancel"]</button>
</div>
</div>
</div>
</div>
</div>
}
@code {
private ElementReference form;
private bool validated = false;
private int _moduleDefinitionId;
private string _name;
private string _version;
private string _description = "";
private string _categories;
private string _moduledefinitionname = "";
private string _description = "";
private string _version;
private string _packagename = "";
private string _owner = "";
private string _url = "";
private string _contact = "";
@ -114,6 +204,12 @@
private PermissionGrid _permissionGrid;
#pragma warning restore 649
private List<Package> _packages;
private List<Language> _languages;
private string _productname = "";
private string _packagelicense = "";
private string _packageid = "";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnInitializedAsync()
@ -125,10 +221,11 @@
if (moduleDefinition != null)
{
_name = moduleDefinition.Name;
_version = moduleDefinition.Version;
_description = moduleDefinition.Description;
_categories = moduleDefinition.Categories;
_moduledefinitionname = moduleDefinition.ModuleDefinitionName;
_description = moduleDefinition.Description;
_version = moduleDefinition.Version;
_packagename = moduleDefinition.PackageName;
_owner = moduleDefinition.Owner;
_url = moduleDefinition.Url;
_contact = moduleDefinition.Contact;
@ -139,6 +236,18 @@
_createdon = moduleDefinition.CreatedOn;
_modifiedby = moduleDefinition.ModifiedBy;
_modifiedon = moduleDefinition.ModifiedOn;
_packages = await PackageService.GetPackagesAsync("translation", "", "", _packagename);
_languages = await LanguageService.GetLanguagesAsync(-1, _packagename);
foreach (var package in _packages)
{
var code = package.PackageId.Split('.').Last();
if (!_languages.Any(item => item.Code == code))
{
_languages.Add(new Language { Code = code, Name = CultureInfo.GetCultureInfo(code).DisplayName, Version = package.Version, IsDefault = true });
}
}
_languages = _languages.OrderBy(item => item.Name).ToList();
}
}
catch (Exception ex)
@ -185,4 +294,82 @@
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
private void HideModal()
{
_productname = "";
_packagelicense = "";
StateHasChanged();
}
private bool UpgradeAvailable(string packagename, string version)
{
var upgradeavailable = false;
if (_packages != null)
{
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
if (package != null)
{
upgradeavailable = (Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0);
}
}
return upgradeavailable;
}
private async Task GetPackage(string packagename)
{
var version = _packages.Where(item => item.PackageId == packagename).FirstOrDefault().Version;
try
{
var package = await PackageService.GetPackageAsync(packagename, version);
if (package != null)
{
_productname = package.Name;
if (!string.IsNullOrEmpty(package.License))
{
_packagelicense = package.License.Replace("\n", "<br />");
}
_packageid = package.PackageId;
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", packagename, version);
AddModuleMessage(Localizer["Error.Translation.Download"], MessageType.Error);
}
}
private async Task DownloadPackage(string packagename)
{
try
{
var version = _packages.Where(item => item.PackageId == packagename).FirstOrDefault().Version;
await PackageService.DownloadPackageAsync(packagename, version, Constants.PackagesFolder);
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", packagename, version);
AddModuleMessage(Localizer["Success.Translation.Download"], MessageType.Success);
_productname = "";
_packagelicense = "";
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packagename, _version);
AddModuleMessage(Localizer["Error.Translation.Download"], MessageType.Error);
}
}
private async Task InstallTranslations()
{
try
{
await PackageService.InstallPackagesAsync();
AddModuleMessage(string.Format(Localizer["Success.Translation.Install"], NavigateUrl("admin/system")), MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Installing Translations");
}
}
}

View File

@ -12,11 +12,32 @@
}
else
{
<div class="container">
<div class="row mb-3 align-items-center">
<div class="col-sm-6">
<ActionLink Action="Add" Text="Install Module" ResourceKey="InstallModule" />
@((MarkupString)"&nbsp;")
<ActionLink Action="Create" Text="Create Module" ResourceKey="CreateModule" Class="btn btn-secondary" />
</div>
<div class="col-sm-6">
<select class="form-select" @onchange="(e => CategoryChanged(e))">
@foreach (var category in _categories)
{
if (category == _category)
{
<option value="@category" selected>@category @Localizer["Modules"]</option>
}
else
{
<option value="@category">@category @Localizer["Modules"]</option>
}
}
</select>
</div>
</div>
</div>
<Pager Items="@_moduleDefinitions">
<Pager Items="@_moduleDefinitions.Where(item => item.Categories.Contains(_category))">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
@ -50,9 +71,12 @@ else
@((MarkupString)PurchaseLink(context.PackageName))
</td>
<td>
@if (UpgradeAvailable(context.PackageName, context.Version))
@{
var version = UpgradeAvailable(context.PackageName, context.Version);
}
@if (version != context.Version)
{
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadModule(context.PackageName, context.Version))>@SharedLocalizer["Upgrade"]</button>
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadModule(context.PackageName, version))>@SharedLocalizer["Upgrade"]</button>
}
</td>
</Row>
@ -62,6 +86,8 @@ else
@code {
private List<ModuleDefinition> _moduleDefinitions;
private List<Package> _packages;
private List<string> _categories = new List<string>();
private string _category = "Common";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
@ -71,6 +97,7 @@ else
{
_moduleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
_packages = await PackageService.GetPackagesAsync("module");
_categories = _moduleDefinitions.SelectMany(m => m.Categories.Split(',')).Distinct().ToList();
}
catch (Exception ex)
{
@ -103,19 +130,17 @@ else
return link;
}
private bool UpgradeAvailable(string packagename, string version)
private string UpgradeAvailable(string packagename, string version)
{
var upgradeavailable = false;
if (!string.IsNullOrEmpty(packagename) && _packages != null)
{
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
if (package != null)
if (package != null && Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0)
{
upgradeavailable = (Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0);
return package.Version;
}
}
return upgradeavailable;
return version;
}
private async Task DownloadModule(string packagename, string version)
@ -148,4 +173,10 @@ else
AddModuleMessage(Localizer["Error.Module.Delete"], MessageType.Error);
}
}
private void CategoryChanged(ChangeEventArgs e)
{
_category = (string)e.Value;
StateHasChanged();
}
}

View File

@ -19,7 +19,7 @@
@code {
private string _content = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Title => "Export Content";
@ -27,7 +27,7 @@
{
try
{
_content = await ModuleService.ExportModuleAsync(ModuleState.ModuleId);
_content = await ModuleService.ExportModuleAsync(ModuleState.ModuleId, PageState.Page.PageId);
AddModuleMessage(Localizer["Success.Content.Export"], MessageType.Success);
}
catch (Exception ex)

View File

@ -25,7 +25,7 @@
private ElementReference form;
private bool validated = false;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Title => "Import Content";
private async Task ImportModule()
@ -38,7 +38,7 @@
{
try
{
bool success = await ModuleService.ImportModuleAsync(ModuleState.ModuleId, _content);
bool success = await ModuleService.ImportModuleAsync(ModuleState.ModuleId, PageState.Page.PageId, _content);
if (success)
{
AddModuleMessage(Localizer["Success.Content.Import"], MessageType.Success);

View File

@ -124,13 +124,16 @@
_containerType = ModuleState.ContainerType;
_allPages = ModuleState.AllPages.ToString();
_permissions = ModuleState.Permissions;
_permissionNames = ModuleState.ModuleDefinition.PermissionNames;
_pageId = ModuleState.PageId.ToString();
createdby = ModuleState.CreatedBy;
createdon = ModuleState.CreatedOn;
modifiedby = ModuleState.ModifiedBy;
modifiedon = ModuleState.ModifiedOn;
if (ModuleState.ModuleDefinition != null)
{
_permissionNames = ModuleState.ModuleDefinition?.PermissionNames;
if (!string.IsNullOrEmpty(ModuleState.ModuleDefinition.SettingsType))
{
// module settings type explicitly declared in IModule interface
@ -156,6 +159,11 @@
builder.CloseComponent();
};
}
}
else
{
AddModuleMessage(string.Format(Localizer["Error.Module.Load"], ModuleState.ModuleDefinitionName), MessageType.Error);
}
var theme = _themes.FirstOrDefault(item => item.Containers.Any(themecontrol => themecontrol.TypeName.Equals(_containerType)));
if (theme != null && !string.IsNullOrEmpty(theme.ContainerSettingsType))
@ -198,6 +206,7 @@
var module = ModuleState;
module.AllPages = bool.Parse(_allPages);
module.PageModuleId = ModuleState.PageModuleId;
module.Permissions = _permissionGrid.GetPermissions();
await ModuleService.UpdateModuleAsync(module);

View File

@ -24,7 +24,7 @@
<div class="col-sm-9">
<select id="parent" class="form-select" @onchange="(e => ParentChanged(e))" required>
<option value="-1">&lt;@Localizer["SiteRoot"]&gt;</option>
@foreach (Page page in _pageList)
@foreach (Page page in PageState.Pages)
{
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option>
}
@ -167,7 +167,6 @@
private List<Theme> _themeList;
private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private List<Page> _pageList;
private string _name;
private string _title;
private string _meta;
@ -201,7 +200,6 @@
_themetype = PageState.Site.DefaultThemeType;
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containertype = PageState.Site.DefaultContainerType;
_pageList = PageState.Pages;
_children = PageState.Pages.Where(item => item.ParentId == null).ToList();
_permissions = string.Empty;
ThemeSettings();
@ -307,6 +305,10 @@
}
if (_path.Contains("/"))
{
if (_path.EndsWith("/") && _path != "/")
{
_path = _path.Substring(0, _path.Length - 1);
}
_path = _path.Substring(_path.LastIndexOf("/") + 1);
}
@ -329,15 +331,16 @@
}
}
if(PagePathIsDeleted(page.Path, page.SiteId, _pageList))
var _pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
if (_pages.Any(item => item.Path == page.Path))
{
AddModuleMessage(string.Format(Localizer["Message.Page.Deleted"], _path), MessageType.Warning);
AddModuleMessage(string.Format(Localizer["Message.Page.Exists"], _path), MessageType.Warning);
return;
}
if (!PagePathIsUnique(page.Path, page.SiteId, _pageList))
if (page.ParentId == null && Constants.ReservedRoutes.Contains(page.Name.ToLower()))
{
AddModuleMessage(string.Format(Localizer["Message.Page.Exists"], _path), MessageType.Warning);
AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], page.Name), MessageType.Warning);
return;
}
@ -421,14 +424,4 @@
NavigationManager.NavigateTo(NavigateUrl());
}
}
private static bool PagePathIsUnique(string pagePath, int siteId, List<Page> existingPages)
{
return !existingPages.Any(page => page.SiteId == siteId && page.Path == pagePath);
}
private static bool PagePathIsDeleted(string pagePath, int siteId, List<Page> existingPages)
{
return existingPages.Any(page => page.SiteId == siteId && page.Path == pagePath && page.IsDeleted == true);
}
}

View File

@ -25,7 +25,7 @@
<div class="col-sm-9">
<select id="parent" class="form-select" value="@_parentid" @onchange="(e => ParentChanged(e))" required>
<option value="-1">&lt;@Localizer["SiteRoot"]&gt;</option>
@foreach (Page page in _pageList)
@foreach (Page page in PageState.Pages)
{
if (page.PageId != _pageId)
{
@ -201,7 +201,6 @@
private List<Theme> _themeList;
private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private List<Page> _pageList;
private List<Module> _pageModules;
private int _pageId;
private string _name;
@ -238,7 +237,6 @@
{
try
{
_pageList = PageState.Pages;
_children = PageState.Pages.Where(item => item.ParentId == null).ToList();
_themeList = await ThemeService.GetThemesAsync();
_themes = ThemeService.GetThemeControls(_themeList);
@ -435,6 +433,10 @@
}
if (_path.Contains("/"))
{
if (_path.EndsWith("/") && _path != "/")
{
_path = _path.Substring(0, _path.Length - 1);
}
_path = _path.Substring(_path.LastIndexOf("/") + 1);
}
@ -457,12 +459,19 @@
}
}
if (!PagePathIsUnique(page.Path, page.SiteId, page.PageId, _pageList))
var _pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
if (_pages.Any(item => item.Path == page.Path && item.PageId != page.PageId))
{
AddModuleMessage(string.Format(Localizer["Mesage.Page.PathExists"], _path), MessageType.Warning);
return;
}
if (page.ParentId == null && Constants.ReservedRoutes.Contains(page.Name.ToLower()))
{
AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], page.Name), MessageType.Warning);
return;
}
if (_insert != "=")
{
Page child;
@ -567,9 +576,4 @@
NavigationManager.NavigateTo(NavigateUrl());
}
}
private static bool PagePathIsUnique(string pagePath, int siteId, int pageId, List<Page> existingPages)
{
return !existingPages.Any(page => page.SiteId == siteId && page.Path == pagePath && page.PageId != pageId);
}
}

View File

@ -58,7 +58,7 @@
<Row>
<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>@PageState.Pages.Find(item => item.PageId == context.PageId).Name</td>
<td>@_pages.Find(item => item.PageId == context.PageId).Name</td>
<td>@context.Title</td>
<td>@context.DeletedBy</td>
<td>@context.DeletedOn</td>
@ -140,6 +140,7 @@
{
try
{
ModuleInstance.ShowProgressIndicator();
foreach (Page page in _pages)
{
await PageService.DeletePageAsync(page.PageId);
@ -148,6 +149,7 @@
await logger.LogInformation("Pages Permanently Deleted");
await Load();
ModuleInstance.HideProgressIndicator();
StateHasChanged();
NavigationManager.NavigateTo(NavigateUrl());
}
@ -155,6 +157,7 @@
{
await logger.LogError(ex, "Error Permanently Deleting Pages {Error}", ex.Message);
AddModuleMessage(ex.Message, MessageType.Error);
ModuleInstance.HideProgressIndicator();
}
}
@ -204,6 +207,7 @@
{
try
{
ModuleInstance.ShowProgressIndicator();
foreach (Module module in _modules)
{
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
@ -218,12 +222,14 @@
await logger.LogInformation("Modules Permanently Deleted");
await Load();
ModuleInstance.HideProgressIndicator();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Permanently Deleting Modules {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Modules.Delete"], MessageType.Error);
ModuleInstance.HideProgressIndicator();
}
}
}

View File

@ -27,13 +27,19 @@
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="password" HelpText="Please choose a sufficiently secure password and enter it here" ResourceKey="Password"></Label>
<div class="col-sm-9">
<input id="password" type="password" class="form-control" @bind="@_password" autocomplete="new-password" required />
<div class="input-group">
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="confirm" HelpText="Enter your password again to confirm it matches the value entered above" ResourceKey="Confirm"></Label>
<div class="col-sm-9">
<input id="confirm" type="password" class="form-control" @bind="@_confirm" autocomplete="new-password" required />
<div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
@ -66,12 +72,19 @@ else
private ElementReference form;
private bool validated = false;
private string _password = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private string _confirm = string.Empty;
private string _email = string.Empty;
private string _displayname = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
protected override void OnParametersSet()
{
_togglepassword = SharedLocalizer["ShowPassword"];
}
private async Task Register()
{
validated = true;
@ -134,4 +147,18 @@ else
{
NavigationManager.NavigateTo(NavigateUrl(string.Empty));
}
private void TogglePassword()
{
if (_passwordtype == "password")
{
_passwordtype = "text";
_togglepassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordtype = "password";
_togglepassword = SharedLocalizer["ShowPassword"];
}
}
}

View File

@ -16,13 +16,19 @@
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="password" HelpText="The new password. It must satisfy complexity rules for the site." ResourceKey="Password">Password: </Label>
<div class="col-sm-9">
<input id="password" type="password" class="form-control" @bind="@_password" required />
<div class="input-group">
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="confirm" HelpText="Enter the password again. It must exactly match the password entered above." ResourceKey="Confirm">Confirm: </Label>
<div class="col-sm-9">
<input id="confirm" type="password" class="form-control" @bind="@_confirm" required />
<div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
</div>
</div>
</div>
</div>
@ -36,12 +42,16 @@
private bool validated = false;
private string _username = string.Empty;
private string _password = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private string _confirm = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
protected override async Task OnInitializedAsync()
{
_togglepassword = SharedLocalizer["ShowPassword"];
if (PageState.QueryString.ContainsKey("name") && PageState.QueryString.ContainsKey("token"))
{
_username = PageState.QueryString["name"];
@ -110,4 +120,18 @@
{
NavigationManager.NavigateTo(NavigateUrl(string.Empty));
}
private void TogglePassword()
{
if (_passwordtype == "password")
{
_passwordtype = "text";
_togglepassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordtype = "password";
_togglepassword = SharedLocalizer["ShowPassword"];
}
}
}

View File

@ -59,7 +59,7 @@ else
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_roles = await RoleService.GetRolesAsync(PageState.Site.SiteId, true);
_roles = _roles.Where(item => item.Name != RoleNames.Everyone).ToList();
_roles.RemoveAll(item => item.Name == RoleNames.Everyone || item.Name == RoleNames.Unauthenticated);
}
else
{

View File

@ -95,11 +95,7 @@ else
roleid = Int32.Parse(PageState.QueryString["id"]);
Role role = await RoleService.GetRoleAsync(roleid);
name = role.Name;
users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId);
users = users
.Where(u => u.Role.Name == RoleNames.Registered)
.OrderBy(u => u.User.DisplayName)
.ToList();
users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
await GetUserRoles();
}
catch (Exception ex)
@ -113,8 +109,7 @@ else
{
try
{
userroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId);
userroles = userroles.Where(item => item.RoleId == roleid).ToList();
userroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, name);
}
catch (Exception ex)
{

View File

@ -70,6 +70,21 @@
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="homepage" HelpText="Select the home page for the site (to be used if there is no page with a path of '/')" ResourceKey="HomePage">Home Page: </Label>
<div class="col-sm-9">
<select id="homepage" class="form-select" @bind="@_homepageid" required>
<option value="-">&lt;@Localizer["Not Specified"]&gt;</option>
@foreach (Page page in PageState.Pages)
{
if (UserSecurity.ContainsRole(page.Permissions, PermissionNames.View, RoleNames.Everyone))
{
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.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>
@ -172,20 +187,50 @@
<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>
<Label Class="col-sm-3" For="aliases" HelpText="The list of aliases for this site" 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())
<button type="button" class="btn btn-primary" @onclick="AddAlias">@SharedLocalizer["Add"]</button>
<Pager Items="@_aliases">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["AliasName"]</th>
<th>@Localizer["AliasDefault"]</th>
</Header>
<Row>
@if (context.AliasId != _aliasid)
{
<option value="@name">@name</option>
<td>
@if (_aliasid == -1)
{
<button type="button" class="btn btn-primary" @onclick="@(() => EditAlias(context))">@SharedLocalizer["Edit"]</button>
}
</td>
<td>
@if (_aliasid == -1)
{
<ActionDialog Action="Delete" OnClick="@(async () => await DeleteAlias(context))" ResourceKey="DeleteModule" Class="btn btn-danger" Header="Delete Alias" Message="@string.Format(Localizer["Confirm.Alias.Delete", context.Name])" />
}
</td>
<td>@context.Name</td>
<td>@context.IsDefault</td>
}
else
{
<td><button type="button" class="btn btn-success" @onclick="@(async () => await SaveAlias())">@SharedLocalizer["Save"]</button></td>
<td><button type="button" class="btn btn-secondary" @onclick="@(async () => await CancelAlias())">@SharedLocalizer["Cancel"]</button></td>
<td>
<input id="aliasname" class="form-control" @bind="@_aliasname" />
</td>
<td>
<select id="defaultaias" class="form-select" @bind="@_defaultalias" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</td>
}
</Row>
</Pager>
</div>
</div>
</div>
@ -198,6 +243,7 @@
<select id="runtime" class="form-select" @bind="@_runtime" required>
<option value="Server">@SharedLocalizer["BlazorServer"]</option>
<option value="WebAssembly">@SharedLocalizer["BlazorWebAssembly"]</option>
<option value="Hybrid">@SharedLocalizer["BlazorHybrid"]</option>
</select>
</div>
</div>
@ -253,8 +299,9 @@
private List<ThemeControl> _containers = new List<ThemeControl>();
private string _name = string.Empty;
private List<Alias> _aliases;
private string _defaultalias = string.Empty;
private string _urls = string.Empty;
private int _aliasid = -1;
private string _aliasname;
private string _defaultalias;
private string _runtime = "";
private string _prerender = "";
private int _logofileid = -1;
@ -264,6 +311,7 @@
private string _themetype = "-";
private string _containertype = "-";
private string _admincontainertype = "-";
private string _homepageid = "-";
private string _smtphost = string.Empty;
private string _smtpport = string.Empty;
private string _smtpssl = "False";
@ -304,10 +352,7 @@
_prerender = site.RenderMode.Replace(_runtime, "");
_isdeleted = site.IsDeleted.ToString();
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
await GetAliases();
}
if (site.LogoFileId != null)
{
@ -325,6 +370,11 @@
_containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer;
_admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer;
if (site.HomePageId != null)
{
_homepageid = site.HomePageId.Value.ToString();
}
_pwaisenabled = site.PwaIsEnabled.ToString();
if (site.PwaAppIconFileId != null)
{
@ -341,7 +391,7 @@
_smtpssl = SettingService.GetSetting(settings, "SMTPSSL", "False");
_smtpusername = SettingService.GetSetting(settings, "SMTPUsername", string.Empty);
_smtppassword = SettingService.GetSetting(settings, "SMTPPassword", string.Empty);
_togglesmtppassword = Localizer["Show"];
_togglesmtppassword = SharedLocalizer["ShowPassword"];
_smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty);
_retention = SettingService.GetSetting(settings, "NotificationRetention", "30");
@ -407,25 +457,7 @@
{
try
{
if (_name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-")
{
var unique = true;
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_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())
{
var alias = aliases.Where(item => item.Name == name).FirstOrDefault();
if (alias != null && unique)
{
unique = (alias.TenantId == PageState.Site.TenantId && alias.SiteId == PageState.Site.SiteId);
}
}
if (unique && string.IsNullOrEmpty(_defaultalias)) unique = false;
}
if (unique)
if (_name != string.Empty && _themetype != "-" && _containertype != "-")
{
var site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null)
@ -469,6 +501,7 @@
refresh = true; // needs to be refreshed on client
}
site.AdminContainerType = _admincontainertype;
site.HomePageId = (_homepageid != "-" ? int.Parse(_homepageid) : null);
if (site.PwaIsEnabled.ToString() != _pwaisenabled)
{
@ -502,43 +535,6 @@
settings = SettingService.SetSetting(settings, "NotificationRetention", _retention, true);
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
var names = _urls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(sValue => sValue.Trim()).ToArray();
foreach (Alias alias in _aliases)
{
if (!names.Contains(alias.Name.Trim()))
{
await AliasService.DeleteAliasAsync(alias.AliasId);
}
}
foreach (string name in names)
{
var alias = _aliases.Find(item => item.Name.Trim() == name);
if (alias == null)
{
alias = new Alias();
alias.Name = name;
alias.TenantId = site.TenantId;
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);
if (refresh || reload)
@ -552,11 +548,6 @@
}
}
}
else // deuplicate alias or default alias not specified
{
AddModuleMessage(Localizer["Message.Aliases.Taken"], MessageType.Warning);
}
}
else
{
AddModuleMessage(Localizer["Message.Required.SiteName"], MessageType.Warning);
@ -578,19 +569,19 @@
{
try
{
var sites = await SiteService.GetSitesAsync();
if (sites.Count > 1)
var aliases = await AliasService.GetAliasesAsync();
if (aliases.Any(item => item.SiteId != PageState.Site.SiteId || item.TenantId != PageState.Site.TenantId))
{
await SiteService.DeleteSiteAsync(PageState.Site.SiteId);
await logger.LogInformation("Site Deleted {SiteId}", PageState.Site.SiteId);
var aliases = await AliasService.GetAliasesAsync();
foreach (Alias a in aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Site.TenantId))
foreach (Alias alias in aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Site.TenantId))
{
await AliasService.DeleteAliasAsync(a.AliasId);
await AliasService.DeleteAliasAsync(alias.AliasId);
}
NavigationManager.NavigateTo(NavigateUrl("admin/sites"));
var redirect = aliases.First(item => item.SiteId != PageState.Site.SiteId || item.TenantId != PageState.Site.TenantId);
NavigationManager.NavigateTo(PageState.Uri.Scheme + "://" + redirect.Name, true);
}
else
{
@ -637,31 +628,97 @@
}
}
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();
}
private void ToggleSMTPPassword()
{
if (_smtppasswordtype == "password")
{
_smtppasswordtype = "text";
_togglesmtppassword = Localizer["Hide"];
_togglesmtppassword = SharedLocalizer["HidePassword"];
}
else
{
_smtppasswordtype = "password";
_togglesmtppassword = Localizer["Show"];
_togglesmtppassword = SharedLocalizer["ShowPassword"];
}
}
private async Task GetAliases()
{
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_aliases = await AliasService.GetAliasesAsync();
_aliases = _aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Site.TenantId).OrderBy(item => item.AliasId).ToList();
}
}
private void AddAlias()
{
_aliases.Add(new Alias { AliasId = 0, Name = "", IsDefault = false });
_aliasid = 0;
_aliasname = "";
_defaultalias = "False";
StateHasChanged();
}
private void EditAlias(Alias alias)
{
_aliasid = alias.AliasId;
_aliasname = alias.Name;
_defaultalias = alias.IsDefault.ToString();
StateHasChanged();
}
private async Task DeleteAlias(Alias alias)
{
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
await AliasService.DeleteAliasAsync(alias.AliasId);
await GetAliases();
StateHasChanged();
}
}
private async Task SaveAlias()
{
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
if (!string.IsNullOrEmpty(_aliasname))
{
var aliases = await AliasService.GetAliasesAsync();
var alias = aliases.Where(item => item.Name == _aliasname).FirstOrDefault();
bool unique = (alias == null || alias.AliasId == _aliasid);
if (unique)
{
if (_aliasid == 0)
{
alias = new Alias { SiteId = PageState.Site.SiteId, TenantId = PageState.Site.TenantId, Name = _aliasname, IsDefault = bool.Parse(_defaultalias) };
await AliasService.AddAliasAsync(alias);
}
else
{
alias = _aliases.Single(item => item.AliasId == _aliasid);
alias.Name = _aliasname;
alias.IsDefault = bool.Parse(_defaultalias);
await AliasService.UpdateAliasAsync(alias);
}
}
else // duplicate alias
{
AddModuleMessage(Localizer["Message.Aliases.Taken"], MessageType.Warning);
}
}
await GetAliases();
_aliasid = -1;
_aliasname = "";
StateHasChanged();
}
}
private async Task CancelAlias()
{
await GetAliases();
_aliasid = -1;
_aliasname = "";
StateHasChanged();
}
}

View File

@ -29,7 +29,7 @@ else
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="alias" HelpText="Enter the aliases for the site. An alias can be a domain name (www.site.com) or a virtual folder (ie. www.site.com/folder). If a site has multiple aliases they can be separated by commas." ResourceKey="Aliases">Aliases: </Label>
<Label Class="col-sm-3" For="alias" HelpText="Enter the aliases for the site. An alias can be a domain name (www.site.com) or a virtual folder (ie. www.site.com/folder)." ResourceKey="Aliases">Aliases: </Label>
<div class="col-sm-9">
<textarea id="alias" class="form-control" @bind="@_urls" rows="3" required></textarea>
</div>
@ -89,6 +89,7 @@ else
<select id="runtime" class="form-select" @bind="@_runtime" required>
<option value="Server">@SharedLocalizer["BlazorServer"]</option>
<option value="WebAssembly">@SharedLocalizer["BlazorWebAssembly"]</option>
<option value="Hybrid">@SharedLocalizer["BlazorHybrid"]</option>
</select>
</div>
</div>
@ -128,24 +129,42 @@ else
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="databaseType" HelpText="Select the database type for the tenant" ResourceKey="DatabaseType">Database Type: </Label>
<div class="col-sm-9">
@if (_databases != null)
{
<div class="input-group">
<select id="databaseType" class="form-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))" required>
@foreach (var database in _databases)
{
if (database.IsDefault)
<option value="@database.Name">@Localizer[@database.Name]</option>
}
</select>
@if (!_showConnectionString)
{
<option value="@database.Name" selected>@Localizer[@database.Name]</option>
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionString"]</button>
}
else
{
<option value="@database.Name">@Localizer[@database.Name]</option>
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionParameters"]</button>
}
</div>
}
</select>
</div>
</div>
@if (!_showConnectionString)
{
if (_databaseConfigType != null)
{
@DatabaseConfigComponent;
@DatabaseConfigComponent
}
}
else
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="connectionstring" HelpText="Enter a complete connection string including all parameters and delimiters" ResourceKey="ConnectionString">String:</Label>
<div class="col-sm-9">
<textarea id="connectionstring" class="form-control" @bind="@_connectionString" rows="3"></textarea>
</div>
</div>
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="hostUsername" HelpText="Enter the username of an existing host user" ResourceKey="HostUsername">Host Username:</Label>
@ -156,7 +175,7 @@ else
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="hostPassword" HelpText="Enter the password of an existing host user" ResourceKey="HostPassword">Host Password:</Label>
<div class="col-sm-9">
<input id="hostPassword" type="password" class="form-control" @bind="@_hostpassword" required />
<input id="hostPassword" type="password" class="form-control" @bind="@_hostpassword" autocomplete="new-password" required />
</div>
</div>
}
@ -172,11 +191,12 @@ else
private List<Database> _databases;
private ElementReference form;
private bool validated = false;
private string _databaseName = "LocalDB";
private string _databaseName;
private Type _databaseConfigType;
private object _databaseConfig;
private RenderFragment DatabaseConfigComponent { get; set; }
private bool _showConnectionString = false;
private string _connectionString = string.Empty;
private List<Theme> _themeList;
private List<ThemeControl> _themes = new List<ThemeControl>();
@ -208,7 +228,16 @@ else
_themeList = await ThemeService.GetThemesAsync();
_themes = ThemeService.GetThemeControls(_themeList);
_siteTemplates = await SiteTemplateService.GetSiteTemplatesAsync();
_databases = await DatabaseService.GetDatabasesAsync();
if (_databases.Exists(item => item.IsDefault))
{
_databaseName = _databases.Find(item => item.IsDefault).Name;
}
else
{
_databaseName = "LocalDB";
}
LoadDatabaseConfigComponent();
}
@ -217,7 +246,7 @@ else
try
{
_databaseName = (string)eventArgs.Value;
_showConnectionString = false;
LoadDatabaseConfigComponent();
}
catch
@ -307,15 +336,23 @@ else
user.SiteId = PageState.Site.SiteId;
user.Username = _hostusername;
user.Password = _hostpassword;
user.LastIPAddress = PageState.RemoteIPAddress;
user = await UserService.LoginUserAsync(user, false, false);
if (user.IsAuthenticated)
{
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
var connectionString = String.Empty;
if (_showConnectionString)
{
connectionString = _connectionString;
}
else
{
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
{
connectionString = databaseConfigControl.GetConnectionString();
}
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
}
if (connectionString != "")
{
@ -396,4 +433,13 @@ else
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
private void ToggleConnectionString()
{
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
{
_connectionString = databaseConfigControl.GetConnectionString();
}
_showConnectionString = !_showConnectionString;
}
}

View File

@ -37,6 +37,12 @@
<div class="col-sm-9">
<input id="ipaddress" class="form-control" @bind="@_ipaddress" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="environment" HelpText="Environment name" ResourceKey="Environment">Environment: </Label>
<div class="col-sm-9">
<input id="environment" class="form-control" @bind="@_environment" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="contentrootpath" HelpText="Root Path" ResourceKey="ContentRootPath">Root Path: </Label>
@ -142,6 +148,7 @@
<ActionDialog Header="Restart Application" Message="Are You Sure You Wish To Restart The Application?" Action="Restart Application" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await RestartApplication())" ResourceKey="RestartApplication" />
</TabPanel>
</TabStrip>
<br /><br />
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
@ -151,6 +158,7 @@
private string _osversion = string.Empty;
private string _machinename = string.Empty;
private string _ipaddress = string.Empty;
private string _environment = string.Empty;
private string _contentrootpath = string.Empty;
private string _webrootpath = string.Empty;
private string _servertime = string.Empty;
@ -175,6 +183,7 @@
_osversion = systeminfo["OSVersion"].ToString();
_machinename = systeminfo["MachineName"].ToString();
_ipaddress = systeminfo["IPAddress"].ToString();
_environment = systeminfo["Environment"].ToString();
_contentrootpath = systeminfo["ContentRootPath"].ToString();
_webrootpath = systeminfo["WebRootPath"].ToString();
_servertime = systeminfo["ServerTime"].ToString() + " UTC";

View File

@ -35,6 +35,7 @@
<strong>@(String.Format("{0:n0}", context.Downloads))</strong> @SharedLocalizer["Search.Downloads"]&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Released"]: <strong>@context.ReleaseDate.ToString("MMM dd, yyyy")</strong>&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Version"]: <strong>@context.Version</strong>
@((MarkupString)(!string.IsNullOrEmpty(context.PackageUrl) ? "&nbsp;&nbsp;|&nbsp;&nbsp;" + SharedLocalizer["Search.Source"] + ": <strong>" + new Uri(context.PackageUrl).Host + "</strong>" : ""))
@((MarkupString)(context.TrialPeriod > 0 ? "&nbsp;&nbsp;|&nbsp;&nbsp;<strong>" + context.TrialPeriod + " " + @SharedLocalizer["Trial"] + "</strong>" : ""))
</td>
<td style="width: 1px; vertical-align: middle;">
@ -113,6 +114,10 @@
<button type="button" class="btn btn-success" @onclick="InstallThemes">@SharedLocalizer["Install"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<br />
<br />
<ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" />
@code {
private List<Package> _packages;
private string _price = "free";

View File

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

View File

@ -40,9 +40,12 @@ else
@((MarkupString)PurchaseLink(context.PackageName))
</td>
<td>
@if (UpgradeAvailable(context.PackageName, context.Version))
@{
var version = UpgradeAvailable(context.PackageName, context.Version);
}
@if (version != context.Version)
{
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadTheme(context.PackageName, context.Version))>@SharedLocalizer["Upgrade"]</button>
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadTheme(context.PackageName, version))>@SharedLocalizer["Upgrade"]</button>
}
</td>
<td></td>
@ -94,18 +97,17 @@ else
return link;
}
private bool UpgradeAvailable(string packagename, string version)
private string UpgradeAvailable(string packagename, string version)
{
var upgradeavailable = false;
if (!string.IsNullOrEmpty(packagename) && _packages != null)
{
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
if (package != null)
if (package != null && Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0)
{
upgradeavailable = (Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0);
return package.Version;
}
}
return upgradeavailable;
return version;
}
private async Task DownloadTheme(string packagename, string version)

View File

@ -24,6 +24,12 @@
<div class="col-sm-9">
<input id="version" class="form-control" @bind="@_version" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this module was installed" ResourceKey="PackageName">Package Name: </Label>
<div class="col-sm-9">
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="owner" HelpText="The owner or creator of the theme" ResourceKey="Owner">Owner: </Label>
@ -56,6 +62,7 @@
private string _themeName = "";
private string _name;
private string _version;
private string _packagename;
private string _owner = "";
private string _url = "";
private string _contact = "";
@ -74,6 +81,7 @@
{
_name = theme.Name;
_version = theme.Version;
_packagename = theme.PackageName;
_owner = theme.Owner;
_url = theme.Url;
_contact = theme.Contact;

View File

@ -32,13 +32,19 @@ else
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="password" HelpText="If you wish to change your password you can enter it here. Please choose a sufficiently secure password." ResourceKey="Password"></Label>
<div class="col-sm-9">
<input id="password" type="password" class="form-control" @bind="@password" autocomplete="new-password" />
<div class="input-group">
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="confirm" HelpText="If you are changing your password you must enter it again to confirm it matches" ResourceKey="Confirm"></Label>
<div class="col-sm-9">
<input id="confirm" type="password" class="form-control" @bind="@confirm" autocomplete="new-password" />
<div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@confirm" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
</div>
</div>
</div>
@if (allowtwofactor)
@ -154,7 +160,7 @@ else
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
<td>@context.FromDisplayName</td>
<td>@context.Subject</td>
<td>@context.CreatedOn</td>
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</td>
</Row>
<Detail>
<td colspan="2"></td>
@ -187,7 +193,7 @@ else
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
<td>@context.ToDisplayName</td>
<td>@context.Subject</td>
<td>@context.CreatedOn</td>
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</td>
</Row>
<Detail>
<td colspan="2"></td>
@ -205,6 +211,8 @@ else
</Detail>
</Pager>
}
<br />
<ActionDialog Header="Clear Notifications" Message="Are You Sure You Wish To Permanently Delete All Notifications ?" Action="Delete All Notifications" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllNotifications())" ResourceKey="DeleteAllNotifications" />
<br /><hr />
<select class="form-select" @onchange="(e => FilterChanged(e))">
<option value="to">@Localizer["Inbox"]</option>
@ -217,7 +225,9 @@ else
@code {
private string username = string.Empty;
private string password = string.Empty;
private string _password = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private string confirm = string.Empty;
private bool allowtwofactor = false;
private string twofactor = "False";
@ -239,9 +249,11 @@ else
{
try
{
_togglepassword = SharedLocalizer["ShowPassword"];
if (PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:TwoFactor"]))
{
allowtwofactor = bool.Parse(PageState.Site.Settings["LoginOptions:TwoFactor"]);
allowtwofactor = (PageState.Site.Settings["LoginOptions:TwoFactor"] == "true");
}
if (PageState.User != null)
@ -301,11 +313,11 @@ else
{
if (username != string.Empty && email != string.Empty && ValidateProfiles())
{
if (password == confirm)
if (_password == confirm)
{
var user = PageState.User;
user.Username = username;
user.Password = password;
user.Password = _password;
user.TwoFactorRequired = bool.Parse(twofactor);
user.Email = email;
user.DisplayName = (displayname == string.Empty ? username : displayname);
@ -413,4 +425,51 @@ else
StateHasChanged();
}
private async Task DeleteAllNotifications()
{
try
{
ModuleInstance.ShowProgressIndicator();
foreach(var Notification in notifications)
{
if (!Notification.IsDeleted)
{
Notification.IsDeleted = true;
await NotificationService.UpdateNotificationAsync(Notification);
}
else
{
await NotificationService.DeleteNotificationAsync(Notification.NotificationId);
}
await logger.LogInformation("Notification Deleted {Notification}", Notification);
}
await logger.LogInformation("Notifications Permanently Deleted");
await LoadNotificationsAsync();
ModuleInstance.HideProgressIndicator();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting Notifications {Error}", ex.Message);
AddModuleMessage(ex.Message, MessageType.Error);
ModuleInstance.HideProgressIndicator();
}
}
private void TogglePassword()
{
if (_passwordtype == "password")
{
_passwordtype = "text";
_togglepassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordtype = "password";
_togglepassword = SharedLocalizer["ShowPassword"];
}
}
}

View File

@ -21,13 +21,19 @@
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password"></Label>
<div class="col-sm-9">
<input id="password" type="password" class="form-control" @bind="@password" />
<div class="input-group">
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm"></Label>
<div class="col-sm-9">
<input id="confirm" type="password" class="form-control" @bind="@confirm" />
<div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@confirm" autocomplete="new-password" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
@ -88,7 +94,9 @@
@code {
private string username = string.Empty;
private string password = string.Empty;
private string _password = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private string confirm = string.Empty;
private string email = string.Empty;
private string displayname = string.Empty;
@ -102,6 +110,7 @@
{
try
{
_togglepassword = SharedLocalizer["ShowPassword"];
profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
settings = new Dictionary<string, string>();
}
@ -119,9 +128,9 @@
{
try
{
if (username != string.Empty && password != string.Empty && confirm != string.Empty && email != string.Empty && ValidateProfiles())
if (username != string.Empty && _password != string.Empty && confirm != string.Empty && email != string.Empty && ValidateProfiles())
{
if (password == confirm)
if (_password == confirm)
{
var user = await UserService.GetUserAsync(username, PageState.Site.SiteId);
if (user == null)
@ -129,7 +138,7 @@
user = new User();
user.SiteId = PageState.Site.SiteId;
user.Username = username;
user.Password = password;
user.Password = _password;
user.Email = email;
user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname;
user.PhotoFileId = null;
@ -193,4 +202,17 @@
settings = SettingService.SetSetting(settings, SettingName, value);
}
private void TogglePassword()
{
if (_passwordtype == "password")
{
_passwordtype = "text";
_togglepassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordtype = "password";
_togglepassword = SharedLocalizer["ShowPassword"];
}
}
}

View File

@ -30,13 +30,19 @@ else
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password"></Label>
<div class="col-sm-9">
<input id="password" type="password" class="form-control" @bind="@password" />
<div class="input-group">
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm"></Label>
<div class="col-sm-9">
<input id="confirm" type="password" class="form-control" @bind="@confirm" />
<div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@confirm" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
@ -66,8 +72,19 @@ else
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="lastlogin" HelpText="The date and time when the user last signed in" ResourceKey="LastLogin"></Label>
<div class="col-sm-9">
<input id="lastlogin" class="form-control" @bind="@lastlogin" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="lastipaddress" HelpText="The IP Address of the user recorded during their last login" ResourceKey="LastIPAddress"></Label>
<div class="col-sm-9">
<input id="lastipaddress" class="form-control" @bind="@lastipaddress" readonly />
</div>
</div>
</div>
}
</TabPanel>
<TabPanel Name="Profile" ResourceKey="Profile">
@ -133,23 +150,29 @@ else
@code {
private int userid;
private string username = string.Empty;
private string password = string.Empty;
private string _password = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private string confirm = string.Empty;
private string email = string.Empty;
private string displayname = string.Empty;
private FileManager filemanager;
private int photofileid = -1;
private File photo = null;
private string isdeleted;
private string lastlogin;
private string lastipaddress;
private List<Profile> profiles;
private Dictionary<string, string> settings;
private string category = string.Empty;
private string createdby;
private DateTime createdon;
private string modifiedby;
private DateTime modifiedon;
private string deletedby;
private DateTime? deletedon;
private string isdeleted;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
@ -157,9 +180,9 @@ else
{
try
{
// OnParametersSetAsync is called when the edit modal is closed - in which case there is no id parameter
if (PageState.QueryString.ContainsKey("id"))
{
_togglepassword = SharedLocalizer["ShowPassword"];
profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
userid = Int32.Parse(PageState.QueryString["id"]);
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
@ -178,6 +201,10 @@ else
photofileid = -1;
photo = null;
}
isdeleted = user.IsDeleted.ToString();
lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn);
lastipaddress = user.LastIPAddress;
settings = await SettingService.GetUserSettingsAsync(user.UserId);
createdby = user.CreatedBy;
createdon = user.CreatedOn;
@ -185,7 +212,6 @@ else
modifiedon = user.ModifiedOn;
deletedby = user.DeletedBy;
deletedon = user.DeletedOn;
isdeleted = user.IsDeleted.ToString();
}
}
}
@ -205,12 +231,12 @@ else
{
if (username != string.Empty && email != string.Empty && ValidateProfiles())
{
if (password == confirm)
if (_password == confirm)
{
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
user.SiteId = PageState.Site.SiteId;
user.Username = username;
user.Password = password;
user.Password = _password;
user.Email = email;
user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname;
user.PhotoFileId = null;
@ -268,4 +294,17 @@ else
settings = SettingService.SetSetting(settings, SettingName, value);
}
private void TogglePassword()
{
if (_passwordtype == "password")
{
_passwordtype = "text";
_togglepassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordtype = "password";
_togglepassword = SharedLocalizer["ShowPassword"];
}
}
}

View File

@ -7,7 +7,7 @@
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (userroles == null)
@if (users == null)
{
<p>
<em>@SharedLocalizer["Loading"]</em>
@ -30,13 +30,14 @@ else
</div>
</div>
</div>
<Pager Items="@userroles">
<Pager Items="@users" RowClass="align-middle">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th>
<th>@SharedLocalizer["Username"]</th>
<th>@SharedLocalizer["Name"]</th>
<th>@Localizer["LastLoginOn"]</th>
</Header>
<Row>
<td>
@ -48,8 +49,9 @@ else
<td>
<ActionLink Action="Roles" Parameters="@($"id=" + context.UserId.ToString())" ResourceKey="Roles" />
</td>
<td>@context.User.DisplayName</td>
<td>@context.User.Username</td>
<td>@((MarkupString)string.Format("<a href=\"mailto:{0}\">{1}</a>", @context.User.Email, @context.User.DisplayName))</td>
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", context.User.LastLoginOn)</td>
</Row>
</Pager>
</TabPanel>
@ -86,28 +88,28 @@ else
</div>
</div>
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="twofactor" HelpText="Do you want to allow users to use two factor authentication? Note that the Notification Job in Scheduled Jobs needs to be enabled for this option to work properly." ResourceKey="TwoFactor">Allow Two Factor?</Label>
<div class="col-sm-9">
<select id="twofactor" class="form-select" @bind="@_twofactor">
<option value="true">@SharedLocalizer["Yes"]</option>
<option value="false">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
@if (!string.IsNullOrEmpty(PageState.Alias.Path))
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="cookietype" HelpText="Cookies are usually managed per domain. However you can also choose to have distinct cookies for each site (this option is only applicable to micro-sites)." ResourceKey="CookieType">Cookie Type:</Label>
<Label Class="col-sm-3" For="twofactor" HelpText="Do you want users to use two factor authentication? Note that you should use the Disabled option until you have successfully verified that the Notification Job in Scheduled Jobs is enabled and your SMTP options in Site Settings are configured or else you will lock yourself out." ResourceKey="TwoFactor">Two Factor?</Label>
<div class="col-sm-9">
<select id="cookietype" class="form-select" @bind="@_cookietype">
<option value="domain">@Localizer["Domain"]</option>
<option value="site">@Localizer["Site"]</option>
<select id="twofactor" class="form-select" @bind="@_twofactor">
<option value="false">@Localizer["Disabled"]</option>
<option value="true">@Localizer["Optional"]</option>
<option value="required">@Localizer["Required"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="cookiename" HelpText="You can choose to use a custom authentication cookie name for each site. However please be aware that if you want to share an authentication cookie between sites on the same domain they need to use a consistent cookie name. Also be aware that changing the authentication cookie name will logout all current users." ResourceKey="CookieName">Cookie Name:</Label>
<div class="col-sm-9">
<input id="cookiename" class="form-control" @bind="@_cookiename" />
</div>
</div>
}
</Section>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
<Section Name="Password" Heading="Password Settings" ResourceKey="PasswordSettings">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="minimumlength" HelpText="The Minimum Length For A Password" ResourceKey="RequiredLength">Minimum Length:</Label>
@ -251,6 +253,12 @@ else
<input id="scopes" class="form-control" @bind="@_scopes" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="parameters" HelpText="Optionally specify any additional parameters as name/value pairs to send to the provider (separated by commas if there are multiple)." ResourceKey="Parameters">Parameters:</Label>
<div class="col-sm-9">
<input id="parameters" class="form-control" @bind="@_parameters" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="pkce" HelpText="Indicate if the provider supports Proof Key for Code Exchange (PKCE)" ResourceKey="PKCE">Use PKCE?</Label>
<div class="col-sm-9">
@ -266,15 +274,18 @@ else
<input id="redirecturl" class="form-control" @bind="@_redirecturl" readonly />
</div>
</div>
@if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="emailclaimtype" HelpText="The type name for the email address claim provided by the provider" ResourceKey="EmailClaimType">Email Claim Type:</Label>
<Label Class="col-sm-3" For="identifierclaimtype" HelpText="The name of the unique user identifier claim provided by the provider" ResourceKey="IdentifierClaimType">Identifier Claim:</Label>
<div class="col-sm-9">
<input id="identifierclaimtype" class="form-control" @bind="@_identifierclaimtype" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="emailclaimtype" HelpText="The name of the email address claim provided by the provider" ResourceKey="EmailClaimType">Email Claim:</Label>
<div class="col-sm-9">
<input id="emailclaimtype" class="form-control" @bind="@_emailclaimtype" />
</div>
</div>
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="domainfilter" HelpText="Provide any email domain filter criteria (separated by commas). Domains to exclude should be prefixed with an exclamation point (!). For example 'microsoft.com,!hotmail.com' would include microsoft.com email addresses but not hotmail.com email addresses." ResourceKey="DomainFilter">Domain Filter:</Label>
<div class="col-sm-9">
@ -330,6 +341,7 @@ else
</div>
</div>
</Section>
}
</div>
<br />
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
@ -338,14 +350,14 @@ else
}
@code {
private List<UserRole> allroles;
private List<UserRole> userroles;
private string _search;
private List<UserRole> allusers;
private List<UserRole> users;
private string _search = "";
private string _allowregistration;
private string _allowsitelogin;
private string _twofactor;
private string _cookietype;
private string _cookiename;
private string _minimumlength;
private string _uniquecharacters;
@ -368,8 +380,10 @@ else
private string _clientsecrettype = "password";
private string _toggleclientsecret = string.Empty;
private string _scopes;
private string _parameters;
private string _pkce;
private string _redirecturl;
private string _identifierclaimtype;
private string _emailclaimtype;
private string _domainfilter;
private string _createusers;
@ -386,15 +400,17 @@ else
protected override async Task OnInitializedAsync()
{
allroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId);
await LoadSettingsAsync();
userroles = Search(_search);
await LoadUserSettingsAsync();
await LoadUsersAsync(true);
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
_allowregistration = PageState.Site.AllowRegistration.ToString();
_allowsitelogin = SettingService.GetSetting(settings, "LoginOptions:AllowSiteLogin", "true");
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_twofactor = SettingService.GetSetting(settings, "LoginOptions:TwoFactor", "false");
_cookietype = SettingService.GetSetting(settings, "LoginOptions:CookieType", "domain");
_cookiename = SettingService.GetSetting(settings, "LoginOptions:CookieName", ".AspNetCore.Identity.Application");
_minimumlength = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredLength", "6");
_uniquecharacters = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", "1");
@ -415,42 +431,53 @@ else
_userinfourl = SettingService.GetSetting(settings, "ExternalLogin:UserInfoUrl", "");
_clientid = SettingService.GetSetting(settings, "ExternalLogin:ClientId", "");
_clientsecret = SettingService.GetSetting(settings, "ExternalLogin:ClientSecret", "");
_toggleclientsecret = Localizer["Show"];
_toggleclientsecret = SharedLocalizer["ShowPassword"];
_scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", "");
_parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", "");
_pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false");
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;
_identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier");
_emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress");
_domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", "");
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
_secret = SettingService.GetSetting(settings, "JwtOptions:Secret", "");
_togglesecret = Localizer["Show"];
_togglesecret = SharedLocalizer["ShowPassword"];
_issuer = SettingService.GetSetting(settings, "JwtOptions:Issuer", PageState.Uri.Scheme + "://" + PageState.Alias.Name);
_audience = SettingService.GetSetting(settings, "JwtOptions:Audience", "");
_lifetime = SettingService.GetSetting(settings, "JwtOptions:Lifetime", "20");
_lifetime = SettingService.GetSetting(settings, "JwtOptions:Lifetime", "20"); }
}
private List<UserRole> Search(string search)
private async Task LoadUsersAsync(bool load)
{
var results = allroles.Where(item => item.Role.Name == RoleNames.Registered || (item.Role.Name == RoleNames.Host && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)));
if (load)
{
allusers = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
var hosts = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Host);
allusers.AddRange(hosts);
allusers = allusers.OrderBy(u => u.User.DisplayName).ToList();
}
}
users = allusers;
if (!string.IsNullOrEmpty(_search))
{
results = results.Where(item =>
users = users.Where(item =>
(
item.User.Username.Contains(search, StringComparison.OrdinalIgnoreCase) ||
item.User.Email.Contains(search, StringComparison.OrdinalIgnoreCase) ||
item.User.DisplayName.Contains(search, StringComparison.OrdinalIgnoreCase)
item.User.Username.Contains(_search, StringComparison.OrdinalIgnoreCase) ||
item.User.Email.Contains(_search, StringComparison.OrdinalIgnoreCase) ||
item.User.DisplayName.Contains(_search, StringComparison.OrdinalIgnoreCase)
)
);
).ToList();
}
return results.ToList();
}
private async Task OnSearch()
{
userroles = Search(_search);
await UpdateSettingsAsync();
await UpdateUserSettingsAsync();
await LoadUsersAsync(false);
}
private async Task DeleteUser(UserRole UserRole)
@ -462,8 +489,7 @@ else
{
await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId);
await logger.LogInformation("User Deleted {User}", UserRole.User);
allroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId);
userroles = Search(_search);
await LoadUsersAsync(true);
StateHasChanged();
}
}
@ -476,13 +502,13 @@ else
private string settingSearch = "AU-search";
private async Task LoadSettingsAsync()
private async Task LoadUserSettingsAsync()
{
Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
_search = SettingService.GetSetting(settings, settingSearch, "");
}
private async Task UpdateSettingsAsync()
private async Task UpdateUserSettingsAsync()
{
Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
SettingService.SetSetting(settings, settingSearch, _search);
@ -499,8 +525,11 @@ else
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
settings = SettingService.SetSetting(settings, "LoginOptions:AllowSiteLogin", _allowsitelogin, false);
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false);
settings = SettingService.SetSetting(settings, "LoginOptions:CookieType", _cookietype, true);
settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredLength", _minimumlength, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", _uniquecharacters, true);
@ -522,7 +551,9 @@ else
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientId", _clientid, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientSecret", _clientsecret, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:Scopes", _scopes, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:Parameters", _parameters, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:IdentifierClaimType", _identifierclaimtype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:EmailClaimType", _emailclaimtype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true);
@ -532,10 +563,16 @@ else
settings = SettingService.SetSetting(settings, "JwtOptions:Issuer", _issuer, true);
settings = SettingService.SetSetting(settings, "JwtOptions:Audience", _audience, true);
settings = SettingService.SetSetting(settings, "JwtOptions:Lifetime", _lifetime, true);
}
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
await SettingService.ClearSiteSettingsCacheAsync();
if (!string.IsNullOrEmpty(_secret))
{
SiteState.AuthorizationToken = await UserService.GetTokenAsync();
}
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
}
catch (Exception ex)
@ -548,13 +585,20 @@ else
private void ProviderTypeChanged(ChangeEventArgs e)
{
_providertype = (string)e.Value;
if (string.IsNullOrEmpty(_providername))
{
if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
{
_scopes = "openid,profile,email";
_identifierclaimtype = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier";
_emailclaimtype = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress";
}
else
{
_scopes = "";
_identifierclaimtype = "sub";
_emailclaimtype = "email";
}
}
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;
StateHasChanged();
@ -562,7 +606,7 @@ else
private async Task CreateToken()
{
_token = await UserService.GetTokenAsync();
_token = await UserService.GetPersonalAccessTokenAsync();
}
private void ToggleClientSecret()
@ -570,12 +614,12 @@ else
if (_clientsecrettype == "password")
{
_clientsecrettype = "text";
_toggleclientsecret = Localizer["Hide"];
_toggleclientsecret = SharedLocalizer["HidePassword"];
}
else
{
_clientsecrettype = "password";
_toggleclientsecret = Localizer["Show"];
_toggleclientsecret = SharedLocalizer["ShowPassword"];
}
}
@ -584,12 +628,12 @@ else
if (_secrettype == "password")
{
_secrettype = "text";
_togglesecret = Localizer["Hide"];
_togglesecret = SharedLocalizer["HidePassword"];
}
else
{
_secrettype = "password";
_togglesecret = Localizer["Show"];
_togglesecret = SharedLocalizer["ShowPassword"];
}
}
}

View File

@ -88,15 +88,17 @@ else
userid = Int32.Parse(PageState.QueryString["id"]);
User user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
name = user.DisplayName;
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
roles = await RoleService.GetRolesAsync(PageState.Site.SiteId, true);
roles = roles.Where(item => item.Name != RoleNames.Everyone).ToList();
roles.RemoveAll(item => item.Name == RoleNames.Everyone || item.Name == RoleNames.Unauthenticated);
}
else
{
roles = await RoleService.GetRolesAsync(PageState.Site.SiteId);
}
await GetUserRoles();
}
catch (Exception ex)
@ -110,8 +112,7 @@ else
{
try
{
userroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId);
userroles = userroles.Where(item => item.UserId == userid).ToList();
userroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, userid);
}
catch (Exception ex)
{

View File

@ -128,13 +128,6 @@
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"]);
}
return (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : NavigateUrl();
}
}

View File

@ -43,7 +43,7 @@ else
<th>@Localizer["Created"]</th>
</Header>
<Row>
<td><ActionLink Action="Detail" Parameters="@($"id=" + context.VisitorId.ToString() + "&type=" + _type.ToString() + "&days=" + _days.ToString() + "&page=" + _page.ToString())" ResourceKey="Details" /></td>
<td><ActionLink Action="Detail" Parameters="@($"id={context.VisitorId}")" ReturnUrl="@(NavigateUrl(PageState.Page.Path, $"type={_type}&days={_days}&page={_page}"))" ResourceKey="Details" /></td>
<td>@context.IPAddress</td>
<td>
@if (context.UserId != null)

View File

@ -1,4 +1,5 @@
@namespace Oqtane.Modules.Controls
@using System.Net
@inherits LocalizableComponent
@inject IUserService UserService
@ -71,6 +72,9 @@
[Parameter]
public bool IconOnly { get; set; } // optional - specifies only icon in link
[Parameter]
public string ReturnUrl { get; set; } // optional - used to set a url to redirect to
protected override void OnParametersSet()
{
base.OnParametersSet();
@ -118,6 +122,10 @@
_permissions = (string.IsNullOrEmpty(Permissions)) ? ModuleState.Permissions : Permissions;
_text = Localize(nameof(Text), _text);
_url = (ModuleId == -1) ? EditUrl(Action, _parameters) : EditUrl(ModuleId, Action, _parameters);
if (!string.IsNullOrEmpty(ReturnUrl))
{
_url += ((_url.Contains("?")) ? "&" : "?") + $"returnurl={WebUtility.UrlEncode(ReturnUrl)}";
}
_authorized = IsAuthorized();
}

View File

@ -310,6 +310,17 @@
var interop = new Interop(JSRuntime);
var upload = await interop.GetFiles(_fileinputid);
if (upload.Length > 0)
{
string restricted = "";
foreach (var file in upload)
{
var extension = (file.LastIndexOf(".") != -1) ? file.Substring(file.LastIndexOf(".") + 1) : "";
if (!Constants.UploadableFiles.Split(',').Contains(extension.ToLower()))
{
restricted += (restricted == "" ? "" : ",") + extension;
}
}
if (restricted == "")
{
try
{
@ -360,6 +371,12 @@
}
}
else
{
_message = string.Format(Localizer["Message.File.Restricted"], restricted);
_messagetype = MessageType.Warning;
}
}
else
{
_message = Localizer["Message.File.NotSelected"];
_messagetype = MessageType.Warning;
@ -397,4 +414,24 @@
public int GetFolderId() => FolderId;
public File GetFile() => _file;
public async Task Refresh()
{
await Refresh(-1);
}
public async Task Refresh(int fileId)
{
await GetFiles();
if (fileId != -1)
{
var file = _files.Where(item => item.FileId == fileId).FirstOrDefault();
if (file != null)
{
FileId = file.FileId;
await SetImage();
}
}
StateHasChanged();
}
}

View File

@ -1,13 +1,13 @@
using System;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Oqtane.Shared;
namespace Oqtane.Modules.Controls
{
public class LocalizableComponent : ModuleControlBase
{
[Inject] public IStringLocalizerFactory LocalizerFactory { get; set; }
private IStringLocalizer _localizer;
[Parameter]
@ -30,48 +30,32 @@ namespace Oqtane.Modules.Controls
var key = $"{ResourceKey}.{propertyName}";
var value = Localize(key);
if (value == key)
if (value == key || value == String.Empty)
{
// Returns default property value (English version) instead of ResourceKey.PropertyName
return propertyValue;
}
else
{
if (value == String.Empty)
{
// Returns default property value (English version)
// return default property value if key does not exist in resource file or value is empty
return propertyValue;
}
else
{
// return localized value
return value;
}
}
}
protected override void OnParametersSet()
{
IsLocalizable = false;
if (string.IsNullOrEmpty(ResourceType))
if (String.IsNullOrEmpty(ResourceType))
{
ResourceType = ModuleState?.ModuleType;
}
if (!String.IsNullOrEmpty(ResourceKey) && !string.IsNullOrEmpty(ResourceType))
if (!String.IsNullOrEmpty(ResourceKey) && !String.IsNullOrEmpty(ResourceType))
{
var moduleType = Type.GetType(ResourceType);
if (moduleType != null)
{
using (var scope = ServiceActivator.GetScope())
{
var localizerFactory = scope.ServiceProvider.GetService<IStringLocalizerFactory>();
_localizer = localizerFactory.Create(moduleType);
_localizer = LocalizerFactory.Create(ResourceType);
IsLocalizable = true;
}
}
}
}
}
}

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleControlBase
@inject IStringLocalizerFactory LocalizerFactory
@typeparam TableItem
@if (ItemList != null)
@ -48,7 +49,7 @@
<a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
</li>
<li class="page-item disabled">
<a class="page-link" style="white-space: nowrap;">Page @_page of @_pages</a>
<a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
</li>
</ul>
}
@ -156,13 +157,14 @@
<a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
</li>
<li class="page-item disabled">
<a class="page-link" style="white-space: nowrap;">Page @_page of @_pages</a>
<a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
</li>
</ul>
}
}
@code {
private IStringLocalizer Localizer;
private int _pages = 0;
private int _page = 1;
private int _maxItems = 10;
@ -215,6 +217,11 @@
private IEnumerable<TableItem> ItemList { get; set; }
protected override void OnInitialized()
{
Localizer = LocalizerFactory.Create(GetType().FullName);
}
protected override void OnParametersSet()
{
if (string.IsNullOrEmpty(Format))

View File

@ -127,11 +127,10 @@
_permissionnames = PermissionNames;
}
_roles = await RoleService.GetRolesAsync(ModuleState.SiteId);
_roles.Insert(0, new Role { Name = RoleNames.Everyone });
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
_roles = await RoleService.GetRolesAsync(ModuleState.SiteId, true);
if (!UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_roles.Add(new Role { Name = RoleNames.Host });
_roles.RemoveAll(item => item.Name == RoleNames.Host);
}
_permissions = new List<PermissionString>();
@ -254,6 +253,7 @@
permission = _permissions[i];
List<string> ids = permission.Permissions.Split(';', StringSplitOptions.RemoveEmptyEntries).ToList();
ids.Remove("!" + RoleNames.Everyone); // remove deny all users
ids.Remove("!" + RoleNames.Unauthenticated); // remove deny unauthenticated
ids.Remove("!" + RoleNames.Registered); // remove deny registered users
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{

View File

@ -6,24 +6,27 @@
<div class="col">
<TabStrip>
<TabPanel Name="Rich" Heading="Rich Text Editor">
@if (AllowFileManagement)
{
@if (_filemanagervisible)
@if (_richfilemanager)
{
<FileManager @ref="_fileManager" Filter="@Constants.ImageFiles" />
<ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
<br />
}
<div class="d-flex justify-content-center mb-2">
<button type="button" class="btn btn-secondary" @onclick="RefreshRichText">@Localizer["SynchronizeContent"]</button>&nbsp;&nbsp;
<button type="button" class="btn btn-primary" @onclick="InsertImage">@Localizer["InsertImage"]</button>
@if (_filemanagervisible)
@if (AllowRawHtml)
{
<button type="button" class="btn btn-secondary" @onclick="RefreshRichText">@Localizer["SynchronizeContent"]</button>@((MarkupString)"&nbsp;&nbsp;")
}
@if (AllowFileManagement)
{
<button type="button" class="btn btn-primary" @onclick="InsertRichImage">@Localizer["InsertImage"]</button>
}
@if (_richfilemanager)
{
@((MarkupString)"&nbsp;&nbsp;")
<button type="button" class="btn btn-secondary" @onclick="CloseFileManager">@Localizer["Close"]</button>
<button type="button" class="btn btn-secondary" @onclick="CloseRichFileManager">@Localizer["Close"]</button>
}
</div>
}
<div class="row">
<div class="col">
<div @ref="@_toolBar">
@ -65,19 +68,37 @@
</div>
</div>
</TabPanel>
@if (AllowRawHtml)
{
<TabPanel Name="Raw" Heading="Raw HTML Editor" ResourceKey="HtmlEditor">
@if (_rawfilemanager)
{
<FileManager @ref="_fileManager" Filter="@Constants.ImageFiles" />
<ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
<br />
}
<div class="d-flex justify-content-center mb-2">
<button type="button" class="btn btn-secondary" @onclick="RefreshRawHtml">@Localizer["SynchronizeContent"]</button>
<button type="button" class="btn btn-secondary" @onclick="RefreshRawHtml">@Localizer["SynchronizeContent"]</button>&nbsp;&nbsp;
@if (AllowFileManagement)
{
<button type="button" class="btn btn-primary" @onclick="InsertRawImage">@Localizer["InsertImage"]</button>
}
@if (_rawfilemanager)
{
@((MarkupString)"&nbsp;&nbsp;")
<button type="button" class="btn btn-secondary" @onclick="CloseRawFileManager">@Localizer["Close"]</button>
}
</div>
@if (ReadOnly)
{
<textarea class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10" readonly></textarea>
<textarea id="rawhtmleditor" class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10" readonly></textarea>
}
else
{
<textarea class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10"></textarea>
<textarea id="rawhtmleditor" class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10"></textarea>
}
</TabPanel>
}
</TabStrip>
</div>
</div>
@ -85,10 +106,11 @@
@code {
private ElementReference _editorElement;
private ElementReference _toolBar;
private bool _filemanagervisible = false;
private bool _richfilemanager = false;
private FileManager _fileManager;
private string _richhtml = string.Empty;
private string _originalrichhtml = string.Empty;
private bool _rawfilemanager = false;
private string _rawhtml = string.Empty;
private string _originalrawhtml = string.Empty;
private string _message = string.Empty;
@ -102,6 +124,12 @@
[Parameter]
public string Placeholder { get; set; } = "Enter Your Content...";
[Parameter]
public bool AllowFileManagement { get; set; } = true;
[Parameter]
public bool AllowRawHtml { get; set; } = true;
// parameters only applicable to rich text editor
[Parameter]
public RenderFragment ToolbarContent { get; set; }
@ -112,9 +140,6 @@
[Parameter]
public string DebugLevel { get; set; } = "info";
[Parameter]
public bool AllowFileManagement { get; set; } = true;
public override List<Resource> Resources => new List<Resource>()
{
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill.min.js" },
@ -150,15 +175,18 @@
// preserve a copy of the rich text content (Quill sanitizes content so we need to retrieve it from the editor)
_originalrichhtml = await interop.GetHtml(_editorElement);
}
else
{
await interop.LoadEditorContent(_editorElement, _richhtml);
}
}
public void CloseFileManager()
public void CloseRichFileManager()
{
_filemanagervisible = false;
_richfilemanager = false;
_message = string.Empty;
StateHasChanged();
}
public void CloseRawFileManager()
{
_rawfilemanager = false;
_message = string.Empty;
StateHasChanged();
}
@ -200,17 +228,17 @@
}
}
public async Task InsertImage()
public async Task InsertRichImage()
{
_message = string.Empty;
if (_filemanagervisible)
if (_richfilemanager)
{
var file = _fileManager.GetFile();
if (file != null)
{
var interop = new RichTextEditorInterop(JSRuntime);
await interop.InsertImage(_editorElement, file.Url, file.Name);
_filemanagervisible = false;
await interop.InsertImage(_editorElement, file.Url, ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name));
_richfilemanager = false;
}
else
{
@ -219,7 +247,33 @@
}
else
{
_filemanagervisible = true;
_richfilemanager = true;
}
StateHasChanged();
}
public async Task InsertRawImage()
{
_message = string.Empty;
if (_rawfilemanager)
{
var file = _fileManager.GetFile();
if (file != null)
{
var interop = new Interop(JSRuntime);
int pos = await interop.GetCaretPosition("rawhtmleditor");
var image = "<img src=\"" + file.Url + "\" alt=\"" + ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name) + "\">";
_rawhtml = _rawhtml.Substring(0, pos) + image + _rawhtml.Substring(pos);
_rawfilemanager = false;
}
else
{
_message = Localizer["Message.Require.Image"];
}
}
else
{
_rawfilemanager = true;
}
StateHasChanged();
}

View File

@ -13,7 +13,7 @@
<TabPanel Name="Edit" Heading="Edit" ResourceKey="Edit">
@if (_content != null)
{
<RichTextEditor Content="@_content" AllowFileManagement="@_allowfilemanagement" @ref="@RichTextEditorHtml"></RichTextEditor>
<RichTextEditor Content="@_content" AllowFileManagement="@_allowfilemanagement" AllowRawHtml="@_allowrawhtml" @ref="@RichTextEditorHtml"></RichTextEditor>
<br />
<button type="button" class="btn btn-success" @onclick="SaveContent">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@ -60,6 +60,7 @@
private RichTextEditor RichTextEditorHtml;
private bool _allowfilemanagement;
private bool _allowrawhtml;
private string _content = null;
private string _createdby;
private DateTime _createdon;
@ -73,6 +74,7 @@
try
{
_allowfilemanagement = bool.Parse(SettingService.GetSetting(ModuleState.Settings, "AllowFileManagement", "true"));
_allowrawhtml = bool.Parse(SettingService.GetSetting(ModuleState.Settings, "AllowRawHtml", "true"));
await LoadContent();
}
catch (Exception ex)

View File

@ -15,17 +15,28 @@
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="files" ResourceKey="AllowRawHtml" ResourceType="@resourceType" HelpText="Specify If Editors Can Enter Raw HTML">Allow Raw HTML: </Label>
<div class="col-sm-9">
<select id="files" class="form-select" @bind="@_allowrawhtml">
<option value="true">@SharedLocalizer["Yes"]</option>
<option value="false">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
@code {
private string resourceType = "Oqtane.Modules.HtmlText.Settings, Oqtane.Client"; // for localization
private string _allowfilemanagement;
private string _allowrawhtml;
protected override void OnInitialized()
{
try
{
_allowfilemanagement = SettingService.GetSetting(ModuleState.Settings, "AllowFileManagement", "true");
_allowrawhtml = SettingService.GetSetting(ModuleState.Settings, "AllowRawHtml", "true");
}
catch (Exception ex)
{
@ -39,6 +50,7 @@
{
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
settings = SettingService.SetSetting(settings, "AllowFileManagement", _allowfilemanagement);
settings = SettingService.SetSetting(settings, "AllowRawHtml", _allowrawhtml);
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
}
catch (Exception ex)

View File

@ -15,6 +15,8 @@ namespace Oqtane.Modules
public abstract class ModuleBase : ComponentBase, IModuleControl
{
private Logger _logger;
private string _urlparametersstate;
private Dictionary<string, string> _urlparameters;
protected Logger logger => _logger ?? (_logger = new Logger(this));
@ -24,6 +26,9 @@ namespace Oqtane.Modules
[Inject]
protected IJSRuntime JSRuntime { get; set; }
[Inject]
protected SiteState SiteState { get; set; }
[CascadingParameter]
protected PageState PageState { get; set; }
@ -44,6 +49,21 @@ namespace Oqtane.Modules
public virtual List<Resource> Resources { get; set; }
// url parameters
public virtual string UrlParametersTemplate { get; set; }
public Dictionary<string, string> UrlParameters {
get
{
if (_urlparametersstate == null || _urlparametersstate != PageState.UrlParameters)
{
_urlparametersstate = PageState.UrlParameters;
_urlparameters = GetUrlParameters(UrlParametersTemplate);
}
return _urlparameters;
}
}
// base lifecycle method for handling JSInterop script registration
protected override async Task OnAfterRenderAsync(bool firstRender)
@ -55,7 +75,8 @@ namespace Oqtane.Modules
var scripts = new List<object>();
foreach (Resource resource in Resources.Where(item => item.ResourceType == ResourceType.Script))
{
scripts.Add(new { href = resource.Url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module });
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + "/" + resource.Url;
scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module });
}
if (scripts.Any())
{
@ -149,15 +170,26 @@ namespace Oqtane.Modules
return Utilities.ImageUrl(PageState.Alias, fileid, width, height, mode, position, background, rotate, recreate);
}
public virtual Dictionary<string, string> GetUrlParameters(string parametersTemplate = "")
public string AddUrlParameters(params object[] parameters)
{
var url = "";
for (var i = 0; i < parameters.Length; i++)
{
url += "/" + parameters[i].ToString();
}
return url;
}
// template is in the form of a standard route template ie. "/{id}/{name}" and produces dictionary of key/value pairs
// if url parameters belong to a specific module you should embed a unique key into the route (ie. /!/blog/1) and validate the url parameter key in the module
public virtual Dictionary<string, string> GetUrlParameters(string template = "")
{
var urlParameters = new Dictionary<string, string>();
string[] templateSegments;
var parameters = PageState.UrlParameters.Split('/', StringSplitOptions.RemoveEmptyEntries);
var parameterId = 0;
var parameters = _urlparametersstate.Split('/', StringSplitOptions.RemoveEmptyEntries);
if (string.IsNullOrEmpty(parametersTemplate))
if (string.IsNullOrEmpty(template))
{
// no template will populate dictionary with generic "parameter#" keys
for (int i = 0; i < parameters.Length; i++)
{
urlParameters.TryAdd("parameter" + i, parameters[i]);
@ -165,39 +197,37 @@ namespace Oqtane.Modules
}
else
{
templateSegments = parametersTemplate.Split('/', StringSplitOptions.RemoveEmptyEntries);
var segments = template.Split('/', StringSplitOptions.RemoveEmptyEntries);
string key;
if (parameters.Length == templateSegments.Length)
{
for (int i = 0; i < parameters.Length; i++)
{
if (parameters.Length > i)
if (i < segments.Length)
{
if (templateSegments[i] == parameters[i])
key = segments[i];
if (key.StartsWith("{") && key.EndsWith("}"))
{
urlParameters.TryAdd("parameter" + parameterId, parameters[i]);
parameterId++;
}
else if (templateSegments[i].StartsWith("{") && templateSegments[i].EndsWith("}"))
{
var key = templateSegments[i].Replace("{", "");
key = key.Replace("}", "");
urlParameters.TryAdd(key, parameters[i]);
// dynamic segment
key = key.Substring(1, key.Length - 2);
}
else
{
i = parameters.Length;
urlParameters.Clear();
// static segments use generic "parameter#" keys
key = "parameter" + i.ToString();
}
}
else // unspecified segments use generic "parameter#" keys
{
key = "parameter" + i.ToString();
}
urlParameters.TryAdd(key, parameters[i]);
}
}
return urlParameters;
}
// user feedback methods
// UI methods
public void AddModuleMessage(string message, MessageType type)
{
ModuleInstance.AddModuleMessage(message, type);
@ -218,6 +248,18 @@ namespace Oqtane.Modules
ModuleInstance.HideProgressIndicator();
}
public void SetModuleTitle(string title)
{
var obj = new { PageModuleId = ModuleState.PageModuleId, Title = title };
SiteState.Properties.ModuleTitle = obj;
}
public void SetModuleVisibility(bool visible)
{
var obj = new { PageModuleId = ModuleState.PageModuleId, Visible = visible };
SiteState.Properties.ModuleVisibility = obj;
}
// logging methods
public async Task Log(Alias alias, LogLevel level, string function, Exception exception, string message, params object[] args)
{

View File

@ -5,15 +5,15 @@
<OutputType>Exe</OutputType>
<RazorLangVersion>3.0</RazorLangVersion>
<Configurations>Debug;Release</Configurations>
<Version>3.1.0</Version>
<Version>3.2.0</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
<Description>Modular Application Framework for Blazor</Description>
<Description>Modular Application Framework for Blazor and MAUI</Description>
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.0</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace>
@ -35,13 +35,8 @@
<ProjectReference Include="..\Oqtane.Shared\Oqtane.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<TrimmerRootAssembly Include="System.Runtime" />
<TrimmerRootAssembly Include="System.Linq.Parallel" />
<TrimmerRootAssembly Include="System.Runtime.CompilerServices.VisualC" />
</ItemGroup>
<PropertyGroup>
<PublishTrimmed>false</PublishTrimmed>
<BlazorEnableCompression>false</BlazorEnableCompression>
</PropertyGroup>

View File

@ -7,6 +7,7 @@ using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Runtime.Loader;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.AspNetCore.Localization;
@ -15,7 +16,6 @@ using Microsoft.JSInterop;
using Oqtane.Documentation;
using Oqtane.Modules;
using Oqtane.Services;
using Oqtane.Shared;
using Oqtane.UI;
namespace Oqtane.Client
@ -33,7 +33,7 @@ namespace Oqtane.Client
builder.Services.AddOptions();
// Register localization services
// register localization services
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
// register auth services
@ -42,7 +42,9 @@ namespace Oqtane.Client
// register scoped core services
builder.Services.AddOqtaneScopedServices();
await LoadClientAssemblies(httpClient);
var serviceProvider = builder.Services.BuildServiceProvider();
await LoadClientAssemblies(httpClient, serviceProvider);
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
foreach (var assembly in assemblies)
@ -54,37 +56,105 @@ namespace Oqtane.Client
RegisterClientStartups(assembly, builder.Services);
}
var host = builder.Build();
await SetCultureFromLocalizationCookie(host.Services);
ServiceActivator.Configure(host.Services);
await host.RunAsync();
await builder.Build().RunAsync();
}
private static async Task LoadClientAssemblies(HttpClient http)
private static async Task LoadClientAssemblies(HttpClient http, IServiceProvider serviceProvider)
{
// get list of loaded assemblies on the client
var assemblies = AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetName().Name).ToList();
var dlls = new Dictionary<string, byte[]>();
var pdbs = new Dictionary<string, byte[]>();
var filter = new List<string>();
var jsRuntime = serviceProvider.GetRequiredService<IJSRuntime>();
var interop = new Interop(jsRuntime);
var files = await interop.GetIndexedDBKeys(".dll");
if (files.Count() != 0)
{
// get list of assemblies from server
var json = await http.GetStringAsync("/api/Installation/list");
var assemblies = JsonSerializer.Deserialize<List<string>>(json);
// determine which assemblies need to be downloaded
foreach (var assembly in assemblies)
{
var file = files.FirstOrDefault(item => item.Contains(assembly));
if (file == null)
{
filter.Add(assembly);
}
else
{
// check if newer version available
if (GetFileDate(assembly) > GetFileDate(file))
{
filter.Add(assembly);
}
}
}
// get assemblies already downloaded
foreach (var file in files)
{
if (assemblies.Contains(file) && !filter.Contains(file))
{
try
{
dlls.Add(file, await interop.GetIndexedDBItem<byte[]>(file));
var pdb = file.Replace(".dll", ".pdb");
if (files.Contains(pdb))
{
pdbs.Add(pdb, await interop.GetIndexedDBItem<byte[]>(pdb));
}
}
catch
{
// ignore
}
}
else // file is deprecated
{
try
{
await interop.RemoveIndexedDBItem(file);
}
catch
{
// ignore
}
}
}
}
else
{
filter.Add("*");
}
if (filter.Count != 0)
{
// get assemblies from server and load into client app domain
var zip = await http.GetByteArrayAsync($"/api/Installation/load");
var zip = await http.GetByteArrayAsync($"/api/Installation/load?list=" + string.Join(",", filter));
// asemblies and debug symbols are packaged in a zip file
using (ZipArchive archive = new ZipArchive(new MemoryStream(zip)))
{
var dlls = new Dictionary<string, byte[]>();
var pdbs = new Dictionary<string, byte[]>();
foreach (ZipArchiveEntry entry in archive.Entries)
{
if (!assemblies.Contains(Path.GetFileNameWithoutExtension(entry.FullName)))
{
using (var memoryStream = new MemoryStream())
{
entry.Open().CopyTo(memoryStream);
byte[] file = memoryStream.ToArray();
// save assembly to indexeddb
try
{
await interop.SetIndexedDBItem(entry.FullName, file);
}
catch
{
// ignore
}
switch (Path.GetExtension(entry.FullName))
{
case ".dll":
@ -97,12 +167,14 @@ namespace Oqtane.Client
}
}
}
}
// load assemblies into app domain
foreach (var item in dlls)
{
if (pdbs.ContainsKey(item.Key))
if (pdbs.ContainsKey(item.Key.Replace(".dll", ".pdb")))
{
AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(item.Value), new MemoryStream(pdbs[item.Key]));
AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(item.Value), new MemoryStream(pdbs[item.Key.Replace(".dll", ".pdb")]));
}
else
{
@ -110,10 +182,16 @@ namespace Oqtane.Client
}
}
}
private static DateTime GetFileDate(string filepath)
{
var segments = filepath.Split('.');
return DateTime.ParseExact(segments[segments.Length - 2], "yyyyMMddHHmmss", CultureInfo.InvariantCulture);
}
private static void RegisterModuleServices(Assembly assembly, IServiceCollection services)
{
// dynamically register module scoped services
var implementationTypes = assembly.GetInterfaces<IService>();
foreach (var implementationType in implementationTypes)
{

View File

@ -121,7 +121,7 @@
<value>Server:</value>
</data>
<data name="Server.HelpText" xml:space="preserve">
<value>Enter the database server</value>
<value>Enter the database server name. This might include a port number as well if you are using a cloud service (ie. servername.database.windows.net,1433) </value>
</data>
<data name="Database.Text" xml:space="preserve">
<value>Database:</value>
@ -133,7 +133,7 @@
<value>Integrated Security:</value>
</data>
<data name="IntegratedSecurity.HelpText" xml:space="preserve">
<value>Select if you want integrated security or not</value>
<value>Select if you are using integrated security</value>
</data>
<data name="Uid.Text" xml:space="preserve">
<value>User Id:</value>
@ -163,7 +163,7 @@
<value>Self Signed</value>
</data>
<data name="TrustServerCertificate.HelpText" xml:space="preserve">
<value>Specify the type of certificate you are using for encryption</value>
<value>Specify the type of certificate you are using for encryption. Verifiable is equivalent to False. Self Signed is equivalent to True.</value>
</data>
<data name="TrustServerCertificate.Text" xml:space="preserve">
<value>Trust Server Certificate:</value>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@ -123,9 +123,15 @@
<data name="Success.Account.Verified" xml:space="preserve">
<value>User Account Verified Successfully. You Can Now Login With Your Username And Password Below.</value>
</data>
<data name="Message.Account.NotVerfied" xml:space="preserve">
<data name="Message.Account.NotVerified" xml:space="preserve">
<value>User Account Could Not Be Verified. Please Contact Your Administrator For Further Instructions.</value>
</data>
<data name="Success.Account.Linked" xml:space="preserve">
<value>User Account Linked Successfully. You Can Now Login With Your External Login Below.</value>
</data>
<data name="Message.Account.NotLinked" xml:space="preserve">
<value>External Login Could Not Be Linked. Please Contact Your Administrator For Further Instructions.</value>
</data>
<data name="Error.Login.Fail" xml:space="preserve">
<value>Login Failed. Please Remember That Passwords Are Case Sensitive. If You Have Attempted To Sign In Multiple Times Unsuccessfully, Your Account Will Be Locked Out For A Period Of Time. Note That User Accounts Require Verification When They Are Initially Created So You May Wish To Check Your Email If You Are A New User.</value>
</data>
@ -183,12 +189,6 @@
<data name="Username.Text" xml:space="preserve">
<value>Username:</value>
</data>
<data name="HidePassword" xml:space="preserve">
<value>Hide</value>
</data>
<data name="ShowPassword" xml:space="preserve">
<value>Show</value>
</data>
<data name="Use" xml:space="preserve">
<value>Use</value>
</data>
@ -201,4 +201,28 @@
<data name="Error.ResetPassword" xml:space="preserve">
<value>Error Resetting Password</value>
</data>
<data name="ExternalLoginStatus.DuplicateEmail" xml:space="preserve">
<value>Multiple User Accounts Already Exist With The Email Address Of Your External Login. Please Contact Your Administrator For Further Instructions.</value>
</data>
<data name="ExternalLoginStatus.InvalidEmail" xml:space="preserve">
<value>The External Login Provider Did Not Provide A Valid Email Address For Your Account. Please Contact Your Administrator For Further Instructions.</value>
</data>
<data name="ExternalLoginStatus.ProviderKeyMismatch" xml:space="preserve">
<value>An Error Occurred Verifying Your External Login. Please Contact Your Administrator For Further Instructions.</value>
</data>
<data name="ExternalLoginStatus.UserDoesNotExist" xml:space="preserve">
<value>A User Account Matching The Email Address Of Your External Login Does Not Exist. Please Contact Your Administrator For Further Instructions.</value>
</data>
<data name="ExternalLoginStatus.UserNotCreated" xml:space="preserve">
<value>A User Account Could Not Be Created For Your External Login. Please Contact Your Administrator For Further Instructions.</value>
</data>
<data name="ExternalLoginStatus.VerificationRequired" xml:space="preserve">
<value>In Order To Link Your External Login With Your User Account You Must Verify Your Identity. Please Check Your Email For Further Instructions.</value>
</data>
<data name="ExternalLoginStatus.AccessDenied" xml:space="preserve">
<value>Your External Login Was Denied Access. Please Contact Your Administrator For Further Instructions.</value>
</data>
<data name="ExternalLoginStatus.RemoteFailure" xml:space="preserve">
<value>Your External Login Failed. Please Contact Your Administrator For Further Instructions.</value>
</data>
</root>

View File

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

View File

@ -150,4 +150,7 @@
<data name="EditModule.Text" xml:space="preserve">
<value>Edit</value>
</data>
<data name="Modules" xml:space="preserve">
<value>Modules</value>
</data>
</root>

View File

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

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@ -160,7 +160,7 @@
<value>Error Loading Pane Layouts For Theme</value>
</data>
<data name="Message.Page.Exists" xml:space="preserve">
<value>A page with path {0} already exists for the selected parent page. The page path needs to be unique for the selected parent.</value>
<value>A page with path '{0}' already exists for this site. Page paths must be unique. You may need to check if a page with this path exists in the Recycle Bin.</value>
</data>
<data name="Message.Required.PageInfo" xml:space="preserve">
<value>You Must Provide Page Name, Theme, and Container</value>
@ -228,13 +228,13 @@
<data name="Appearance.Name" xml:space="preserve">
<value>Appearance</value>
</data>
<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>
</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>
<data name="Message.Page.Reserved" xml:space="preserve">
<value>The page name {0} is reserved. Please enter a different name for your page.</value>
</data>
</root>

View File

@ -151,7 +151,7 @@
<value>Error Loading Pane Layouts For Theme</value>
</data>
<data name="Mesage.Page.PathExists" xml:space="preserve">
<value>A page with path {0} already exists for the selected parent page. The page path needs to be unique for the selected parent.</value>
<value>A page with path '{0}' already exists for this site. Page paths must be unique. You may need to check if a page with this path exists in the Recycle Bin.</value>
</data>
<data name="Message.Required.PageInfo" xml:space="preserve">
<value>You Must Provide Page Name, Theme, and Container</value>
@ -270,4 +270,7 @@
<data name="Meta.Text" xml:space="preserve">
<value>Meta:</value>
</data>
<data name="Message.Page.Reserved" xml:space="preserve">
<value>The page name {0} is reserved. Please enter a different name for your page.</value>
</data>
</root>

View File

@ -142,7 +142,7 @@
<value>Site Settings Saved</value>
</data>
<data name="Message.Aliases.Taken" xml:space="preserve">
<value>The Default Alias Has Not Been Specified Or An Alias Was Specified That Has Already Been Used For Another Site</value>
<value>An Alias Was Specified That Has Already Been Used For Another Site</value>
</data>
<data name="Message.Required.SiteName" xml:space="preserve">
<value>You Must Provide A Site Name, Alias, And Default Theme/Container</value>
@ -166,7 +166,7 @@
<value>Enter the tenant for the site</value>
</data>
<data name="Aliases.HelpText" xml:space="preserve">
<value>The aliases for the site. An alias can be a domain name (www.site.com) or a virtual folder (ie. www.site.com/folder). If a site has multiple aliases they should be separated by commas.</value>
<value>The aliases for the site. An alias can be a domain name (www.site.com) or a virtual folder (ie. www.site.com/folder).</value>
</data>
<data name="IsDeleted.HelpText" xml:space="preserve">
<value>Is this site deleted?</value>
@ -324,10 +324,19 @@
<data name="Aliases.Heading" xml:space="preserve">
<value>Aliases</value>
</data>
<data name="Hide" xml:space="preserve">
<value>Hide</value>
<data name="AliasName" xml:space="preserve">
<value>Name</value>
</data>
<data name="Show" xml:space="preserve">
<value>Show</value>
<data name="AliasDefault" xml:space="preserve">
<value>Default?</value>
</data>
<data name="Confirm.Alias.Delete" xml:space="preserve">
<value>Are You Sure You Wish To Delete {0}?</value>
</data>
<data name="HomePage.HelpText" xml:space="preserve">
<value>Select the home page for the site (to be used if there is no page with a path of '/')</value>
</data>
<data name="HomePage.Text" xml:space="preserve">
<value>Home Page:</value>
</data>
</root>

View File

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

View File

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

View File

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

View File

@ -210,4 +210,19 @@
<data name="TwoFactor.Text" xml:space="preserve">
<value>Two Factor?</value>
</data>
<data name="DeleteAllNotifications.Header" xml:space="preserve">
<value>Clear Notifications</value>
</data>
<data name="DeleteAllNotifications.Message" xml:space="preserve">
<value>Are You Sure You Wish To Permanently Delete All Notifications?</value>
</data>
<data name="DeleteAllNotifications.Text" xml:space="preserve">
<value>Delete ALL Notifications</value>
</data>
<data name="Notifications.Heading" xml:space="preserve">
<value>Notifications</value>
</data>
<data name="Profile.Heading" xml:space="preserve">
<value>Profile</value>
</data>
</root>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@ -168,4 +168,7 @@
<data name="Username.Text" xml:space="preserve">
<value>Username:</value>
</data>
<data name="Password.Placeholder" xml:space="preserve">
<value>Password</value>
</data>
</root>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@ -183,4 +183,19 @@
<data name="Profile.Heading" xml:space="preserve">
<value>Profile</value>
</data>
<data name="Password.Placeholder" xml:space="preserve">
<value>Password</value>
</data>
<data name="LastIPAddress.HelpText" xml:space="preserve">
<value>The IP Address of the user recorded during their last login</value>
</data>
<data name="LastIPAddress.Text" xml:space="preserve">
<value>Last IP Address: </value>
</data>
<data name="LastLogin.HelpText" xml:space="preserve">
<value>The date and time when the user last signed in</value>
</data>
<data name="LastLogin.Text" xml:space="preserve">
<value>Last Login:</value>
</data>
</root>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@ -247,10 +247,10 @@
<value>Domain Filter:</value>
</data>
<data name="EmailClaimType.HelpText" xml:space="preserve">
<value>The type name for the email address claim provided by the provider</value>
<value>The name of the email address claim provided by the provider</value>
</data>
<data name="EmailClaimType.Text" xml:space="preserve">
<value>Email Claim Type:</value>
<value>Email Claim:</value>
</data>
<data name="ExternalLoginSettings.Heading" xml:space="preserve">
<value>External Login Settings</value>
@ -318,11 +318,11 @@
<data name="UserSettings.Heading" xml:space="preserve">
<value>User Settings</value>
</data>
<data name="CookieType.HelpText" xml:space="preserve">
<value>Cookies are usually managed per domain. However you can also choose to have distinct cookies for each site (this option is only applicable to micro-sites).</value>
<data name="CookieName.HelpText" xml:space="preserve">
<value>You can choose to use a custom authentication cookie name for each site. However please be aware that if you want to share an authentication cookie between sites on the same domain they need to use a consistent cookie name. Also be aware that changing the authentication cookie name will logout all current users.</value>
</data>
<data name="CookieType.Text" xml:space="preserve">
<value>Login Cookie Type:</value>
<data name="CookieName.Text" xml:space="preserve">
<value>Cookie Name:</value>
</data>
<data name="CreateToken" xml:space="preserve">
<value>Create Token</value>
@ -355,15 +355,33 @@
<value>Token Settings</value>
</data>
<data name="TwoFactor.HelpText" xml:space="preserve">
<value>Do you want to allow users to use two factor authentication? Note that the Notification Job in Scheduled Jobs needs to be enabled and your SMTP options need to be configured in Site Settings for this option to work properly.</value>
<value>Do you want users to use two factor authentication? Note that you should use the Disabled option until you have successfully verified that the Notification Job in Scheduled Jobs is enabled and your SMTP options in Site Settings are configured or else you will lock yourself out.</value>
</data>
<data name="TwoFactor.Text" xml:space="preserve">
<value>Allow Two Factor?</value>
<value>Two Factor?</value>
</data>
<data name="Hide" xml:space="preserve">
<value>Hide</value>
<data name="Disabled" xml:space="preserve">
<value>Disabled</value>
</data>
<data name="Show" xml:space="preserve">
<value>Show</value>
<data name="Optional" xml:space="preserve">
<value>Optional</value>
</data>
<data name="Required" xml:space="preserve">
<value>Required</value>
</data>
<data name="LastLoginOn" xml:space="preserve">
<value>Last Login</value>
</data>
<data name="IdentifierClaimType.HelpText" xml:space="preserve">
<value>The name of the unique user identifier claim provided by the provider</value>
</data>
<data name="IdentifierClaimType.Text" xml:space="preserve">
<value>Identifier Claim:</value>
</data>
<data name="Parameters.HelpText" xml:space="preserve">
<value>Optionally specify any additional parameters as name/value pairs to send to the provider (separated by commas if there are multiple).</value>
</data>
<data name="Parameters.Text" xml:space="preserve">
<value>Parameters:</value>
</data>
</root>

View File

@ -141,4 +141,7 @@
<data name="Success.File.Upload" xml:space="preserve">
<value>File Upload Succeeded</value>
</data>
<data name="Message.File.Restricted" xml:space="preserve">
<value>Files With Extension Of {0} Are Restricted From Upload. Please Contact Your Administrator For More Information.</value>
</data>
</root>

View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
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.
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 id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="PageOfPages" xml:space="preserve">
<value>Page {0} of {1}</value>
</data>
</root>

View File

@ -123,4 +123,10 @@
<data name="AllowFileManagement.Text" xml:space="preserve">
<value>Allow File Management: </value>
</data>
<data name="AllowRawHtml.HelpText" xml:space="preserve">
<value>Specify If Editors Can Enter Raw HTML</value>
</data>
<data name="AllowRawHtml.Text" xml:space="preserve">
<value>Allow Raw HTML:</value>
</data>
</root>

View File

@ -274,7 +274,7 @@
<value>Full Name:</value>
</data>
<data name="LocalVersion" xml:space="preserve">
<value>Local Version</value>
<value>Installed Version</value>
</data>
<data name="Search.Source" xml:space="preserve">
<value>source</value>
@ -318,7 +318,28 @@
<data name="BlazorWebAssembly" xml:space="preserve">
<value>Blazor WebAssembly</value>
</data>
<data name="BlazorHybrid" xml:space="preserve">
<value>Blazor Hybrid</value>
</data>
<data name="Settings" xml:space="preserve">
<value>Settings</value>
</data>
<data name="HidePassword" xml:space="preserve">
<value>Hide</value>
</data>
<data name="ShowPassword" xml:space="preserve">
<value>Show</value>
</data>
<data name="PageOfPages" xml:space="preserve">
<value>Page {0} of {1}</value>
</data>
<data name="Url Mappings" xml:space="preserve">
<value>Url Mappings</value>
</data>
<data name="Visitor Management" xml:space="preserve">
<value>Visitor Management</value>
</data>
<data name="Oqtane.Marketplace" xml:space="preserve">
<value>Please note that the third party extensions displayed above have been registered in the &lt;a href="https://www.oqtane.net" target="_new"&gt;Oqtane Marketplace&lt;/a&gt; which enables them to be seamlessly downloaded and installed into the framework.</value>
</data>
</root>

View File

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

View File

@ -12,18 +12,12 @@ namespace Oqtane.Services
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
public class AliasService : ServiceBase, IAliasService
{
private readonly SiteState _siteState;
/// <summary>
/// Constructor - should only be used by Dependency Injection
/// </summary>
public AliasService(HttpClient http, SiteState siteState) : base(http)
{
_siteState = siteState;
}
public AliasService(HttpClient http, SiteState siteState) : base(http, siteState) { }
private string ApiUrl => CreateApiUrl("Alias", _siteState.Alias);
private string ApiUrl => CreateApiUrl("Alias");
/// <inheritdoc />
public async Task<List<Alias>> GetAliasesAsync()

View File

@ -11,15 +11,9 @@ namespace Oqtane.Services
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
public class DatabaseService : ServiceBase, IDatabaseService
{
public DatabaseService(HttpClient http, SiteState siteState) : base(http, siteState) { }
private readonly SiteState _siteState;
public DatabaseService(HttpClient http, SiteState siteState) : base(http)
{
_siteState = siteState;
}
private string Apiurl => CreateApiUrl("Database", _siteState.Alias);
private string Apiurl => CreateApiUrl("Database");
public async Task<List<Database>> GetDatabasesAsync()
{

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
@ -17,13 +18,13 @@ namespace Oqtane.Services
private readonly SiteState _siteState;
private readonly IJSRuntime _jsRuntime;
public FileService(HttpClient http, SiteState siteState, IJSRuntime jsRuntime) : base(http)
public FileService(HttpClient http, SiteState siteState, IJSRuntime jsRuntime) : base(http, siteState)
{
_siteState = siteState;
_jsRuntime = jsRuntime;
}
private string Apiurl => CreateApiUrl("File", _siteState.Alias);
private string Apiurl => CreateApiUrl("File");
public async Task<List<File>> GetFilesAsync(int folderId)
{
@ -32,7 +33,8 @@ namespace Oqtane.Services
public async Task<List<File>> GetFilesAsync(string folder)
{
return await GetJsonAsync<List<File>>($"{Apiurl}?folder={folder}");
List<File> files = await GetJsonAsync<List<File>>($"{Apiurl}?folder={folder}");
return files.OrderBy(item => item.Name).ToList();
}
public async Task<List<File>> GetFilesAsync(int siteId, string folderPath)
@ -44,7 +46,8 @@ namespace Oqtane.Services
var path = WebUtility.UrlEncode(folderPath);
return await GetJsonAsync<List<File>>($"{Apiurl}/{siteId}/{path}");
List<File> files = await GetJsonAsync<List<File>>($"{Apiurl}/{siteId}/{path}");
return files?.OrderBy(item => item.Name).ToList();
}
public async Task<File> GetFileAsync(int fileId)
@ -82,7 +85,7 @@ namespace Oqtane.Services
string result = "";
var interop = new Interop(_jsRuntime);
await interop.UploadFiles($"{Apiurl}/upload", folder, id);
await interop.UploadFiles($"{Apiurl}/upload", folder, id, _siteState.AntiForgeryToken);
// uploading files is asynchronous so we need to wait for the upload to complete
bool success = false;

View File

@ -1,10 +1,8 @@
using Oqtane.Models;
using System.Threading.Tasks;
using System.Linq;
using System.Net.Http;
using System.Collections.Generic;
using Oqtane.Shared;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using Oqtane.Documentation;
@ -14,20 +12,13 @@ namespace Oqtane.Services
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
public class FolderService : ServiceBase, IFolderService
{
private readonly SiteState _siteState;
public FolderService(HttpClient http, SiteState siteState) : base(http, siteState) { }
public FolderService(HttpClient http, SiteState siteState) : base(http)
{
_siteState = siteState;
}
private string ApiUrl => CreateApiUrl("Folder", _siteState.Alias);
private string ApiUrl => CreateApiUrl("Folder");
public async Task<List<Folder>> GetFoldersAsync(int siteId)
{
List<Folder> folders = await GetJsonAsync<List<Folder>>($"{ApiUrl}?siteid={siteId}");
folders = GetFoldersHierarchy(folders);
return folders;
return await GetJsonAsync<List<Folder>>($"{ApiUrl}?siteid={siteId}");
}
public async Task<Folder> GetFolderAsync(int folderId)
@ -63,48 +54,5 @@ namespace Oqtane.Services
{
await DeleteAsync($"{ApiUrl}/{folderId}");
}
private static List<Folder> GetFoldersHierarchy(List<Folder> folders)
{
List<Folder> hierarchy = new List<Folder>();
Action<List<Folder>, Folder> getPath = null;
var folders1 = folders;
getPath = (folderList, folder) =>
{
IEnumerable<Folder> children;
int level;
if (folder == null)
{
level = -1;
children = folders1.Where(item => item.ParentId == null);
}
else
{
level = folder.Level;
children = folders1.Where(item => item.ParentId == folder.FolderId);
}
foreach (Folder child in children)
{
child.Level = level + 1;
child.HasChildren = folders1.Any(item => item.ParentId == child.FolderId);
hierarchy.Add(child);
if (getPath != null) getPath(folderList, child);
}
};
folders = folders.OrderBy(item => item.Order).ToList();
getPath(folders, null);
// add any non-hierarchical items to the end of the list
foreach (Folder folder in folders)
{
if (hierarchy.Find(item => item.FolderId == folder.FolderId) == null)
{
hierarchy.Add(folder);
}
}
return hierarchy;
}
}
}

View File

@ -15,7 +15,7 @@ namespace Oqtane.Services
private readonly NavigationManager _navigationManager;
private readonly SiteState _siteState;
public InstallationService(HttpClient http, NavigationManager navigationManager, SiteState siteState) : base(http)
public InstallationService(HttpClient http, SiteState siteState, NavigationManager navigationManager) : base(http, siteState)
{
_navigationManager = navigationManager;
_siteState = siteState;

View File

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

View File

@ -50,13 +50,13 @@ namespace Oqtane.Services
/// <param name="moduleId"></param>
/// <param name="content">module in JSON format</param>
/// <returns></returns>
Task<bool> ImportModuleAsync(int moduleId, string content);
Task<bool> ImportModuleAsync(int moduleId, int pageId, string content);
/// <summary>
/// Exports a given module
/// </summary>
/// <param name="moduleId"></param>
/// <returns>module in JSON</returns>
Task<string> ExportModuleAsync(int moduleId);
Task<string> ExportModuleAsync(int moduleId, int pageId);
}
}

View File

@ -16,6 +16,31 @@ namespace Oqtane.Services
/// <returns></returns>
Task<List<UserRole>> GetUserRolesAsync(int siteId);
/// <summary>
/// Get all <see cref="UserRole"/>s on a <see cref="Site"/>
/// </summary>
/// <param name="siteId">ID-reference to a <see cref="Site"/></param>
/// <param name="userId">ID-reference to a <see cref="User"/></param>
/// <returns></returns>
Task<List<UserRole>> GetUserRolesAsync(int siteId, int userId);
/// <summary>
/// Get all <see cref="UserRole"/>s on a <see cref="Site"/>
/// </summary>
/// <param name="siteId">ID-reference to a <see cref="Site"/></param>
/// <param name="roleName">Name reference a <see cref="Role"/></param>
/// <returns></returns>
Task<List<UserRole>> GetUserRolesAsync(int siteId, string roleName);
/// <summary>
/// Get all <see cref="UserRole"/>s on a <see cref="Site"/>
/// </summary>
/// <param name="siteId">ID-reference to a <see cref="Site"/></param>
/// <param name="userId">ID-reference to a <see cref="User"/></param>
/// <param name="roleName">Name reference a <see cref="Role"/></param>
/// <returns></returns>
Task<List<UserRole>> GetUserRolesAsync(int siteId, int userId, string roleName);
/// <summary>
/// Get one specific <see cref="UserRole"/>
/// </summary>

View File

@ -54,8 +54,8 @@ namespace Oqtane.Services
/// Note that this will probably not be a real User, but a user object where the `Username` and `Password` have been filled.
/// </summary>
/// <param name="user">A <see cref="User"/> object which should have at least the <see cref="User.Username"/> and <see cref="User.Password"/> set.</param>
/// <param name="setCookie">Determines if the login should be stored in the cookie.</param>
/// <param name="isPersistent">Determines if the login should be persisted in the cookie for a long time.</param>
/// <param name="setCookie">Determines if the login cookie should be set (only relevant for Hybrid scenarios)</param>
/// <param name="isPersistent">Determines if the login cookie should be persisted for a long time.</param>
/// <returns></returns>
Task<User> LoginUserAsync(User user, bool setCookie, bool isPersistent);
@ -109,5 +109,24 @@ namespace Oqtane.Services
/// </summary>
/// <returns></returns>
Task<string> GetTokenAsync();
/// <summary>
/// Get personal access token for current user (administrators only)
/// </summary>
/// <returns></returns>
Task<string> GetPersonalAccessTokenAsync();
/// <summary>
/// Link an external login with a local user account
/// </summary>
/// <param name="user">The <see cref="User"/> we're verifying</param>
/// <param name="token">A Hash value in the URL which verifies this user got the e-mail (containing this token)</param>
/// <param name="type">External Login provider type</param>
/// <param name="key">External Login provider key</param>
/// <param name="name">External Login provider display name</param>
/// <returns></returns>
Task<User> LinkUserAsync(User user, string token, string type, string key, string name);
}
}

View File

@ -11,14 +11,9 @@ namespace Oqtane.Services
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
public class JobLogService : ServiceBase, IJobLogService
{
private readonly SiteState _siteState;
public JobLogService(HttpClient http, SiteState siteState) : base(http, siteState) { }
public JobLogService(HttpClient http, SiteState siteState) : base(http)
{
_siteState = siteState;
}
private string Apiurl => CreateApiUrl("JobLog", _siteState.Alias);
private string Apiurl => CreateApiUrl("JobLog");
public async Task<List<JobLog>> GetJobLogsAsync()
{

View File

@ -11,14 +11,9 @@ namespace Oqtane.Services
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
public class JobService : ServiceBase, IJobService
{
private readonly SiteState _siteState;
public JobService(HttpClient http, SiteState siteState) : base(http, siteState) { }
public JobService(HttpClient http, SiteState siteState) : base(http)
{
_siteState = siteState;
}
private string Apiurl => CreateApiUrl("Job", _siteState.Alias);
private string Apiurl => CreateApiUrl("Job");
public async Task<List<Job>> GetJobsAsync()
{

View File

@ -11,30 +11,33 @@ namespace Oqtane.Services
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
public class LanguageService : ServiceBase, ILanguageService
{
public LanguageService(HttpClient http, SiteState siteState) : base(http, siteState) { }
private readonly SiteState _siteState;
public LanguageService(HttpClient http, SiteState siteState) : base(http)
{
_siteState = siteState;
}
private string Apiurl => CreateApiUrl("Language", _siteState.Alias);
private string Apiurl => CreateApiUrl("Language");
public async Task<List<Language>> GetLanguagesAsync(int siteId)
{
var languages = await GetJsonAsync<List<Language>>($"{Apiurl}?siteid={siteId}");
return await GetLanguagesAsync(siteId, "");
}
return languages?.OrderBy(l => l.Name).ToList() ?? Enumerable.Empty<Language>().ToList();
public async Task<List<Language>> GetLanguagesAsync(int siteId, string packageName)
{
return await GetJsonAsync<List<Language>>($"{Apiurl}?siteid={siteId}&packagename={packageName}");
}
public async Task<Language> GetLanguageAsync(int languageId)
=> await GetJsonAsync<Language>($"{Apiurl}/{languageId}");
{
return await GetJsonAsync<Language>($"{Apiurl}/{languageId}");
}
public async Task<Language> AddLanguageAsync(Language language)
=> await PostJsonAsync<Language>(Apiurl, language);
{
return await PostJsonAsync<Language>(Apiurl, language);
}
public async Task DeleteLanguageAsync(int languageId)
=> await DeleteAsync($"{Apiurl}/{languageId}");
{
await DeleteAsync($"{Apiurl}/{languageId}");
}
}
}

View File

@ -10,14 +10,9 @@ namespace Oqtane.Services
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
public class LocalizationService : ServiceBase, ILocalizationService
{
private readonly SiteState _siteState;
public LocalizationService(HttpClient http, SiteState siteState) : base(http, siteState) { }
public LocalizationService(HttpClient http, SiteState siteState) : base(http)
{
_siteState = siteState;
}
private string Apiurl => CreateApiUrl("Localization", _siteState.Alias);
private string Apiurl => CreateApiUrl("Localization");
public async Task<IEnumerable<Culture>> GetCulturesAsync() => await GetJsonAsync<IEnumerable<Culture>>(Apiurl);
}

View File

@ -14,18 +14,16 @@ namespace Oqtane.Services
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
public class LogService : ServiceBase, ILogService
{
private readonly SiteState _siteState;
private readonly NavigationManager _navigationManager;
public LogService(HttpClient http, SiteState siteState, NavigationManager navigationManager) : base(http)
public LogService(HttpClient http, SiteState siteState, NavigationManager navigationManager) : base(http, siteState)
{
_siteState = siteState;
_navigationManager = navigationManager;
}
private string Apiurl => CreateApiUrl("Log", _siteState.Alias);
private string Apiurl => CreateApiUrl("Log");
public async Task<List<Log>> GetLogsAsync(int siteId, string level, string function, int rows)
{

View File

@ -14,16 +14,9 @@ namespace Oqtane.Services
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
public class ModuleDefinitionService : ServiceBase, IModuleDefinitionService
{
private readonly HttpClient _http;
private readonly SiteState _siteState;
public ModuleDefinitionService(HttpClient http, SiteState siteState) : base(http, siteState) { }
public ModuleDefinitionService(HttpClient http, SiteState siteState) : base(http)
{
_http = http;
_siteState = siteState;
}
private string Apiurl => CreateApiUrl("ModuleDefinition", _siteState.Alias);
private string Apiurl => CreateApiUrl("ModuleDefinition");
public async Task<List<ModuleDefinition>> GetModuleDefinitionsAsync(int siteId)
{

View File

@ -5,21 +5,16 @@ using System.Net.Http;
using System.Threading.Tasks;
using Oqtane.Documentation;
using Oqtane.Shared;
using Oqtane.Modules.Controls;
namespace Oqtane.Services
{
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
public class ModuleService : ServiceBase, IModuleService
{
public ModuleService(HttpClient http, SiteState siteState) : base(http, siteState) { }
private readonly SiteState _siteState;
public ModuleService(HttpClient http, SiteState siteState) : base(http)
{
_siteState = siteState;
}
private string Apiurl => CreateApiUrl("Module", _siteState.Alias);
private string Apiurl => CreateApiUrl("Module");
public async Task<List<Module>> GetModulesAsync(int siteId)
{
@ -50,14 +45,14 @@ namespace Oqtane.Services
await DeleteAsync($"{Apiurl}/{moduleId.ToString()}");
}
public async Task<bool> ImportModuleAsync(int moduleId, string content)
public async Task<bool> ImportModuleAsync(int moduleId, int pageId, string content)
{
return await PostJsonAsync<string,bool>($"{Apiurl}/import?moduleid={moduleId}", content);
return await PostJsonAsync<string,bool>($"{Apiurl}/import?moduleid={moduleId}&pageid={pageId}", content);
}
public async Task<string> ExportModuleAsync(int moduleId)
public async Task<string> ExportModuleAsync(int moduleId, int pageId)
{
return await GetStringAsync($"{Apiurl}/export?moduleid={moduleId}");
return await GetStringAsync($"{Apiurl}/export?moduleid={moduleId}&pageid={pageId}");
}
}
}

View File

@ -11,14 +11,9 @@ namespace Oqtane.Services
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
public class NotificationService : ServiceBase, INotificationService
{
private readonly SiteState _siteState;
public NotificationService(HttpClient http, SiteState siteState) : base(http, siteState) { }
public NotificationService(HttpClient http, SiteState siteState) : base(http)
{
_siteState = siteState;
}
private string Apiurl => CreateApiUrl("Notification", _siteState.Alias);
private string Apiurl => CreateApiUrl("Notification");
public async Task<List<Notification>> GetNotificationsAsync(int siteId, string direction, int userId)
{

View File

@ -12,13 +12,9 @@ namespace Oqtane.Services
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
public class PackageService : ServiceBase, IPackageService
{
private readonly SiteState _siteState;
public PackageService(HttpClient http, SiteState siteState) : base(http, siteState) { }
public PackageService(HttpClient http, SiteState siteState) : base(http)
{
_siteState = siteState;
}
private string Apiurl => CreateApiUrl("Package", _siteState.Alias);
private string Apiurl => CreateApiUrl("Package");
public async Task<List<Package>> GetPackagesAsync(string type)
{

View File

@ -9,15 +9,9 @@ namespace Oqtane.Services
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
public class PageModuleService : ServiceBase, IPageModuleService
{
public PageModuleService(HttpClient http, SiteState siteState) : base(http, siteState) { }
private readonly SiteState _siteState;
public PageModuleService(HttpClient http, SiteState siteState) : base(http)
{
_siteState = siteState;
}
private string Apiurl => CreateApiUrl("PageModule", _siteState.Alias);
private string Apiurl => CreateApiUrl("PageModule");
public async Task<PageModule> GetPageModuleAsync(int pageModuleId)
{

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