Compare commits

...

812 Commits

Author SHA1 Message Date
be1d124e78 Merge pull request #3322 from oqtane/master
Merge pull request #3321 from oqtane/dev
2023-09-25 15:55:54 -04:00
6fabf84d05 Merge pull request #3321 from oqtane/dev
4.0.4 release
2023-09-25 15:55:37 -04:00
3299275da2 Merge pull request #3320 from sbwalker/dev
resolve cookie configuration
2023-09-25 15:10:50 -04:00
5539243bf3 resolve cookie configuration 2023-09-25 15:10:36 -04:00
c3d330f500 Merge pull request #3319 from sbwalker/dev
refactor logic to redirect to external login provider when local site login is disabled
2023-09-25 13:53:26 -04:00
8c9e886136 refactor logic to redirect to external login provider when local site login is disabled 2023-09-25 13:53:05 -04:00
cfde9944f8 Merge pull request #3318 from sbwalker/dev
include Logout link in Control Panel for scenarios where a theme does not include a Login/Logout component
2023-09-25 12:42:59 -04:00
b35d778abe include Logout link in Control Panel for scenarios where a theme does not include a Login/Logout component 2023-09-25 12:42:39 -04:00
1e9ee81df3 Merge pull request #3317 from sbwalker/dev
updated database provider packages for 4.0.4
2023-09-25 10:01:59 -04:00
545d5730b6 database provider packages for 4.0.4 2023-09-25 09:59:07 -04:00
28e645f9ca Merge branch 'dev' of https://github.com/sbwalker/oqtane.framework into dev 2023-09-25 09:54:38 -04:00
5b5c8a4beb database provider packages for 4.0.4 2023-09-25 09:54:27 -04:00
6178be974a Merge pull request #3310 from leigh-pointer/LangSwitchPadding
upated LanguageSwitcher with end padding
2023-09-25 07:42:24 -04:00
7771c3159b upated LanguageSwitcher with end padding
When the LanguageSwitcher is dsiplayed the wsa no end padding on the button so it appeared flush with the following button.  Adding "pe-1" to the class corrects the issue
2023-09-24 18:49:50 +02:00
fc1e9c5d71 Merge pull request #3308 from sbwalker/dev
revert remaining logic which forced page themes to be a member of a site theme (4.0.0)
2023-09-23 11:47:08 -04:00
359de5b85e revert remaining logic which forced page themes to be a member of a site theme 2023-09-23 11:46:35 -04:00
68dc4904cf Merge pull request #3307 from sbwalker/dev
improve user import API
2023-09-23 11:37:43 -04:00
057fd02e26 improve user import API 2023-09-23 11:37:29 -04:00
5c86ef6682 Merge pull request #3303 from leigh-pointer/Retention-resx
Fix for  Site 'SMTP retention' is not in resource file (Issue #3301) and #3304 Users 'Import Users' resource
2023-09-23 09:12:33 -04:00
7c14bd799f Merge pull request #3305 from sbwalker/dev
improvements based on user import testing
2023-09-23 09:04:35 -04:00
edac046fcd improvements based on user import testing 2023-09-23 09:04:18 -04:00
8b23b386f7 ImportUsers Added to Resx and Razor resource key 2023-09-23 10:56:17 +02:00
e9bed59032 Fix for Site 'SMTP retention' is not in resource file (Issue #3301)
Fix for  Site 'SMTP retention' is not in resource file (Issue #3301)
2023-09-23 10:42:39 +02:00
54077be32c Merge pull request #3299 from sbwalker/dev
fix reference to DisplayName
2023-09-22 15:03:57 -04:00
30ad442dd1 fix reference to DisplayName 2023-09-22 15:03:45 -04:00
b0c677d5a7 Merge pull request #3298 from sbwalker/dev
removing href link from DisplayName as Email is now included
2023-09-22 14:44:36 -04:00
abe1b93e3c removing href link from DisplayName as Email is now included 2023-09-22 14:44:25 -04:00
fd699cbb73 Merge pull request #3293 from W6HBR/dev
Add Email column to User Manager screen
2023-09-22 14:31:56 -04:00
6f93ca11de Merge pull request #3297 from sbwalker/dev
fix issue where module migrations were not being executed on upgrade due to version not being overridden
2023-09-22 14:12:36 -04:00
916663b493 fix issue where module migrations were not being executed on upgrade due to version not being overridden 2023-09-22 14:12:18 -04:00
dbf2cddd87 Update Index.razor to include Email column. 2023-09-21 16:58:48 -07:00
d3c248cf5c Update Index.resx for Email text. 2023-09-21 16:57:01 -07:00
bd93a94bb7 Merge pull request #3292 from thabaum/patch-6
Fix typo in app.css
2023-09-21 19:42:02 -04:00
a46836e2a6 Fix typo in app.css 2023-09-21 16:34:57 -07:00
a1d88b4faa Merge pull request #3290 from sbwalker/dev
add named site option support to factory
2023-09-21 17:17:59 -04:00
29741c0ec8 add named site option support to factory 2023-09-21 17:17:46 -04:00
0b5fb92452 Merge pull request #3289 from sbwalker/dev
add support for named site options
2023-09-21 16:54:04 -04:00
65782e87c1 add support for named site options 2023-09-21 16:53:52 -04:00
6403df3330 Merge pull request #3288 from sbwalker/dev
set DefaultScheme for authentication
2023-09-21 14:45:09 -04:00
c6a8f5305a set DefaultScheme for authentication 2023-09-21 14:44:57 -04:00
d354e63267 Merge pull request #3285 from sbwalker/dev
fix localization in Profile Management
2023-09-21 10:02:36 -04:00
836b174d15 fix localization in Profile Management 2023-09-21 10:02:21 -04:00
4435f25ee6 Merge pull request #3275 from W6HBR/dev
Update to Profiles field list to include Title, Category and ViewOrder
2023-09-21 09:57:55 -04:00
4ab4bf4a3a Merge pull request #3279 from thabaum/patch-5
Updates Child Page Link Color in Theme Creator Template - Theme.css - Fixes #3278
2023-09-21 09:57:30 -04:00
d2f1d3c86c Merge pull request #3283 from sbwalker/dev
user import improvements
2023-09-21 09:37:44 -04:00
98257de005 user import improvements 2023-09-21 09:37:29 -04:00
5ea5c6ff7d Updates Child Page Link Color in Theme.css 2023-09-20 17:12:04 -07:00
441324d354 Update to Profiles field list to include Title, Category and ViewOrder
When adding additional profile fields, this enhancement makes it easier to see details of the profile fields for the purpose of field organization.
2023-09-20 14:59:44 -07:00
44f093b2fa Merge pull request #3266 from W6HBR/W6HBR-patch-1
Add password complexity requirements message to password Reset module.
2023-09-20 15:19:03 -04:00
38a16d1ae2 Merge pull request #3273 from sbwalker/dev
prepare for 4.0.4 release
2023-09-20 14:59:10 -04:00
6b9de40442 prepare for 4.0.4 release 2023-09-20 14:58:54 -04:00
d9c9eb9799 Merge pull request #3258 from leigh-pointer/ViewNotification
Removed the ReadOnly attribute in the Notifications Reply TextArea
2023-09-20 14:32:41 -04:00
e88c8eee0f Merge pull request #3271 from sbwalker/dev
fix #3231 - validate module description
2023-09-20 14:29:21 -04:00
a6ca843ced fix #3231 - validate module description 2023-09-20 14:29:06 -04:00
6967a5cc14 Merge pull request #3270 from sbwalker/dev
if site login is disabled redirect Login to external identity provider
2023-09-20 13:45:34 -04:00
e0ebf70907 if site login is disabled redirect Login to external identity provider 2023-09-20 13:45:19 -04:00
53d0202f3b Merge pull request #3268 from sbwalker/dev
prevent System Update in development environment
2023-09-20 12:34:20 -04:00
8daf38654d prevent System Update in development environment 2023-09-20 12:33:43 -04:00
17337440f2 Merge pull request #3267 from sbwalker/dev
add ability to import users
2023-09-20 10:44:05 -04:00
8e5e79a799 add ability to import users 2023-09-20 10:43:49 -04:00
5c1cf14303 Add password complexity requirements message to password Reset module.
This adds the same functionality that already exists in the UserProfile module.
2023-09-19 18:59:38 -07:00
f2ad796104 Merge pull request #3263 from sbwalker/dev
adjust error message text when adding users with duplicate email addresses
2023-09-19 09:00:11 -04:00
886df69058 adjust error message text when adding users with duplicate email addresses 2023-09-19 08:59:53 -04:00
703aa95e60 Merge pull request #3262 from sbwalker/dev
fix error when Allow User Login is set to false (ElementReference has not been configured correctly)
2023-09-19 08:49:21 -04:00
7919e14d90 fix error when Allow User Login is set to false (ElementReference has not been configured correctly) 2023-09-19 08:49:03 -04:00
c8ef191660 Merge pull request #3261 from sbwalker/dev
remove using statements added by Visual Studio
2023-09-18 09:47:52 -04:00
840b656d1c remove using statements added by Visual Studio 2023-09-18 09:47:38 -04:00
d28516b6b1 Merge pull request #3260 from sbwalker/dev
fix issue with module order in panes caused by transition from Admin to Default pane naming
2023-09-18 09:46:40 -04:00
5797bf549c fix issue with module order in panes caused by transition from Admin to Default pane naming 2023-09-18 09:46:16 -04:00
e1ea721ea9 Removed the ReadOnly attribute
The replay textarea can now be used to enter a reply to the notification.
2023-09-16 18:35:22 +02:00
5ed1284baf Merge pull request #3257 from sbwalker/dev
fix #3255 - behavior when moving pages to other parents
2023-09-15 16:23:28 -04:00
e507023a03 fix #3255 - behavior when moving pages to other parents 2023-09-15 16:23:14 -04:00
2b328b9bb2 Merge pull request #3254 from sbwalker/dev
fix #3253 - login needs to validate User.IsDeleted property
2023-09-13 10:02:27 -04:00
d155e13399 fix #3253 - login needs to validate User.IsDeleted property 2023-09-13 10:02:11 -04:00
6993d4782d Merge pull request #3244 from sbwalker/dev
improve polling in file upload to use actual file sizes to calculate duration
2023-09-08 17:16:28 -04:00
9267efce01 improve polling in file upload to use actual file sizes to calculate duration 2023-09-08 17:16:09 -04:00
8c88cec863 Merge pull request #3242 from sbwalker/dev
retain querystring parameters on url mapping redirect
2023-09-08 12:05:35 -04:00
b4b9976567 retain querystring parameters on url mapping redirect 2023-09-08 12:05:20 -04:00
3cbd820d9c Merge pull request #3240 from sbwalker/dev
fix #3236 - script injection issue
2023-09-08 11:25:40 -04:00
edd77b0222 fix #3236 - script injection issue 2023-09-08 11:25:27 -04:00
c0991df9a2 Merge pull request #3239 from sbwalker/dev
fix #3235 - </script> not being removed from Head Content
2023-09-08 11:09:46 -04:00
26921c899e fix #3235 - </script> not being removed from Head Content 2023-09-08 11:09:07 -04:00
a1e5912d37 Merge pull request #3238 from sbwalker/dev
fix #3229 - changing page path in Edot Page when invoked from Control Panel results in 404 when redirecting
2023-09-08 10:35:52 -04:00
037f1ec887 fix #3229 - changing page path in Edot Page when invoked from Control Panel results in 404 when redirecting 2023-09-08 10:35:22 -04:00
89ab44590b Merge pull request #3224 from sbwalker/dev
capitalize
2023-09-05 11:28:52 -04:00
4a20be8b34 capitalize 2023-09-05 11:28:36 -04:00
32a401ba67 Merge pull request #3223 from sbwalker/dev
display Site Guid in Site Settings
2023-09-05 11:27:30 -04:00
138c01aef8 display Site Guid in Site Settings 2023-09-05 11:27:17 -04:00
3e54bc9c77 Merge pull request #3221 from sbwalker/dev
add ability to validate and download packages
2023-09-02 14:08:34 -04:00
9966fc4651 add ability to validate and download packages 2023-09-02 14:08:21 -04:00
c9ed5ec98f Merge pull request #3220 from sbwalker/dev
improve help text for paclage name
2023-09-01 14:04:38 -04:00
1f4ae5dbfb improve help text for paclage name 2023-09-01 14:04:27 -04:00
d0d3cc8faa Merge pull request #3219 from sbwalker/dev
fix paths in Edit Page / Modules tab / Edit option
2023-09-01 13:57:26 -04:00
7513ae28f0 fix paths in Edit Page / Modules tab / Edit option 2023-09-01 13:57:14 -04:00
c151c6f55a Merge pull request #3218 from sbwalker/dev
include User Settings when calling UserService
2023-09-01 13:16:51 -04:00
d491aeeba6 include User Settings when calling UserService 2023-09-01 13:16:39 -04:00
ab93d0acea Merge pull request #3217 from sbwalker/dev
Fix #3215 - module creator creates incorrect ServerManagerType value
2023-09-01 13:03:01 -04:00
f40f3f934f Fix #3215 - module creator creates incorrect ServerManagerType value 2023-09-01 13:02:44 -04:00
4d57adb393 Merge pull request #3216 from sbwalker/dev
allow an administratot to browse to the SiteMap, handle default module panes and ordering for PageTemplates, allow trial products to be purchased
2023-09-01 12:14:24 -04:00
e91db18677 allow an administratot to browse to the SiteMap, handle default module panes and ordering for PageTemplates, allow trial products to be purchased 2023-09-01 12:14:04 -04:00
9a88d65ff7 Merge pull request #3212 from sbwalker/dev
need to use context rather than filter
2023-08-31 14:18:31 -04:00
211d8c7f19 need to use context rather than filter 2023-08-31 14:18:19 -04:00
763cdca114 Merge pull request #3211 from sbwalker/dev
Marketplace UI consistency
2023-08-31 13:55:08 -04:00
0d56d35646 Marketplace UI consistency 2023-08-31 13:54:56 -04:00
d1e80cb86a Update README.md 2023-08-29 14:01:00 -04:00
2e116489c8 Merge pull request #3208 from oqtane/master
Merge pull request #3207 from oqtane/dev
2023-08-29 13:54:50 -04:00
a94daa1216 Merge pull request #3207 from oqtane/dev
4.0.3 Release
2023-08-29 13:54:31 -04:00
ad57d665cc Merge pull request #3205 from sbwalker/dev
filter deleted pages and modules on the server
2023-08-28 17:15:45 -04:00
db2c42f0f4 filter deleted pages and modules on the server 2023-08-28 17:15:31 -04:00
b713cf86c7 Merge pull request #3204 from sbwalker/dev
fix page template update logic
2023-08-28 16:12:47 -04:00
11d02ccf44 fix page template update logic 2023-08-28 16:12:32 -04:00
54f3c8beac Merge pull request #3203 from sbwalker/dev
documentation for ReleaseVersions property
2023-08-28 15:27:47 -04:00
2ed51bf1f3 documentation for ReleaseVersions property 2023-08-28 15:27:33 -04:00
9f859f024c Merge pull request #3202 from sbwalker/dev
improve solution related to module installation issue where ModuleDefinition Version property was not being populated correctly (#3175)
2023-08-28 15:20:29 -04:00
d47175df8a improve solution related to module installation issue where ModuleDefinition Version property was not being populated correctly (#3175) 2023-08-28 15:20:09 -04:00
d6a80d36b1 Merge pull request #3200 from sbwalker/dev
abstract namespace specification to template.json so that module and theme templates can use their own naming conventions
2023-08-28 09:03:26 -04:00
a857e3f31e abstract namespace specification to template.json so that module and theme templates can use their own naming conventions 2023-08-28 09:03:05 -04:00
918dfb05cd Merge pull request #3199 from sbwalker/dev
prepare for 4.0.3 release
2023-08-28 07:58:49 -04:00
f22e0c4384 prepare for 4.0.3 release 2023-08-28 07:58:33 -04:00
daa08626ae Merge pull request #3198 from sbwalker/dev
support ~ notation for license urls
2023-08-28 07:49:22 -04:00
45592e6acd support ~ notation for license urls 2023-08-28 07:49:06 -04:00
6944eb3392 Merge pull request #3197 from sbwalker/dev
allow module or theme License property to be a Url
2023-08-27 08:57:50 -04:00
85f20aca58 allow module or theme License property to be a Url 2023-08-27 08:57:30 -04:00
ca8d8b3587 Merge pull request #3196 from sbwalker/dev
move password requirement localization back to Client project
2023-08-27 08:43:43 -04:00
74921a0686 move password requirement localization back to Client project 2023-08-27 08:43:23 -04:00
be4296f288 Merge pull request #3195 from sbwalker/dev
include only distinct items which have package names
2023-08-25 17:44:39 -04:00
836de56276 include only distinct items which have package names 2023-08-25 17:44:27 -04:00
ef60ac3836 Merge pull request #3194 from sbwalker/dev
optimize package update queries
2023-08-25 15:42:58 -04:00
bdd1ba05e8 optimize package update queries 2023-08-25 15:42:45 -04:00
f0a22d5517 Merge pull request #3193 from sbwalker/dev
remove Module Creator in favor of using Create Module in Module Management
2023-08-25 13:49:08 -04:00
417c8d7874 remove Module Creator in favor of using Create Module in Module Management 2023-08-25 13:48:51 -04:00
5a111cdb2a Merge pull request #3192 from sbwalker/dev
fix #3174 - display accurate password complexity requirements (this is now implemented in registration, user profiles, and user management - add/edit)
2023-08-25 13:31:22 -04:00
ef2f779f71 fix #3174 - display accurate password complexity requirements (this is now implemented in registration, user profiles, and user management - add/edit) 2023-08-25 13:31:02 -04:00
f0fd0db7bb Merge pull request #3191 from sbwalker/dev
simplified method names
2023-08-25 12:30:01 -04:00
b4ab45d2e7 simplified method names 2023-08-25 12:29:46 -04:00
73c4bcee30 Merge pull request #3185 from leigh-pointer/SchedularTimeControls
Fix for Schedular Allows incorrect Time format #3184
2023-08-25 10:54:13 -04:00
d4daf098e1 Merge pull request #3190 from sbwalker/dev
resolve #3189 - make path a querystring parameter
2023-08-25 09:49:22 -04:00
95de1fff69 resolve #3189 - make path a querystring parameter 2023-08-25 09:49:07 -04:00
96480b4382 Override and 2 new functions 2023-08-25 12:09:16 +02:00
14ae553557 Merge pull request #3188 from sbwalker/dev
remove unecessary NotMapped attribute
2023-08-24 16:42:30 -04:00
4de809e275 remove unecessary NotMapped attribute 2023-08-24 16:41:58 -04:00
a383382529 Merge pull request #3187 from sbwalker/dev
include PackageRegistryUrl in System Info
2023-08-24 16:39:51 -04:00
d2b3061ed9 include PackageRegistryUrl in System Info 2023-08-24 16:39:38 -04:00
073b10929a Fix for Schedular Allows incorrect Time format #3184
Modified code to accept correct time types.
2023-08-24 18:37:33 +02:00
6a2cfcab34 Merge pull request #3183 from leigh-pointer/IconResources
Update IconRescources
2023-08-24 11:38:09 -04:00
00c85ae7d3 Update IconResources.resx 2023-08-24 17:03:24 +02:00
d1b1e88389 Update IconRescources 2023-08-24 15:46:24 +02:00
5679ff5df9 Merge pull request #3182 from sbwalker/dev
add refresh button to module and theme installation page
2023-08-24 09:33:46 -04:00
3f28b39da0 add refresh button to module and theme installation page 2023-08-24 09:33:32 -04:00
ee21536742 Merge pull request #3181 from sbwalker/dev
reverse InputList Dictionary usage
2023-08-24 08:50:45 -04:00
6fafeedeb9 reverse InputList Dictionary usage 2023-08-24 08:50:33 -04:00
d86c53858e Merge pull request #3180 from sbwalker/dev
added defensive logic to resolve regression issue caused by #3175
2023-08-24 08:10:06 -04:00
0a22f80942 added defensive logic to resolve regression issue caused by #3175 2023-08-24 08:09:53 -04:00
fb247197e8 Merge pull request #3179 from sbwalker/dev
Allow InputList component to be localizable and support multiple instances on a page. Implement icon localization in Page Add/Edit using IconResources.resx
2023-08-23 16:43:36 -04:00
261ed05fa3 Allow InputList component to be localizable and support multiple instances on a page. Implement icon localization in Page Add/Edit using IconResources.resx 2023-08-23 16:43:14 -04:00
f5ce48a7c2 Merge pull request #3178 from sbwalker/dev
move icon loading reflection logic to server
2023-08-23 15:25:54 -04:00
82d128974c move icon loading reflection logic to server 2023-08-23 15:25:39 -04:00
34ae731959 Merge pull request #3169 from leigh-pointer/InputList
Add InputList control to select values from a dictionary of string
2023-08-23 14:29:38 -04:00
030a92d048 Merge pull request #3171 from vnetonline/fix-3163
[FIX] Added the ability to clear the Message in control panel
2023-08-23 14:29:28 -04:00
a327358b7a Merge pull request #3177 from sbwalker/dev
add support for background color padding during image resizing
2023-08-23 10:05:46 -04:00
9bd078d3e9 add support for background color padding during image resizing 2023-08-23 10:05:27 -04:00
412405e22c Merge pull request #3176 from sbwalker/dev
improve UX for Extend license option
2023-08-23 09:39:04 -04:00
2b20b34a74 improve UX for Extend license option 2023-08-23 09:38:48 -04:00
f269a07463 Merge pull request #3175 from sbwalker/dev
resolve module installation issue where ModuleDefinition Version property was not being populated correctly
2023-08-23 09:24:56 -04:00
d9948e8ee0 resolve module installation issue where ModuleDefinition Version property was not being populated correctly 2023-08-23 09:24:36 -04:00
ddb2fe4b03 Update Edit.razor
Moved Icon to the end of control
2023-08-23 07:49:50 +02:00
471f7184fb Added Icon Preview
Added an icon preview to the icon selecting control
2023-08-23 07:47:35 +02:00
92ea5da358 [FIX] Added the ability to clear the Message in control panel on open or close
This fix is related to issue #3163
2023-08-23 10:13:16 +10:00
09f1d3ca15 Add InputList control to select values from a dictionary of string
The control accepts a dictionary of string that can be searched using an input control.
Pages Add and Edit have implemented the control to render the list of Icons found in the Oqtane Shared namespace.
2023-08-22 09:50:59 +02:00
3729b8eac2 Merge pull request #3161 from rcpacheco/rcp_show_passwd_dbs
Update database installers to show/hide passwd
2023-08-21 11:02:29 -04:00
ca5f345414 Update database installers to show/hide passwd 2023-08-18 13:25:32 -06:00
15cf1e8a53 Merge pull request #3160 from sbwalker/dev
fix #3157 - provide support for asynchronous scheduled jobs
2023-08-18 13:34:52 -04:00
da74b0ece1 fix #3157 - provide support for asynchronous scheduled jobs 2023-08-18 13:34:38 -04:00
f50fe6f22f Merge pull request #3158 from sbwalker/dev
add support for * ImageSizes for folders
2023-08-18 10:55:25 -04:00
542eec2a9e add support for * ImageSizes for folders 2023-08-18 10:55:10 -04:00
4c5460fc9e Merge pull request #3156 from sbwalker/dev
migrate LocalizerFactory logic from SiteRouter to ModuleTitle component
2023-08-17 08:23:30 -04:00
394b8f1ce6 migrate LocalizerFactory logic from SiteRouter to ModuleTitle component 2023-08-17 08:23:17 -04:00
abeeda1a2b Merge pull request #3155 from sbwalker/dev
rollback #3125 and localize module component Title using LocalizerFactory
2023-08-17 07:56:58 -04:00
9e6ea3f486 rollback #3125 and localize module component Title using LocalizerFactory 2023-08-17 07:56:39 -04:00
2777a0946c Merge pull request #3154 from vnetonline/enhance-expando-object
[ENHANCE] - Change to ExpandoObject instead of an Anonymous Object
2023-08-17 07:47:57 -04:00
7f43c2659a Merge pull request #3153 from leigh-pointer/UserSort
Update the TH with link-primary Decoration classes
2023-08-17 07:46:39 -04:00
a4fa11c881 [ENHANCE] - Change to ExpandoObject instead of an Anonymous Object
Anonymous Object are not able to be used across assemblies however ExpandoObject is refer to #3145
2023-08-17 10:45:38 +10:00
8230e51c05 Update the TH with link-primary Decoration classes
This emphasizes that the header is clickable.
2023-08-16 19:09:12 +02:00
6e62d4791c Update the TH with link-primary Decoration classes
This emphasizes that the header is clickable.
2023-08-16 19:06:59 +02:00
ce8abdb8cd Merge pull request #3152 from sbwalker/dev
modify column size to prevent text wrapping
2023-08-16 12:25:53 -04:00
941bb7edaa modify column size to prevent text wrapping 2023-08-16 12:25:39 -04:00
1acbdf8b9e Merge pull request #3150 from alikoli/dev
Fix missing translations part 3
2023-08-15 15:51:58 -04:00
530804c847 Merge pull request #3151 from sbwalker/dev
In FileManager fix the id handling for the progressinfo and progressbar and include OnSelect events on Upload and Delete. Add missing backend implementation for AddFileAsync
2023-08-15 15:51:32 -04:00
b00b426e9c fix the id handling for the progressinfo and progressbar, include OnSelect events on Upload and Delete, add missing backend implementation for AddFileAsync 2023-08-15 15:50:35 -04:00
128f1753c0 Remove resx backups 2023-08-15 16:17:23 +02:00
1922629d27 Merge branch 'oqtane:dev' into dev 2023-08-15 16:12:21 +02:00
283a03a7cc Fix missing translations part 3 2023-08-15 16:09:59 +02:00
cdab26a97b Merge pull request #3146 from HonesDK/dev
Fixed localization in AuditInfo
2023-08-15 08:03:29 -04:00
5d3311c46b Merge pull request #3147 from sbwalker/dev
Notification job must convert \n to <br /> now that IsNodyHtml is set to True
2023-08-14 17:03:43 -04:00
7cbc21671b Notification job must convert \n to <br /> now that IsNodyHtml is set to True 2023-08-14 17:03:21 -04:00
d14d820e25 Merge branch 'dev' of https://github.com/HonesDK/oqtane.framework into dev 2023-08-14 16:54:41 +02:00
b567d02df2 Fixed localization in AuditInfo 2023-08-14 16:52:30 +02:00
dbb58541f2 Revert "Fixed modified by localization, and added DateTimeFormat to localization"
This reverts commit 4a8bcc6f71.
2023-08-14 16:51:21 +02:00
5a42caf4f8 Merge pull request #3143 from sbwalker/dev
add ability to get user based on username or email address
2023-08-13 08:35:24 -04:00
c2acd010ce add ability to get user based on username or email address 2023-08-13 08:35:03 -04:00
4a8bcc6f71 Fixed modified by localization, and added DateTimeFormat to localization 2023-08-13 10:11:29 +02:00
89e4dc7a08 Merge pull request #3141 from sbwalker/dev
change "price" to "from" to reflect multiple options
2023-08-12 10:31:56 -04:00
c344eedb12 change "price" to "from" to reflect multiple options 2023-08-12 10:31:44 -04:00
0aeb1a62b9 Merge pull request #3137 from sbwalker/dev
fix #3134 improve parsing of headcontent to handle space delimiters
2023-08-11 15:53:43 -04:00
316e0f5a68 fix #3134 improve parsing of headcontent to handle space delimiters 2023-08-11 15:53:32 -04:00
6f9314f76f Merge pull request #3133 from alikoli/dev
Fix missing translations part 2
2023-08-11 15:33:21 -04:00
6ef21795ba fix heading 2023-08-10 02:13:31 +02:00
e44855493e fix heading 2023-08-10 02:12:36 +02:00
101a3c8af5 Revert launchSettings 2023-08-10 02:03:46 +02:00
8fbbbce4ec Fix missing translations part 2 2023-08-10 01:52:46 +02:00
1de8bb850f Update README.md 2023-08-09 08:54:45 -04:00
25d09186fe Update README.md 2023-08-09 08:34:35 -04:00
9552b7c6f9 Update README.md 2023-08-09 08:34:13 -04:00
3d692e4198 Merge pull request #3132 from oqtane/master
Merge pull request #3131 from oqtane/dev
2023-08-09 08:22:44 -04:00
215ca9f755 Merge pull request #3131 from oqtane/dev
4.0.2 Release
2023-08-09 08:22:24 -04:00
7dbcb24f3d Merge pull request #3129 from alikoli/fix-missing-translations
Fix missing translations
2023-08-09 08:16:06 -04:00
18b4bd62f7 Update appsettings.json 2023-08-09 04:06:14 +02:00
20f3cf5327 Add empty appsettings 2023-08-09 03:58:34 +02:00
c1d35e8af5 Fix missing translations 2023-08-09 03:06:15 +02:00
e1cd318f61 Merge pull request #3128 from sbwalker/dev
fix installed cultures logic to recognize all satellite resources
2023-08-08 13:22:10 -04:00
4da0952091 fix installed cultures logic to recognize all satellite resources 2023-08-08 13:21:56 -04:00
a60f75f0a9 Merge pull request #3127 from sbwalker/dev
fix refresh logic in router
2023-08-08 13:07:43 -04:00
3814009ad9 fix refresh logic in router 2023-08-08 13:07:29 -04:00
fe7bb00112 Merge pull request #3125 from leigh-pointer/ModuleTitleLoc
Updated Module Title using SetModuleTitle on ModuleBase
2023-08-08 12:23:36 -04:00
2356753dfc Updated Module Title using SetModuleTitle on ModuleBase 2023-08-08 16:37:40 +02:00
bd23ec268b Merge remote-tracking branch 'oqtane/dev' into dev 2023-08-08 15:34:25 +02:00
1d5f23c3c7 Merge pull request #3123 from leigh-pointer/TransKeys
Trans keys
2023-08-08 09:33:43 -04:00
f424bf53b3 Revert Title to "Folder Management"
This is due to the public virtual string Title can not use the Localize instance as it not yet created so error with null reference.
2023-08-08 15:30:39 +02:00
efbdf0006e Merge remote-tracking branch 'oqtane/dev' into TransKeys 2023-08-08 15:22:03 +02:00
625b173ee4 Merge pull request #3122 from leigh-pointer/TransKeys
Updated the Settings Heading
2023-08-08 08:05:21 -04:00
0a8d9bc3d3 Updated the Settings Heading 2023-08-08 14:01:45 +02:00
0d0909a461 Merge pull request #3120 from leigh-pointer/TransKeys
Translation for thread #3094
2023-08-08 07:55:35 -04:00
ebb0a538f8 Merge pull request #3121 from sbwalker/dev
improve sync service to always rely on server dates
2023-08-08 07:51:26 -04:00
337a566617 improve sync service to always rely on server dates 2023-08-08 07:51:07 -04:00
fecee7a12b Translation for thread #3094
Updated the Missing or incorrect keys
2023-08-08 13:30:10 +02:00
282ec99ce8 Merge remote-tracking branch 'oqtane/dev' into dev 2023-08-08 12:14:18 +02:00
17a4985ebe Merge pull request #3119 from sbwalker/dev
fix Section component localization
2023-08-07 17:11:36 -04:00
13b17d91a9 fix Section component localizatioon 2023-08-07 17:11:17 -04:00
5782005007 Merge pull request #3118 from sbwalker/dev
improve reload in router to prevent looping
2023-08-07 15:40:00 -04:00
258f2dbe8f improve reload in router to prevent looping 2023-08-07 15:39:44 -04:00
e7b35bd0c2 Merge pull request #3117 from sbwalker/dev
fix #3094 - localization of admin module titles
2023-08-07 12:14:11 -04:00
176bd229e6 fix #3094 - localization of admin module titles 2023-08-07 12:13:53 -04:00
c12f1251b0 Merge remote-tracking branch 'oqtane/dev' into dev 2023-08-07 18:07:11 +02:00
0710736661 Merge pull request #3116 from sbwalker/dev
improve code documentation
2023-08-07 10:39:40 -04:00
51ebe520f4 improve code documentation 2023-08-07 10:39:24 -04:00
1ef566c824 Merge pull request #3115 from sbwalker/dev
fix issue where user could not be shared across multiple sites
2023-08-07 09:58:41 -04:00
96cf06fe8d fix issue where user could not be shared across multiple sites 2023-08-07 09:58:24 -04:00
38ebfdcd0c Merge pull request #3114 from sbwalker/dev
fix #3108 - raise reload event after user logs out
2023-08-07 09:34:37 -04:00
b5649e2a6f fix #3108 - raise reload event after user logs out 2023-08-07 09:34:20 -04:00
5fc6dadeae Merge pull request #3113 from sbwalker/dev
prepare for 4.0.2 release
2023-08-06 08:34:05 -04:00
22cfec9276 prepare for 4.0.2 release 2023-08-06 08:33:36 -04:00
0a82ec5f0a Update README.md 2023-08-06 08:32:05 -04:00
3bdd1f6c4f Merge pull request #3112 from sbwalker/dev
use new GetJsonAsync method in external module template
2023-08-06 08:19:16 -04:00
1e466dc1fe use new GetJsonAsync method in external module template 2023-08-06 08:17:56 -04:00
3b302d2f86 Merge pull request #3111 from sbwalker/dev
change Help button style
2023-08-06 07:58:50 -04:00
7f108eebf9 change Help button style 2023-08-06 07:58:27 -04:00
9d41fee2fe Merge remote-tracking branch 'oqtane/dev' into dev 2023-08-06 09:39:41 +02:00
bef686f95a Merge pull request #3110 from sbwalker/dev
change defaultvalue parameter to defaultResult to make more intuitive
2023-08-05 16:35:04 -04:00
4bdcb974bd change defaultvalue parameter to defaultResult to make more intuitive 2023-08-05 16:34:41 -04:00
af042bd23c Merge pull request #3109 from sbwalker/dev
introduce new GetJsonAsync method with default value parameter
2023-08-05 09:01:03 -04:00
19e3cef7dd introduce new GetJsonAsync method with default value parameter 2023-08-05 09:00:38 -04:00
085ab4bfb4 Merge pull request #3107 from sbwalker/dev
add transparency support on image resizing
2023-08-04 16:07:50 -04:00
f7bd03d051 add transparency support on image resizing 2023-08-04 16:07:37 -04:00
b48d751717 Merge pull request #3106 from sbwalker/dev
update Module and Theme Install UI to match Marketplace - including logos and support for sorting
2023-08-04 15:17:36 -04:00
92a4a1b210 update Module and Theme Install UI to match Marketplace - including logos and support for sorting 2023-08-04 15:17:17 -04:00
6a57fcc04a Merge pull request #3103 from sbwalker/dev
fix GetFolderByPath to support root folder path
2023-08-03 17:29:26 -04:00
749e11762f fix GetFolderByPath to support root folder path 2023-08-03 17:29:02 -04:00
61817726c3 Merge pull request #3102 from sbwalker/dev
fix issue where meta name="description" tags were being excluded from output
2023-08-03 16:02:49 -04:00
808354e969 fix issue where meta name="description" tags were being excluded from output 2023-08-03 16:02:34 -04:00
d2f594ff4a Merge pull request #3101 from sbwalker/dev
fix #3069 - exclude templates from release packages
2023-08-03 15:27:05 -04:00
fa18467cdd fix #3069 - exclude templates from release packages 2023-08-03 15:26:23 -04:00
4c940d02ef Merge pull request #3100 from sbwalker/dev
add error handling and logging to folder creation logic
2023-08-03 14:48:58 -04:00
04202a6b70 Merge branch 'dev' of https://github.com/sbwalker/oqtane.framework into dev 2023-08-03 14:45:39 -04:00
4483901270 fix #3072 - add error handling and logging to folder creation logic 2023-08-03 14:45:27 -04:00
f02d894697 Merge remote-tracking branch 'oqtane/dev' into dev 2023-08-03 20:25:17 +02:00
2bc130856a Merge pull request #3047 from leigh-pointer/FixTemplateNULL
Fixes Module Creator problem  #3041
2023-08-03 14:23:55 -04:00
aa3a4dca65 Merge remote-tracking branch 'oqtane/dev' into dev 2023-08-03 18:53:56 +02:00
4f65931f51 Merge pull request #3099 from sbwalker/dev
fix #3065 - redirect user if they are logged in and navigating to Login page
2023-08-03 12:47:02 -04:00
93be61e483 fix #3065 - redirect user if they are logged in and navigating to Login page 2023-08-03 12:46:42 -04:00
cb7fe364bc Merge pull request #3098 from sbwalker/dev
remove unecessary using statement added by Visual Studio
2023-08-03 11:51:38 -04:00
9cdcb4b22c remove unecessary using statement added by Visual Studio 2023-08-03 11:51:26 -04:00
c49072f9b6 Merge pull request #3097 from sbwalker/dev
fix #3082 - handle username claim as "unique_name" with "name" as fallback, improve validation logic and logging
2023-08-03 11:49:24 -04:00
61df26b667 fix #3082 - handle username claim as "unique_name" with "name" as fallback, improve validation logic and logging 2023-08-03 11:48:59 -04:00
25c935f1db Merge pull request #3096 from sbwalker/dev
fix #3093 - user email links include extra "://"
2023-08-03 11:11:31 -04:00
6b982bc0c7 fix #3093 - user email links include extra "://" 2023-08-03 11:11:14 -04:00
caccc803d3 Merge pull request #3095 from sbwalker/dev
improve appsettings.json in Maui client
2023-08-03 10:15:26 -04:00
5be640632b improve appsettings.json in Maui client 2023-08-03 10:15:12 -04:00
40912f0ffd Merge pull request #3092 from sbwalker/dev
fix WebAssembly startup alias handling
2023-08-03 08:25:59 -04:00
d518860a34 fix WebAssembly startup alias handling 2023-08-03 08:25:44 -04:00
185617ed9e Merge remote-tracking branch 'oqtane/dev' into dev 2023-08-03 05:39:32 +02:00
5ba96b627e Merge pull request #3090 from sbwalker/dev
support for appsettings.json in Maui app
2023-08-02 17:23:41 -04:00
edf955f4cd support for appsettings.json in Maui app 2023-08-02 17:23:27 -04:00
8d0b04668e Merge pull request #3089 from sbwalker/dev
add appicon to Maui client project
2023-08-02 17:11:47 -04:00
a72e5e02da add appicon to Maui client project 2023-08-02 17:11:33 -04:00
4740404c2e Merge pull request #3088 from sbwalker/dev
add version to support url
2023-08-02 16:46:11 -04:00
34253916ea add version to support url 2023-08-02 16:45:59 -04:00
1d77ba2694 Merge pull request #3055 from leigh-pointer/AutoCompleteRequired
Extended AutoComplete control to allow the Required attribute
2023-08-02 14:53:14 -04:00
765041c587 Merge pull request #3058 from vnetonline/fix-file-manager-show-folders
[ENHANCE] - #3057 FileManager when ShowFolders = False however a Folder is set by path
2023-08-02 14:52:04 -04:00
8c6bca7666 Merge pull request #3087 from sbwalker/dev
trim dependencies for Themes (related to #3079)
2023-08-02 14:49:06 -04:00
c1b1cef590 trim dependencies for Themes (related to #3079) 2023-08-02 14:48:51 -04:00
507f7804c3 Merge pull request #3079 from vnetonline/fix-3078
[FIX] - #3078 - ModuleInfo Dependency needs to be trimmed so there is no white space (credit @maxmontgmx))
2023-08-02 14:45:23 -04:00
453bacf9bd Merge remote-tracking branch 'oqtane/dev' into dev 2023-08-02 20:13:17 +02:00
25778458c6 Merge pull request #3086 from sbwalker/dev
Fix #3068 - support microsites in .NET MAUI
2023-08-02 13:55:23 -04:00
7a42646bed Fix #3068 - support microsites in .NET MAUI 2023-08-02 13:55:01 -04:00
755b7034d2 Merge pull request #3085 from sbwalker/dev
Fix #3068 - support microsites in .NET MAUI
2023-08-02 13:54:47 -04:00
122fcfd701 Fix #3068 - support microsites in .NET MAUI 2023-08-02 13:53:55 -04:00
2c905eddc2 Merge branch 'fix-file-manager-show-folders' of https://github.com/vnetonline/oqtane.framework into fix-file-manager-show-folders 2023-08-01 21:12:02 +10:00
3e8eb9abb5 [ENHANCE] - #3057 FileManager when ShowFolders = false however a Folder is set by path 2023-08-01 21:11:54 +10:00
d2df3b2d9a ModuleInfo Dependency needs to be trimmed so there is no white space at the end (credit @maxmontgmx)) 2023-08-01 21:02:19 +10:00
2a0c983c2e Handle if Parameter is set dynamically 2023-07-26 11:28:14 +02:00
1319432fde [Fix] - #3057 FileManager when ShowFolders = false however a Folder is set by path 2023-07-24 08:19:06 +10:00
02e2aeb6d1 Extended control to set the Required attribute
The control now allow the required attribute to be set.  This will now give better UX feedback.
2023-07-20 13:56:29 +02:00
5e35edd976 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-20 13:34:27 +02:00
4d63c6266c Merge pull request #3053 from sbwalker/dev
disable ServiceBase logic which does not work with legacy ControllerRoutes.Default routes (modules created prior to 2.1)
2023-07-19 20:07:46 -04:00
48f8d41993 disable ServiceBase logic which does not work with legacy ControllerRoutes.Default routes (modules created prior to 2.1) 2023-07-19 20:07:27 -04:00
0b41ccad83 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-19 22:39:24 +02:00
f994afbc11 Merge pull request #3051 from sbwalker/dev
fix #3048 - uploading to Packages folder showing unsuccessful message
2023-07-19 16:27:39 -04:00
5dea783677 fix #3048 - uploading to Packages folder showing unsuccessful message 2023-07-19 16:27:26 -04:00
347ef9a1d0 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-19 22:16:18 +02:00
739aa5311c Merge pull request #3050 from sbwalker/dev
fix #3046 - user folders displayed in folder lists
2023-07-19 16:15:04 -04:00
805018286c fix #3046 - user folders displayed in folder lists 2023-07-19 16:14:48 -04:00
45f2a47ba5 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-19 21:53:10 +02:00
fe503b7bee Merge pull request #3049 from sbwalker/dev
FileManager modification to support ShowFolders = False
2023-07-19 15:45:25 -04:00
6736570ee7 FileManager modification to support ShowFolders = False 2023-07-19 15:45:12 -04:00
88c06eea6e Fixes Module Creator problem #3041
This fix is to handle a returned null from the database.  This issue came to light while using Postgresql
2023-07-19 09:58:36 +02:00
13693ef9e5 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-19 09:53:29 +02:00
3a54326e73 Update README.md 2023-07-18 12:42:49 -04:00
941c1c8a45 Update README.md 2023-07-18 12:35:27 -04:00
f56e102f20 Merge pull request #3045 from oqtane/master
Merge pull request #3044 from oqtane/dev
2023-07-18 12:25:59 -04:00
010c669ce2 Merge pull request #3044 from oqtane/dev
4.0.1 Release
2023-07-18 12:25:41 -04:00
d328058075 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-18 17:40:29 +02:00
9609e7b297 Merge pull request #3042 from leigh-pointer/ControlPanelPadding
Fixed up margins in Control Panel
2023-07-18 11:08:57 -04:00
92718372b8 Fixed up margins in Control Panel
Added top margin to the Module Management
2023-07-18 12:47:48 +02:00
9108eb5616 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-18 12:37:53 +02:00
354ad9d0d5 Merge pull request #3040 from sbwalker/dev
use html line breaks in error log notifications
2023-07-17 16:59:54 -04:00
100b92036b use html line breaks in error log notifications 2023-07-17 16:59:40 -04:00
e9ed1ecc8c Merge pull request #3039 from sbwalker/dev
prevent client looping if server is down
2023-07-17 11:49:16 -04:00
92595ccc35 prevent client looping if server is down 2023-07-17 11:49:06 -04:00
ea45044dc7 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-17 16:48:24 +02:00
1ef28494b3 Merge pull request #3038 from sbwalker/dev
use discretion with LogLevel.Error messages
2023-07-17 09:29:00 -04:00
985d324593 use discretion with LogLevel.Error messages 2023-07-17 09:28:29 -04:00
96e7178cd2 Merge pull request #3036 from sbwalker/dev
Added logic to ensure assembly version being installed is equal to or greater than existing assembly
2023-07-17 08:16:16 -04:00
f41e2358a9 Added logic to ensure assembly version being installed is equal to or greater than existing assembly 2023-07-17 08:15:56 -04:00
0f146ff355 Merge pull request #3035 from sbwalker/dev
Added a ShowProgress parameter to FileManager (enabled by default). Disabling will display a simple spinner rather than detailed progress information during upload.
2023-07-17 07:43:18 -04:00
465f241e89 Added a ShowProgress parameter to FileManager (enabled by default). Disabling will display a simple spinner rather than detailed progress information during upload. 2023-07-17 07:42:48 -04:00
2bdb011240 Merge pull request #3026 from vnetonline/enhance-filemanager
[ENHANCE] - Moved `SetImage()` before the OnSelect fires so that the …
2023-07-17 07:11:41 -04:00
dd55725f9b Merge pull request #3034 from vnetonline/fix-publish-module
[FIX] - Page disappears if you unpublish and publish a module
2023-07-17 07:10:55 -04:00
6b16808207 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-17 11:01:10 +02:00
411111fa55 [FIX] - Page disappears if you unpublish and publish a module on the page 2023-07-17 09:33:04 +10:00
5345a12aca [ENHANCE] - Moved SetImage() before the OnSelect fires so that the Parent can retrieve the Image from fileManager rather then calling FileService to retrieve again 2023-07-17 07:58:39 +10:00
39adcde16d Merge pull request #3030 from sbwalker/dev
display message if package service does not return package requested
2023-07-15 15:27:01 -04:00
3c37ee60af display message if package service does not return package requested 2023-07-15 15:26:48 -04:00
4effc4dd60 Merge pull request #3028 from sbwalker/dev
marketplace changes
2023-07-15 10:01:41 -04:00
fec9fa17e8 marketplace changes 2023-07-15 10:01:27 -04:00
4071b285ce Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-15 12:34:10 +02:00
fca306ffa6 Merge pull request #3025 from sbwalker/dev
SupportUrl integration
2023-07-14 15:19:04 -04:00
0e042925f2 SupportUrl integration 2023-07-14 15:18:52 -04:00
99421846c7 Merge pull request #3024 from sbwalker/dev
remove OnSelect call from OnParametersSet() which was causing infinite loop
2023-07-14 12:15:34 -04:00
592885ff6c remove OnSelect call from OnParametersSet() which was causing infinite loop 2023-07-14 12:15:16 -04:00
a95306c317 Merge pull request #3022 from sbwalker/dev
moved UserManager to Managers namespace
2023-07-13 17:05:21 -04:00
8bdbf7b994 moved UserManager to Managers namespace 2023-07-13 17:05:01 -04:00
7bc80f0814 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-13 17:21:55 +02:00
aed65d2594 Merge pull request #3020 from sbwalker/dev
add handling for DisplayName
2023-07-13 11:17:04 -04:00
63b3e4d90d add handling for DisplayName 2023-07-13 11:16:49 -04:00
f9c459eab2 Merge pull request #3019 from sbwalker/dev
fix bash script line endings and preserve using gitattributes (credit @rcpacheco in #2958)
2023-07-13 10:19:14 -04:00
a6d83149a4 fix bash script line endings and preserve using gitattributes (credit @rcpacheco in #2958) 2023-07-13 10:18:52 -04:00
387b477034 Merge pull request #3018 from sbwalker/dev
allow page themes to be different from site theme - display warning message
2023-07-13 09:09:00 -04:00
68cdcd193c allow page themes to be different from site theme - display warning message 2023-07-13 08:58:43 -04:00
08438c7725 Merge pull request #2964 from OpenEugene/release/v4.0.0
Show all themes on page add/edit
2023-07-13 07:52:34 -04:00
1894b46d3a Merge pull request #3016 from leigh-pointer/UserSort
User management sort
2023-07-13 07:41:40 -04:00
367a825955 Merge pull request #3017 from sbwalker/dev
added logging to ServiceBase to capture HTTP errors
2023-07-13 07:40:55 -04:00
be799eb254 added logging to ServiceBase to capture HTTP errors 2023-07-13 07:39:24 -04:00
4820a27016 User management sort
Added Sorting to User management component.
2023-07-13 12:54:08 +02:00
6b57202421 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-13 11:40:58 +02:00
4e82212956 Merge pull request #3015 from sbwalker/dev
fix issue for site folders
2023-07-12 19:49:56 -04:00
6b0f5ed12d fix issue for site folders 2023-07-12 19:49:38 -04:00
0a258caf48 Merge pull request #3013 from sbwalker/dev
remove unnecessary using statement
2023-07-12 17:59:34 -04:00
f546a68f7c remove unnecessary using statement 2023-07-12 17:59:21 -04:00
1e5bab09db Merge pull request #3012 from sbwalker/dev
migrate remaining methods to UserManager
2023-07-12 17:52:29 -04:00
35fa18a852 migrate remaining methods to UserManager 2023-07-12 17:52:16 -04:00
90c1eeb312 Merge pull request #3009 from ajahangard/dev
Using DynamicComponent Instead of RenderFragment in ContainerBuilder
2023-07-12 16:40:09 -04:00
404c32b49b Merge pull request #3011 from sbwalker/dev
add a UserManager to simplify user creation, improve response validation in ServiceBase, allow Section component to support parameter changes
2023-07-12 16:37:54 -04:00
c0f4cd2097 add a UserManager to simplify user creation, improve response validation in ServiceBase, allow Section component to support parameter changes 2023-07-12 16:37:18 -04:00
fa98908dea Merge branch 'oqtane:dev' into dev 2023-07-12 12:43:40 +03:30
23e8567e86 Using DynamicComponent Instead of RenderFragment in ContainerBuilder 2023-07-12 12:43:00 +03:30
ca3edb6687 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-11 15:22:30 +02:00
a02e28f493 Merge pull request #3005 from leigh-pointer/DropIsPublic
The Down path is incorrect
2023-07-11 08:59:39 -04:00
7428f87899 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-11 14:47:53 +02:00
d6a7e32ca9 Merge pull request #3006 from sbwalker/dev
support both 404 and 403 status codes in GET API response (404 should not log)
2023-07-11 08:14:52 -04:00
df0f562817 support both 404 andf 403 status codes in API response (404 should not log) 2023-07-11 08:14:00 -04:00
0dea2bc79e The Down path is incorrect
The Down Path drops the wrong column
2023-07-11 08:37:23 +02:00
02481560a9 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-11 08:29:05 +02:00
dc1d1015ed Merge pull request #2999 from sbwalker/dev
prepare for 4.0.1 release
2023-07-10 16:51:20 -04:00
59fffbd3ee prepare for 4.0.1 release 2023-07-10 16:51:03 -04:00
655cbca574 Merge pull request #2998 from sbwalker/dev
add module name to the Module Settings UI
2023-07-10 16:36:57 -04:00
e22c28a3c9 add module name to the Module Settings UI 2023-07-10 16:36:37 -04:00
e823e371f6 Merge pull request #2997 from sbwalker/dev
fix #2976 - add logging methods to ThemeBase
2023-07-10 16:26:49 -04:00
feae7d6269 fix #2976 - add logging methods to ThemeBase 2023-07-10 16:26:30 -04:00
e1c0ea0720 Merge pull request #2996 from sbwalker/dev
fix #2977 - margin between edit and cog
2023-07-10 16:20:33 -04:00
4e1a389c8b fix #2977 - margin between edit and cog 2023-07-10 16:20:16 -04:00
0362a6a3dc Merge pull request #2995 from sbwalker/dev
fix #2978 - allow host users and admins to have personalized pages
2023-07-10 16:14:14 -04:00
3891dea009 fix #2978 - allow host users and admins to have personalized pages 2023-07-10 16:13:56 -04:00
2d66063763 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-10 21:19:27 +02:00
1ccb7e2092 Merge pull request #2994 from sbwalker/dev
prevent logging of error for personalized pages
2023-07-10 14:51:58 -04:00
62ad99d0b6 prevent logging of error for personalized pages 2023-07-10 14:51:32 -04:00
66d07fbed3 Merge pull request #2980 from vnetonline/fix-ispersonalised
Fixing personalized page created to be UserName if DisplayName is null
2023-07-10 14:28:55 -04:00
ec117adb2d Merge pull request #2993 from sbwalker/dev
make GetHttpClient() public
2023-07-10 14:16:35 -04:00
3aff31b826 make GetHttpClient() public 2023-07-10 14:16:17 -04:00
1621944105 Merge pull request #2992 from sbwalker/dev
refactored upload so that it is not dependent on Folder Browse permission
2023-07-10 11:44:28 -04:00
16d0d11e0b refactored upload so that it is not dependent on Folder Browse permission 2023-07-10 11:44:05 -04:00
9158e24295 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-10 17:41:35 +02:00
0b81f28e99 Merge pull request #2991 from sbwalker/dev
fix validation issue in FileManager related to Browse permissions
2023-07-10 10:10:16 -04:00
2ccb814223 fix validation issue in FileManager related to Browse permissions 2023-07-10 10:09:57 -04:00
f213abd70a Merge pull request #2990 from sbwalker/dev
fix migration error caused by new IsRead field in Notifications. Microsoft.Data.SqlClient.SqlException: 'ALTER TABLE only allows columns to be added that can contain nulls, or have a DEFAULT definition specified.'
2023-07-10 08:59:37 -04:00
b87eddeeda fix migration error caused by new IsRead field in Notifications. Microsoft.Data.SqlClient.SqlException: 'ALTER TABLE only allows columns to be added that can contain nulls, or have a DEFAULT definition specified.' 2023-07-10 08:59:09 -04:00
92fc56e70d Merge pull request #2988 from leigh-pointer/TemplateConfig
Remove Build for Oqtane Server in ConfigManger
2023-07-10 08:45:16 -04:00
e22f3238fb Merge pull request #2989 from sbwalker/dev
add API method to get File based on name, and fix permission validation for Folder
2023-07-10 08:44:41 -04:00
c597c4c234 add API method to get File based on name, and fix permission validation for Folder 2023-07-10 08:44:14 -04:00
5973c3d1a0 Remove Build for Oqtane Server in ConfigManger
Remove Build for Oqtane Server in ConfigManger
2023-07-10 14:40:00 +02:00
118e177756 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-10 14:37:16 +02:00
d50472cd7b Merge pull request #2984 from leigh-pointer/ConfigExtern
Fix for #2983 Oqtane. Server selected in for build in configmanager
2023-07-10 07:44:46 -04:00
11604bbcfd Merge pull request #2985 from vnetonline/fix-modulesettings-resource-type-name
[FIX] - Fixed the ResourceType in Module Settings with correct NameSpace
2023-07-10 07:41:34 -04:00
cf428598d5 [FIX] - Fixed the Resource Type in Module Settings with correct Namespace 2023-07-10 12:52:02 +10:00
a45fc0c4df Fix for #2983 Oqtane. Server selected in for build in configmanager
Removed Oqtane. Server build from configuration manager debug and release
2023-07-09 21:30:45 +02:00
88ac82a013 Merge remote-tracking branch 'oqtane/dev' into dev 2023-07-09 21:12:47 +02:00
5abf0df154 Merge pull request #2982 from sbwalker/dev
Package enhancements for Marketplace
2023-07-09 08:36:34 -04:00
9a3b458c45 Package enhancements for Marketplace 2023-07-09 08:36:14 -04:00
1b2de37c4e Merge pull request #2975 from vnetonline/enhance-notifications-isread
[ENHANCE] - Added IsRead property to Notifications
2023-07-08 10:07:44 -04:00
f7338bf00e Update GetNotifications (read) to retrieve descending oder by CreatedOn at repository level 2023-07-07 10:21:22 +10:00
bad7be39a6 Fixing personalized page created to be UserName if DisplayName is null 2023-07-07 09:21:49 +10:00
40459defa4 Cosmetic change to more the filter drop down to top of the notifications tab 2023-07-06 15:28:11 +10:00
b7de4b81a6 [ENHANCE] - Added IsRead property to Notifications
Fixed Version to Tenant.04.00.01.01 and reverted the Program.cs back to the way it was

This reverts commit 82fef82c4f.

[ENHANCE] - Added API to get Count of New Notifications based on IsRead

Fixed Typo in Notification Controller

[ENHANCE] - Added API to get Notifications by Count and IsRead
2023-07-06 01:02:05 +10:00
563695cdfa Merge pull request #2967 from leigh-pointer/RemoveRazorLangVersion 2023-07-01 16:03:48 -04:00
fd30112ee8 Removed RazorLangVersion tag from Client project files
as to discussion #2965
2023-07-01 14:41:03 +02:00
a36794b98b Merge pull request #1 from OpenEugene/markdav-is-patch-theme-dropdown
Show all themes when editing
2023-06-30 17:18:30 -07:00
e52653469d Merge pull request #2 from OpenEugene/markdav-is-patch-add-themes
show all themes when adding
2023-06-30 17:18:14 -07:00
b61035a3a3 show all themes when adding 2023-06-30 17:12:14 -07:00
b3039e71be Show all themes when editing 2023-06-30 17:07:46 -07:00
c025013ca8 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-30 22:04:18 +02:00
2c614f3bb6 Merge pull request #2961 from leigh-pointer/UnPubPage
Fixes #2960 - cant unpublish page.
2023-06-30 15:43:52 -04:00
07a27b25e0 Merge pull request #2959 from leigh-pointer/ModActRoleButt
ModuleActions not displaying the correct mouse pointer
2023-06-30 15:43:34 -04:00
4c080572ac Merge pull request #2940 from vnetonline/dev
Added ThemeSettings and ContainerSettings to Default Theme Template
2023-06-30 15:43:22 -04:00
95cd6ea48e Fixes #2960 - cant unpublish page.
Added the reverse code to change a published page to unpublished.
2023-06-30 08:53:09 +02:00
2e3c57226f ModuleActions not displaying the correct mouse pointer
Upated the <ul> tag with a role attribute button.
2023-06-30 08:20:57 +02:00
69f7367a18 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-30 08:17:33 +02:00
8ff7f0bf5e Merge branch 'dev' of https://github.com/vnetonline/oqtane.framework into dev 2023-06-30 08:04:26 +10:00
e2a9470f31 Adding Solution file back to Default Theme Template
Added ThemeSettings and ContainerSettings to Default Theme Template

Added ThemeSettings and ContainerSettings to Default Theme Template to Demonstrate how these features could be added to developers designing a theme as requested and discussed in Issue #2633

Includeda few setting options as per suggestion by @sbwalker  intended to provide a simple demonstration of how to use Theme and Container settings
2023-06-30 08:04:13 +10:00
a2a64e73bc Merge branch 'oqtane:dev' into dev 2023-06-30 07:41:49 +10:00
15305e3a0b Merge pull request #2956 from leigh-pointer/RoleDates
Closes #2955 User Roles Effective and Expiry date Rendering as Text
2023-06-29 14:42:17 -04:00
bcb484ba62 Closes #2955 User Roles Effective and Expiry date Rendering as Text
Updated the User Roles to use Date control and fixed the code to accept DateTime values
2023-06-29 19:17:38 +02:00
278fd8023b Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-29 18:37:15 +02:00
ba7efb45e7 Merge pull request #2954 from sbwalker/dev
handle paths during Folder add/update
2023-06-29 12:24:37 -04:00
8d3d218067 handle paths during Folder add/update 2023-06-29 12:24:20 -04:00
5185934f25 Merge pull request #2953 from sbwalker/dev
fix progress bar styling in FileManager
2023-06-29 11:54:20 -04:00
0a9cbfd0e5 fix progress bar styling in FileManager 2023-06-29 11:54:05 -04:00
35e237b5b3 Merge pull request #2952 from sbwalker/dev
allow FileManager to support changes in parameters
2023-06-29 11:21:02 -04:00
ba18cd505b allow FileManager to support changes in parameters 2023-06-29 11:20:45 -04:00
662df53a10 Fixed Indenting in ThemeSettings.cs 2023-06-29 13:22:51 +10:00
eadf2e89a8 Fix HasChildren property not considering if page is not in navigation (i.e. hidden) 2023-06-29 11:56:19 +10:00
a3b54b9891 Merge branch 'oqtane:dev' into dev 2023-06-29 11:05:12 +10:00
18cab2c759 Merge pull request #2950 from sbwalker/dev
allow admin to navigate to site settings for deleted site
2023-06-28 13:11:29 -04:00
8edc4fd67a allow admin to navigate to site settings for deleted site 2023-06-28 13:11:15 -04:00
b914163499 Merge pull request #2949 from sbwalker/dev
fix #2941 - HasChildren property not considering deleted pages
2023-06-28 13:05:46 -04:00
c55c8bff6e fix #2941 - HasChildren property not considering deleted pages 2023-06-28 13:05:34 -04:00
e669bb1f82 Merge pull request #2948 from sbwalker/dev
fix #2942 - reverse ordering of page name and site name for page title
2023-06-28 12:51:11 -04:00
452c3fd355 fix #2942 - reverse ordering of page name and site name for page title 2023-06-28 12:50:58 -04:00
60be0c92da Merge pull request #2947 from sbwalker/dev
improve helptext for Aliases
2023-06-28 12:44:16 -04:00
03bd88a2d5 improve helptext for Aliases 2023-06-28 12:44:00 -04:00
7f355698bc Merge pull request #2946 from sbwalker/dev
use case insensitive comparison for activetab name
2023-06-28 12:27:34 -04:00
22ec675cec use case insensitive comparison for activetab name 2023-06-28 12:27:21 -04:00
3954c482ce Merge pull request #2945 from sbwalker/dev
set Expanded to lowercase if specified
2023-06-28 12:25:13 -04:00
d06feec37b set Expanded to lowercase if specified 2023-06-28 12:24:59 -04:00
536af96ccc Merge pull request #2944 from sbwalker/dev
fix #2938 - path not updated correctly when parent page changed
2023-06-28 12:17:42 -04:00
e1a8d3db54 fix #2938 - path not updated correctly when parent page changed 2023-06-28 12:17:27 -04:00
83d51a85ce Adding Solution file back to Default Theme Template 2023-06-28 22:32:42 +10:00
4400744f35 Added ThemeSettings and ContainerSettings to Default Theme Template
Added ThemeSettings and ContainerSettings to Default Theme Template to Demonstrate how these features could be added to developers designing a theme as requested and discussed in Issue #2633
2023-06-28 22:25:07 +10:00
ea4b85ad54 Merge pull request #2939 from sbwalker/dev
filter deleted sites in notification job
2023-06-27 11:42:36 -04:00
60b6a9ce47 filter deleted sites in notification job 2023-06-27 11:42:15 -04:00
927e00bd66 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-27 09:31:07 +02:00
cd8d2dd8f3 Update README.md 2023-06-26 14:36:53 -04:00
6861965779 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-26 20:14:07 +02:00
8a9a1d7df7 Merge pull request #2937 from sbwalker/dev
cosmetic fix for input elements within a table intoduced with Bootstrap 5.3
2023-06-26 14:00:03 -04:00
e7e66699b1 cosmetic fix for input elements within a table intoduced with Bootstrap 5.3 2023-06-26 13:59:44 -04:00
681321feeb Merge pull request #2936 from oqtane/master
Merge pull request #2935 from oqtane/dev
2023-06-26 11:31:16 -04:00
a637b6d4d0 Merge pull request #2935 from oqtane/dev
4.0.0 Release
2023-06-26 11:30:59 -04:00
97026746f8 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-26 14:57:48 +02:00
5161ade786 Merge pull request #2932 from sbwalker/dev
added deprecation message for IHostResources
2023-06-26 08:12:01 -04:00
6dd62a164e added deprecation message for IHostResources 2023-06-26 08:11:46 -04:00
3bece7599c Merge pull request #2931 from sbwalker/dev
fix validation message behavior in installer
2023-06-25 09:10:07 -04:00
b2f3a53980 fix validation message behavior in installer 2023-06-25 09:09:53 -04:00
cea31a8573 Merge pull request #2930 from sbwalker/dev
remove A/B testing logic for stylesheets - discard root component approach in favor of legacy JS Interop approach to eliminate FOUC issues
2023-06-25 08:39:41 -04:00
ce6647a84a remove A/B testing logic for stylesheets - discard root component approach in favor of legacy JS Interop approach to eliminate FOUC issues 2023-06-25 08:39:12 -04:00
d54ba9a5c6 Merge pull request #2929 from sbwalker/dev
added logging to HostedServiceBase
2023-06-23 15:19:33 -04:00
1e18d913e0 added logging to HostedServiceBase 2023-06-23 15:19:18 -04:00
ef8c47a49f Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-23 14:32:00 +02:00
0041c7d3b3 Merge pull request #2928 from sbwalker/dev
allow for testing CSS page transitions
2023-06-23 08:16:56 -04:00
f6cca5cb35 allow for testing CSS page transitions 2023-06-23 08:16:39 -04:00
fa5abbf96f Merge pull request #2927 from sbwalker/dev
fix ordering of stylesheets
2023-06-23 08:07:22 -04:00
2ff365765d fix ordering of stylesheets 2023-06-23 08:07:06 -04:00
ee31e4a411 Merge pull request #2926 from sbwalker/dev
clarify documentation and parameter names
2023-06-23 07:44:31 -04:00
ee4068d671 clarify documentation and parameter names 2023-06-23 07:44:15 -04:00
5e86c95db6 Merge pull request #2925 from sbwalker/dev
add validation logic to ensure page theme martches site theme
2023-06-23 07:38:52 -04:00
6126624631 add validation logic to ensure page theme martches site theme 2023-06-23 07:38:33 -04:00
a320e4d48d Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-22 21:59:51 +02:00
a7c5841e76 Merge pull request #2924 from sbwalker/dev
external template changes to support non-Windows environments
2023-06-22 15:24:43 -04:00
0f242a94b4 external template changes to support non-Windows environments 2023-06-22 15:24:27 -04:00
8f8a0897e4 Add files via upload 2023-06-22 15:23:10 -04:00
7c6b64123c Add files via upload 2023-06-22 15:22:10 -04:00
55642093ed Merge pull request #2923 from sbwalker/dev
allow CSS testing using old and new method
2023-06-22 14:05:25 -04:00
5660f40512 allow CSS testing using old and new method 2023-06-22 14:05:08 -04:00
3259b7183b Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-22 17:43:30 +02:00
df7cd648ac Merge pull request #2921 from sbwalker/dev
restrict to a single theme per site
2023-06-22 11:32:21 -04:00
2a5713ed67 restrict to a single theme per site 2023-06-22 11:32:03 -04:00
b7c36c07c6 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-22 06:03:04 +02:00
de93281f3e Merge pull request #2920 from sbwalker/dev
cleanup and ensure site level scripts work properly
2023-06-21 15:49:13 -04:00
86fbdced1b cleanup and ensure site level scripts work properly 2023-06-21 15:49:00 -04:00
43bcfb9a4e Merge pull request #2910 from vnetonline/dev
[ENHANCE] - Added Module to NameSpace [Owner].Module.[Module]
2023-06-21 09:57:36 -04:00
0daf6af3b8 Merge pull request #2918 from sbwalker/dev
fix #2387 - improve container selection in Edit Page
2023-06-21 09:20:23 -04:00
c3bccbade8 fix #2387 - improve container selection in Edit Page 2023-06-21 09:20:05 -04:00
3261628da4 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-21 14:59:49 +02:00
1e39f48235 Merge pull request #2917 from sbwalker/dev
fix null handling in stylesheet logic
2023-06-21 08:47:39 -04:00
05b5d9da9b fix null handling in stylesheet logic 2023-06-21 08:47:25 -04:00
3491dde94a Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-21 14:46:44 +02:00
db2aa920e2 Merge pull request #2916 from sbwalker/dev
fix #2912 - move JavaScript handling from ThemeBuilder to component OnAfterRenderAsync
2023-06-21 08:32:11 -04:00
8067b2e634 fix #2912 - move JavaScript handling from ThemeBuilder to component OnAfterRenderAsync 2023-06-21 08:31:51 -04:00
273c6d7dee Merge branch 'oqtane:dev' into dev 2023-06-21 10:21:30 +10:00
4d0149a4a0 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-20 14:53:57 +02:00
d9f3db5bf3 Merge pull request #2914 from sbwalker/dev
integrate old logic for managing stylesheets using JS Interop
2023-06-20 08:52:18 -04:00
c8a679ecce integrate old logic for managing stylesheets using JS Interop 2023-06-20 08:52:02 -04:00
3abe47ae9e [ENHANCE] - Added Module to NameSpace [Owner].Module.[Module]
[ENHANCE] - Added Module to NameSpace [Owner].Module.[Module]
2023-06-20 16:58:30 +10:00
725a8dc237 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-20 07:50:00 +02:00
fcb05a7834 Merge pull request #2908 from sbwalker/dev
fix parent page selection in add page and fix navigation on save/cancel
2023-06-19 16:22:57 -04:00
02e22df4d5 fix parent page selection in add page and fix navigation on save/cancel 2023-06-19 16:22:49 -04:00
694c0af146 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-19 21:52:12 +02:00
8bfca2f10a Merge pull request #2905 from sbwalker/dev
more optimizations of Head component
2023-06-19 15:11:07 -04:00
6bea9a087c more optimizations of Head component 2023-06-19 15:10:58 -04:00
3f4218f75f Merge pull request #2904 from sbwalker/dev
fix #2900 - theme deletion and fallback
2023-06-19 12:29:51 -04:00
3849f59126 fix #2900 - theme deletion and fallback 2023-06-19 12:29:43 -04:00
239b12cbb0 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-19 17:03:37 +02:00
8ccee87378 Merge pull request #2898 from vnetonline/dev
[ENHANCE] - Added Theme to Name Space [Owner].Theme.[Theme]
2023-06-19 10:25:01 -04:00
9e518ffc4c Merge pull request #2903 from sbwalker/dev
add defensive logic
2023-06-19 10:11:32 -04:00
e3233fd19f add defensive logic 2023-06-19 10:11:24 -04:00
d3af9b0f14 Merge pull request #2902 from sbwalker/dev
optimize head component rendering
2023-06-19 09:01:53 -04:00
08daca848b optimize head component rendering 2023-06-19 09:01:40 -04:00
c5da8e3b10 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-19 10:32:47 +02:00
3b0ffde1fb [ENHANCE] - Added Theme to Name Space [Owner].Theme.[Theme]
changes all template files to conform to  [Owner].Theme.[Theme]
2023-06-16 14:52:13 +10:00
6ca97e8d5d Merge pull request #2897 from sbwalker/dev
allow selection of site template during installation
2023-06-15 19:10:52 -04:00
34a727b435 allow selection of site template during installation 2023-06-15 19:10:37 -04:00
6a9c865fec Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-14 15:52:56 +02:00
b1b9100600 Merge pull request #2896 from sbwalker/dev
create AppendHeadContent method to consolidate logic
2023-06-14 09:37:48 -04:00
bda0943d58 create AppendHeadContent method to consolidate logic 2023-06-14 09:37:34 -04:00
ad6db71f7e Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-14 15:11:40 +02:00
b21d202c7f Merge pull request #2895 from vnetonline/dev 2023-06-14 07:32:11 -04:00
3ea8ea1e3b Fixing the Library needed for declaring new way of theme resources 2023-06-14 19:13:04 +10:00
ee897a9973 Fixed targeting .NET 7.0 in ThemeController and ModuleDefinitionController missed in #2888 and #2890 2023-06-14 16:51:23 +10:00
bb9ea3f60f Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-14 06:35:28 +02:00
5f8cfb9977 Merge pull request #2893 from sbwalker/dev
update Installer to use new method for including CSS
2023-06-13 17:26:23 -04:00
d7928a70bf update Installer to use new method for including CSS 2023-06-13 17:26:07 -04:00
93568af5a9 Merge pull request #2892 from sbwalker/dev
upgrade external theme template to Bootstrap 5.3
2023-06-13 17:14:13 -04:00
9e86d97253 upgrade external theme template to Bootstrap 5.3 2023-06-13 17:13:59 -04:00
84bac0c462 Merge pull request #2891 from sbwalker/dev
upgrade to Bootstrap 5.3
2023-06-13 17:10:03 -04:00
3c18a258ff upgrade to Bootstrap 5.3 2023-06-13 17:09:49 -04:00
9759c68d58 Merge pull request #2890 from sbwalker/dev
upgrade external theme template to .NET 7
2023-06-13 14:16:58 -04:00
f57a190405 upgrade external theme template to .NET 7 2023-06-13 14:16:43 -04:00
d2eb203aa1 Merge pull request #2889 from sbwalker/dev
fix regression issue from #2852
2023-06-13 14:00:24 -04:00
dd2c2dbe61 fix regression issue from #2852 2023-06-13 14:00:09 -04:00
2529592b66 Merge pull request #2888 from sbwalker/dev
upgraded external module template to .NET 7
2023-06-13 13:10:41 -04:00
f248d2fe4e upgraded external module template to .NET 7 2023-06-13 13:10:20 -04:00
875bcd8ce2 Merge pull request #2885 from sbwalker/dev
handle type for favicon and improve helptext
2023-06-12 12:31:09 -04:00
726f9375e1 handle type for favicon and improve helptext 2023-06-12 12:30:56 -04:00
0c2e200245 Merge pull request #2884 from sbwalker/dev
allow PNG and GIF for favicon specification
2023-06-12 12:00:42 -04:00
a10db462eb allow PNG and GIF for favicon specification 2023-06-12 12:00:25 -04:00
6df28d8171 Merge pull request #2883 from vnetonline/dev
Made the default SQL Server and App Service provisioned Basic Tier
2023-06-12 09:56:34 -04:00
8367f15638 Made the default SQL Server and App Service provisioned Basic Tier 2023-06-12 23:51:36 +10:00
69456d3569 Merge pull request #2881 from sbwalker/dev
move logic for populating theme control names
2023-06-10 13:35:44 -04:00
3b644338bc move logic for populating theme control names 2023-06-10 13:35:22 -04:00
02e29aa22f Merge pull request #2880 from sbwalker/dev
fix for child pages in templates
2023-06-10 10:10:59 -04:00
88c489a585 fix for child pages in templates 2023-06-10 10:10:34 -04:00
706e73210d Merge pull request #2879 from sbwalker/dev
fix page template logic on install
2023-06-10 09:18:13 -04:00
d52809c914 fix page template logic on install 2023-06-10 09:17:52 -04:00
6058286577 Update README.md 2023-06-09 12:37:28 -04:00
a271f24157 Merge pull request #2878 from sbwalker/dev
change project tagline
2023-06-09 12:36:27 -04:00
2299375aaa change project tagline 2023-06-09 12:36:15 -04:00
c3290526a4 Merge pull request #2877 from sbwalker/dev
improvements to page template processing in cases where a page parent and name is specified without a path
2023-06-09 12:33:27 -04:00
a9d871e9af improvements to page template processing in cases where a page parent and name is specified without a path 2023-06-09 12:33:07 -04:00
5eb25796ca Merge pull request #2876 from sbwalker/dev
order theme controls and container comtrols in alphabetical order based on name
2023-06-09 08:50:03 -04:00
5d650bd276 order theme controls and container comtrols in alphabetical order based on name 2023-06-09 08:49:41 -04:00
b6be519537 Merge pull request #2875 from sbwalker/dev
add ScrollToPageTop method to ThemeBase
2023-06-08 08:39:33 -04:00
160477d612 add ScrollToPageTop method to ThemeBase 2023-06-08 08:39:20 -04:00
4a89403c37 Merge remote-tracking branch 'origin/dev' into dev 2023-06-08 08:32:54 +02:00
e54cfa629e Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-08 08:32:33 +02:00
b2bc09be16 Merge pull request #2873 from sbwalker/dev
fix to allow Theme Settings to be supported in Page Management
2023-06-07 10:50:39 -04:00
2f5d1cebb0 fix to allow Theme Settings to be supported in Page Management 2023-06-07 10:50:25 -04:00
e4d144651e Merge pull request #2872 from sbwalker/dev
fixed page setting cleanup on delete and centralized module delete logic within PageModuleRepository
2023-06-06 09:11:28 -04:00
ce56dfc239 fixed page setting cleanup on delete and centralized module delete logic within PageModuleRepository 2023-06-06 09:11:08 -04:00
ad4f8fd7a0 Merge pull request #2871 from leigh-pointer/ExtModDescription
Fix for External Module Description not updated
2023-06-06 08:20:48 -04:00
c4070cb603 External Module Description not updated
Fix for External Module Description not updated #2870
2023-06-06 12:26:39 +02:00
75c9322f75 Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-06 07:28:06 +02:00
602fd7d658 Merge pull request #2869 from sbwalker/dev
fix JavaScript injection issue on first render for Resources defined in IModule and ITheme
2023-06-05 22:00:27 -04:00
818fd2fb43 fix JavaScript injection issue on first render for Resources defined in IModule and ITheme 2023-06-05 22:00:02 -04:00
e4912fcb09 Merge pull request #2868 from sbwalker/dev
Head root component integration in .NET MAUI
2023-06-05 14:51:06 -04:00
3692ec49c1 Head root component integration in .NET MAUI 2023-06-05 14:50:54 -04:00
9a1ea3f375 Merge pull request #2867 from sbwalker/dev
ability for non-administrators to edit page settings
2023-06-05 14:33:17 -04:00
10a754642a ability for non-administrators to edit page settings 2023-06-05 14:33:05 -04:00
bc9de5d094 Merge pull request #2865 from sbwalker/dev
use ReturnUrl for Page Management invoked by Control Panel
2023-06-05 09:42:54 -04:00
9f93476167 use ReturnUrl for Page Management invoked by Control Panel 2023-06-05 09:42:33 -04:00
f61492d353 Merge pull request #2864 from sbwalker/dev
fix personalization redirect
2023-06-05 09:30:29 -04:00
50cf67546b fix personalization redirect 2023-06-05 09:30:17 -04:00
41d83ec421 Merge pull request #2863 from sbwalker/dev
improvements for personalized pages
2023-06-05 08:22:47 -04:00
cc9377b37d improvements for personalized pages 2023-06-05 08:22:29 -04:00
7f986d5f60 Merge pull request #2862 from sbwalker/dev
fix page add/edit path
2023-06-02 15:24:31 -04:00
d272bf8a29 fix page add/edit path 2023-06-02 15:24:14 -04:00
ba5260f5b1 Merge pull request #2861 from sbwalker/dev
include methods for dynamic scenarios
2023-06-02 08:34:23 -04:00
40c788fc33 include methods for dynamic scenarios 2023-06-02 08:34:05 -04:00
7eca15d50f Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-02 10:42:16 +02:00
8f147e7de2 Merge pull request #2859 from sbwalker/dev
make page path and moduile title lookups case insensitive
2023-06-01 14:41:32 -04:00
85358c7dd4 make page path and moduile title lookups case insensitive 2023-06-01 14:40:59 -04:00
87c7eafb87 Merge pull request #2858 from sbwalker/dev
use constant rather than magic string
2023-06-01 12:41:14 -04:00
613f4848da use constant rather than magic string 2023-06-01 12:41:03 -04:00
3cb06e4819 Update README.md 2023-06-01 12:22:42 -04:00
811701ef7f Merge pull request #2856 from sbwalker/dev
fix log message spelling
2023-06-01 12:16:53 -04:00
c14dc67d8a fix log message spelling 2023-06-01 12:16:41 -04:00
867767f009 Merge pull request #2855 from sbwalker/dev
ensure PageModule Order is updated
2023-06-01 11:29:43 -04:00
640216d076 ensure PageModule Order is updated 2023-06-01 11:29:31 -04:00
3a1f378e2d Merge remote-tracking branch 'oqtane/dev' into dev 2023-06-01 16:58:00 +02:00
6b7c61f228 Merge pull request #2853 from sbwalker/dev
make AliasName explicit
2023-06-01 10:37:53 -04:00
28ba2c00fc make AliasName explicit 2023-06-01 10:37:39 -04:00
b56daae321 Merge pull request #2852 from sbwalker/dev
optimize pane rendering, preserve querystring parameters in edit mode , relocate anchor tags to ensure they are always injected, add ability to determine if navigation is internal
2023-06-01 08:44:30 -04:00
b57450398c optimize pane rendering, preserve querystring parameters in edit mode, relocate anchor tags to ensure they are always injected, add ability to determine if navigation is internal 2023-06-01 08:44:07 -04:00
0c25cf0ec5 Merge remote-tracking branch 'oqtane/dev' into dev 2023-05-31 15:42:33 +02:00
c3d884e895 Merge pull request #2851 from sbwalker/dev
added IEventSubscriber amd EventDistributorHostedService to optimize event processing
2023-05-31 09:36:56 -04:00
7a21f96552 added IEventSubscriber amd EventDistributorHostedService to optimize event processing 2023-05-31 09:36:32 -04:00
55cfbb721b Merge remote-tracking branch 'oqtane/dev' into dev 2023-05-31 08:25:26 +02:00
4ccb4c636f Merge pull request #2849 from sbwalker/dev
ability to specify PageTemplates for modules
2023-05-30 15:52:42 -04:00
0d5c3a3a0c ability to specify PageTemplates for modules 2023-05-30 15:52:27 -04:00
6bc1507114 Merge remote-tracking branch 'oqtane/dev' into dev 2023-05-26 16:32:21 +02:00
b093cbff15 Merge pull request #2847 from sbwalker/dev
ensure consistent admin dashboard styling
2023-05-26 08:54:58 -04:00
30cb8ec9c3 ensure consistent admin dashboard styling 2023-05-26 08:54:47 -04:00
d5e51d6c38 Merge pull request #2846 from sbwalker/dev
clarify scroll method name
2023-05-26 07:41:59 -04:00
6f0a6c7f69 clarify scroll method name 2023-05-26 07:41:49 -04:00
549d71073e Merge pull request #2845 from sbwalker/dev
add module base class method for ScrollToTop
2023-05-26 07:39:22 -04:00
4ad5522f9e add module base class method for ScrollToTop 2023-05-26 07:39:06 -04:00
2145c1a48c Merge pull request #2844 from sbwalker/dev
utilize new Resources capability in default module/theme
2023-05-25 17:14:14 -04:00
0f093b1238 utilize new Resources capability in default module/theme 2023-05-25 17:14:00 -04:00
1aa3fd7056 Merge pull request #2843 from sbwalker/dev
added validation support for user profile fields
2023-05-25 16:16:28 -04:00
cc4c47c3ee added validation support for user profile fields 2023-05-25 16:16:16 -04:00
6e3f3fac9d Merge pull request #2842 from sbwalker/dev
added ability to disable SMTP and set IsBodyHtml by default
2023-05-25 15:19:20 -04:00
261adefbc7 added ability to disable SMTP and set IsBodyHtml by default 2023-05-25 15:19:05 -04:00
7eafcfd7ad Merge pull request #2841 from sbwalker/dev
add support for Job and Theme settings in API
2023-05-25 15:00:44 -04:00
6183d6a22e add support for Job and Theme settings in API 2023-05-25 15:00:30 -04:00
51d42692ba Merge pull request #2840 from sbwalker/dev
fix site provisioning issue for host module definitions
2023-05-25 14:56:04 -04:00
18a9c059f4 fix site provisioning issue for host module definitions 2023-05-25 14:55:46 -04:00
50a13ffd6d Update README.md 2023-05-25 13:02:03 -04:00
b360944742 Merge pull request #2839 from sbwalker/dev
add ability to modify Theme Name
2023-05-25 12:57:02 -04:00
59d1a47846 add ability to modify Theme Name 2023-05-25 12:56:49 -04:00
22969d8daa Merge pull request #2838 from sbwalker/dev
optimize client assembly download service, add support for site level scripts
2023-05-25 12:32:39 -04:00
95ba87945b optimize client assembly download service, add support for site level scripts 2023-05-25 12:32:21 -04:00
63aeee8b22 Merge remote-tracking branch 'oqtane/dev' into dev 2023-05-25 13:14:52 +02:00
da11a86f99 Merge pull request #2837 from sbwalker/dev
ability to specify if a theme is enabled for a site
2023-05-24 13:09:28 -04:00
98c2f012ee ability to specify if a theme is enabled for a site 2023-05-24 13:09:10 -04:00
386c6144f8 Merge pull request #2835 from sbwalker/dev
use SiteKey as a cache key for multi-tenancy
2023-05-24 10:29:57 -04:00
666f9c2db9 use SiteKey as a cache key for multi-tenancy 2023-05-24 10:29:45 -04:00
f7c49588fb Merge pull request #2834 from sbwalker/dev
ability to specify if a module definition is enabled for a site
2023-05-24 09:40:20 -04:00
c0e6f06a5c ability to specify if a module definition is enabled for a site 2023-05-24 09:40:05 -04:00
6668c8ff66 Merge remote-tracking branch 'oqtane/dev' into dev 2023-05-23 17:19:25 +02:00
31907f2d16 Merge pull request #2833 from sbwalker/dev
add null check
2023-05-23 11:16:21 -04:00
452d0af8c9 add null check 2023-05-23 11:16:07 -04:00
c5a28f3601 Merge remote-tracking branch 'origin/dev' into dev 2023-05-23 15:55:00 +02:00
d13f26dc7e Merge pull request #2832 from sbwalker/dev
format head content, remove scripts, and filter duplicate elements
2023-05-23 09:12:21 -04:00
03374483e4 format head content, remove scripts, and filter duplicate elements 2023-05-23 09:12:03 -04:00
b1c0a865a0 Merge pull request #2831 from sbwalker/dev
utilize ResourceLocation consistently
2023-05-23 08:08:31 -04:00
7b0799a6f6 utilize ResourceLocation consistently 2023-05-23 08:08:16 -04:00
0de2976933 Merge pull request #2830 from sbwalker/dev
add support for body content
2023-05-22 15:02:49 -04:00
20c7bf3c48 add support for body content 2023-05-22 15:02:36 -04:00
55e090ba2a Merge pull request #2829 from sbwalker/dev
changes to support page level scripts, ability to detect prerendering
2023-05-22 13:57:06 -04:00
ded326c822 changes to support page level scripts, ability to detect prerendering 2023-05-22 13:56:48 -04:00
1ee9c2cf0e Merge pull request #2828 from sbwalker/dev
ability to specify Resources in IModule and ITheme interfaces,, fixed module settings error for personalized pages
2023-05-19 18:08:40 -04:00
e41d9008b3 ability to specify Resources in IModule and ITheme interfaces,, fixed module settings for personalized pages 2023-05-19 18:08:15 -04:00
1ec24251a2 Merge pull request #2827 from sbwalker/dev
remove unused local reference to ThemeType
2023-05-19 11:59:06 -04:00
2be48c3847 remove unused local reference to ThemeType 2023-05-19 11:58:52 -04:00
2137b79f6d Merge pull request #2824 from sbwalker/dev
optimize JavaScript handling
2023-05-18 14:36:19 -04:00
0b8086bd36 optimize JavaScript handling 2023-05-18 14:36:06 -04:00
d16659890c Merge pull request #2823 from sbwalker/dev
add support for type attribute in JSInterop IncludeScript
2023-05-18 09:36:23 -04:00
076d150f72 add support for type attribute in JSInterop IncludeScript 2023-05-18 09:36:09 -04:00
62deffd017 Merge pull request #2822 from sbwalker/dev
move PWA elements back to _Host
2023-05-18 08:37:36 -04:00
f1ec70ff14 move PWA elements back to _Host 2023-05-18 08:37:21 -04:00
af27c763e0 Merge pull request #2821 from sbwalker/dev
handle id attribute automatically for headcontent inline scripts
2023-05-18 08:27:07 -04:00
7336417634 handle id attribute automatically for headcontent inline scripts 2023-05-18 08:26:51 -04:00
cde46c0889 Merge pull request #2820 from sbwalker/dev
allow HeadContent to support script tags
2023-05-17 17:13:25 -04:00
5da4dadc31 allow HeadContent to support script tags 2023-05-17 17:13:08 -04:00
e32ca089ec Merge pull request #2817 from sbwalker/dev
added HeadContent property to Site and replaced Meta property on Page with HeadContent property.
2023-05-16 16:23:34 -04:00
8d2f644177 added HeadContent property to Site and replaced Meta property on Page with HeadContent property. 2023-05-16 16:23:07 -04:00
a003010be5 Merge pull request #2815 from sbwalker/dev
migrate CSS references and remove JS Interop methods
2023-05-16 09:09:32 -04:00
89ada83012 migrate CSS references and remove JS Interop methods 2023-05-16 09:09:18 -04:00
65fbf6926c Merge pull request #2814 from sbwalker/dev
migrate PWA script injection
2023-05-16 08:01:00 -04:00
5e652364c9 migrate PWA script injection 2023-05-16 08:00:48 -04:00
8b7c3d6cfa Merge pull request #2813 from sbwalker/dev
relocate favicon rendering
2023-05-16 07:42:06 -04:00
3b214a0105 relocate favicon rendering 2023-05-16 07:41:50 -04:00
c0c4073bc4 Merge pull request #2811 from sbwalker/dev
ability to add arbitrary content to head and body during client and server rendering
2023-05-15 16:43:40 -04:00
dbe7324c7f ability to add arbitrary content to head and body during client and server rendering 2023-05-15 16:43:22 -04:00
9316255e87 Merge pull request #2809 from sbwalker/dev
added AboutAssets.txt to Maui project and changed assembly references to Oqtane 4.0.0
2023-05-15 13:21:59 -04:00
7f7dff7019 added AboutAssets.txt to Maui project and changed assembly references to Oqtane 4.0.0 2023-05-15 13:21:30 -04:00
961e004a49 Merge pull request #2808 from sbwalker/dev
remove unneccesary package reference to Microsoft.AspNetCore.Mvc.ViewFeatures (not used and deprecated)
2023-05-15 12:58:43 -04:00
02c1e4fb65 remove unneccesary package reference to Microsoft.AspNetCore.Mvc.ViewFeatures (not used and deprecated) 2023-05-15 12:58:14 -04:00
bcbc2a6e95 Merge pull request #2807 from sbwalker/dev
initial changes to upgrade to .NET 7
2023-05-15 12:01:53 -04:00
0c749a126c initial changes to upgrade to .NET 7 2023-05-15 12:01:29 -04:00
3698ebad7c Merge pull request #2804 from sbwalker/dev
Modify the FilterModuleDefinition() method to return null if the object passed to the method is null instead of returning an initialized object
2023-05-11 17:11:02 -04:00
f59a5c90a5 Modify the FilterModuleDefinition() method to return null if the object passed to the method is null instead of returning an initialized object 2023-05-11 17:10:39 -04:00
36e4a69891 Merge pull request #2802 from chlupac/Backslash_fix
FileService backslash fix
2023-05-11 16:58:17 -04:00
19b560f7c1 Update README.md 2023-05-11 11:58:15 -04:00
0bec92e87e FileService backslash fix 2023-05-10 17:08:56 +02:00
a0d02f16cd Update README.md 2023-05-10 08:44:08 -04:00
4a0f655e1c Update README.md 2023-05-09 08:56:42 -04:00
91bf65292d Update README.md 2023-05-09 08:55:07 -04:00
a69d52d688 Merge pull request #2799 from sbwalker/dev
fix #2795 - remove protocol from alias names during add/update
2023-05-08 11:44:18 -04:00
79fe224cea fix #2795 - remove protocol from alias names during add/update 2023-05-08 11:43:59 -04:00
757d8e59a2 Merge pull request #2797 from thabaum/patch-25
Add missing settings excluded from Fix #2727
2023-05-08 07:29:38 -04:00
53ec1416b4 Add missing settings excluded from Fix #2727 2023-05-05 08:44:29 -07:00
e5add21612 Merge remote-tracking branch 'oqtane/dev' into dev 2023-05-04 09:15:58 +02:00
d1b52534de Update README.md 2023-05-03 15:46:38 -04:00
35edc053b5 Update README.md 2023-05-03 15:31:34 -04:00
2aa8d3be0f Update README.md 2023-05-03 15:20:55 -04:00
ec0a5377b3 Merge pull request #2784 from oqtane/master
Merge pull request #2783 from oqtane/dev
2023-05-03 14:59:27 -04:00
b876da069d Merge pull request #2783 from oqtane/dev
3.4.3 release
2023-05-03 14:59:09 -04:00
f06063d7eb Merge pull request #2782 from sbwalker/dev
fix #2763 - prevent module definitions from having duplicate names
2023-05-03 12:46:44 -04:00
c6ba4f4bee fix #2763 - prevent module definitions from having duplicate names 2023-05-03 12:46:29 -04:00
89da4ab2a1 Merge pull request #2781 from sbwalker/dev
add defensive logic in case list of pages is empty
2023-05-03 12:32:44 -04:00
99ac0a3cab add defensive logic in case list of pages is empty 2023-05-03 12:32:28 -04:00
a964c30705 Merge pull request #2776 from thabaum/patch-24
Updated Dashboard Index
2023-05-03 12:27:32 -04:00
2334d0297d Merge pull request #2780 from sbwalker/dev
fix #2777 - module rendering order within pane - moved default module ordering logic to server API for consistency and better performance
2023-05-03 12:26:17 -04:00
e444c6bcf0 fix #2777 - module rendering order within pane - moved default module ordering logic to server API for consistency and better performance 2023-05-03 12:25:52 -04:00
601582fc98 Update Index.razor 2023-05-03 08:19:24 -07:00
e939dbe24e Updated SecurityAccessLevel.Admin 2023-05-02 17:14:46 -07:00
143ad85fd5 Update README.md 2023-05-02 16:00:23 -04:00
20377e9789 Merge pull request #2775 from sbwalker/dev
prepare for 3.4.3 release
2023-05-02 15:54:49 -04:00
e4a24df7b4 prepare for 3.4.3 release 2023-05-02 15:54:36 -04:00
e88ca00658 Merge pull request #2770 from thabaum/patch-23
Remove Admin Page/Module Registered User View
2023-05-02 15:41:27 -04:00
2312c612d7 Merge pull request #2774 from sbwalker/dev
remove message items as these are handled by MessageType enum and UX styles
2023-05-02 15:14:34 -04:00
3aee52482d remove message items as these are handled by MessageType enum and UX styles 2023-05-02 15:14:17 -04:00
32248e0be6 Merge pull request #2745 from thabaum/patch-18
Add support for new language translation values for #2732.
2023-05-02 15:05:36 -04:00
6c18c320bd Merge pull request #2773 from sbwalker/dev
fixed compilation error and improved UTF8 support
2023-05-02 15:03:43 -04:00
e31f32e5aa fixed compilation error and improved UTF8 support 2023-05-02 15:03:26 -04:00
a856390566 Merge pull request #2772 from sbwalker/dev
improve module/theme/translation upload user experience to be consistent with download
2023-05-02 14:22:56 -04:00
64b8291487 improve module/theme/translation upload user experience to be consistent with download 2023-05-02 14:22:34 -04:00
2ebd1310c9 Merge pull request #2747 from thabaum/patch-19
Add content-type to sitemap: Fixes issues 2749 2764
2023-05-02 14:20:39 -04:00
09118fcb42 Updates lastmod date 2023-05-02 07:15:22 -07:00
5d5167abbc Merge pull request #2771 from vnetonline/dev
Invalid template when I try and use the Deploy to Azure button
2023-05-02 09:01:41 -04:00
bf43dea060 Merge branch 'oqtane:dev' into dev 2023-05-02 15:53:56 +10:00
6ebab830c5 Invalid template when I try and use the Deploy to Azure button
1. removed depends on connection strings
2. Moved sku outside properties for type Microsoft.Web/serverfarms and added various app plans
3. Removed worker size and replaced with instance capacity
4. Replaced database and sqlserver to use various editions
5. Updated ApiVersions for various types
2023-05-02 15:47:31 +10:00
9a9a78e0bd Remove Admin Page/Module Registered User View 2023-05-01 20:01:49 -07:00
418eff7d21 Merge pull request #2754 from pepsinio/dev
Add ability to use environment variables in order to set them as app settings in Azure
2023-05-01 16:14:47 -04:00
d39869ca8e Merge pull request #2759 from leigh-pointer/MenuHorizontal#2757
Fix for MenuHorizontal #2757
2023-05-01 16:14:16 -04:00
7829168ea7 Merge pull request #2768 from sbwalker/dev
fix #2761 - updating Module Definition name, description, category not invalidating cache
2023-05-01 15:38:46 -04:00
dd83e3ee67 fix #2761 - updating Module Definition name, description, category not invalidating cache 2023-05-01 15:38:18 -04:00
c3ac0e365d Removed PR comments 2023-04-27 16:32:48 -07:00
2986625605 Update content type to XML and include UTF-8 2023-04-27 16:22:35 -07:00
8beaeabf09 Include utf-8 encoding. 2023-04-27 16:21:14 -07:00
fa9b4b6112 Fix for MenuHorizontal #2757
Add the css class to MenuHorizontal to handle scrolling when hamburger Menu is in use.
2023-04-26 09:43:11 +02:00
a20c2aa996 Merge remote-tracking branch 'oqtane/dev' into dev 2023-04-26 09:29:31 +02:00
d81fbe4585 Fixed missing logic from PR 2023-04-19 10:18:27 -07:00
536c044139 Add environment settings needed for Azure deployment 2023-04-19 19:18:19 +02:00
376531195e Add environment settings needed for Azure deployment 2023-04-19 19:17:33 +02:00
abf4ff71d7 re-add missing settings 2023-04-19 10:13:31 -07:00
d25debcea3 cleanup using 2023-04-19 10:11:16 -07:00
948c186cb5 fixed formatting 2023-04-19 10:09:00 -07:00
ba27e70fe3 Removed unnecessary cache comments. 2023-04-19 10:05:43 -07:00
c93d2576af Updates content-type to "application/xml"
removes sitemap cache from previous commits.
2023-04-19 10:00:33 -07:00
e0b0156640 allow module and theme dependencies setting to include .dll file extension, added testmode config setting for validating list of assemblies sent to client 2023-04-19 08:48:52 -07:00
a3ca6a3071 Merge pull request #2753 from sbwalker/dev
allow module and theme dependencies setting to include .dll file extension, added testmode config setting for validating list of assemblies sent to client
2023-04-19 08:46:12 -07:00
b20157450b Add caching and content-type 2023-04-14 18:08:39 -07:00
7e4f0923d7 Add support for new language translation values. 2023-04-13 07:42:04 -07:00
f8c1fde358 Merge remote-tracking branch 'oqtane/dev' into dev 2023-04-13 13:27:33 +02:00
e0c2b2982f improvements to #2736 to support scenarios where module is not explicitly assigned to a page 2023-04-11 13:01:34 -04:00
9a231c28af Merge pull request #2742 from sbwalker/dev
improvements to #2736 to support scenarios where module is not explicitly assigned to a page
2023-04-11 12:59:33 -04:00
94a02b7bf9 add filter to exclude orphaned permissions 2023-04-11 10:35:23 -04:00
22c8fb411a Merge pull request #2741 from sbwalker/dev
add filter to exclude orphaned permissions
2023-04-11 10:33:09 -04:00
cf46210ff8 Merge pull request #2725 from thabaum/patch-17
Fixes null reference permissions issue: Fixes #2724
2023-04-11 10:22:08 -04:00
f32d988297 Merge pull request #2740 from sbwalker/dev
Routes with Module ID and no Action can be displayed on any page regardless of whether a PageModule record exists (ie. Admin Dashboard)
2023-04-11 10:19:28 -04:00
7fe4577158 Routes with Module ID and no Action can be displayed on any page regardless of whether a PageModule record exists (ie. Admin Dashboard) 2023-04-11 10:21:37 -04:00
8985dcb4c0 fix #2736 - UI not loading correct module instance in scenarios where a module exists on multiple pages 2023-04-10 08:37:35 -04:00
d346444c51 Merge pull request #2739 from sbwalker/dev
fix #2736 - UI not loading correct module instance in scenarios where a module exists on multiple pages
2023-04-10 08:35:42 -04:00
113658f3f7 Merge remote-tracking branch 'oqtane/dev' into dev 2023-04-05 18:32:26 +02:00
ef27a0d6b0 Merge pull request #2727 from leigh-pointer/ModulePassSettings
Fix for #2718 SiteMap functionality missing Settings
2023-04-05 10:53:09 -04:00
627158cb5d Merge pull request #2729 from leigh-pointer/PwdControlCancelFocus
Fix for #2728 Toggle Password button focus
2023-04-05 10:51:27 -04:00
648edcabba Merge pull request #2731 from sbwalker/dev
fix #2720 - module definition permissions not being created properly for new sites
2023-04-05 10:27:44 -04:00
0f34c6efc5 fix #2720 - module definition permissions not being created properly for new sites 2023-04-05 10:29:51 -04:00
cc3cc55269 consolidated package installation so that it always occurs during startup and added logging in case of errors 2023-04-05 10:26:21 -04:00
a503a9d5bc Merge pull request #2730 from sbwalker/dev
consolidated package installation so that it always occurs during startup and added logging in case of errors
2023-04-05 10:24:15 -04:00
789baf99ff Fix for #2728 Toggle Password button focus
Added  tabindex="-1" to the Button control so that focus is not received and passed the the following control.
2023-04-05 07:51:32 +02:00
036279a54c Fix for #2718 SiteMap functionality missing Settings
Added the Module Settings to the Module parameter passed in the GetUrls interface call,
2023-04-05 07:31:57 +02:00
481f18cf1c Fixes null reference permissions issue 2023-04-04 11:19:25 -07:00
c54a48865b Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-31 12:42:51 +02:00
2f1e386554 Update README.md 2023-03-29 14:13:27 -04:00
4c4e94dba0 Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-29 08:38:41 +02:00
ec142cbbe1 Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-24 10:42:07 +01:00
702ff964fe Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-14 16:52:08 +01:00
919439977c Merge remote-tracking branch 'origin/dev' into dev 2023-03-14 09:44:04 +01:00
a6478c5146 Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-13 08:11:31 +01:00
524c4ab5a1 Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-12 11:04:23 +01:00
3c795eec7c Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-11 22:41:21 +01:00
b9e352cbcb Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-10 16:42:44 +01:00
e23744a0d0 Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-09 19:04:26 +01:00
626528fa5e Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-08 19:08:55 +01:00
274cde1c21 Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-06 18:55:42 +01:00
4c8cb31a6f Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-06 05:54:29 +01:00
bea89be67b Merge remote-tracking branch 'origin/dev' into dev 2023-03-04 08:07:48 +01:00
4cb17de82a Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-02 06:40:17 +01:00
da3a4d5855 Merge remote-tracking branch 'oqtane/dev' into dev 2023-03-01 10:34:47 +01:00
b0c5d1e991 Merge remote-tracking branch 'oqtane/dev' into dev 2023-02-24 10:33:52 +01:00
231fb80c2c Merge remote-tracking branch 'oqtane/dev' into dev 2023-02-20 16:14:42 +01:00
c49af06e57 Merge remote-tracking branch 'oqtane/dev' into dev 2023-02-16 12:05:02 +01:00
79fc03bb0f Merge remote-tracking branch 'oqtane/dev' into dev 2023-01-04 21:27:31 +01:00
347 changed files with 11752 additions and 6644 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
Oqtane.Server/wwwroot/Modules/Templates/External/Package/*.sh eol=lf
Oqtane.Server/wwwroot/Themes/Templates/External/Package/*.sh eol=lf

View File

@ -1,6 +1,8 @@
@using Microsoft.AspNetCore.Http
@inject IInstallationService InstallationService
@inject IJSRuntime JSRuntime
@inject SiteState SiteState
@inject IServiceProvider ServiceProvider
@if (_initialized)
{
@ -30,35 +32,47 @@
}
@code {
[Parameter]
public string AntiForgeryToken { get; set; }
[Parameter]
public string AntiForgeryToken { get; set; }
[Parameter]
public string Runtime { get; set; }
[Parameter]
public string Runtime { get; set; }
[Parameter]
public string RenderMode { get; set; }
[Parameter]
public string RenderMode { get; set; }
[Parameter]
public int VisitorId { get; set; }
[Parameter]
public int VisitorId { get; set; }
[Parameter]
public string RemoteIPAddress { get; set; }
[Parameter]
public string RemoteIPAddress { get; set; }
[Parameter]
public string AuthorizationToken { get; set; }
[Parameter]
public string AuthorizationToken { get; set; }
private bool _initialized = false;
private string _display = "display: none;";
private Installation _installation = new Installation { Success = false, Message = "" };
private bool _initialized = false;
private string _display = "display: none;";
private Installation _installation = new Installation { Success = false, Message = "" };
private PageState PageState { get; set; }
private PageState PageState { get; set; }
protected override async Task OnParametersSetAsync()
{
SiteState.RemoteIPAddress = RemoteIPAddress;
SiteState.AntiForgeryToken = AntiForgeryToken;
SiteState.AuthorizationToken = AuthorizationToken;
private IHttpContextAccessor accessor;
protected override async Task OnParametersSetAsync()
{
SiteState.RemoteIPAddress = RemoteIPAddress;
SiteState.AntiForgeryToken = AntiForgeryToken;
SiteState.AuthorizationToken = AuthorizationToken;
accessor = (IHttpContextAccessor)ServiceProvider.GetService(typeof(IHttpContextAccessor));
if (accessor != null)
{
SiteState.IsPrerendering = !accessor.HttpContext.Response.HasStarted;
}
else
{
SiteState.IsPrerendering = true;
}
_installation = await InstallationService.IsInstalled();
if (_installation.Alias != null)
@ -72,6 +86,7 @@
{
if (firstRender)
{
// prevents flash on initial page load
_display = "";
StateHasChanged();
}

64
Oqtane.Client/Head.razor Normal file
View File

@ -0,0 +1,64 @@
@using System.ComponentModel
@using Oqtane.Shared
@inject SiteState SiteState
@if (!string.IsNullOrEmpty(_title))
{
@((MarkupString)_title)
}
@if (!string.IsNullOrEmpty(_content))
{
@((MarkupString)_content)
}
@code {
private string _title = "";
private string _content = "";
protected override void OnInitialized()
{
((INotifyPropertyChanged)SiteState.Properties).PropertyChanged += PropertyChanged;
}
private void PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "PageTitle":
var title = "\n<title>" + SiteState.Properties.PageTitle + "</title>";
if (title != _title)
{
_title = title;
StateHasChanged();
}
break;
case "HeadContent":
var content = RemoveScripts(SiteState.Properties.HeadContent) + "\n";
if (content != _content)
{
_content = content;
StateHasChanged();
}
break;
}
}
private string RemoveScripts(string headcontent)
{
if (!string.IsNullOrEmpty(headcontent))
{
var index = headcontent.IndexOf("<script");
while (index >= 0)
{
headcontent = headcontent.Remove(index, headcontent.IndexOf("</script>") + 9 - index);
index = headcontent.IndexOf("<script");
}
}
return headcontent;
}
public void Dispose()
{
((INotifyPropertyChanged)SiteState.Properties).PropertyChanged -= PropertyChanged;
}
}

View File

@ -0,0 +1,14 @@
namespace Oqtane
{
/// <summary>
/// Dummy class used to collect shared resource strings for this application
/// </summary>
/// <remarks>
/// This class is mostly used with IStringLocalizer and IHtmlLocalizer interfaces.
/// The class must reside at the project root.
/// </remarks>
public class IconResources
{
}
}

View File

@ -1,5 +1,6 @@
@namespace Oqtane.Installer.Controls
@implements Oqtane.Interfaces.IDatabaseConfigControl
@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>
@ -28,7 +29,10 @@
<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" autocomplete="new-password" />
<div class="input-group">
<input id="pwd" type="@_passwordType" class="form-control" @bind="@_pwd" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglePassword</button>
</div>
</div>
</div>
@ -38,6 +42,13 @@
private string _database = "Oqtane-" + DateTime.UtcNow.ToString("yyyyMMddHHmm");
private string _uid = String.Empty;
private string _pwd = String.Empty;
private string _passwordType = "password";
private string _togglePassword = string.Empty;
protected override void OnInitialized()
{
_togglePassword = SharedLocalizer["ShowPassword"];
}
public string GetConnectionString()
{
@ -55,4 +66,18 @@
return connectionString;
}
private void TogglePassword()
{
if (_passwordType == "password")
{
_passwordType = "text";
_togglePassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordType = "password";
_togglePassword = SharedLocalizer["ShowPassword"];
}
}
}

View File

@ -1,6 +1,7 @@
@namespace Oqtane.Installer.Controls
@implements Oqtane.Interfaces.IDatabaseConfigControl
@inject IStringLocalizer<PostgreSQLConfig> Localizer
@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>
@ -40,7 +41,10 @@
<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" autocomplete="new-password" />
<div class="input-group">
<input id="pwd" type="@_passwordType" class="form-control" @bind="@_pwd" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglePassword</button>
</div>
</div>
</div>
}
@ -52,6 +56,13 @@
private string _security = "integrated";
private string _uid = String.Empty;
private string _pwd = String.Empty;
private string _passwordType = "password";
private string _togglePassword = string.Empty;
protected override void OnInitialized()
{
_togglePassword = SharedLocalizer["ShowPassword"];
}
public string GetConnectionString()
{
@ -80,4 +91,18 @@
return connectionString;
}
private void TogglePassword()
{
if (_passwordType == "password")
{
_passwordType = "text";
_togglePassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordType = "password";
_togglePassword = SharedLocalizer["ShowPassword"];
}
}
}

View File

@ -35,7 +35,10 @@
<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" autocomplete="new-password" />
<div class="input-group">
<input id="pwd" type="@_passwordType" class="form-control" @bind="@_pwd" autocomplete="new-password" />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglePassword</button>
</div>
</div>
</div>
}
@ -67,9 +70,16 @@
private string _security = "integrated";
private string _uid = String.Empty;
private string _pwd = String.Empty;
private string _passwordType = "password";
private string _togglePassword = string.Empty;
private string _encryption = "false";
private string _trustservercertificate = "false";
protected override void OnInitialized()
{
_togglePassword = SharedLocalizer["ShowPassword"];
}
public string GetConnectionString()
{
var connectionString = String.Empty;
@ -92,4 +102,18 @@
return connectionString;
}
private void TogglePassword()
{
if (_passwordType == "password")
{
_passwordType = "text";
_togglePassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordType = "password";
_togglePassword = SharedLocalizer["ShowPassword"];
}
}
}

View File

@ -5,15 +5,17 @@
@inject ISiteService SiteService
@inject IUserService UserService
@inject IDatabaseService DatabaseService
@inject ISiteTemplateService SiteTemplateService
@inject IJSRuntime JSRuntime
@inject IStringLocalizer<Installer> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@inject SiteState SiteState
<div class="container">
<div class="row">
<div class="mx-auto text-center">
<img src="oqtane-black.png" />
<div style="font-weight: bold">@SharedLocalizer["Version"] @Constants.Version</div>
<div style="font-weight: bold">@SharedLocalizer["Version"] @Constants.Version (.NET 7)</div>
</div>
</div>
<hr class="app-rule" />
@ -61,7 +63,7 @@
</div>
</div>
}
</div>
</div>
</div>
<div class="col text-center">
<h2>@Localizer["ApplicationAdmin"]</h2><br />
@ -77,7 +79,7 @@
<div class="col-sm-9">
<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>
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglePassword</button>
</div>
</div>
</div>
@ -86,7 +88,7 @@
<div class="col-sm-9">
<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>
<button type="button" class="btn btn-secondary" @onclick="@ToggleConfirmPassword" tabindex="-1">@_toggleConfirmPassword</button>
</div>
</div>
</div>
@ -96,6 +98,20 @@
<input type="text" class="form-control" @bind="@_hostEmail" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="template" HelpText="Select a site template" ResourceKey="Template">Template:</Label>
<div class="col-sm-9">
@if (_templates != null)
{
<select id="template" class="form-select" @bind="@_template" required>
@foreach (var template in _templates)
{
<option value="@template.TypeName">@template.Name</option>
}
</select>
}
</div>
</div>
</div>
</div>
</div>
@ -103,7 +119,13 @@
<div class="row">
<div class="mx-auto text-center">
<button type="button" class="btn btn-success" @onclick="Install">@Localizer["InstallNow"]</button><br /><br />
<ModuleMessage Message="@_message" Type="MessageType.Error"></ModuleMessage>
@if (!string.IsNullOrEmpty(_message))
{
<div class="alert alert-danger alert-dismissible fade show mb-3" role="alert">
@((MarkupString)_message)
<button type="button" class="btn-close" aria-label="Close" @onclick="DismissModal"></button>
</div>
}
</div>
<div class="app-progress-indicator" style="@_loadingDisplay"></div>
</div>
@ -115,29 +137,35 @@
</div>
@code {
private List<Database> _databases;
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<Database> _databases;
private string _databaseName;
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;
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 List<SiteTemplate> _templates;
private string _template = Constants.DefaultSiteTemplate;
private bool _register = true;
private string _message = string.Empty;
private string _loadingDisplay = "display: none;";
protected override async Task OnInitializedAsync()
{
_togglePassword = SharedLocalizer["ShowPassword"];
// include CSS
var content = "<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css\" integrity=\"sha512-t4GWSVZO1eC8BM339Xd7Uphw5s17a86tIZIj8qRxhnKub6WoyhnrxeCIMeAqBPgdZGlCcG2PrZjMc+Wr78+5Xg==\" crossorigin=\"anonymous\" type=\"text/css\"/>";
SiteState.AppendHeadContent(content);
_togglePassword = SharedLocalizer["ShowPassword"];
_toggleConfirmPassword = SharedLocalizer["ShowPassword"];
_databases = await DatabaseService.GetDatabasesAsync();
@ -150,7 +178,9 @@
_databaseName = "LocalDB";
}
LoadDatabaseConfigComponent();
}
_templates = await SiteTemplateService.GetSiteTemplatesAsync();
}
private void DatabaseChanged(ChangeEventArgs eventArgs)
{
@ -185,9 +215,9 @@
{
if (firstRender)
{
// include JavaScript
var interop = new Interop(JSRuntime);
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");
await interop.IncludeScript("", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/js/bootstrap.bundle.min.js", "sha512-VK2zcvntEufaimc+efOYi622VN5ZacdnufnmX7zIhCPmjhKnOi9ZDMtg1/ug5l183f19gG1/cBstPO4D8N/Img==", "anonymous", "", "head");
}
}
@ -229,7 +259,8 @@
TenantName = TenantNames.Master,
IsNewTenant = true,
SiteName = Constants.DefaultSite,
Register = _register
Register = _register,
SiteTemplate = _template
};
var installation = await InstallationService.Install(config);
@ -291,4 +322,9 @@
_showConnectionString = !_showConnectionString;
}
private void DismissModal()
{
_message = "";
StateHasChanged();
}
}

View File

@ -4,32 +4,35 @@
@inject IUserService UserService
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="row">
@foreach (var p in _pages)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
@if (_pages != null)
{
<div class="row">
@foreach (var p in _pages)
{
string url = NavigateUrl(p.Path);
<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>
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{
string url = NavigateUrl(p.Path);
<div class="col-md-2 mx-auto text-center mb-3">
<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>
}
}
}
</div>
</div>
}
@code {
private List<Page> _pages;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
protected override void OnInitialized()
{
var admin = PageState.Pages.FirstOrDefault(item => item.Path == "admin");
if (admin != null)
{
_pages = PageState.Pages.Where(item => item.ParentId == admin?.PageId).ToList();
_pages = PageState.Pages.Where(item => item.ParentId == admin.PageId).ToList();
}
}
}

View File

@ -48,7 +48,7 @@
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="imagesizes" HelpText="Enter a list of image sizes which can be generated dynamically from uploaded images (ie. 200x200,x200,200x)" ResourceKey="ImageSizes">Image Sizes: </Label>
<Label Class="col-sm-3" For="imagesizes" HelpText="Enter a list of image sizes which can be generated dynamically from uploaded images (ie. 200x200,400x400). Use * to indicate the folder supports all image sizes." ResourceKey="ImageSizes">Image Sizes: </Label>
<div class="col-sm-9">
<input id="imagesizes" class="form-control" @bind="@_imagesizes" maxlength="512" />
</div>
@ -110,7 +110,7 @@
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override string Title => "Folder Management";
public override string Title => "Folder Management";
protected override async Task OnInitializedAsync()
{

View File

@ -56,7 +56,7 @@
<input id="starting" type="date" class="form-control" @bind="@_startDate" />
</div>
<div class="col">
<input id="starting" type="text" class="form-control" placeholder="hh:mm" @bind="@_startTime" />
<input id="starting" type="time" class="form-control" placeholder="hh:mm" @bind="@_startTime" />
</div>
</div>
</div>
@ -69,7 +69,7 @@
<input id="ending" type="date" class="form-control" @bind="@_endDate" />
</div>
<div class="col">
<input id="ending" type="text" class="form-control" placeholder="hh:mm" @bind="@_endTime" />
<input id="ending" type="time" class="form-control" placeholder="hh:mm" @bind="@_endTime" />
</div>
</div>
</div>
@ -82,7 +82,7 @@
<input id="next" type="date" class="form-control" @bind="@_nextDate" />
</div>
<div class="col">
<input id="next" type="text" class="form-control" placeholder="hh:mm" @bind="@_nextTime" />
<input id="next" type="time" class="form-control" placeholder="hh:mm" @bind="@_nextTime" />
</div>
</div>
</div>
@ -97,22 +97,22 @@
</form>
@code {
private ElementReference form;
private bool validated = false;
private int _jobId;
private string _name = string.Empty;
private string _jobType = string.Empty;
private string _isEnabled = "True";
private string _interval = string.Empty;
private string _frequency = string.Empty;
private DateTime? _startDate = null;
private string _startTime = string.Empty;
private DateTime? _endDate = null;
private string _endTime = string.Empty;
private string _retentionHistory = string.Empty;
private DateTime? _nextDate = null;
private string _nextTime = string.Empty;
private string createdby;
private ElementReference form;
private bool validated = false;
private int _jobId;
private string _name = string.Empty;
private string _jobType = string.Empty;
private string _isEnabled = "True";
private string _interval = string.Empty;
private string _frequency = string.Empty;
private DateTime? _startDate = null;
private DateTime? _startTime = null;
private DateTime? _endDate = null;
private DateTime? _endTime = null;
private string _retentionHistory = string.Empty;
private DateTime? _nextDate = null;
private DateTime? _nextTime = null;
private string createdby;
private DateTime createdon;
private string modifiedby;
private DateTime modifiedon;
@ -132,11 +132,14 @@
_isEnabled = job.IsEnabled.ToString();
_interval = job.Interval.ToString();
_frequency = job.Frequency;
(_startDate, _startTime) = Utilities.UtcAsLocalDateAndTime(job.StartDate);
(_endDate, _endTime) = Utilities.UtcAsLocalDateAndTime(job.EndDate);
_retentionHistory = job.RetentionHistory.ToString();
(_nextDate, _nextTime) = Utilities.UtcAsLocalDateAndTime(job.NextExecution);
createdby = job.CreatedBy;
_startDate = Utilities.UtcAsLocalDate(job.StartDate);
_startTime = Utilities.UtcAsLocalDateTime(job.StartDate);
_endDate = Utilities.UtcAsLocalDate(job.EndDate);
_endTime = Utilities.UtcAsLocalDateTime(job.EndDate);
_retentionHistory = job.RetentionHistory.ToString();
_nextDate = Utilities.UtcAsLocalDate(job.NextExecution);
_nextTime = Utilities.UtcAsLocalDateTime(job.NextExecution);
createdby = job.CreatedBy;
createdon = job.CreatedOn;
modifiedby = job.ModifiedBy;
modifiedon = job.ModifiedOn;

View File

@ -11,7 +11,7 @@
else
{
<ActionLink Action="Log" Class="btn btn-secondary" Text="View Logs" ResourceKey="ViewJobs" />
<button type="button" class="btn btn-secondary" @onclick="(async () => await Refresh())">Refresh</button>
<button type="button" class="btn btn-secondary" @onclick="(async () => await Refresh())">@Localizer["Refresh.Text"]</button>
<br />
<br />

View File

@ -16,7 +16,7 @@
else
{
<TabStrip>
<TabPanel Name="Manage" ResourceKey="Manage">
<TabPanel Name="Manage" ResourceKey="Manage" Heading="Manage">
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
@ -45,16 +45,15 @@ else
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</form>
</TabPanel>
<TabPanel Name="Upload" ResourceKey="Upload" Security="SecurityAccessLevel.Host">
<TabPanel Name="Upload" ResourceKey="Upload" Security="SecurityAccessLevel.Host" Heading="Upload">
<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">Translation: </Label>
<Label Class="col-sm-3" HelpText="Upload one or more translations. Once they are uploaded click Install." ResourceKey="LanguageUpload">Translation: </Label>
<div class="col-sm-9">
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" />
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" OnUpload="OnUpload" />
</div>
</div>
</div>
<button type="button" class="btn btn-success" @onclick="InstallTranslations">@SharedLocalizer["Install"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</TabPanel>
</TabStrip>
@ -119,24 +118,11 @@ else
AddModuleMessage(Localizer["Error.Language.Add"], MessageType.Error);
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
private async Task InstallTranslations()
{
try
{
await PackageService.InstallPackagesAsync();
AddModuleMessage(string.Format(Localizer["Success.Language.Install"], NavigateUrl("admin/system")), MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Installing Translations");
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
private async Task SetCultureAsync(string culture)
{
@ -149,4 +135,9 @@ else
NavigationManager.NavigateTo(NavigationManager.Uri, true);
}
}
private void OnUpload()
{
AddModuleMessage(string.Format(Localizer["Success.Language.Download"], NavigateUrl("admin/system")), MessageType.Success);
}
}

View File

@ -22,7 +22,7 @@ else
<th>@Localizer["Default"]</th>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
<th style="width: 1px;">@Localizer["Translation"]</th>
<th style="width: 1px;">@Localizer["Translation"]</th>
<th style="width: 1px;">&nbsp;</th>
}
</Header>
@ -33,7 +33,7 @@ else
<td><TriStateCheckBox Value="@(context.IsDefault)" Disabled="true"></TriStateCheckBox></td>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
<td>@((string.IsNullOrEmpty(context.Version)) ? "---" : context.Version)</td>
<td>@((string.IsNullOrEmpty(context.Version)) ? "---" : context.Version)</td>
<td>
@{
var translation = TranslationAvailable(context.Code, context.Version);
@ -56,10 +56,6 @@ else
}
</Row>
</Pager>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && _install)
{
<button type="button" class="btn btn-success" @onclick="InstallTranslations">@SharedLocalizer["Install"]</button>
}
}
@if (_package != null)
@ -103,57 +99,64 @@ else
}
@code {
private List<Language> _languages;
private List<Package> _packages;
private Package _package;
private bool _install = false;
private List<Language> _languages;
private List<Package> _packages;
private Package _package;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnParametersSetAsync()
{
await GetLanguages();
protected override async Task OnParametersSetAsync()
{
await GetLanguages();
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_packages = await PackageService.GetPackagesAsync("translation");
}
}
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_packages = await PackageService.GetPackagesAsync("translation");
}
}
private async Task GetLanguages()
{
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId, Constants.ClientId);
}
private async Task GetLanguages()
{
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId, Constants.ClientId);
}
private async Task DeleteLanguage(Language language)
{
try
{
await LanguageService.DeleteLanguageAsync(language.LanguageId);
await logger.LogInformation("Language Deleted {Language}", language);
await GetLanguages();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting Language {Language} {Error}", language, ex.Message);
private async Task DeleteLanguage(Language language)
{
try
{
await LanguageService.DeleteLanguageAsync(language.LanguageId);
await logger.LogInformation("Language Deleted {Language}", language);
await GetLanguages();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting Language {Language} {Error}", language, ex.Message);
AddModuleMessage(Localizer["Error.Language.Delete"], MessageType.Error);
}
}
AddModuleMessage(Localizer["Error.Language.Delete"], MessageType.Error);
}
}
private Package TranslationAvailable(string code, string version)
{
return _packages?.FirstOrDefault(item => item.PackageId == (Constants.PackageId + "." + code));
}
private Package TranslationAvailable(string code, string version)
{
return _packages?.FirstOrDefault(item => item.PackageId == (Constants.PackageId + "." + code));
}
private async Task GetPackage(string code, string version)
{
try
{
_package = await PackageService.GetPackageAsync(Constants.PackageId + "." + code, version);
StateHasChanged();
}
private async Task GetPackage(string code, string version)
{
try
{
_package = await PackageService.GetPackageAsync(Constants.PackageId + "." + code, version, false);
if (_package != null)
{
StateHasChanged();
}
else
{
await logger.LogError("Error Getting Package {PackageId} {Version}", Constants.PackageId + "." + code, Constants.Version);
AddModuleMessage(Localizer["Error.Translation.Download"], MessageType.Error);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", Constants.PackageId + "." + code, Constants.Version);
@ -165,11 +168,10 @@ else
{
try
{
await PackageService.DownloadPackageAsync(_package.PackageId, _package.Version, Constants.PackagesFolder);
await PackageService.DownloadPackageAsync(_package.PackageId, _package.Version);
await logger.LogInformation("Language Package {Name} {Version} Downloaded Successfully", _package.PackageId, _package.Version);
AddModuleMessage(Localizer["Success.Language.Download"], MessageType.Success);
AddModuleMessage(string.Format(Localizer["Success.Language.Download"], NavigateUrl("admin/system")), MessageType.Success);
_package = null;
_install = true;
StateHasChanged();
}
catch (Exception ex)
@ -184,19 +186,4 @@ else
_package = null;
StateHasChanged();
}
private async Task InstallTranslations()
{
try
{
await PackageService.InstallPackagesAsync();
AddModuleMessage(string.Format(Localizer["Success.Language.Install"], NavigateUrl("admin/system")), MessageType.Success);
_install = false;
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Installing Translations");
}
}
}

View File

@ -3,6 +3,7 @@
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IUserService UserService
@inject ISettingService SettingService
@inject IServiceProvider ServiceProvider
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@ -11,9 +12,6 @@
<Authorizing>
<text>...</text>
</Authorizing>
<Authorized>
<div>@Localizer["Info.SignedIn"]</div>
</Authorized>
<NotAuthorized>
@if (!twofactor)
{
@ -34,7 +32,7 @@
<Label Class="control-label" For="password" HelpText="Please enter your Password" ResourceKey="Password">Password:</Label>
<div class="input-group">
<input id="password" type="@_passwordtype" name="Password" class="form-control" placeholder="@Localizer["Password.Placeholder"]" @bind="@_password" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
<div class="form-group mt-2">
@ -69,259 +67,264 @@
</AuthorizeView>
@code {
private bool _allowsitelogin = true;
private bool _allowexternallogin = false;
private ElementReference login;
private bool validated = false;
private bool twofactor = false;
private string _username = string.Empty;
private ElementReference username;
private string _password = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private bool _remember = false;
private string _code = string.Empty;
private bool _allowsitelogin = true;
private bool _allowexternallogin = false;
private ElementReference login;
private bool validated = false;
private bool twofactor = false;
private string _username = string.Empty;
private ElementReference username;
private string _password = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private bool _remember = false;
private string _code = string.Empty;
private string _returnUrl = string.Empty;
private string _returnUrl = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
public override List<Resource> Resources => new List<Resource>()
public override List<Resource> Resources => new List<Resource>()
{
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }
};
protected override async Task OnInitializedAsync()
{
try
{
_togglepassword = SharedLocalizer["ShowPassword"];
protected override async Task OnInitializedAsync()
{
try
{
if (PageState.QueryString.ContainsKey("returnurl"))
{
_returnUrl = PageState.QueryString["returnurl"];
}
if (PageState.Site.Settings.ContainsKey("LoginOptions:AllowSiteLogin") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:AllowSiteLogin"]))
{
_allowsitelogin = bool.Parse(PageState.Site.Settings["LoginOptions:AllowSiteLogin"]);
}
_allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false;
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
if (PageState.Site.Settings.ContainsKey("ExternalLogin:ProviderType") && !string.IsNullOrEmpty(PageState.Site.Settings["ExternalLogin:ProviderType"]))
{
_allowexternallogin = true;
}
if (_allowexternallogin && !_allowsitelogin)
{
// redirect to external login
NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + _returnUrl), true);
return;
}
if (PageState.QueryString.ContainsKey("returnurl"))
{
_returnUrl = PageState.QueryString["returnurl"];
}
_togglepassword = SharedLocalizer["ShowPassword"];
if (PageState.QueryString.ContainsKey("name"))
{
_username = PageState.QueryString["name"];
}
if (PageState.QueryString.ContainsKey("name"))
{
_username = PageState.QueryString["name"];
}
if (PageState.QueryString.ContainsKey("token") && !string.IsNullOrEmpty(_username))
{
var user = new User();
user.SiteId = PageState.Site.SiteId;
user.Username = _username;
if (PageState.QueryString.ContainsKey("token") && !string.IsNullOrEmpty(_username))
{
var user = new User();
user.SiteId = PageState.Site.SiteId;
user.Username = _username;
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);
AddModuleMessage(Localizer["Success.Account.Verified"], MessageType.Info);
}
else
{
await logger.LogError(LogFunction.Security, "Email Verification Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Message.Account.NotVerified"], MessageType.Warning);
}
}
}
else
{
if (PageState.QueryString.ContainsKey("status"))
{
AddModuleMessage(Localizer["ExternalLoginStatus." + PageState.QueryString["status"]], MessageType.Info);
}
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Login {Error}", ex.Message);
AddModuleMessage(Localizer["Error.LoadLogin"], MessageType.Error);
}
}
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);
AddModuleMessage(Localizer["Success.Account.Verified"], MessageType.Info);
}
else
{
await logger.LogError(LogFunction.Security, "Email Verification Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Message.Account.NotVerified"], MessageType.Warning);
}
}
}
else
{
if (PageState.QueryString.ContainsKey("status"))
{
AddModuleMessage(Localizer["ExternalLoginStatus." + PageState.QueryString["status"]], MessageType.Info);
}
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Login {Error}", ex.Message);
AddModuleMessage(Localizer["Error.LoadLogin"], MessageType.Error);
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && PageState.User == null)
{
await username.FocusAsync();
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && PageState.User == null && _allowsitelogin)
{
await username.FocusAsync();
}
private async Task Login()
{
try
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(login))
{
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, hybrid, _remember);
}
else
{
user = await UserService.VerifyTwoFactorAsync(user, _code);
}
// redirect logged in user to specified page
if (PageState.User != null)
{
NavigationManager.NavigateTo(PageState.ReturnUrl);
}
}
if (user.IsAuthenticated)
{
await logger.LogInformation(LogFunction.Security, "Login Successful For Username {Username}", _username);
private async Task Login()
{
try
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(login))
{
var hybrid = (PageState.Runtime == Shared.Runtime.Hybrid);
var user = new User { SiteId = PageState.Site.SiteId, Username = _username, Password = _password, LastIPAddress = SiteState.RemoteIPAddress};
if (hybrid)
{
// hybrid apps utilize an interactive login
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider
.GetService(typeof(IdentityAuthenticationStateProvider));
authstateprovider.NotifyAuthenticationChanged();
NavigationManager.NavigateTo(NavigateUrl(WebUtility.UrlDecode(_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
{
if ((PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && PageState.Site.Settings["LoginOptions:TwoFactor"] == "required") || user.TwoFactorRequired)
{
twofactor = true;
validated = false;
AddModuleMessage(Localizer["Message.TwoFactor"], MessageType.Info);
}
else
{
if (!twofactor)
{
await logger.LogInformation(LogFunction.Security, "Login Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error);
}
else
{
await logger.LogInformation(LogFunction.Security, "Two Factor Verification Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Error.TwoFactor.Fail"], MessageType.Error);
}
}
}
}
else
{
AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Performing Login {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Login"], MessageType.Error);
}
}
if (!twofactor)
{
user = await UserService.LoginUserAsync(user, hybrid, _remember);
}
else
{
user = await UserService.VerifyTwoFactorAsync(user, _code);
}
private void Cancel()
{
NavigationManager.NavigateTo(_returnUrl);
}
if (user.IsAuthenticated)
{
await logger.LogInformation(LogFunction.Security, "Login Successful For Username {Username}", _username);
private async Task Forgot()
{
try
{
if (_username != string.Empty)
{
var user = await UserService.GetUserAsync(_username, PageState.Site.SiteId);
if (user != null)
{
await UserService.ForgotPasswordAsync(user);
await logger.LogInformation(LogFunction.Security, "Password Reset Notification Sent For Username {Username}", _username);
AddModuleMessage(Localizer["Message.ForgotUser"], MessageType.Info);
}
else
{
AddModuleMessage(Localizer["Message.UserDoesNotExist"], MessageType.Warning);
}
}
else
{
AddModuleMessage(Localizer["Message.ForgotPassword"], MessageType.Info);
}
if (hybrid)
{
// hybrid apps utilize an interactive login
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
authstateprovider.NotifyAuthenticationChanged();
NavigationManager.NavigateTo(NavigateUrl(WebUtility.UrlDecode(_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
{
if (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "required" || user.TwoFactorRequired)
{
twofactor = true;
validated = false;
AddModuleMessage(Localizer["Message.TwoFactor"], MessageType.Info);
}
else
{
if (!twofactor)
{
await logger.LogInformation(LogFunction.Security, "Login Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error);
}
else
{
await logger.LogInformation(LogFunction.Security, "Two Factor Verification Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Error.TwoFactor.Fail"], MessageType.Error);
}
}
}
}
else
{
AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Performing Login {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Login"], MessageType.Error);
}
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Resetting Password {Error}", ex.Message);
AddModuleMessage(Localizer["Error.ResetPassword"], MessageType.Error);
}
}
private void Cancel()
{
NavigationManager.NavigateTo(_returnUrl);
}
private void Reset()
{
twofactor = false;
_username = "";
_password = "";
ClearModuleMessage();
StateHasChanged();
}
private async Task Forgot()
{
try
{
if (_username != string.Empty)
{
var user = await UserService.GetUserAsync(_username, PageState.Site.SiteId);
if (user != null)
{
await UserService.ForgotPasswordAsync(user);
await logger.LogInformation(LogFunction.Security, "Password Reset Notification Sent For Username {Username}", _username);
AddModuleMessage(Localizer["Message.ForgotUser"], MessageType.Info);
}
else
{
AddModuleMessage(Localizer["Message.UserDoesNotExist"], MessageType.Warning);
}
}
else
{
AddModuleMessage(Localizer["Message.ForgotPassword"], MessageType.Info);
}
private async Task KeyPressed(KeyboardEventArgs e)
{
if (e.Code == "Enter" || e.Code == "NumpadEnter")
{
await Login();
}
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Resetting Password {Error}", ex.Message);
AddModuleMessage(Localizer["Error.ResetPassword"], MessageType.Error);
}
}
private void TogglePassword()
{
if (_passwordtype == "password")
{
_passwordtype = "text";
_togglepassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordtype = "password";
_togglepassword = SharedLocalizer["ShowPassword"];
}
}
private void Reset()
{
twofactor = false;
_username = "";
_password = "";
ClearModuleMessage();
StateHasChanged();
}
private void ExternalLogin()
{
private async Task KeyPressed(KeyboardEventArgs e)
{
if (e.Code == "Enter" || e.Code == "NumpadEnter")
{
await Login();
}
}
private void TogglePassword()
{
if (_passwordtype == "password")
{
_passwordtype = "text";
_togglepassword = SharedLocalizer["HidePassword"];
}
else
{
_passwordtype = "password";
_togglepassword = SharedLocalizer["ShowPassword"];
}
}
private void ExternalLogin()
{
NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + _returnUrl), true);
}
}
}

View File

@ -37,7 +37,7 @@
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="category" HelpText="The categories that were affected" ResourceKey="Category">Category: </Label>
<Label Class="col-sm-3" For="category" HelpText="The fully qualified type type that was affected" ResourceKey="Category">Type Name: </Label>
<div class="col-sm-9">
<input id="category" class="form-control" @bind="@_category" readonly />
</div>

View File

@ -1,203 +0,0 @@
@namespace Oqtane.Modules.Admin.ModuleCreator
@inherits ModuleBase
@using System.Text.RegularExpressions
@inject NavigationManager NavigationManager
@inject IModuleDefinitionService ModuleDefinitionService
@inject IModuleService ModuleService
@inject ISettingService SettingService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (string.IsNullOrEmpty(_moduledefinitionname) && _templates != null)
{
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="owner" HelpText="Enter the name of the organization who is developing this module. It should not contain spaces or punctuation." ResourceKey="OwnerName">Owner Name: </Label>
<div class="col-sm-9">
<input id="owner" class="form-control" @bind="@_owner" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="module" HelpText="Enter a name for this module. It should not contain spaces or punctuation." ResourceKey="ModuleName">Module Name: </Label>
<div class="col-sm-9">
<input id="module" class="form-control" @bind="@_module" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="description" HelpText="Enter a short description for the module" ResourceKey="Description">Description: </Label>
<div class="col-sm-9">
<textarea id="description" class="form-control" @bind="@_description" rows="3" ></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="template" HelpText="Select a module template. Templates are located in the wwwroot/Modules/Templates folder on the server." ResourceKey="Template">Template: </Label>
<div class="col-sm-9">
<select id="template" class="form-select" @onchange="(e => TemplateChanged(e))" required>
<option value="-">&lt;@Localizer["Template.Select"]&gt;</option>
@foreach (Template template in _templates)
{
<option value="@template.Name">@template.Title</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
<div class="col-sm-9">
<select id="reference" class="form-select" @bind="@_reference" required>
@foreach (string version in _versions)
{
if (Version.Parse(version).CompareTo(Version.Parse(_minversion)) >= 0)
{
<option value="@(version)">@(version)</option>
}
}
<option value="local">@SharedLocalizer["LocalVersion"]</option>
</select>
</div>
</div>
@if (!string.IsNullOrEmpty(_location))
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="location" HelpText="Location where the module will be created" ResourceKey="Location">Location: </Label>
<div class="col-sm-9">
<input id="module" class="form-control" @bind="@_location" readonly />
</div>
</div>
}
</div>
</form>
<button type="button" class="btn btn-success" @onclick="CreateModule">@Localizer["Module.Create"]</button>
}
else
{
<button type="button" class="btn btn-success" @onclick="ActivateModule">@Localizer["Module.Activate"]</button>
}
@code {
private ElementReference form;
private bool validated = false;
private string _moduledefinitionname = string.Empty;
private string _owner = string.Empty;
private string _module = string.Empty;
private string _description = string.Empty;
private List<Template> _templates;
private string _template = "-";
private string[] _versions;
private string _reference = "local";
private string _minversion = "2.0.0";
private string _location = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override void OnInitialized()
{
_moduledefinitionname = SettingService.GetSetting(ModuleState.Settings, "ModuleDefinitionName", "");
if (string.IsNullOrEmpty(_moduledefinitionname))
{
AddModuleMessage(Localizer["Info.Module.Creator"], MessageType.Info);
}
else
{
AddModuleMessage(Localizer["Info.Module.Activate"], MessageType.Info);
}
}
protected override async Task OnParametersSetAsync()
{
try
{
_templates = await ModuleDefinitionService.GetModuleDefinitionTemplatesAsync();
_versions = Constants.ReleaseVersions.Split(',').Where(item => Version.Parse(item).CompareTo(Version.Parse("2.0.0")) >= 0).ToArray();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Module Creator");
}
}
private async Task CreateModule()
{
validated = true;
var interop = new Interop(JSRuntime);
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);
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
settings = SettingService.SetSetting(settings, "ModuleDefinitionName", moduleDefinition.ModuleDefinitionName);
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
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");
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
private async Task ActivateModule()
{
try
{
if (!string.IsNullOrEmpty(_moduledefinitionname))
{
Module module = await ModuleService.GetModuleAsync(ModuleState.ModuleId);
module.ModuleDefinitionName = _moduledefinitionname;
await ModuleService.UpdateModuleAsync(module);
NavigationManager.NavigateTo(NavigateUrl(), true);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Activating Module");
}
}
private bool IsValid(string name)
{
// must contain letters, underscores and digits and first character must be letter or underscore
return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
}
private void TemplateChanged(ChangeEventArgs e)
{
_template = (string)e.Value;
_minversion = "2.0.0";
if (_template != "-")
{
var template = _templates.FirstOrDefault(item => item.Name == _template);
_minversion = template.Version;
}
GetLocation();
}
private void GetLocation()
{
_location = string.Empty;
if (_owner != "" && _module != "" && _template != "-")
{
var template = _templates.FirstOrDefault(item => item.Name == _template);
_location = template.Location + _owner + "." + _module;
}
StateHasChanged();
}
}

View File

@ -1,17 +0,0 @@
using Oqtane.Documentation;
using Oqtane.Models;
namespace Oqtane.Modules.Admin.ModuleCreator
{
[PrivateApi("Mark this as private, since it's not very useful in the public docs")]
public class ModuleInfo : IModule
{
public ModuleDefinition ModuleDefinition => new ModuleDefinition
{
Name = "Module Creator",
Description = "Enables software developers to quickly create modules by automating many of the initial module creation tasks",
Version = "1.0.0",
Categories = "Developer"
};
}
}

View File

@ -8,70 +8,124 @@
@inject IStringLocalizer<SharedResources> SharedLocalizer
<TabStrip>
<TabPanel Name="Download" ResourceKey="Download">
<TabPanel Name="Download" ResourceKey="Download" Heading="Download">
<div class="row justify-content-center mb-3">
<div class="col-sm-6">
<div class="input-group">
<select id="price" class="form-select custom-select" @onchange="(e => PriceChanged(e))">
<option value="free">@SharedLocalizer["Free"]</option>
<option value="paid">@SharedLocalizer["Paid"]</option>
</select>
<input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" />
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
<div class="text-center">
<div class="form-check form-check-inline">
<input id="free" class="form-check-input" type="radio" checked="@(_price == "free")" name="Price" @onchange="@(() => PriceChanged("free"))" />
<label class="form-check-label" for="free">@SharedLocalizer["Free"]</label>
</div>
<div class="form-check form-check-inline">
<input id="paid" class="form-check-input" type="radio" checked="@(_price == "paid")" name="Price" @onchange="@(() => PriceChanged("paid"))" />
<label class="form-check-label" for="paid">@SharedLocalizer["Paid"]</label>
</div>
</div>
</div>
@if (_packages != null)
{
if (_packages.Count > 0)
{
<Pager Items="@_packages">
<Row>
<td>
<h3 style="display: inline;"><a href="@context.ProductUrl" target="_new">@context.Name</a></h3>&nbsp;&nbsp;by:&nbsp;&nbsp;<strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
<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;">
@if (context.Price != null && !string.IsNullOrEmpty(context.PackageUrl))
{
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
}
</td>
<td style="width: 1px; vertical-align: middle;">
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
{
<a class="btn btn-primary" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@context.Price.Value.ToString("$#,##0.00")</a>
}
else
{
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
}
</td>
</Row>
</Pager>
}
else
{
<br />
<div class="mx-auto text-center">
@Localizer["Search.NoResults"]
<div class="row justify-content-center mb-3">
<div class="col">
<div class="input-group">
<span class="input-group-text">@Localizer["Product"]</span>
<input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" />
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
<button type="button" class="btn btn-primary ms-2" @onclick="Refresh"><span class="@Icons.Reload" aria-hidden="true"></span></button>
</div>
}
}
</div>
</div>
<div class="row mb-3">
<div class="col">
@if (_initialized)
{
<br />
<div class="row mb-3">
<div class="col-sm-4">
<h3>@((_packages != null) ? _packages.Count : 0) @SharedLocalizer["Search.Results"]</h3>
</div>
<div class="col-sm-4">
&nbsp;
</div>
<div class="col-sm-4">
<select class="form-select" value="@_sort" @onchange="(e => SortChanged(e))">
<option value="popularity">@SharedLocalizer["Search.Popularity"]</option>
<option value="alphabetical">@SharedLocalizer["Search.Alphabetical"]</option>
@if (_price == "free")
{
<option value="downloads">@SharedLocalizer["Search.Downloads"]</option>
}
<option value="recent">@SharedLocalizer["Search.RecentlyReleased"]</option>
@if (_price == "paid")
{
<option value="price">@SharedLocalizer["Search.Price"]</option>
}
</select>
</div>
</div>
<Pager Format="Grid" Items="@_packages" DisplayPages="1" PageSize="9" Toolbar="Both" Class="container-fluid px-0" RowClass="row g-0" ColumnClass="col-lg-4 col-md-6" CurrentPage="@_page.ToString()" OnPageChange="OnPageChange">
<Row>
<div class="m-2 p-2 d-flex justify-content-center">
<div class="container-fluid px-0">
<div class="row g-0 mb-2">
<div class="col-4">
@if (context.LogoUrl != null)
{
<img src="@context.LogoUrl" class="img-fluid" alt="@context.Name" />
}
else
{
<img src="/package.png" class="img-fluid" alt="@context.Name" />
}
</div>
<div class="col-8 text-end">
<small>@SharedLocalizer["Search.Version"]:</small> <strong>@context.Version</strong>
<br /><small>@SharedLocalizer["Search.Released"]:</small> <strong>@context.ReleaseDate.ToString("MM/dd/yyyy")</strong>
@if (!string.IsNullOrEmpty(context.PackageUrl))
{
<br /><small>@SharedLocalizer["Search.Source"]:</small> <strong>@(new Uri(context.PackageUrl).Host)</strong>
}
@if (context.Price == null)
{
<br /><small>@SharedLocalizer["Search.Downloads"]:</small> <strong>@(String.Format("{0:n0}", context.Downloads))</strong>
}
else
{
<br /><small>@SharedLocalizer["From"]:</small> <strong>@context.Price.Value.ToString("$#,##0.00")</strong>
@((MarkupString)(context.TrialPeriod > 0 ? " <strong>(" + context.TrialPeriod + " Day Trial)</strong>" : ""))
}
</div>
</div>
<div class="row g-0">
<div class="col">
<h3 style="display: inline;"><a href="@context.ProductUrl" target="_blank">@context.Name</a></h3><br />
<small>@SharedLocalizer["Search.By"]:</small> <strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
<br />
@if (!string.IsNullOrEmpty(context.PackageUrl))
{
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
}
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
{
<a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
}
<br />
</div>
</div>
</div>
</div>
</Row>
</Pager>
}
</div>
</div>
<br />
<ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" />
</TabPanel>
<TabPanel Name="Upload" ResourceKey="Upload">
<TabPanel Name="Upload" ResourceKey="Upload" Heading="Upload">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" HelpText="Upload one or more module packages. Once they are uploaded click Install to complete the installation." ResourceKey="Module">Module: </Label>
<div class="col-sm-9">
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" />
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" OnUpload="OnUpload" />
</div>
</div>
</div>
@ -111,153 +165,149 @@
</div>
}
<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 _packageid = "";
private string _packagelicense = "";
private string _packageversion = "";
private bool _initialized = false;
private int _page = 1;
private List<Package> _packages;
private string _price = "free";
private string _sort = "popularity";
private string _search = "";
private string _productname = "";
private string _packageid = "";
private string _packagelicense = "";
private string _packageversion = "";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync()
{
try
{
await LoadModuleDefinitions();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Packages {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Package.Load"], MessageType.Error);
}
}
protected override async Task OnInitializedAsync()
{
try
{
await LoadModuleDefinitions();
_initialized = true;
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Packages {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Package.Load"], MessageType.Error);
}
}
private async Task LoadModuleDefinitions()
{
var moduledefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
_packages = await PackageService.GetPackagesAsync("module", _search, _price, "");
private async Task LoadModuleDefinitions()
{
ShowProgressIndicator();
if (_packages != null)
{
foreach (Package package in _packages.ToArray())
{
if (moduledefinitions.Exists(item => item.PackageName == package.PackageId))
{
_packages.Remove(package);
}
}
}
}
var moduledefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
_packages = await PackageService.GetPackagesAsync("module", _search, _price, "", _sort);
private async void PriceChanged(ChangeEventArgs e)
{
try
{
_price = (string)e.Value;
_search = "";
await LoadModuleDefinitions();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On PriceChanged");
}
}
if (_packages != null)
{
foreach (Package package in _packages.ToArray())
{
if (moduledefinitions.Exists(item => item.PackageName == package.PackageId))
{
_packages.Remove(package);
}
}
}
private async Task Search()
{
try
{
await LoadModuleDefinitions();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On Search");
}
}
HideProgressIndicator();
}
private async Task Reset()
{
try
{
_search = "";
await LoadModuleDefinitions();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On Reset");
}
}
private async void PriceChanged(string price)
{
_price = price;
_sort = "popularity";
await LoadModuleDefinitions();
StateHasChanged();
}
private void HideModal()
{
_productname = "";
_packagelicense = "";
StateHasChanged();
}
private async Task Search()
{
await LoadModuleDefinitions();
}
private async Task GetPackage(string packageid, string version)
{
try
{
var package = await PackageService.GetPackageAsync(packageid, version);
if (package != null)
{
_productname = package.Name;
_packageid = package.PackageId;
if (!string.IsNullOrEmpty(package.License))
{
_packagelicense = package.License.Replace("\n", "<br />");
}
_packageversion = package.Version;
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", packageid, version);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
}
}
private async Task Reset()
{
_page = 1;
_search = "";
await LoadModuleDefinitions();
}
private async Task DownloadPackage()
{
try
{
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 = "";
_packagelicense = "";
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packageid, _packageversion);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
}
}
private async Task Refresh()
{
await LoadModuleDefinitions();
}
private async Task InstallModules()
{
try
{
await ModuleDefinitionService.InstallModuleDefinitionsAsync();
AddModuleMessage(string.Format(Localizer["Success.Module.Install"], NavigateUrl("admin/system")), MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Installing Modules");
}
}
private void OnPageChange(int page)
{
_page = page;
}
private async void SortChanged(ChangeEventArgs e)
{
_sort = (string)e.Value;
await LoadModuleDefinitions();
}
private void HideModal()
{
_productname = "";
_packagelicense = "";
StateHasChanged();
}
private async Task GetPackage(string packageid, string version)
{
try
{
var package = await PackageService.GetPackageAsync(packageid, version, false);
if (package != null)
{
_productname = package.Name;
_packageid = package.PackageId;
if (!string.IsNullOrEmpty(package.License))
{
_packagelicense = package.License.Replace("\n", "<br />");
}
_packageversion = package.Version;
StateHasChanged();
}
else
{
await logger.LogError("Error Getting Package {PackageId} {Version}", packageid, version);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", packageid, version);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
}
}
private async Task DownloadPackage()
{
try
{
await PackageService.DownloadPackageAsync(_packageid, _packageversion);
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _packageid, _packageversion);
AddModuleMessage(string.Format(Localizer["Success.Module.Download"], NavigateUrl("admin/system")), MessageType.Success);
_productname = "";
_packagelicense = "";
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packageid, _packageversion);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
}
}
private void OnUpload()
{
AddModuleMessage(string.Format(Localizer["Success.Module.Download"], NavigateUrl("admin/system")), MessageType.Success);
}
}

View File

@ -89,7 +89,10 @@
protected override void OnInitialized()
{
AddModuleMessage(Localizer["Info.Module.Development"], MessageType.Info);
if (!NavigationManager.BaseUri.Contains("localhost:"))
{
AddModuleMessage(Localizer["Info.Module.Development"], MessageType.Info);
}
}
protected override async Task OnParametersSetAsync()
@ -115,10 +118,18 @@
{
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);
GetLocation();
AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success);
if (IsValidXML(_description))
{
var template = _templates.FirstOrDefault(item => item.Name == _template);
var moduleDefinition = new ModuleDefinition { Owner = _owner, Name = _module, Description = _description, Template = _template, Version = _reference, ModuleDefinitionName = template.Namespace };
moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition);
GetLocation();
AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success);
}
else
{
AddModuleMessage(Localizer["Message.Require.ValidDescription"], MessageType.Warning);
}
}
else
{
@ -139,7 +150,13 @@
private bool IsValid(string name)
{
// must contain letters, underscores and digits and first character must be letter or underscore
return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
}
private bool IsValidXML(string description)
{
// must contain letters, digits, or spaces
return Regex.IsMatch(description, "^[A-Za-z0-9 ]+$");
}
private void TemplateChanged(ChangeEventArgs e)
@ -151,7 +168,7 @@
var template = _templates.FirstOrDefault(item => item.Name == _template);
_minversion = template.Version;
}
GetLocation();
GetLocation();
}
private void GetLocation()
@ -160,8 +177,14 @@
if (_owner != "" && _module != "" && _template != "-")
{
var template = _templates.FirstOrDefault(item => item.Name == _template);
_location = template.Location + _owner + "." + _module;
if (!string.IsNullOrEmpty(template.Namespace))
{
_location = template.Location + template.Namespace.Replace("[Owner]", _owner).Replace("[Module]", _module);
}
else
{
_location = template.Location + _owner + ".Module." + _module;
}
}
StateHasChanged();
}

View File

@ -12,7 +12,7 @@
@if (_initialized)
{
<TabStrip>
<TabPanel Name="Definition" ResourceKey="Definition">
<TabPanel Name="Definition" ResourceKey="Definition" Heading="Definition">
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
@ -33,7 +33,16 @@
<input id="categories" class="form-control" @bind="@_categories" maxlength="200" required />
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="isenabled" HelpText="Is module enabled for this site?" ResourceKey="IsEnabled">Enabled? </Label>
<div class="col-sm-9">
<select id="isenabled" class="form-select" @bind="@_isenabled" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
</form>
<Section Name="Information" ResourceKey="Information">
<div class="container">
@ -50,9 +59,26 @@
</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>
<Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this module was installed. This value must be specified within the module's IModule interface specification." ResourceKey="PackageName">Package Name: </Label>
<div class="col-sm-9">
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
@if (!string.IsNullOrEmpty(_packagename))
{
<div class="input-group">
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
@if (string.IsNullOrEmpty(_packageurl))
{
<button type="button" class="btn btn-secondary" @onclick="ValidatePackage">@Localizer["Validate"]</button>
}
else
{
<a href="@_packageurl" target="_blank" class="btn btn-primary">@SharedLocalizer["Download"]</a>
}
</div>
}
else
{
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
}
</div>
</div>
<div class="row mb-1 align-items-center">
@ -76,13 +102,14 @@
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="license" HelpText="The module license terms" ResourceKey="License">License: </Label>
<div class="col-sm-9">
<textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="runtimes" HelpText="The Blazor runtimes which this module supports" ResourceKey="Runtimes">Runtimes: </Label>
<div class="col-sm-9">
<input id="runtimes" class="form-control" @bind="@_runtimes" disabled />
@if (_license.StartsWith("http") || _license.StartsWith("/") || _license.StartsWith("~"))
{
<a href="@_license.Replace("~", PageState?.Alias.BaseUrl + "/Modules/" + Utilities.GetTypeName(_moduledefinitionname))" class="btn btn-info" style="text-decoration: none !important" target="_new">@Localizer["View License"]</a>
}
else
{
<textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea>
}
</div>
</div>
</div>
@ -94,7 +121,7 @@
<br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
</TabPanel>
<TabPanel Name="Permissions" ResourceKey="Permissions">
<TabPanel Name="Permissions" ResourceKey="Permissions" Heading="Permissions">
<div class="container">
<div class="row mb-1 align-items-center">
<PermissionGrid EntityName="@EntityNames.ModuleDefinition" PermissionNames="@PermissionNames.Utilize" PermissionList="@_permissions" @ref="_permissionGrid" />
@ -104,7 +131,7 @@
<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">
<TabPanel Name="Translations" ResourceKey="Translations" Heading="Translations">
@if (_languages != null && _languages.Count > 0)
{
<Pager Items="@_languages">
@ -131,10 +158,6 @@
</td>
</Row>
</Pager>
@if (_install)
{
<button type="button" class="btn btn-success" @onclick="InstallTranslations">@SharedLocalizer["Install"]</button>
}
}
else
{
@ -185,7 +208,7 @@
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success" @onclick="DownloadPackage">@SharedLocalizer["Accept"]</button>
<button type="button" class="btn btn-success" @onclick="DownloadTranslation">@SharedLocalizer["Accept"]</button>
<button type="button" class="btn btn-secondary" @onclick="HideModal">@SharedLocalizer["Cancel"]</button>
</div>
</div>
@ -196,200 +219,225 @@
}
@code {
private bool _initialized = false;
private ElementReference form;
private bool validated = false;
private int _moduleDefinitionId;
private string _name;
private string _description = "";
private string _categories;
private string _moduledefinitionname = "";
private string _version;
private string _packagename = "";
private string _owner = "";
private string _url = "";
private string _contact = "";
private string _license = "";
private string _runtimes = "";
private List<Permission> _permissions = null;
private string _createdby;
private DateTime _createdon;
private string _modifiedby;
private DateTime _modifiedon;
private bool _initialized = false;
private ElementReference form;
private bool validated = false;
private int _moduleDefinitionId;
private string _name;
private string _description = "";
private string _categories;
private string _isenabled;
private string _moduledefinitionname = "";
private string _version;
private string _packagename = "";
private string _packageurl = "";
private string _owner = "";
private string _url = "";
private string _contact = "";
private string _license = "";
private List<Permission> _permissions = null;
private string _createdby;
private DateTime _createdon;
private string _modifiedby;
private DateTime _modifiedon;
#pragma warning disable 649
private PermissionGrid _permissionGrid;
private PermissionGrid _permissionGrid;
#pragma warning restore 649
private List<Package> _packages;
private List<Language> _languages;
private Package _package;
private bool _install = false;
private List<Package> _packages;
private List<Language> _languages;
private Package _package;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnInitializedAsync()
{
try
{
_moduleDefinitionId = Int32.Parse(PageState.QueryString["id"]);
var moduleDefinition = await ModuleDefinitionService.GetModuleDefinitionAsync(_moduleDefinitionId, ModuleState.SiteId);
if (moduleDefinition != null)
{
_name = moduleDefinition.Name;
_description = moduleDefinition.Description;
_categories = moduleDefinition.Categories;
_moduledefinitionname = moduleDefinition.ModuleDefinitionName;
_version = moduleDefinition.Version;
_packagename = moduleDefinition.PackageName;
_owner = moduleDefinition.Owner;
_url = moduleDefinition.Url;
_contact = moduleDefinition.Contact;
_license = moduleDefinition.License;
_runtimes = moduleDefinition.Runtimes;
_permissions = moduleDefinition.PermissionList;
_createdby = moduleDefinition.CreatedBy;
_createdon = moduleDefinition.CreatedOn;
_modifiedby = moduleDefinition.ModifiedBy;
_modifiedon = moduleDefinition.ModifiedOn;
protected override async Task OnInitializedAsync()
{
try
{
_moduleDefinitionId = Int32.Parse(PageState.QueryString["id"]);
var moduleDefinition = await ModuleDefinitionService.GetModuleDefinitionAsync(_moduleDefinitionId, ModuleState.SiteId);
if (moduleDefinition != null)
{
_name = moduleDefinition.Name;
_description = moduleDefinition.Description;
_categories = moduleDefinition.Categories;
_isenabled = moduleDefinition.IsEnabled.ToString();
_moduledefinitionname = moduleDefinition.ModuleDefinitionName;
_version = moduleDefinition.Version;
_packagename = moduleDefinition.PackageName;
_owner = moduleDefinition.Owner;
_url = moduleDefinition.Url;
_contact = moduleDefinition.Contact;
_license = moduleDefinition.License;
_permissions = moduleDefinition.PermissionList;
_createdby = moduleDefinition.CreatedBy;
_createdon = moduleDefinition.CreatedOn;
_modifiedby = moduleDefinition.ModifiedBy;
_modifiedon = moduleDefinition.ModifiedOn;
if (!string.IsNullOrEmpty(_packagename))
{
_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();
}
if (!string.IsNullOrEmpty(_packagename))
{
_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();
}
_initialized = true;
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading ModuleDefinition {ModuleDefinitionId} {Error}", _moduleDefinitionId, ex.Message);
AddModuleMessage(Localizer["Error.Module.Load"], MessageType.Error);
}
}
_initialized = true;
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading ModuleDefinition {ModuleDefinitionId} {Error}", _moduleDefinitionId, ex.Message);
AddModuleMessage(Localizer["Error.Module.Load"], MessageType.Error);
}
}
private async Task SaveModuleDefinition()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
try
{
var moduledefinition = await ModuleDefinitionService.GetModuleDefinitionAsync(_moduleDefinitionId, ModuleState.SiteId);
if (moduledefinition.Name != _name)
{
moduledefinition.Name = _name;
}
if (moduledefinition.Description != _description)
{
moduledefinition.Description = _description;
}
if (moduledefinition.Categories != _categories)
{
moduledefinition.Categories = _categories;
}
moduledefinition.PermissionList = _permissionGrid.GetPermissionList();
await ModuleDefinitionService.UpdateModuleDefinitionAsync(moduledefinition);
await logger.LogInformation("ModuleDefinition Saved {ModuleDefinition}", moduledefinition);
NavigationManager.NavigateTo(NavigateUrl());
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving ModuleDefinition {ModuleDefinitionId} {Error}", _moduleDefinitionId, ex.Message);
AddModuleMessage(Localizer["Error.Module.Save"], MessageType.Error);
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
private async Task SaveModuleDefinition()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
try
{
var moduleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
if (!moduleDefinitions.Any(item => item.Name.ToLower() == _name.ToLower() && item.ModuleDefinitionId != _moduleDefinitionId))
{
var moduledefinition = await ModuleDefinitionService.GetModuleDefinitionAsync(_moduleDefinitionId, ModuleState.SiteId);
if (moduledefinition.Name != _name)
{
moduledefinition.Name = _name;
}
if (moduledefinition.Description != _description)
{
moduledefinition.Description = _description;
}
if (moduledefinition.Categories != _categories)
{
moduledefinition.Categories = _categories;
}
moduledefinition.IsEnabled = (_isenabled == null ? true : bool.Parse(_isenabled));
moduledefinition.PermissionList = _permissionGrid.GetPermissionList();
await ModuleDefinitionService.UpdateModuleDefinitionAsync(moduledefinition);
await logger.LogInformation("ModuleDefinition Saved {ModuleDefinition}", moduledefinition);
NavigationManager.NavigateTo(NavigateUrl());
}
else
{
AddModuleMessage(Localizer["Message.DuplicateName"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving ModuleDefinition {ModuleDefinitionId} {Error}", _moduleDefinitionId, ex.Message);
AddModuleMessage(Localizer["Error.Module.Save"], MessageType.Error);
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
private void HideModal()
{
_package = null;
StateHasChanged();
}
private void HideModal()
{
_package = null;
StateHasChanged();
}
private string TranslationAvailable(string packagename, string version)
{
if (_packages != null)
{
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
if (package != null)
{
if (string.IsNullOrEmpty(version))
{
return "install";
}
else
{
if (Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0)
{
return "upgrade";
}
}
}
}
return "";
}
private string TranslationAvailable(string packagename, string version)
{
if (_packages != null)
{
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
if (package != null)
{
if (string.IsNullOrEmpty(version))
{
return "install";
}
else
{
if (Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0)
{
return "upgrade";
}
}
}
}
return "";
}
private async Task GetPackage(string packagename)
{
var version = _packages.Where(item => item.PackageId == packagename).FirstOrDefault().Version;
try
{
_package = await PackageService.GetPackageAsync(packagename, version);
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 GetPackage(string packagename)
{
var version = _packages.Where(item => item.PackageId == packagename).FirstOrDefault().Version;
try
{
_package = await PackageService.GetPackageAsync(packagename, version, false);
if (_package != null)
{
StateHasChanged();
}
else
{
await logger.LogError("Error Getting Package {PackageId} {Version}", packagename, version);
AddModuleMessage(Localizer["Error.Translation.Download"], MessageType.Error);
}
}
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()
{
try
{
await PackageService.DownloadPackageAsync(_package.PackageId, _package.Version, Constants.PackagesFolder);
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _package.PackageId, _package.Version);
AddModuleMessage(Localizer["Success.Translation.Download"], MessageType.Success);
_package = null;
_install = true;
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 DownloadTranslation()
{
try
{
await PackageService.DownloadPackageAsync(_package.PackageId, _package.Version);
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _package.PackageId, _package.Version);
AddModuleMessage(string.Format(Localizer["Success.Translation.Download"], NavigateUrl("admin/system")), MessageType.Success);
_package = null;
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 ValidatePackage()
{
try
{
var package = await PackageService.GetPackageAsync(_packagename, _version, true);
if (package == null || string.IsNullOrEmpty(package.PackageUrl))
{
AddModuleMessage(Localizer["Message.Validate"], MessageType.Warning);
}
else
{
_packageurl = package.PackageUrl;
AddModuleMessage(Localizer["Message.Download"], MessageType.Info);
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packagename, _version);
AddModuleMessage(Localizer["Error.Validate"], MessageType.Error);
}
}
private async Task InstallTranslations()
{
try
{
await PackageService.InstallPackagesAsync();
AddModuleMessage(string.Format(Localizer["Success.Translation.Install"], NavigateUrl("admin/system")), MessageType.Success);
_install = false;
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Installing Translations");
}
}
}

View File

@ -37,13 +37,15 @@ else
</div>
</div>
<Pager Items="@_moduleDefinitions.Where(item => item.Categories.Contains(_category))">
<Pager Items="@_moduleDefinitions">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th>
<th>@SharedLocalizer["Version"]</th>
<th>@Localizer["Enabled"]</th>
<th>@Localizer["InUse"]</th>
<th>@SharedLocalizer["Support"]</th>
<th>@SharedLocalizer["Expires"]</th>
<th style="width: 1px;">&nbsp;</th>
</Header>
@ -57,6 +59,16 @@ else
</td>
<td>@context.Name</td>
<td>@context.Version</td>
<td>
@if (context.IsEnabled)
{
<span>@SharedLocalizer["Yes"]</span>
}
else
{
<span>@SharedLocalizer["No"]</span>
}
</td>
<td>
@if (context.AssemblyName == Constants.ClientId || PageState.Modules.Where(m => m.ModuleDefinition?.ModuleDefinitionId == context.ModuleDefinitionId).FirstOrDefault() != null)
{
@ -67,6 +79,9 @@ else
<span>@SharedLocalizer["No"]</span>
}
</td>
<td>
@((MarkupString)SupportLink(context.PackageName, context.Version))
</td>
<td>
@((MarkupString)PurchaseLink(context.PackageName))
</td>
@ -84,52 +99,76 @@ else
}
@code {
private List<ModuleDefinition> _moduleDefinitions;
private List<Package> _packages;
private List<string> _categories = new List<string>();
private string _category = "Common";
private List<ModuleDefinition> _allModuleDefinitions;
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;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnParametersSetAsync()
{
try
{
_moduleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
_packages = await PackageService.GetPackagesAsync("module");
_categories = _moduleDefinitions.SelectMany(m => m.Categories.Split(',')).Distinct().ToList();
}
catch (Exception ex)
{
if (_moduleDefinitions == null)
{
await logger.LogError(ex, "Error Loading Modules {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Module.Load"], MessageType.Error);
}
}
}
protected override async Task OnParametersSetAsync()
{
try
{
_allModuleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
_categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',')).Distinct().ToList();
await LoadModuleDefinitions();
}
catch (Exception ex)
{
if (_moduleDefinitions == null)
{
await logger.LogError(ex, "Error Loading Modules {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Module.Load"], MessageType.Error);
}
}
}
private string PurchaseLink(string packagename)
{
string link = "";
if (!string.IsNullOrEmpty(packagename) && _packages != null)
{
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
if (package != null)
{
if (package.ExpiryDate != null && package.ExpiryDate.Value.Date != DateTime.MaxValue.Date)
{
link += "<span>" + package.ExpiryDate.Value.Date.ToString("MMM dd, yyyy") + "</span>";
if (!string.IsNullOrEmpty(package.PaymentUrl))
{
link += "&nbsp;&nbsp;<a class=\"btn btn-primary\" style=\"text-decoration: none !important\" href=\"" + package.PaymentUrl + "\" target=\"_new\">" + SharedLocalizer["Extend"] + "</a>";
}
}
private async Task LoadModuleDefinitions()
{
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(_category)).ToList();
_packages = await PackageService.GetPackageUpdatesAsync("module");
}
private string PurchaseLink(string packagename)
{
string link = "";
if (!string.IsNullOrEmpty(packagename) && _packages != null)
{
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
if (package != null)
{
if (package.ExpiryDate != null && package.ExpiryDate.Value.Date != DateTime.MaxValue.Date)
{
if (string.IsNullOrEmpty(package.PaymentUrl))
{
link = "<span>" + package.ExpiryDate.Value.Date.ToString("MMM dd, yyyy") + "</span>";
}
else
{
link = "<a class=\"btn btn-primary\" style=\"text-decoration: none !important\" href=\"" + package.PaymentUrl + "\" target=\"_new\">" + package.ExpiryDate.Value.Date.ToString("MMM dd, yyyy") + "</a>";
}
}
}
}
return link;
}
private string SupportLink(string packagename, string version)
{
string link = "";
if (!string.IsNullOrEmpty(packagename) && _packages != null)
{
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
if (package != null && !string.IsNullOrEmpty(package.SupportUrl))
{
link += "<a class=\"btn btn-info\" style=\"text-decoration: none !important\" href=\"" + package.SupportUrl.Replace("{Version}", version) + "\" target=\"_new\">" + SharedLocalizer["Help"] + "</a>";
}
}
return link;
}
private string UpgradeAvailable(string packagename, string version)
{
if (!string.IsNullOrEmpty(packagename) && _packages != null)
@ -147,9 +186,8 @@ else
{
try
{
await PackageService.DownloadPackageAsync(packagename, version, Constants.PackagesFolder);
await PackageService.DownloadPackageAsync(packagename, version);
await logger.LogInformation("Module Downloaded {ModuleDefinitionName} {Version}", packagename, version);
await ModuleDefinitionService.InstallModuleDefinitionsAsync();
AddModuleMessage(string.Format(Localizer["Success.Module.Install"], NavigateUrl("admin/system")), MessageType.Success);
}
catch (Exception ex)
@ -174,9 +212,9 @@ else
}
}
private void CategoryChanged(ChangeEventArgs e)
private async Task CategoryChanged(ChangeEventArgs e)
{
_category = (string)e.Value;
StateHasChanged();
await LoadModuleDefinitions();
}
}

View File

@ -22,7 +22,6 @@
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Title => "Export Content";
private async Task ExportModule()
{
try

View File

@ -14,10 +14,16 @@
@if (_containers != null)
{
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="module" HelpText="The name of the module" ResourceKey="Module">Module: </Label>
<div class="col-sm-9">
<input id="module" type="text" class="form-control" @bind="@_module" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="title" HelpText="Enter the title of the module" ResourceKey="Title">Title: </Label>
<div class="col-sm-9">
<input id="title" type="text" name="Title" class="form-control" @bind="@_title" required />
<input id="title" type="text" class="form-control" @bind="@_title" required />
</div>
</div>
<div class="row mb-1 align-items-center">
@ -44,12 +50,20 @@
<Label Class="col-sm-3" For="page" HelpText="The page that the module is located on" ResourceKey="Page">Page: </Label>
<div class="col-sm-9">
<select id="page" class="form-select" @bind="@_pageId" required>
@foreach (Page p in PageState.Pages)
@if (PageState.Page.UserId != null)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
<option value="@PageState.Page.PageId">@(PageState.Page.Name)</option>
}
else
{
foreach (Page p in PageState.Pages)
{
<option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option>
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, p.PermissionList))
{
<option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option>
}
}
}
</select>
</div>
@ -90,37 +104,37 @@
</form>
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Title => "Module Settings";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Title => "Module Settings";
private ElementReference form;
private bool validated = false;
private List<Theme> _themes;
private List<ThemeControl> _containers = new List<ThemeControl>();
private string _title;
private string _containerType;
private string _allPages = "false";
private string _permissionNames = "";
private List<Permission> _permissions = null;
private string _pageId;
private PermissionGrid _permissionGrid;
private Type _moduleSettingsType;
private object _moduleSettings;
private string _moduleSettingsTitle = "Module Settings";
private RenderFragment ModuleSettingsComponent { get; set; }
private Type _containerSettingsType;
private object _containerSettings;
private RenderFragment ContainerSettingsComponent { get; set; }
private string createdby;
private DateTime createdon;
private string modifiedby;
private DateTime modifiedon;
private ElementReference form;
private bool validated = false;
private List<ThemeControl> _containers = new List<ThemeControl>();
private string _module;
private string _title;
private string _containerType;
private string _allPages = "false";
private string _permissionNames = "";
private List<Permission> _permissions = null;
private string _pageId;
private PermissionGrid _permissionGrid;
private Type _moduleSettingsType;
private object _moduleSettings;
private string _moduleSettingsTitle = "Module Settings";
private RenderFragment ModuleSettingsComponent { get; set; }
private Type _containerSettingsType;
private object _containerSettings;
private RenderFragment ContainerSettingsComponent { get; set; }
private string createdby;
private DateTime createdon;
private string modifiedby;
private DateTime modifiedon;
protected override async Task OnInitializedAsync()
{
_title = ModuleState.Title;
_themes = await ThemeService.GetThemesAsync();
_containers = ThemeService.GetContainerControls(_themes, PageState.Page.ThemeType);
protected override void OnInitialized()
{
_module = ModuleState.ModuleDefinition.Name;
_title = ModuleState.Title;
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, PageState.Page.ThemeType);
_containerType = ModuleState.ContainerType;
_allPages = ModuleState.AllPages.ToString();
_permissions = ModuleState.PermissionList;
@ -165,7 +179,7 @@
AddModuleMessage(string.Format(Localizer["Error.Module.Load"], ModuleState.ModuleDefinitionName), MessageType.Error);
}
var theme = _themes.FirstOrDefault(item => item.Containers.Any(themecontrol => themecontrol.TypeName.Equals(_containerType)));
var theme = PageState.Site.Themes.FirstOrDefault(item => item.Containers.Any(themecontrol => themecontrol.TypeName.Equals(_containerType)));
if (theme != null && !string.IsNullOrEmpty(theme.ContainerSettingsType))
{
_containerSettingsType = Type.GetType(theme.ContainerSettingsType);

View File

@ -3,15 +3,15 @@
@inject NavigationManager NavigationManager
@inject IPageService PageService
@inject IThemeService ThemeService
@inject ISystemService SystemService
@inject IStringLocalizer<Add> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<TabStrip Refresh="@_refresh">
<TabPanel Name="Settings" ResourceKey="Settings">
@if (_themeList != null)
{
@if (_initialized)
{
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<TabStrip Refresh="@_refresh">
<TabPanel Name="Settings" ResourceKey="Settings" Heading="Settings">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
@ -19,42 +19,67 @@
<input id="name" class="form-control" @bind="@_name" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="parent" HelpText="Select the parent for the page in the site hierarchy" ResourceKey="Parent">Parent: </Label>
<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 PageState.Pages)
{
<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="insert" HelpText="Select the location where you would like the page to be inserted in relation to other pages" ResourceKey="Insert">Insert: </Label>
<div class="col-sm-9">
<select id="insert" class="form-select" @bind="@_insert" required>
<option value="<<">@Localizer["AtBeginning"]</option>
@if (_children != null && _children.Count > 0)
{
<option value="<">@Localizer["Before"]</option>
<option value=">">@Localizer["After"]</option>
}
<option value=">>">@Localizer["AtEnd"]</option>
</select>
@if (_children != null && _children.Count > 0 && (_insert == "<" || _insert == ">"))
{
<select class="form-select" @bind="@_childid">
<option value="-1">&lt;@Localizer["Page.Select"]&gt;</option>
@foreach (Page page in _children)
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="parent" HelpText="Select the parent for the page in the site hierarchy" ResourceKey="Parent">Parent: </Label>
<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 PageState.Pages)
{
<option value="@(page.PageId)">@(page.Name)</option>
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, page.PermissionList))
{
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option>
}
}
</select>
}
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="insert" HelpText="Select the location where you would like the page to be inserted in relation to other pages" ResourceKey="Insert">Insert: </Label>
<div class="col-sm-9">
<select id="insert" class="form-select" @bind="@_insert" required>
@if (_children != null && _children.Count > 0)
{
<option value="<<">@Localizer["AtBeginning"]</option>
<option value="<">@Localizer["Before"]</option>
<option value=">">@Localizer["After"]</option>
}
<option value=">>">@Localizer["AtEnd"]</option>
</select>
@if (_children != null && _children.Count > 0 && (_insert == "<" || _insert == ">"))
{
<select class="form-select" @bind="@_childid">
<option value="-1">&lt;@Localizer["Page.Select"]&gt;</option>
@foreach (Page page in _children)
{
<option value="@(page.PageId)">@(page.Name)</option>
}
</select>
}
</div>
</div>
}
else
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="parent" HelpText="Select the parent for the page in the site hierarchy" ResourceKey="Parent">Parent: </Label>
<div class="col-sm-9">
<select id="parent" class="form-select" @onchange="(e => ParentChanged(e))" required>
<option value="@(_parent.PageId)">@(new string('-', _parent.Level * 2))@(_parent.Name)</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="insert" HelpText="Select the location where you would like the page to be inserted in relation to other pages" ResourceKey="Insert">Insert: </Label>
<div class="col-sm-9">
<select id="insert" class="form-select" @bind="@_insert" required>
<option value=">>">@Localizer["AtEnd"]</option>
</select>
</div>
</div>
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
<div class="col-sm-9">
@ -85,9 +110,27 @@
<input id="url" class="form-control" @bind="@_url" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
<div class="col-sm-8">
<InputList Value="@_icon" ValueChanged="IconChanged" DataList="@_icons" ResourceKey="Icon" ResourceType="@_iconresources" />
</div>
<div class="col-sm-1">
<i class="@_icon"></i>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label>
<div class="col-sm-9">
<select id="personalizable" class="form-select" @bind="@_ispersonalizable" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
<Section Name="Appearance" ResourceKey="Appearance">
<Section Name="Appearance" ResourceKey="Appearance" Heading=@Localizer["Appearance.Name"]>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
@ -95,12 +138,6 @@
<input id="title" class="form-control" @bind="@_title" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="meta" HelpText="Optionally enter meta tags (in exactly the form you want them to be included in the page output)." ResourceKey="Meta">Meta: </Label>
<div class="col-sm-9">
<textarea id="meta" class="form-control" @bind="@_meta" rows="3"></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
<div class="col-sm-9">
@ -116,7 +153,6 @@
<Label Class="col-sm-3" For="container" HelpText="Select the default container for the page" ResourceKey="DefaultContainer">Default Container: </Label>
<div class="col-sm-9">
<select id="container" class="form-select" @bind="@_containertype" required>
<option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
@ -124,90 +160,124 @@
</select>
</div>
</div>
</div>
</Section>
<Section Name="PageContent" ResourceKey="PageContent" Heading=@Localizer["PageContent.Heading"]>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
<Label Class="col-sm-3" For="headcontent" HelpText="Optionally enter content to be included in the page head (ie. meta, link, or script tags)" ResourceKey="HeadContent">Head Content: </Label>
<div class="col-sm-9">
<input id="icon" class="form-control" @bind="@_icon" />
<textarea id="headcontent" class="form-control" @bind="@_headcontent" rows="3"></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label>
<Label Class="col-sm-3" For="bodycontent" HelpText="Optionally enter content to be included in the page body (ie. script tags)" ResourceKey="BodyContent">Body Content: </Label>
<div class="col-sm-9">
<select id="personalizable" class="form-select" @bind="@_ispersonalizable" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
<textarea id="bodycontent" class="form-control" @bind="@_bodycontent" rows="3"></textarea>
</div>
</div>
</div>
</Section>
}
</TabPanel>
<TabPanel Name="Permissions" ResourceKey="Permissions">
<div class="container">
<div class="row mb-1 align-items-center">
<PermissionGrid EntityName="@EntityNames.Page" Permissions="@_permissions" @ref="_permissionGrid" />
</div>
</div>
</TabPanel>
@if (_themeSettingsType != null)
{
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
@ThemeSettingsComponent
</TabPanel>
}
</TabStrip>
<br />
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</form>
<TabPanel Name="Permissions" ResourceKey="Permissions" Heading=@Localizer["Permissions.Heading"]>
<div class="container">
<div class="row mb-1 align-items-center">
<PermissionGrid EntityName="@EntityNames.Page" Permissions="@_permissions" @ref="_permissionGrid" />
</div>
</div>
</TabPanel>
@if (_themeSettingsType != null)
{
<TabPanel Name="ThemeSettings" Heading=@Localizer["Theme.Heading"] ResourceKey="ThemeSettings">
@ThemeSettingsComponent
</TabPanel>
}
</TabStrip>
<br />
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</form>
}
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
private List<Theme> _themeList;
private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private string _name;
private string _title;
private string _meta;
private string _path = string.Empty;
private string _parentid = "-1";
private string _insert = ">>";
private List<Page> _children;
private int _childid = -1;
private string _isnavigation = "True";
private string _isclickable = "True";
private string _url;
private string _ispersonalizable = "False";
private string _themetype = string.Empty;
private string _containertype = string.Empty;
private string _icon = string.Empty;
private string _permissions = null;
private PermissionGrid _permissionGrid;
private Type _themeSettingsType;
private object _themeSettings;
private RenderFragment ThemeSettingsComponent { get; set; }
private bool _refresh = false;
private ElementReference form;
private bool validated = false;
private bool _initialized = false;
private ElementReference form;
private bool validated = false;
private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private int _pageId;
private string _name;
private string _parentid = "-1";
private string _insert = ">>";
private List<Page> _children;
private int _childid = -1;
private string _isnavigation = "True";
private string _isclickable = "True";
private string _path = string.Empty;
private string _url;
private string _ispersonalizable = "False";
private string _title;
private string _icon = string.Empty;
private string _themetype = string.Empty;
private string _containertype = string.Empty;
private string _headcontent;
private string _bodycontent;
private string _permissions = null;
private PermissionGrid _permissionGrid;
private Type _themeSettingsType;
private object _themeSettings;
private RenderFragment ThemeSettingsComponent { get; set; }
private bool _refresh = false;
protected Page _parent = null;
protected Dictionary<string, string> _icons;
private string _iconresources = "";
protected override async Task OnInitializedAsync()
{
try
{
_themeList = await ThemeService.GetThemesAsync();
_themes = ThemeService.GetThemeControls(_themeList);
_themetype = PageState.Site.DefaultThemeType;
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containertype = PageState.Site.DefaultContainerType;
_children = PageState.Pages.Where(item => item.ParentId == null).ToList();
ThemeSettings();
}
protected override async Task OnInitializedAsync()
{
try
{
if (PageState.QueryString.ContainsKey("id"))
{
_pageId = Int32.Parse(PageState.QueryString["id"]);
_parent = await PageService.GetPageAsync(_pageId);
if (_parent != null)
{
_parentid = _parent.PageId.ToString();
}
}
_icons = await SystemService.GetIconsAsync();
_iconresources = typeof(IconResources).FullName;
// if admin or user has edit access to parent page
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) || (_parent != null && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, _parent.PermissionList)))
{
_themetype = PageState.Site.DefaultThemeType;
_themes = ThemeService.GetThemeControls(PageState.Site.Themes);
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
_containertype = PageState.Site.DefaultContainerType;
_children = new List<Page>();
foreach (Page p in PageState.Pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{
_children.Add(p);
}
}
ThemeSettings();
_initialized = true;
}
else
{
await logger.LogWarning("Error Loading Page {ParentId}", _parentid);
AddModuleMessage(Localizer["Error.Page.Load"], MessageType.Error);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Initializing Page {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Page.Initialize"], MessageType.Error);
await logger.LogError(ex, "Error Loading Page {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Page.Load"], MessageType.Error);
}
}
@ -216,28 +286,15 @@
try
{
_parentid = (string)e.Value;
_children = new List<Page>();
if (_parentid == "-1")
{
foreach (Page p in PageState.Pages.Where(item => item.ParentId == null))
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{
_children.Add(p);
}
}
}
else
{
foreach (Page p in PageState.Pages.Where(item => item.ParentId == int.Parse(_parentid)))
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{
_children.Add(p);
}
}
}
StateHasChanged();
_children = new List<Page>();
foreach (Page p in PageState.Pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{
_children.Add(p);
}
}
StateHasChanged();
}
catch (Exception ex)
{
@ -246,27 +303,23 @@
}
}
private async void ThemeChanged(ChangeEventArgs e)
{
try
{
_themetype = (string)e.Value;
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containertype = "-";
ThemeSettings();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Pane Layouts For Theme {ThemeType} {Error}", _themetype, ex.Message);
AddModuleMessage(Localizer["Error.Pane.Load"], MessageType.Error);
}
}
private void ThemeChanged(ChangeEventArgs e)
{
_themetype = (string)e.Value;
if (ThemeService.GetTheme(PageState.Site.Themes, _themetype)?.ThemeName != ThemeService.GetTheme(PageState.Site.Themes, PageState.Site.DefaultThemeType)?.ThemeName)
{
AddModuleMessage(Localizer["ThemeChanged.Message"], MessageType.Warning);
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
_containertype = _containers.First().TypeName;
ThemeSettings();
StateHasChanged();
}
}
private void ThemeSettings()
{
_themeSettingsType = null;
var theme = _themeList.FirstOrDefault(item => item.Themes.Any(themecontrol => themecontrol.TypeName.Equals(_themetype)));
var theme = PageState.Site.Themes.FirstOrDefault(item => item.Themes.Any(themecontrol => themecontrol.TypeName.Equals(_themetype)));
if (theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType))
{
_themeSettingsType = Type.GetType(theme.ThemeSettingsType);
@ -292,12 +345,11 @@
Page page = null;
try
{
if (!string.IsNullOrEmpty(_themetype) && _containertype != "-")
if (!string.IsNullOrEmpty(_themetype) && !string.IsNullOrEmpty(_containertype))
{
page = new Page();
page.SiteId = PageState.Page.SiteId;
page.Name = _name;
page.Title = _title;
if (string.IsNullOrEmpty(_path))
{
@ -365,34 +417,42 @@
page.IsNavigation = (_isnavigation == null ? true : Boolean.Parse(_isnavigation));
page.IsClickable = (_isclickable == null ? true : Boolean.Parse(_isclickable));
page.Url = _url;
page.ThemeType = (_themetype != "-") ? _themetype : string.Empty;
page.Url = _url;
page.IsPersonalizable = (_ispersonalizable == null ? false : Boolean.Parse(_ispersonalizable));
page.UserId = null;
// appearance
page.Title = _title;
page.Icon = (_icon == null ? string.Empty : _icon);
page.ThemeType = _themetype;
if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType)
{
page.ThemeType = string.Empty;
}
page.DefaultContainerType = (_containertype != "-") ? _containertype : string.Empty;
page.DefaultContainerType = _containertype;
if (!string.IsNullOrEmpty(page.DefaultContainerType) && page.DefaultContainerType == PageState.Site.DefaultContainerType)
{
page.DefaultContainerType = string.Empty;
}
page.Icon = (_icon == null ? string.Empty : _icon);
// page content
page.HeadContent = _headcontent;
page.BodyContent = _bodycontent;
// permissions
page.PermissionList = _permissionGrid.GetPermissionList();
page.IsPersonalizable = (_ispersonalizable == null ? false : Boolean.Parse(_ispersonalizable));
page.UserId = null;
page.Meta = _meta;
page = await PageService.AddPageAsync(page);
await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, page.ParentId);
await logger.LogInformation("Page Added {Page}", page);
if (PageState.QueryString.ContainsKey("cp"))
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
{
NavigationManager.NavigateTo(NavigateUrl(PageState.Pages.First(item => item.PageId == int.Parse(PageState.QueryString["cp"])).Path));
NavigationManager.NavigateTo(page.Path); // redirect to new page
}
else
{
NavigationManager.NavigateTo(NavigateUrl(page.Path));
NavigationManager.NavigateTo(NavigateUrl()); // redirect to page management
}
}
else
@ -415,13 +475,17 @@
private void Cancel()
{
if (PageState.QueryString.ContainsKey("cp"))
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
{
NavigationManager.NavigateTo(NavigateUrl(PageState.Pages.First(item => item.PageId == int.Parse(PageState.QueryString["cp"])).Path));
NavigationManager.NavigateTo(PageState.ReturnUrl);
}
else
{
NavigationManager.NavigateTo(NavigateUrl());
}
}
private void IconChanged(string NewIcon)
{
_icon = NewIcon;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (PageState.Pages != null)
@if (PageState.Pages != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
<ActionLink Action="Add" Text="Add Page" ResourceKey="AddPage" />

View File

@ -64,6 +64,12 @@
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="validation" HelpText="Optionally provide a regular expression (RegExp) for validating the value entered" ResourceKey="Validation">Validation: </Label>
<div class="col-sm-9">
<input id="validation" class="form-control" @bind="@_validation" maxlength="200" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="private" HelpText="Should this profile item be visible to all users?" ResourceKey="Private">Private? </Label>
<div class="col-sm-9">
@ -97,6 +103,7 @@
private string _maxlength = "0";
private string _defaultvalue = string.Empty;
private string _options = string.Empty;
private string _validation = string.Empty;
private string _isrequired = "False";
private string _isprivate = "False";
private string createdby;
@ -126,6 +133,7 @@
_maxlength = profile.MaxLength.ToString();
_defaultvalue = profile.DefaultValue;
_options = profile.Options;
_validation = profile.Validation;
_isrequired = profile.IsRequired.ToString();
_isprivate = profile.IsPrivate.ToString();
createdby = profile.CreatedBy;
@ -169,6 +177,7 @@
profile.MaxLength = int.Parse(_maxlength);
profile.DefaultValue = _defaultvalue;
profile.Options = _options;
profile.Validation = _validation;
profile.IsRequired = (_isrequired == null ? false : Boolean.Parse(_isrequired));
profile.IsPrivate = (_isprivate == null ? false : Boolean.Parse(_isprivate));
if (_profileid != -1)

View File

@ -17,11 +17,17 @@ else
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th>
<th>@Localizer["Title"]</th>
<th>@Localizer["Category"]</th>
<th>@Localizer["Order"]</th>
</Header>
<Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ProfileId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="EditProfile" /></td>
<td><ActionDialog Header="Delete Profile" Message="@string.Format(Localizer["Confirm.Profile.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteProfile(context.ProfileId))" ResourceKey="DeleteProfile" /></td>
<td>@context.Name</td>
<td>@context.Title</td>
<td>@context.Category</td>
<td>@context.ViewOrder</td>
</Row>
</Pager>
}

View File

@ -14,7 +14,7 @@
else
{
<TabStrip>
<TabPanel Name="Pages" ResourceKey="Pages">
<TabPanel Name="Pages" ResourceKey="Pages" Heading="Pages">
@if (!_pages.Where(item => item.IsDeleted).Any())
{
<br />
@ -31,7 +31,7 @@ else
<th>@Localizer["DeletedOn"]</th>
</Header>
<Row>
<td><button type="button" @onclick="@(() => RestorePage(context))" class="btn btn-success" title="Restore">Restore</button></td>
<td><button type="button" @onclick="@(() => RestorePage(context))" class="btn btn-success" title="Restore">@Localizer["Restore"]</button></td>
<td><ActionDialog Header="Delete Page" Message="@string.Format(Localizer["Confirm.Page.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
<td>@context.Name</td>
<td>@context.DeletedBy</td>
@ -42,7 +42,7 @@ else
<ActionDialog Header="Remove All Deleted Pages" Message="Are You Sure You Wish To Permanently Remove All Deleted Pages?" Action="Remove All Deleted Pages" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllPages())" ResourceKey="DeleteAllPages" />
}
</TabPanel>
<TabPanel Name="Modules" ResourceKey="Modules">
<TabPanel Name="Modules" ResourceKey="Modules" Heading="Modules">
@if (!_modules.Where(item => item.IsDeleted).Any())
{
<br />
@ -76,8 +76,8 @@ else
}
@code {
private List<Page> _pages;
private List<Module> _modules;
private List<Page> _pages;
private List<Module> _modules;
private int _pagePage = 1;
private int _pageModule = 1;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
@ -184,13 +184,6 @@ else
try
{
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
// check if there are any remaining module instances in the site
if (!_modules.Exists (item => item.ModuleId == module.ModuleId && item.PageModuleId != module.PageModuleId))
{
await ModuleService.DeleteModuleAsync(module.ModuleId);
}
await logger.LogInformation("Module Permanently Deleted {Module}", module);
await Load();
StateHasChanged();
@ -210,16 +203,7 @@ else
foreach (Module module in _modules.Where(item => item.IsDeleted).ToList())
{
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
// DeletePageModuleAsync does not update _modules so remove it.
_modules.Remove(module);
// check if there are any remaining module instances in the site
if (!_modules.Exists(item => item.ModuleId == module.ModuleId && item.PageModuleId != module.PageModuleId))
{
await ModuleService.DeleteModuleAsync(module.ModuleId);
}
}
await logger.LogInformation("Modules Permanently Deleted");
await Load();
ModuleInstance.HideProgressIndicator();

View File

@ -16,7 +16,7 @@
<ModuleMessage Message="@Localizer["Info.Registration.Exists"]" Type="MessageType.Info" />
</Authorized>
<NotAuthorized>
<ModuleMessage Message="@_passwordconstruction" Type="MessageType.Info" />
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
@ -30,7 +30,7 @@
<div class="col-sm-9">
<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>
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
@ -39,7 +39,7 @@
<div class="col-sm-9">
<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>
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
@ -69,6 +69,7 @@ else
}
@code {
private string _passwordrequirements;
private string _username = string.Empty;
private ElementReference form;
private bool validated = false;
@ -79,44 +80,16 @@ else
private string _email = string.Empty;
private string _displayname = string.Empty;
//Password construction
private string _minimumlength;
private string _uniquecharacters;
private bool _requiredigit;
private bool _requireupper;
private bool _requirelower;
private bool _requirepunctuation;
private string _passwordconstruction;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
protected override async Task OnInitializedAsync()
{
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
_minimumlength = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredLength", "6");
_uniquecharacters = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", "1");
_requiredigit = bool.Parse(SettingService.GetSetting(settings, "IdentityOptions:Password:RequireDigit", "true"));
_requireupper = bool.Parse(SettingService.GetSetting(settings, "IdentityOptions:Password:RequireUppercase", "true"));
_requirelower = bool.Parse(SettingService.GetSetting(settings, "IdentityOptions:Password:RequireLowercase", "true"));
_requirepunctuation = bool.Parse(SettingService.GetSetting(settings, "IdentityOptions:Password:RequireNonAlphanumeric", "true"));
// Replace the placeholders with the actual values of the variables
string digitRequirement = _requiredigit ? Localizer["Password.DigitRequirement"] + ", " : "";
string uppercaseRequirement = _requireupper ? Localizer["Password.UppercaseRequirement"] + ", " : "";
string lowercaseRequirement = _requirelower ? Localizer["Password.LowercaseRequirement"] + ", " : "";
string punctuationRequirement = _requirepunctuation ? Localizer["Password.PunctuationRequirement"] + ", " : "";
// Replace the placeholders with the actual values of the variables
string passwordValidationCriteriaTemplate = Localizer["Password.ValidationCriteria"];
_passwordconstruction = Localizer["Info.Registration.InvalidEmail"] + ". " + string.Format(passwordValidationCriteriaTemplate,
_minimumlength, _uniquecharacters, digitRequirement, uppercaseRequirement, lowercaseRequirement, punctuationRequirement);
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
}
protected override void OnParametersSet()
{
_togglepassword = SharedLocalizer["ShowPassword"];
_togglepassword = SharedLocalizer["ShowPassword"];
}
private async Task Register()

View File

@ -6,6 +6,7 @@
@inject IStringLocalizer<SharedResources> SharedLocalizer
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="username" HelpText="Your username will be populated from the link you received in the password reset notification" ResourceKey="Username">Username: </Label>
@ -18,7 +19,7 @@
<div class="col-sm-9">
<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>
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
@ -27,7 +28,7 @@
<div class="col-sm-9">
<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>
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
@ -45,12 +46,14 @@
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private string _confirm = string.Empty;
private string _passwordrequirements;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
protected override async Task OnInitializedAsync()
{
_togglepassword = SharedLocalizer["ShowPassword"];
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
if (PageState.QueryString.ContainsKey("name") && PageState.QueryString.ContainsKey("token"))
{

View File

@ -22,60 +22,11 @@
<input id="name" class="form-control" @bind="@_name" maxlength="200" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="logo" HelpText="Specify a logo for the site" ResourceKey="Logo">Logo: </Label>
<div class="col-sm-9">
<FileManager FileId="@_logofileid" Filter="@Constants.ImageFiles" @ref="_logofilemanager" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="favicon" HelpText="Specify a Favicon" ResourceKey="FavoriteIcon">Favicon: </Label>
<div class="col-sm-9">
<FileManager FileId="@_faviconfileid" Filter="ico" @ref="_faviconfilemanager" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultTheme" HelpText="Select the sites default theme" ResourceKey="DefaultTheme">Default Theme: </Label>
<div class="col-sm-9">
<select id="defaultTheme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
<option value="-">&lt;@Localizer["Theme.Select"]&gt;</option>
@foreach (var theme in _themes)
{
<option value="@theme.TypeName">@theme.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label>
<div class="col-sm-9">
<select id="defaultContainer" class="form-select" @bind="@_containertype" required>
<option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultAdminContainer" HelpText="Select the default admin container for the site" ResourceKey="DefaultAdminContainer">Default Admin Container: </Label>
<div class="col-sm-9">
<select id="defaultAdminContainer" class="form-select" @bind="@_admincontainertype" required>
<option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
<option value="@Constants.DefaultAdminContainer">&lt;@Localizer["DefaultAdminContainer"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</div>
</div>
<div 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>
<option value="-">&lt;@SharedLocalizer["Not Specified"]&gt;</option>
@foreach (Page page in PageState.Pages)
{
if (UserSecurity.ContainsRole(page.PermissionList, PermissionNames.View, RoleNames.Everyone))
@ -98,10 +49,92 @@
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="sitemap" HelpText="The site map url for this site which can be submitted to search engines for indexing" ResourceKey="SiteMap">Site Map: </Label>
<div class="col-sm-9">
<input id="sitemap" class="form-control" @bind="@_sitemap" required disabled />
</div>
<div class="input-group">
<input id="sitemap" class="form-control" @bind="@_sitemap" disabled />
<a href="@_sitemap" class="btn btn-secondary" target="_new">@Localizer["Browse"]</a>
</div>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="siteguid" HelpText="The Unique Identifier For The Site" ResourceKey="SiteGuid">ID: </Label>
<div class="col-sm-9">
<input id="siteguid" class="form-control" @bind="@_siteguid" required disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="version" HelpText="The site version (for site content migrations)" ResourceKey="Version">Version: </Label>
<div class="col-sm-9">
<input id="version" class="form-control" @bind="@_version" required disabled />
</div>
</div>
</div>
<br />
<Section Name="Appearance" Heading="Appearance" ResourceKey="Appearance">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="logo" HelpText="Specify a logo for the site" ResourceKey="Logo">Logo: </Label>
<div class="col-sm-9">
<FileManager FileId="@_logofileid" Filter="@Constants.ImageFiles" @ref="_logofilemanager" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="favicon" HelpText="Specify a Favicon" ResourceKey="FavoriteIcon">Favicon: </Label>
<div class="col-sm-9">
<FileManager FileId="@_faviconfileid" Filter="ico,png,gif" @ref="_faviconfilemanager" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultTheme" HelpText="Select the sites default theme" ResourceKey="DefaultTheme">Default Theme: </Label>
<div class="col-sm-9">
<select id="defaultTheme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
@foreach (var theme in _themes)
{
<option value="@theme.TypeName">@theme.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label>
<div class="col-sm-9">
<select id="defaultContainer" class="form-select" @bind="@_containertype" required>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultAdminContainer" HelpText="Select the default admin container for the site" ResourceKey="DefaultAdminContainer">Default Admin Container: </Label>
<div class="col-sm-9">
<select id="defaultAdminContainer" class="form-select" @bind="@_admincontainertype" required>
<option value="@Constants.DefaultAdminContainer">&lt;@Localizer["DefaultAdminContainer"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</div>
</div>
</div>
</Section>
<Section Name="PageContent" Heading="Page Content" ResourceKey="PageContent">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="headcontent" HelpText="Optionally enter content to be included in the page head (ie. meta, link, or script tags)" ResourceKey="HeadContent">Head Content: </Label>
<div class="col-sm-9">
<textarea id="headcontent" class="form-control" @bind="@_headcontent" rows="3"></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="bodycontent" HelpText="Optionally enter content to be included in the page body (ie. script tags)" ResourceKey="BodyContent">Body Content: </Label>
<div class="col-sm-9">
<textarea id="bodycontent" class="form-control" @bind="@_bodycontent" rows="3"></textarea>
</div>
</div>
</div>
</Section>
<Section Name="SMTP" Heading="SMTP Settings" ResourceKey="SMTPSettings">
<div class="container">
<div class="row mb-1 align-items-center">
@ -124,9 +157,9 @@
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="enabledSSl" HelpText="Specify if SSL is required for your SMTP server" ResourceKey="UseSsl">SSL Enabled: </Label>
<Label Class="col-sm-3" For="smtpssl" HelpText="Specify if SSL is required for your SMTP server" ResourceKey="UseSsl">SSL Enabled: </Label>
<div class="col-sm-9">
<select id="enabledSSl" class="form-select" @bind="@_smtpssl" >
<select id="smtpssl" class="form-select" @bind="@_smtpssl" >
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
@ -143,7 +176,7 @@
<div class="col-sm-9">
<div class="input-group">
<input id="password" type="@_smtppasswordtype" class="form-control" @bind="@_smtppassword" />
<button type="button" class="btn btn-secondary" @onclick="@ToggleSMTPPassword">@_togglesmtppassword</button>
<button type="button" class="btn btn-secondary" @onclick="@ToggleSMTPPassword" tabindex="-1">@_togglesmtppassword</button>
</div>
</div>
</div>
@ -162,7 +195,16 @@
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="smtpenabled" HelpText="Specify if SMTP is enabled for this site" ResourceKey="SMTPEnabled">Enabled? </Label>
<div class="col-sm-9">
<select id="smtpenabled" class="form-select" @bind="@_smtpenabled">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="retention" HelpText="Number of days of notifications to retain" ResourceKey="Retention">Retention (Days): </Label>
<div class="col-sm-9">
<input id="retention" class="form-control" @bind="@_retention" />
@ -201,53 +243,53 @@
{
<Section Name="Aliases" Heading="Aliases" ResourceKey="Aliases">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="aliases" HelpText="The list of aliases for this site" ResourceKey="Aliases">Aliases: </Label>
<div class="col-sm-9">
<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)
{
<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 class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="aliases" HelpText="The urls for the site. This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or virtual folders (ie. domain.com/folder)." ResourceKey="Aliases">Aliases: </Label>
<div class="col-sm-9">
<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)
{
<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="DeleteAlias" 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="defaultalias" 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>
</Section>
<Section Name="Hosting" Heading="Hosting Model" ResourceKey="Hosting">
@ -306,222 +348,236 @@
}
@code {
private ElementReference form;
private bool validated = false;
private bool _initialized = false;
private List<Theme> _themeList;
private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private string _name = string.Empty;
private List<Alias> _aliases;
private int _aliasid = -1;
private string _aliasname;
private string _defaultalias;
private string _runtime = "";
private string _prerender = "";
private int _logofileid = -1;
private FileManager _logofilemanager;
private int _faviconfileid = -1;
private FileManager _faviconfilemanager;
private string _themetype = "-";
private string _containertype = "-";
private string _admincontainertype = "-";
private string _homepageid = "-";
private string _sitemap = "";
private string _smtphost = string.Empty;
private string _smtpport = string.Empty;
private string _smtpssl = "False";
private string _smtpusername = string.Empty;
private string _smtppassword = string.Empty;
private string _smtppasswordtype = "password";
private string _togglesmtppassword = string.Empty;
private string _smtpsender = string.Empty;
private string _smtprelay = "False";
private string _retention = string.Empty;
private string _pwaisenabled;
private int _pwaappiconfileid = -1;
private FileManager _pwaappiconfilemanager;
private int _pwasplashiconfileid = -1;
private FileManager _pwasplashiconfilemanager;
private string _tenant = string.Empty;
private string _database = string.Empty;
private string _connectionstring = string.Empty;
private string _createdby;
private DateTime _createdon;
private string _modifiedby;
private DateTime _modifiedon;
private string _deletedby;
private DateTime? _deletedon;
private string _isdeleted;
private ElementReference form;
private bool validated = false;
private bool _initialized = false;
private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private string _name = string.Empty;
private string _homepageid = "-";
private string _isdeleted;
private string _sitemap = "";
private string _siteguid = "";
private string _version = "";
private int _logofileid = -1;
private FileManager _logofilemanager;
private int _faviconfileid = -1;
private FileManager _faviconfilemanager;
private string _themetype = "";
private string _containertype = "";
private string _admincontainertype = "";
private string _headcontent = string.Empty;
private string _bodycontent = string.Empty;
private string _smtphost = string.Empty;
private string _smtpport = string.Empty;
private string _smtpssl = "False";
private string _smtpusername = string.Empty;
private string _smtppassword = string.Empty;
private string _smtppasswordtype = "password";
private string _togglesmtppassword = string.Empty;
private string _smtpsender = string.Empty;
private string _smtprelay = "False";
private string _smtpenabled = "True";
private string _retention = string.Empty;
private string _pwaisenabled;
private int _pwaappiconfileid = -1;
private FileManager _pwaappiconfilemanager;
private int _pwasplashiconfileid = -1;
private FileManager _pwasplashiconfilemanager;
private List<Alias> _aliases;
private int _aliasid = -1;
private string _aliasname;
private string _defaultalias;
private string _runtime = "";
private string _prerender = "";
private string _tenant = string.Empty;
private string _database = string.Empty;
private string _connectionstring = string.Empty;
private string _createdby;
private DateTime _createdon;
private string _modifiedby;
private DateTime _modifiedon;
private string _deletedby;
private DateTime? _deletedon;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnInitializedAsync()
{
try
{
_themeList = await ThemeService.GetThemesAsync();
Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null)
{
_name = site.Name;
_runtime = site.Runtime;
_prerender = site.RenderMode.Replace(_runtime, "");
_isdeleted = site.IsDeleted.ToString();
_sitemap = PageState.Alias.Protocol + PageState.Alias.Name + "/pages/sitemap.xml";
protected override async Task OnInitializedAsync()
{
try
{
Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null)
{
_name = site.Name;
if (site.HomePageId != null)
{
_homepageid = site.HomePageId.Value.ToString();
}
_isdeleted = site.IsDeleted.ToString();
_sitemap = PageState.Alias.Protocol + PageState.Alias.Name + "/pages/sitemap.xml";
_siteguid = site.SiteGuid;
_version = site.Version;
await GetAliases();
// appearance
if (site.LogoFileId != null)
{
_logofileid = site.LogoFileId.Value;
}
if (site.LogoFileId != null)
{
_logofileid = site.LogoFileId.Value;
}
if (site.FaviconFileId != null)
{
_faviconfileid = site.FaviconFileId.Value;
}
_themes = ThemeService.GetThemeControls(PageState.Site.Themes);
_themetype = (!string.IsNullOrEmpty(site.DefaultThemeType)) ? site.DefaultThemeType : Constants.DefaultTheme;
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
_containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer;
_admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer;
if (site.FaviconFileId != null)
{
_faviconfileid = site.FaviconFileId.Value;
}
// page content
_headcontent = site.HeadContent;
_bodycontent = site.BodyContent;
_themes = ThemeService.GetThemeControls(_themeList);
_themetype = (!string.IsNullOrEmpty(site.DefaultThemeType)) ? site.DefaultThemeType : Constants.DefaultTheme;
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer;
_admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer;
// PWA
_pwaisenabled = site.PwaIsEnabled.ToString();
if (site.PwaAppIconFileId != null)
{
_pwaappiconfileid = site.PwaAppIconFileId.Value;
}
if (site.PwaSplashIconFileId != null)
{
_pwasplashiconfileid = site.PwaSplashIconFileId.Value;
}
if (site.HomePageId != null)
{
_homepageid = site.HomePageId.Value.ToString();
}
// SMTP
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
_smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty);
_smtpport = SettingService.GetSetting(settings, "SMTPPort", string.Empty);
_smtpssl = SettingService.GetSetting(settings, "SMTPSSL", "False");
_smtpusername = SettingService.GetSetting(settings, "SMTPUsername", string.Empty);
_smtppassword = SettingService.GetSetting(settings, "SMTPPassword", string.Empty);
_togglesmtppassword = SharedLocalizer["ShowPassword"];
_smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty);
_smtprelay = SettingService.GetSetting(settings, "SMTPRelay", "False");
_smtpenabled = SettingService.GetSetting(settings, "SMTPEnabled", "True");
_retention = SettingService.GetSetting(settings, "NotificationRetention", "30");
_pwaisenabled = site.PwaIsEnabled.ToString();
if (site.PwaAppIconFileId != null)
{
_pwaappiconfileid = site.PwaAppIconFileId.Value;
}
if (site.PwaSplashIconFileId != null)
{
_pwasplashiconfileid = site.PwaSplashIconFileId.Value;
}
// aliases
await GetAliases();
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
_smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty);
_smtpport = SettingService.GetSetting(settings, "SMTPPort", string.Empty);
_smtpssl = SettingService.GetSetting(settings, "SMTPSSL", "False");
_smtpusername = SettingService.GetSetting(settings, "SMTPUsername", string.Empty);
_smtppassword = SettingService.GetSetting(settings, "SMTPPassword", string.Empty);
_togglesmtppassword = SharedLocalizer["ShowPassword"];
_smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty);
_smtprelay = SettingService.GetSetting(settings, "SMTPRelay", "False");
_retention = SettingService.GetSetting(settings, "NotificationRetention", "30");
// hosting model
_runtime = site.Runtime;
_prerender = site.RenderMode.Replace(_runtime, "");
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
var tenants = await TenantService.GetTenantsAsync();
var _databases = await DatabaseService.GetDatabasesAsync();
var tenant = tenants.Find(item => item.TenantId == site.TenantId);
if (tenant != null)
{
_tenant = tenant.Name;
_database = _databases.Find(item => item.DBType == tenant.DBType)?.Name;
_connectionstring = tenant.DBConnectionString;
}
}
// database
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
var tenants = await TenantService.GetTenantsAsync();
var _databases = await DatabaseService.GetDatabasesAsync();
var tenant = tenants.Find(item => item.TenantId == site.TenantId);
if (tenant != null)
{
_tenant = tenant.Name;
_database = _databases.Find(item => item.DBType == tenant.DBType)?.Name;
_connectionstring = tenant.DBConnectionString;
}
}
_createdby = site.CreatedBy;
_createdon = site.CreatedOn;
_modifiedby = site.ModifiedBy;
_modifiedon = site.ModifiedOn;
_deletedby = site.DeletedBy;
_deletedon = site.DeletedOn;
// audit
_createdby = site.CreatedBy;
_createdon = site.CreatedOn;
_modifiedby = site.ModifiedBy;
_modifiedon = site.ModifiedOn;
_deletedby = site.DeletedBy;
_deletedon = site.DeletedOn;
_initialized = true;
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message);
AddModuleMessage(ex.Message, MessageType.Error);
}
}
_initialized = true;
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message);
AddModuleMessage(ex.Message, MessageType.Error);
}
}
private async void ThemeChanged(ChangeEventArgs e)
{
try
{
_themetype = (string)e.Value;
if (_themetype != "-")
{
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
}
else
{
_containers = new List<ThemeControl>();
}
_containertype = "-";
_admincontainertype = Constants.DefaultAdminContainer;
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Containers For Theme {ThemeType} {Error}", _themetype, ex.Message);
AddModuleMessage(Localizer["Error.Theme.LoadPane"], MessageType.Error);
}
}
private async void ThemeChanged(ChangeEventArgs e)
{
try
{
_themetype = (string)e.Value;
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
_containertype = _containers.First().TypeName;
_admincontainertype = Constants.DefaultAdminContainer;
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Containers For Theme {ThemeType} {Error}", _themetype, ex.Message);
AddModuleMessage(Localizer["Error.Theme.LoadPane"], MessageType.Error);
}
}
private async Task SaveSite()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
try
{
if (_name != string.Empty && _themetype != "-" && _containertype != "-")
{
var site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null)
{
bool refresh = false;
bool reload = false;
private async Task SaveSite()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
try
{
if (_name != string.Empty && _themetype != "-" && _containertype != "-")
{
var site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null)
{
bool refresh = false;
bool reload = false;
site.Name = _name;
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
if (site.Runtime != _runtime || site.RenderMode != _runtime + _prerender)
{
site.Runtime = _runtime;
site.RenderMode = _runtime + _prerender;
reload = true; // needs to be reloaded on server
}
}
site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
site.Name = _name;
site.HomePageId = (_homepageid != "-" ? int.Parse(_homepageid) : null);
site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
site.LogoFileId = null;
var logofileid = _logofilemanager.GetFileId();
if (logofileid != -1)
{
site.LogoFileId = logofileid;
}
int? faviconFieldId = _faviconfilemanager.GetFileId();
if (faviconFieldId == -1) faviconFieldId = null;
if (site.FaviconFileId != faviconFieldId)
{
site.FaviconFileId = faviconFieldId;
reload = true; // needs to be reloaded on server
}
if (site.DefaultThemeType != _themetype)
{
site.DefaultThemeType = _themetype;
refresh = true; // needs to be refreshed on client
}
if (site.DefaultContainerType != _containertype)
{
site.DefaultContainerType = _containertype;
refresh = true; // needs to be refreshed on client
}
site.AdminContainerType = _admincontainertype;
site.HomePageId = (_homepageid != "-" ? int.Parse(_homepageid) : null);
// appearance
site.LogoFileId = null;
var logofileid = _logofilemanager.GetFileId();
if (logofileid != -1)
{
site.LogoFileId = logofileid;
}
int? faviconFieldId = _faviconfilemanager.GetFileId();
if (faviconFieldId == -1) faviconFieldId = null;
if (site.FaviconFileId != faviconFieldId)
{
site.FaviconFileId = faviconFieldId;
reload = true; // needs to be reloaded on server
}
if (site.DefaultThemeType != _themetype)
{
site.DefaultThemeType = _themetype;
refresh = true; // needs to be refreshed on client
}
if (site.DefaultContainerType != _containertype)
{
site.DefaultContainerType = _containertype;
refresh = true; // needs to be refreshed on client
}
site.AdminContainerType = _admincontainertype;
// page content
if (site.HeadContent != _headcontent)
{
site.HeadContent = _headcontent;
reload = true;
}
if (site.BodyContent != _bodycontent)
{
site.BodyContent = _bodycontent;
reload = true;
}
// PWA
if (site.PwaIsEnabled.ToString() != _pwaisenabled)
{
site.PwaIsEnabled = Boolean.Parse(_pwaisenabled);
@ -542,17 +598,31 @@
reload = true; // needs to be reloaded on server
}
// hosting model
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
if (site.Runtime != _runtime || site.RenderMode != _runtime + _prerender)
{
site.Runtime = _runtime;
site.RenderMode = _runtime + _prerender;
reload = true; // needs to be reloaded on server
}
}
site = await SiteService.UpdateSiteAsync(site);
// SMTP
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
settings = SettingService.SetSetting(settings, "SMTPHost", _smtphost, true);
settings = SettingService.SetSetting(settings, "SMTPHost", _smtphost, true);
settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true);
settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true);
settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true);
settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true);
settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
settings = SettingService.SetSetting(settings, "SMTPRelay", _smtprelay, true);
settings = SettingService.SetSetting(settings, "NotificationRetention", _retention, true);
settings = SettingService.SetSetting(settings, "SMTPEnabled", _smtpenabled, true);
settings = SettingService.SetSetting(settings, "SiteGuid", _siteguid, true);
settings = SettingService.SetSetting(settings, "NotificationRetention", _retention, true);
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
await logger.LogInformation("Site Settings Saved {Site}", site);
@ -564,8 +634,8 @@
else
{
AddModuleMessage(Localizer["Success.Settings.SaveSite"], MessageType.Success);
await interop.ScrollTo(0, 0, "smooth");
}
await ScrollToPageTop();
}
}
}
else
@ -633,9 +703,8 @@
await NotificationService.AddNotificationAsync(new Notification(PageState.Site.SiteId, PageState.User, PageState.Site.Name + " SMTP Configuration Test", "SMTP Server Is Configured Correctly."));
AddModuleMessage(Localizer["Info.Smtp.SaveSettings"], MessageType.Info);
var interop = new Interop(JSRuntime);
await interop.ScrollTo(0, 0, "smooth");
}
await ScrollToPageTop();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Testing SMTP Configuration");

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)." ResourceKey="Aliases">Aliases: </Label>
<Label Class="col-sm-3" For="alias" HelpText="The urls for the site (comman delimited). This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or virtual folders (ie. domain.com/folder)." ResourceKey="Aliases">Urls: </Label>
<div class="col-sm-9">
<textarea id="alias" class="form-control" @bind="@_urls" rows="3" required></textarea>
</div>
@ -288,12 +288,13 @@ else
if (_themetype != "-")
{
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
}
_containertype = _containers.First().TypeName;
}
else
{
_containers = new List<ThemeControl>();
}
_containertype = "-";
_containertype = "-";
}
_admincontainertype = "";
StateHasChanged();
}

View File

@ -8,7 +8,7 @@
@if (_sites == null)
{
<p><em>Loading...</em></p>
<p><em>@SharedLocalizer["Loading"]</em></p>
}
else
{
@ -18,7 +18,7 @@ else
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th>
<th>@Localizer["AliasName"]</th>
</Header>
<Row>
<td><button type="button" class="btn btn-primary" @onclick="@(async () => Edit(context.Name))">@SharedLocalizer["Edit"]</button></td>

View File

@ -133,12 +133,9 @@
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="packageservice" HelpText="Specify If The Package Service Is Enabled For Installing Modules, Themes, And Translations" ResourceKey="PackageService">Enable Package Service? </Label>
<Label Class="col-sm-3" For="packageregistryurl" HelpText="Specify The Package Manager Service For Installing Modules, Themes, And Translations. If This Field Is Blank It Means The Package Manager Service Is Disabled For This Installation." ResourceKey="PackageManager">Package Manager: </Label>
<div class="col-sm-9">
<select id="packageservice" class="form-select" @bind="@_packageservice">
<option value="true">@SharedLocalizer["True"]</option>
<option value="false">@SharedLocalizer["False"]</option>
</select>
<input id="packageregistryurl" class="form-control" @bind="@_packageregistryurl" />
</div>
</div>
</div>
@ -182,7 +179,7 @@
private string _logginglevel = string.Empty;
private string _notificationlevel = string.Empty;
private string _swagger = string.Empty;
private string _packageservice = string.Empty;
private string _packageregistryurl = string.Empty;
private string _log = string.Empty;
@ -213,7 +210,7 @@
_logginglevel = systeminfo["Logging:LogLevel:Default"].ToString();
_notificationlevel = systeminfo["Logging:LogLevel:Notify"].ToString();
_swagger = systeminfo["UseSwagger"].ToString();
_packageservice = systeminfo["PackageService"].ToString();
_packageregistryurl = systeminfo["PackageRegistryUrl"].ToString();
}
systeminfo = await SystemService.GetSystemInfoAsync("log");
@ -232,7 +229,7 @@
settings.Add("Logging:LogLevel:Default", _logginglevel);
settings.Add("Logging:LogLevel:Notify", _notificationlevel);
settings.Add("UseSwagger", _swagger);
settings.Add("PackageService", _packageservice);
settings.Add("PackageRegistryUrl", _packageregistryurl);
await SystemService.UpdateSystemInfoAsync(settings);
AddModuleMessage(Localizer["Success.UpdateConfig.Restart"], MessageType.Success);
}

View File

@ -10,68 +10,122 @@
<TabStrip>
<TabPanel Name="Download" ResourceKey="Download">
<div class="row justify-content-center mb-3">
<div class="col-sm-6">
<div class="input-group">
<select id="price" class="form-select custom-select" @onchange="(e => PriceChanged(e))">
<option value="free">@SharedLocalizer["Free"]</option>
<option value="paid">@SharedLocalizer["Paid"]</option>
</select>
<input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" />
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
<div class="text-center">
<div class="form-check form-check-inline">
<input id="free" class="form-check-input" type="radio" checked="@(_price == "free")" name="Price" @onchange="@(() => PriceChanged("free"))" />
<label class="form-check-label" for="free">@SharedLocalizer["Free"]</label>
</div>
<div class="form-check form-check-inline">
<input id="paid" class="form-check-input" type="radio" checked="@(_price == "paid")" name="Price" @onchange="@(() => PriceChanged("paid"))" />
<label class="form-check-label" for="paid">@SharedLocalizer["Paid"]</label>
</div>
</div>
</div>
@if (_packages != null)
{
if (_packages.Count > 0)
{
<Pager Items="@_packages">
<Row>
<td>
<h3 style="display: inline;"><a href="@context.ProductUrl" target="_new">@context.Name</a></h3>&nbsp;&nbsp;@SharedLocalizer["Search.By"]:&nbsp;&nbsp;<strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
<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;">
@if (context.Price != null && !string.IsNullOrEmpty(context.PackageUrl))
{
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
}
</td>
<td style="width: 1px; vertical-align: middle;">
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
{
<a class="btn btn-primary" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@context.Price.Value.ToString("$#,##0.00")</a>
}
else
{
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
}
</td>
</Row>
</Pager>
}
else
{
<br />
<div class="mx-auto text-center">
@Localizer["Search.NoResults"]
<div class="row justify-content-center mb-3">
<div class="col">
<div class="input-group">
<span class="input-group-text">@Localizer["Product"]</span>
<input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" />
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
<button type="button" class="btn btn-primary ms-2" @onclick="Refresh"><span class="@Icons.Reload" aria-hidden="true"></span></button>
</div>
}
}
</div>
</div>
<div class="row mb-3">
<div class="col">
@if (_initialized)
{
<br />
<div class="row mb-3">
<div class="col-sm-4">
<h3>@((_packages != null) ? _packages.Count : 0) @SharedLocalizer["Search.Results"]</h3>
</div>
<div class="col-sm-4">
&nbsp;
</div>
<div class="col-sm-4">
<select class="form-select" value="@_sort" @onchange="(e => SortChanged(e))">
<option value="popularity">@SharedLocalizer["Search.Popularity"]</option>
<option value="alphabetical">@SharedLocalizer["Search.Alphabetical"]</option>
@if (_price == "free")
{
<option value="downloads">@SharedLocalizer["Search.Downloads"]</option>
}
<option value="recent">@SharedLocalizer["Search.RecentlyReleased"]</option>
@if (_price == "paid")
{
<option value="price">@SharedLocalizer["Search.Price"]</option>
}
</select>
</div>
</div>
<Pager Format="Grid" Items="@_packages" DisplayPages="1" PageSize="9" Toolbar="Both" Class="container-fluid px-0" RowClass="row g-0" ColumnClass="col-lg-4 col-md-6">
<Row>
<div class="m-2 p-2 d-flex justify-content-center">
<div class="container-fluid px-0">
<div class="row g-0 mb-2">
<div class="col-4">
@if (context.LogoUrl != null)
{
<img src="@context.LogoUrl" class="img-fluid" alt="@context.Name" />
}
else
{
<img src="/package.png" class="img-fluid" alt="@context.Name" />
}
</div>
<div class="col-8 text-end">
<small>@SharedLocalizer["Search.Version"]:</small> <strong>@context.Version</strong>
<br /><small>@SharedLocalizer["Search.Released"]:</small> <strong>@context.ReleaseDate.ToString("MM/dd/yyyy")</strong>
@if (!string.IsNullOrEmpty(context.PackageUrl))
{
<br /><small>@SharedLocalizer["Search.Source"]:</small> <strong>@(new Uri(context.PackageUrl).Host)</strong>
}
@if (context.Price == null)
{
<br /><small>@SharedLocalizer["Search.Downloads"]:</small> <strong>@(String.Format("{0:n0}", context.Downloads))</strong>
}
else
{
<br /><small>@SharedLocalizer["From"]:</small> <strong>@context.Price.Value.ToString("$#,##0.00")</strong>
@((MarkupString)(context.TrialPeriod > 0 ? " <strong>(" + context.TrialPeriod + " Day Trial)</strong>" : ""))
}
</div>
</div>
<div class="row g-0">
<div class="col">
<h3 style="display: inline;"><a href="@context.ProductUrl" target="_blank">@context.Name</a></h3><br />
<small>@SharedLocalizer["Search.By"]:</small> <strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
<br />
@if (!string.IsNullOrEmpty(context.PackageUrl))
{
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
}
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
{
<a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
}
<br />
</div>
</div>
</div>
</div>
</Row>
</Pager>
}
</div>
</div>
<br />
<ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" />
</TabPanel>
<TabPanel Name="Upload" ResourceKey="Upload">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" HelpText="Upload one or more theme packages. Once they are uploaded click Install to complete the installation." ResourceKey="Theme">Theme: </Label>
<div class="col-sm-9">
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" />
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" OnUpload="OnUpload" />
</div>
</div>
</div>
@ -111,16 +165,14 @@
</div>
}
<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 bool _initialized = false;
private int _page = 1;
private List<Package> _packages;
private string _price = "free";
private string _sort = "popularity";
private string _search = "";
private string _productname = "";
private string _license = "";
@ -134,6 +186,7 @@
try
{
await LoadThemes();
_initialized = true;
}
catch (Exception ex)
{
@ -144,8 +197,10 @@
private async Task LoadThemes()
{
ShowProgressIndicator();
var themes = await ThemeService.GetThemesAsync();
_packages = await PackageService.GetPackagesAsync("theme", _search, _price, "");
_packages = await PackageService.GetPackagesAsync("theme", _search, _price, "", _sort);
if (_packages != null)
{
@ -157,46 +212,44 @@
}
}
}
HideProgressIndicator();
}
private async void PriceChanged(ChangeEventArgs e)
private async void PriceChanged(string price)
{
try
{
_price = (string)e.Value;
_search = "";
await LoadThemes();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On PriceChanged");
}
_price = price;
_sort = "popularity";
await LoadThemes();
StateHasChanged();
}
private async Task Search()
{
try
{
await LoadThemes();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On Search");
}
await LoadThemes();
}
private async Task Reset()
{
try
{
_search = "";
await LoadThemes();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On Reset");
}
_page = 1;
_search = "";
await LoadThemes();
}
private async Task Refresh()
{
await LoadThemes();
}
private void OnPageChange(int page)
{
_page = page;
}
private async void SortChanged(ChangeEventArgs e)
{
_sort = (string)e.Value;
await LoadThemes();
}
private void HideModal()
@ -210,7 +263,7 @@
{
try
{
var package = await PackageService.GetPackageAsync(packageid, version);
var package = await PackageService.GetPackageAsync(packageid, version, false);
if (package != null)
{
_productname = package.Name;
@ -220,8 +273,13 @@
}
_packageid = package.PackageId;
_version = package.Version;
StateHasChanged();
}
else
{
await logger.LogError("Error Getting Package {PackageId} {Version}", packageid, version);
AddModuleMessage(Localizer["Error.Theme.Download"], MessageType.Error);
}
StateHasChanged();
}
catch (Exception ex)
{
@ -234,9 +292,9 @@
{
try
{
await PackageService.DownloadPackageAsync(_packageid, _version, Constants.PackagesFolder);
await PackageService.DownloadPackageAsync(_packageid, _version);
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _packageid, _version);
AddModuleMessage(Localizer["Success.Theme.Download"], MessageType.Success);
AddModuleMessage(string.Format(Localizer["Success.Theme.Download"], NavigateUrl("admin/system")), MessageType.Success);
_productname = "";
_license = "";
StateHasChanged();
@ -248,16 +306,8 @@
}
}
private async Task InstallThemes()
private void OnUpload()
{
try
{
await ThemeService.InstallThemesAsync();
AddModuleMessage(string.Format(Localizer["Success.Theme.Install"], NavigateUrl("admin/system")), MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Installing Theme");
}
AddModuleMessage(string.Format(Localizer["Success.Theme.Download"], NavigateUrl("admin/system")), MessageType.Success);
}
}

View File

@ -80,7 +80,10 @@
protected override void OnInitialized()
{
AddModuleMessage(Localizer["Info.Theme.CreatorIntent"], MessageType.Info);
if (!NavigationManager.BaseUri.Contains("localhost:"))
{
AddModuleMessage(Localizer["Info.Theme.CreatorIntent"], MessageType.Info);
}
}
protected override async Task OnParametersSetAsync()
@ -102,7 +105,8 @@
{
if (IsValid(_owner) && IsValid(_theme) && _owner != _theme && _template != "-")
{
var theme = new Theme { Owner = _owner, Name = _theme, Template = _template, Version = _reference };
var template = _templates.FirstOrDefault(item => item.Name == _template);
var theme = new Theme { Owner = _owner, Name = _theme, Template = _template, Version = _reference, ThemeName = template.Namespace };
theme = await ThemeService.CreateThemeAsync(theme);
GetLocation();
AddModuleMessage(string.Format(Localizer["Success.Theme.Create"], NavigateUrl("admin/system")), MessageType.Success);
@ -142,8 +146,14 @@
if (_owner != "" && _theme != "" && _template != "-")
{
var template = _templates.FirstOrDefault(item => item.Name == _template);
_location = template.Location + _owner + "." + _theme;
if (!string.IsNullOrEmpty(template.Namespace))
{
_location = template.Location + template.Namespace.Replace("[Owner]", _owner).Replace("[Theme]", _theme);
}
else
{
_location = template.Location + _owner + ".Theme." + _theme;
}
}
StateHasChanged();
}

View File

@ -0,0 +1,211 @@
@namespace Oqtane.Modules.Admin.Themes
@using System.Net
@inherits ModuleBase
@inject IThemeService ThemeService
@inject IPackageService PackageService
@inject NavigationManager NavigationManager
@inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_initialized)
{
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="The name of the module" ResourceKey="Name">Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="isenabled" HelpText="Is theme enabled for this site?" ResourceKey="IsEnabled">Enabled? </Label>
<div class="col-sm-9">
<select id="isenabled" class="form-select" @bind="@_isenabled" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
</form>
<Section Name="Information" ResourceKey="Information" Heading="Information">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="themename" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
<div class="col-sm-9">
<input id="themename" class="form-control" @bind="@_themeName" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="version" HelpText="The version of the theme" ResourceKey="Version">Version: </Label>
<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 theme was installed. This value must be specified within the theme's ITheme interface specification." ResourceKey="PackageName">Package Name: </Label>
<div class="col-sm-9">
@if (!string.IsNullOrEmpty(_packagename))
{
<div class="input-group">
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
@if (string.IsNullOrEmpty(_packageurl))
{
<button type="button" class="btn btn-secondary" @onclick="ValidatePackage">@Localizer["Validate"]</button>
}
else
{
<a href="@_packageurl" target="_blank" class="btn btn-primary">@SharedLocalizer["Download"]</a>
}
</div>
}
else
{
<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>
<div class="col-sm-9">
<input id="owner" class="form-control" @bind="@_owner" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="url" HelpText="The reference url of the theme" ResourceKey="ReferenceUrl">Reference Url: </Label>
<div class="col-sm-9">
<input id="url" class="form-control" @bind="@_url" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="contact" HelpText="The contact for the theme" ResourceKey="Contact">Contact: </Label>
<div class="col-sm-9">
<input id="contact" class="form-control" @bind="@_contact" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="license" HelpText="The license of the theme" ResourceKey="License">License: </Label>
<div class="col-sm-9">
@if (_license.StartsWith("http") || _license.StartsWith("/") || _license.StartsWith("~"))
{
<a href="@_license.Replace("~", PageState?.Alias.BaseUrl + "/Themes/" + Utilities.GetTypeName(_themeName))" class="btn btn-info" style="text-decoration: none !important" target="_new">@Localizer["View License"]</a>
}
else
{
<textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea>
}
</div>
</div>
</div>
</Section>
<br />
<button type="button" class="btn btn-success" @onclick="SaveTheme">@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>
}
@code {
private bool _initialized = false;
private ElementReference form;
private bool validated = false;
private int _themeId;
private string _themeName = "";
private string _isenabled;
private string _name;
private string _version;
private string _packagename = "";
private string _packageurl = "";
private string _owner = "";
private string _url = "";
private string _contact = "";
private string _license = "";
private string _createdby;
private DateTime _createdon;
private string _modifiedby;
private DateTime _modifiedon;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync()
{
try
{
_themeId = Int32.Parse(PageState.QueryString["id"]);
var theme = await ThemeService.GetThemeAsync(_themeId, ModuleState.SiteId);
if (theme != null)
{
_name = theme.Name;
_isenabled =theme.IsEnabled.ToString();
_version = theme.Version;
_packagename = theme.PackageName;
_owner = theme.Owner;
_url = theme.Url;
_contact = theme.Contact;
_license = theme.License;
_createdby = theme.CreatedBy;
_createdon = theme.CreatedOn;
_modifiedby = theme.ModifiedBy;
_modifiedon = theme.ModifiedOn;
_initialized = true;
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Theme {ThemeName} {Error}", _themeName, ex.Message);
AddModuleMessage(Localizer["Error.Theme.Loading"], MessageType.Error);
}
}
private async Task SaveTheme()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
try
{
var theme = await ThemeService.GetThemeAsync(_themeId, ModuleState.SiteId);
theme.Name = _name;
theme.IsEnabled = (_isenabled == null ? true : bool.Parse(_isenabled));
await ThemeService.UpdateThemeAsync(theme);
await logger.LogInformation("Theme Saved {Theme}", theme);
NavigationManager.NavigateTo(NavigateUrl());
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Theme {ThemeId} {Error}", _themeId, ex.Message);
AddModuleMessage(Localizer["Error.Module.Save"], MessageType.Error);
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
private async Task ValidatePackage()
{
try
{
var package = await PackageService.GetPackageAsync(_packagename, _version, true);
if (package == null || string.IsNullOrEmpty(package.PackageUrl))
{
AddModuleMessage(Localizer["Message.Validate"], MessageType.Warning);
}
else
{
_packageurl = package.PackageUrl;
AddModuleMessage(Localizer["Message.Download"], MessageType.Info);
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packagename, _version);
AddModuleMessage(Localizer["Error.Validate"], MessageType.Error);
}
}
}

View File

@ -13,7 +13,7 @@
}
else
{
<ActionLink Action="Add" Text="Install Theme" />
<ActionLink Action="Add" Text="Install Theme" ResourceKey="InstallTheme" />
@((MarkupString)"&nbsp;")
<ActionLink Action="Create" Text="Create Theme" ResourceKey="CreateTheme" Class="btn btn-secondary" />
@ -23,11 +23,13 @@ else
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th>
<th>@SharedLocalizer["Version"]</th>
<th>@Localizer["Enabled"]</th>
<th>@SharedLocalizer["Support"]</th>
<th>@SharedLocalizer["Expires"]</th>
<th>&nbsp;</th>
</Header>
<Row>
<td><ActionLink Action="View" Parameters="@($"name=" + WebUtility.UrlEncode(context.ThemeName))" ResourceKey="ViewTheme" /></td>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ThemeId.ToString())" ResourceKey="EditTheme" /></td>
<td>
@if (context.AssemblyName != Constants.ClientId)
{
@ -36,6 +38,19 @@ else
</td>
<td>@context.Name</td>
<td>@context.Version</td>
<td>
@if (context.IsEnabled)
{
<span>@SharedLocalizer["Yes"]</span>
}
else
{
<span>@SharedLocalizer["No"]</span>
}
</td>
<td>
@((MarkupString)SupportLink(context.PackageName, context.Version))
</td>
<td>
@((MarkupString)PurchaseLink(context.PackageName))
</td>
@ -64,7 +79,7 @@ else
try
{
_themes = await ThemeService.GetThemesAsync();
_packages = await PackageService.GetPackagesAsync("theme");
_packages = await PackageService.GetPackageUpdatesAsync("theme");
}
catch (Exception ex)
{
@ -85,11 +100,14 @@ else
if (package != null)
{
if (package.ExpiryDate != null && package.ExpiryDate.Value.Date != DateTime.MaxValue.Date)
{
link += "<span>" + package.ExpiryDate.Value.Date.ToString("MMM dd, yyyy") + "</span>";
if (!string.IsNullOrEmpty(package.PaymentUrl))
{
if (string.IsNullOrEmpty(package.PaymentUrl))
{
link += "&nbsp;&nbsp;<a class=\"btn btn-primary\" style=\"text-decoration: none !important\" href=\"" + package.PaymentUrl + "\" target=\"_new\">" + SharedLocalizer["Extend"] + "</a>";
link = "<span>" + package.ExpiryDate.Value.Date.ToString("MMM dd, yyyy") + "</span>";
}
else
{
link = "<a class=\"btn btn-primary\" style=\"text-decoration: none !important\" href=\"" + package.PaymentUrl + "\" target=\"_new\">" + package.ExpiryDate.Value.Date.ToString("MMM dd, yyyy") + "</a>";
}
}
}
@ -97,6 +115,20 @@ else
return link;
}
private string SupportLink(string packagename, string version)
{
string link = "";
if (!string.IsNullOrEmpty(packagename) && _packages != null)
{
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
if (package != null && !string.IsNullOrEmpty(package.SupportUrl))
{
link += "<a class=\"btn btn-info\" style=\"text-decoration: none !important\" href=\"" + package.SupportUrl.Replace("{Version}", version) + "\" target=\"_new\">" + SharedLocalizer["Help"] + "</a>";
}
}
return link;
}
private string UpgradeAvailable(string packagename, string version)
{
if (!string.IsNullOrEmpty(packagename) && _packages != null)
@ -114,9 +146,8 @@ else
{
try
{
await PackageService.DownloadPackageAsync(packagename, version, Constants.PackagesFolder);
await PackageService.DownloadPackageAsync(packagename, version);
await logger.LogInformation("Theme Downloaded {ThemeName} {Version}", packagename, version);
await ThemeService.InstallThemesAsync();
AddModuleMessage(string.Format(Localizer["Success.Theme.Install"], NavigateUrl("admin/system")), MessageType.Success);
}
catch (Exception ex)

View File

@ -1,97 +0,0 @@
@namespace Oqtane.Modules.Admin.Themes
@using System.Net
@inherits ModuleBase
@inject IThemeService ThemeService
@inject NavigationManager NavigationManager
@inject IStringLocalizer<View> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="The name of the theme" ResourceKey="Name">Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="themename" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
<div class="col-sm-9">
<input id="themename" class="form-control" @bind="@_themeName" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="version" HelpText="The version of the theme" ResourceKey="Version">Version: </Label>
<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>
<div class="col-sm-9">
<input id="owner" class="form-control" @bind="@_owner" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="url" HelpText="The reference url of the theme" ResourceKey="ReferenceUrl">Reference Url: </Label>
<div class="col-sm-9">
<input id="url" class="form-control" @bind="@_url" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="contact" HelpText="The contact for the theme" ResourceKey="Contact">Contact: </Label>
<div class="col-sm-9">
<input id="contact" class="form-control" @bind="@_contact" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="license" HelpText="The license of the theme" ResourceKey="License">License: </Label>
<div class="col-sm-9">
<textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea>
</div>
</div>
</div>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@code {
private string _themeName = "";
private string _name;
private string _version;
private string _packagename;
private string _owner = "";
private string _url = "";
private string _contact = "";
private string _license = "";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync()
{
try
{
_themeName = WebUtility.UrlDecode(PageState.QueryString["name"]);
var themes = await ThemeService.GetThemesAsync();
var theme = themes.FirstOrDefault(item => item.ThemeName == _themeName);
if (theme != null)
{
_name = theme.Name;
_version = theme.Version;
_packagename = theme.PackageName;
_owner = theme.Owner;
_url = theme.Url;
_contact = theme.Contact;
_license = theme.License;
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Theme {ThemeName} {Error}", _themeName, ex.Message);
AddModuleMessage(Localizer["Error.Theme.Loading"], MessageType.Error);
}
}
}

View File

@ -7,34 +7,38 @@
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<TabStrip>
<TabPanel Name="Download" ResourceKey="Download">
@if (_package != null && _upgradeavailable)
{
<ModuleMessage Type="MessageType.Info" Message="Select The Download Button To Download The Framework Upgrade Package And Then Select Upgrade"></ModuleMessage>
<button type="button" class="btn btn-primary" @onclick=@(async () => await Download(Constants.PackageId, @_package.Version))>@SharedLocalizer["Download"] @_package.Version</button>
<button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button>
}
else
{
<ModuleMessage Type="MessageType.Info" Message="Framework Is Already Up To Date"></ModuleMessage>
}
</TabPanel>
<TabPanel Name="Upload" ResourceKey="Upload">
<ModuleMessage Type="MessageType.Info" Message="Upload A Framework Package (Oqtane.Framework.version.nupkg) And Then Select Upgrade"></ModuleMessage>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" HelpText="Upload A Framework Package And Then Select Upgrade" ResourceKey="Framework">Framework: </Label>
<div class="col-sm-9">
<FileManager Folder="@Constants.PackagesFolder" />
@if (_initialized)
{
<TabStrip>
<TabPanel Name="Download" ResourceKey="Download">
@if (_package != null && _upgradeavailable)
{
<ModuleMessage Type="MessageType.Info" Message="Select The Download Button To Download The Framework Upgrade Package And Then Select Upgrade"></ModuleMessage>
<button type="button" class="btn btn-primary" @onclick=@(async () => await Download(Constants.PackageId, @_package.Version))>@SharedLocalizer["Download"] @_package.Version</button>
<button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button>
}
else
{
<ModuleMessage Type="MessageType.Info" Message=@Localizer["Message.Text"]></ModuleMessage>
}
</TabPanel>
<TabPanel Name="Upload" ResourceKey="Upload">
<ModuleMessage Type="MessageType.Info" Message=@Localizer["MessageUpgrade.Text"]></ModuleMessage>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" HelpText="Upload A Framework Package And Then Select Upgrade" ResourceKey="Framework">Framework: </Label>
<div class="col-sm-9">
<FileManager Folder="@Constants.PackagesFolder" />
</div>
</div>
</div>
</div>
<button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button>
</TabPanel>
</TabStrip>
<button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button>
</TabPanel>
</TabStrip>
}
@code {
private bool _initialized = false;
private Package _package;
private bool _upgradeavailable = false;
@ -44,18 +48,26 @@
{
try
{
List<Package> packages = await PackageService.GetPackagesAsync("framework", "", "", "");
if (packages != null)
if (NavigationManager.BaseUri.Contains("localhost:"))
{
_package = packages.Where(item => item.PackageId.StartsWith(Constants.PackageId)).FirstOrDefault();
if (_package != null)
AddModuleMessage(Localizer["Localhost.Text"], MessageType.Info);
}
else
{
List<Package> packages = await PackageService.GetPackagesAsync("framework", "", "", "");
if (packages != null)
{
_upgradeavailable = (Version.Parse(_package.Version).CompareTo(Version.Parse(Constants.Version)) > 0);
}
else
{
_package = new Package { Name = Constants.PackageId, Version = Constants.Version };
_package = packages.Where(item => item.PackageId.StartsWith(Constants.PackageId)).FirstOrDefault();
if (_package != null)
{
_upgradeavailable = (Version.Parse(_package.Version).CompareTo(Version.Parse(Constants.Version)) > 0);
}
else
{
_package = new Package { Name = Constants.PackageId, Version = Constants.Version };
}
}
_initialized = true;
}
}
catch
@ -85,8 +97,8 @@
{
try
{
await PackageService.DownloadPackageAsync(packageid, version, Constants.PackagesFolder);
await PackageService.DownloadPackageAsync(Constants.UpdaterPackageId, version, Constants.PackagesFolder);
await PackageService.DownloadPackageAsync(packageid, version);
await PackageService.DownloadPackageAsync(Constants.UpdaterPackageId, version);
AddModuleMessage(Localizer["Success.Framework.Download"], MessageType.Success);
}
catch (Exception ex)

View File

@ -1,4 +1,5 @@
@namespace Oqtane.Modules.Admin.UserProfile
@using System.Text.RegularExpressions;
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IUserService UserService
@ -22,6 +23,7 @@ else
<TabPanel Name="Identity" ResourceKey="Identity">
@if (profiles != null && settings != null)
{
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="username" HelpText="Your username. Note that this field can not be modified." ResourceKey="Username"></Label>
@ -34,7 +36,7 @@ else
<div class="col-sm-9">
<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>
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
@ -43,7 +45,7 @@ else
<div class="col-sm-9">
<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>
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
@ -143,6 +145,11 @@ else
<TabPanel Name="Notifications" ResourceKey="Notifications">
@if (notifications != null)
{
<select class="form-select" @onchange="(e => FilterChanged(e))">
<option value="to">@Localizer["Inbox"]</option>
<option value="from">@Localizer["Items.Sent"]</option>
</select>
<br />
<ActionLink Action="Add" Text="Send Notification" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="SendNotification" />
<br /><br />
@if (filter == "to")
@ -158,22 +165,41 @@ else
<Row>
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" /></td>
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
<td>@context.FromDisplayName</td>
<td>@context.Subject</td>
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</td>
@if (context.IsRead)
{
<td>@context.FromDisplayName</td>
<td>@context.Subject</td>
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</td>
}
else
{
<td><b>@context.FromDisplayName</b></td>
<td><b>@context.Subject</b></td>
<td><b>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</b></td>
}
</Row>
<Detail>
<td colspan="2"></td>
<td colspan="3">
@{
string input = "___";
if (context.Body.Contains(input))
{
context.Body = context.Body.Split(input)[0];
context.Body = context.Body.Replace("\n", "");
context.Body = context.Body.Replace("\r", "");
} }
@(context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body)
string input = "___";
if (context.Body.Contains(input))
{
context.Body = context.Body.Split(input)[0];
context.Body = context.Body.Replace("\n", "");
context.Body = context.Body.Replace("\r", "");
}
notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body;
}
@if (context.IsRead)
{
@notificationSummary
}
else
{
<b>@notificationSummary</b>
}
</td>
</Detail>
</Pager>
@ -191,22 +217,42 @@ else
<Row>
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" /></td>
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
<td>@context.ToDisplayName</td>
<td>@context.Subject</td>
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</td>
@if (context.IsRead)
{
<td>@context.ToDisplayName</td>
<td>@context.Subject</td>
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</td>
}
else
{
<td><b>@context.ToDisplayName</b></td>
<td><b>@context.Subject</b></td>
<td><b>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</b></td>
}
</Row>
<Detail>
<td colspan="2"></td>
<td colspan="3">
@{
string input = "___";
if (context.Body.Contains(input))
{
context.Body = context.Body.Split(input)[0];
context.Body = context.Body.Replace("\n", "");
context.Body = context.Body.Replace("\r", "");
} }
@(context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body)
string input = "___";
if (context.Body.Contains(input))
{
context.Body = context.Body.Split(input)[0];
context.Body = context.Body.Replace("\n", "");
context.Body = context.Body.Replace("\r", "");
}
notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body;
}
@if (context.IsRead)
{
@notificationSummary
}
else
{
<b>@notificationSummary</b>
}
</td>
</Detail>
</Pager>
@ -216,151 +262,147 @@ else
<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>
<option value="from">@Localizer["Items.Sent"]</option>
</select>
}
</TabPanel>
</TabStrip>
<br /><br />
@code {
private string username = string.Empty;
private string _password = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private string confirm = string.Empty;
private bool allowtwofactor = false;
private string twofactor = "False";
private string email = string.Empty;
private string displayname = string.Empty;
private FileManager filemanager;
private int folderid = -1;
private int photofileid = -1;
private File photo = null;
private List<Profile> profiles;
private Dictionary<string, string> settings;
private string category = string.Empty;
private string filter = "to";
private List<Notification> notifications;
private string _passwordrequirements;
private string username = string.Empty;
private string _password = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private string confirm = string.Empty;
private bool allowtwofactor = false;
private string twofactor = "False";
private string email = string.Empty;
private string displayname = string.Empty;
private FileManager filemanager;
private int folderid = -1;
private int photofileid = -1;
private File photo = null;
private List<Profile> profiles;
private Dictionary<string, string> settings;
private string category = string.Empty;
private string filter = "to";
private List<Notification> notifications;
private string notificationSummary = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
protected override async Task OnParametersSetAsync()
{
try
{
_togglepassword = SharedLocalizer["ShowPassword"];
protected override async Task OnParametersSetAsync()
{
try
{
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
if (PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:TwoFactor"]))
{
allowtwofactor = (PageState.Site.Settings["LoginOptions:TwoFactor"] == "true");
}
_togglepassword = SharedLocalizer["ShowPassword"];
if (PageState.User != null)
{
username = PageState.User.Username;
twofactor = PageState.User.TwoFactorRequired.ToString();
email = PageState.User.Email;
displayname = PageState.User.DisplayName;
allowtwofactor = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "true");
// get user folder
var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath);
if (folder != null)
{
folderid = folder.FolderId;
}
if (PageState.User != null)
{
username = PageState.User.Username;
twofactor = PageState.User.TwoFactorRequired.ToString();
email = PageState.User.Email;
displayname = PageState.User.DisplayName;
if (PageState.User.PhotoFileId != null)
{
photofileid = PageState.User.PhotoFileId.Value;
photo = await FileService.GetFileAsync(photofileid);
}
else
{
photofileid = -1;
photo = null;
}
// get user folder
var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath);
if (folder != null)
{
folderid = folder.FolderId;
}
profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
if (PageState.User.PhotoFileId != null)
{
photofileid = PageState.User.PhotoFileId.Value;
photo = await FileService.GetFileAsync(photofileid);
}
else
{
photofileid = -1;
photo = null;
}
await LoadNotificationsAsync();
}
else
{
AddModuleMessage(Localizer["Message.User.NoLogIn"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading User Profile {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Profile.Load"], MessageType.Error);
}
}
profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
private async Task LoadNotificationsAsync()
{
notifications = await NotificationService.GetNotificationsAsync(PageState.Site.SiteId, filter, PageState.User.UserId);
notifications = notifications.Where(item => item.DeletedBy != PageState.User.Username).ToList();
}
await LoadNotificationsAsync();
}
else
{
AddModuleMessage(Localizer["Message.User.NoLogIn"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading User Profile {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Profile.Load"], MessageType.Error);
}
}
private string GetProfileValue(string SettingName, string DefaultValue)
{
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
if (value.Contains("]"))
{
value = value.Substring(value.IndexOf("]") + 1);
}
return value;
}
private async Task LoadNotificationsAsync()
{
notifications = await NotificationService.GetNotificationsAsync(PageState.Site.SiteId, filter, PageState.User.UserId);
notifications = notifications.Where(item => item.DeletedBy != PageState.User.Username).ToList();
}
private async Task Save()
{
try
{
if (username != string.Empty && email != string.Empty && ValidateProfiles())
{
if (_password == confirm)
{
var user = PageState.User;
user.Username = username;
user.Password = _password;
user.TwoFactorRequired = bool.Parse(twofactor);
user.Email = email;
user.DisplayName = (displayname == string.Empty ? username : displayname);
user.PhotoFileId = filemanager.GetFileId();
if (user.PhotoFileId == -1)
{
user.PhotoFileId = null;
}
if (user.PhotoFileId != null)
{
photofileid = user.PhotoFileId.Value;
photo = await FileService.GetFileAsync(photofileid);
}
else
{
photofileid = -1;
photo = null;
}
private string GetProfileValue(string SettingName, string DefaultValue)
{
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
if (value.Contains("]"))
{
value = value.Substring(value.IndexOf("]") + 1);
}
return value;
}
user = await UserService.UpdateUserAsync(user);
if (user != null)
{
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
await logger.LogInformation("User Profile Saved");
private async Task Save()
{
try
{
if (username != string.Empty && email != string.Empty && ValidateProfiles())
{
if (_password == confirm)
{
var user = PageState.User;
user.Username = username;
user.Password = _password;
user.TwoFactorRequired = bool.Parse(twofactor);
user.Email = email;
user.DisplayName = (displayname == string.Empty ? username : displayname);
user.PhotoFileId = filemanager.GetFileId();
if (user.PhotoFileId == -1)
{
user.PhotoFileId = null;
}
if (user.PhotoFileId != null)
{
photofileid = user.PhotoFileId.Value;
photo = await FileService.GetFileAsync(photofileid);
}
else
{
photofileid = -1;
photo = null;
}
AddModuleMessage(Localizer["Success.Profile.Update"], MessageType.Success);
StateHasChanged();
}
else
{
AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error);
}
}
user = await UserService.UpdateUserAsync(user);
if (user != null)
{
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
await logger.LogInformation("User Profile Saved");
AddModuleMessage(Localizer["Success.Profile.Update"], MessageType.Success);
StateHasChanged();
}
else
{
AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error);
}
}
else
{
AddModuleMessage(Localizer["Message.Password.Invalid"], MessageType.Warning);
@ -370,6 +412,8 @@ else
{
AddModuleMessage(Localizer["Message.Required.ProfileInfo"], MessageType.Warning);
}
await ScrollToPageTop();
}
catch (Exception ex)
{
@ -389,10 +433,15 @@ else
}
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
if (profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)))
if (valid == true && profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)))
{
valid = false;
}
if (valid == true && !string.IsNullOrEmpty(profile.Validation))
{
Regex regex = new Regex(profile.Validation);
valid = regex.Match(SettingService.GetSetting(settings, profile.Name, string.Empty)).Success;
}
}
}
return valid;

View File

@ -55,7 +55,7 @@
<div class="row mb-1 align-items-center">
<label class="col-sm-3">@Localizer["Message"] </label>
<div class="col-sm-9">
<textarea class="form-control" @bind="@body" rows="5" readonly />
<textarea id="txtFrom" class="form-control" @bind="@body" rows="5" readonly />
</div>
</div>
@ -66,7 +66,7 @@
<div class="row mb-1 align-items-center">
<label class="col-sm-3">@Localizer["Message"] </label>
<div class="col-sm-9">
<textarea class="form-control" @bind="@body" rows="5" readonly />
<textarea id="txtTo" class="form-control" @bind="@body" rows="5" />
</div>
</div>
@ -93,7 +93,7 @@
{
<div class="control-group">
<label class="control-label">@Localizer["OriginalMessage"] </label>
<textarea class="form-control" @bind="@reply" rows="5" readonly />
<textarea id="txtReply" class="form-control" @bind="@reply" rows="5" readonly />
</div>
}
}
@ -118,6 +118,9 @@
Notification notification = await NotificationService.GetNotificationAsync(notificationid);
if (notification != null)
{
notification.IsRead = true;
notification = await NotificationService.UpdateNotificationAsync(notification);
int userid = -1;
if (notification.ToUserId == PageState.User.UserId)
{

View File

@ -1,4 +1,5 @@
@namespace Oqtane.Modules.Admin.Users
@using System.Text.RegularExpressions;
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IUserService UserService
@ -11,11 +12,12 @@
<TabPanel Name="Identity" ResourceKey="Identity">
@if (profiles != null)
{
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="username" HelpText="A unique username for a user. Note that this field can not be modified once it is saved." ResourceKey="Username"></Label>
<div class="col-sm-9">
<input id="username" class="form-control" @bind="@username" />
<input id="username" class="form-control" @bind="@_username" />
</div>
</div>
<div class="row mb-1 align-items-center">
@ -23,7 +25,7 @@
<div class="col-sm-9">
<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>
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
@ -31,25 +33,33 @@
<Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm"></Label>
<div class="col-sm-9">
<div class="input-group">
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@confirm" autocomplete="new-password" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" required />
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email"></Label>
<div class="col-sm-9">
<input id="email" class="form-control" @bind="@email" />
<input id="email" class="form-control" @bind="@_email" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="displayname" HelpText="The full name of the user" ResourceKey="DisplayName"></Label>
<div class="col-sm-9">
<input id="displayname" class="form-control" @bind="@displayname" />
<input id="displayname" class="form-control" @bind="@_displayname" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="notify" HelpText="Indicate if new users should receive an email notification" ResourceKey="Notify">Notify? </Label>
<div class="col-sm-9">
<select id="notify" class="form-select" @bind="@_notify" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
}
</TabPanel>
<TabPanel Name="Profile" ResourceKey="Profile">
@ -93,13 +103,15 @@
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@code {
private string username = string.Empty;
private string _passwordrequirements;
private string _username = string.Empty;
private string _password = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private string confirm = string.Empty;
private string email = string.Empty;
private string displayname = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private string _confirm = string.Empty;
private string _email = string.Empty;
private string _displayname = string.Empty;
private string _notify = "True";
private List<Profile> profiles;
private Dictionary<string, string> settings;
private string category = string.Empty;
@ -110,6 +122,7 @@
{
try
{
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
_togglepassword = SharedLocalizer["ShowPassword"];
profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
settings = new Dictionary<string, string>();
@ -121,52 +134,45 @@
}
}
private string GetProfileValue(string SettingName, string DefaultValue)
{
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
if (value.Contains("]"))
{
value = value.Substring(value.IndexOf("]") + 1);
}
return value;
}
private string GetProfileValue(string SettingName, string DefaultValue)
{
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
if (value.Contains("]"))
{
value = value.Substring(value.IndexOf("]") + 1);
}
return value;
}
private async Task SaveUser()
{
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)
var user = new User();
user.SiteId = PageState.Site.SiteId;
user.Username = _username;
user.Password = _password;
user.Email = _email;
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
user.PhotoFileId = null;
user.SuppressNotification = !bool.Parse(_notify);
user = await UserService.AddUserAsync(user);
if (user != null)
{
user = new User();
user.SiteId = PageState.Site.SiteId;
user.Username = username;
user.Password = _password;
user.Email = email;
user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname;
user.PhotoFileId = null;
user = await UserService.AddUserAsync(user);
if (user != null)
{
await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
await logger.LogInformation("User Created {User}", user);
NavigationManager.NavigateTo(NavigateUrl());
}
else
{
await logger.LogError("Error Adding User {Username} {Email}", username, email);
AddModuleMessage(Localizer["Error.User.AddCheckPass"], MessageType.Error);
}
await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
await logger.LogInformation("User Created {User}", user);
NavigationManager.NavigateTo(NavigateUrl());
}
else
{
AddModuleMessage(Localizer["Message.Username.Exists"], MessageType.Warning);
await logger.LogError("Error Adding User {Username} {Email}", _username, _email);
AddModuleMessage(Localizer["Error.User.AddCheckPass"], MessageType.Error);
}
}
else
@ -181,7 +187,7 @@
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Adding User {Username} {Email} {Error}", username, email, ex.Message);
await logger.LogError(ex, "Error Adding User {Username} {Email} {Error}", _username, _email, ex.Message);
AddModuleMessage(Localizer["Error.User.Add"], MessageType.Error);
}
}
@ -195,9 +201,17 @@
{
settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue);
}
if (profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)))
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
valid = false;
if (valid == true && profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)))
{
valid = false;
}
if (valid == true && !string.IsNullOrEmpty(profile.Validation))
{
Regex regex = new Regex(profile.Validation);
valid = regex.Match(SettingService.GetSetting(settings, profile.Name, string.Empty)).Success;
}
}
}
return valid;

View File

@ -1,4 +1,5 @@
@namespace Oqtane.Modules.Admin.Users
@using System.Text.RegularExpressions;
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IUserService UserService
@ -20,6 +21,7 @@ else
<TabPanel Name="Identity" ResourceKey="Identity">
@if (profiles != null)
{
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="username" HelpText="The unique username for a user. Note that this field can not be modified." ResourceKey="Username"></Label>
@ -32,7 +34,7 @@ else
<div class="col-sm-9">
<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>
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
@ -41,7 +43,7 @@ else
<div class="col-sm-9">
<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>
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
</div>
</div>
</div>
@ -148,124 +150,126 @@ else
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon" DeletedBy="@deletedby" DeletedOn="@deletedon"></AuditInfo>
@code {
private int userid;
private string username = string.Empty;
private string _password = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private string confirm = string.Empty;
private string email = string.Empty;
private string displayname = string.Empty;
private FileManager filemanager;
private int photofileid = -1;
private File photo = null;
private string isdeleted;
private string lastlogin;
private string lastipaddress;
private string _passwordrequirements;
private int userid;
private string username = string.Empty;
private string _password = string.Empty;
private string _passwordtype = "password";
private string _togglepassword = string.Empty;
private string confirm = string.Empty;
private string email = string.Empty;
private string displayname = string.Empty;
private 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 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 createdby;
private DateTime createdon;
private string modifiedby;
private DateTime modifiedon;
private string deletedby;
private DateTime? deletedon;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
protected override async Task OnParametersSetAsync()
{
try
{
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);
if (user != null)
{
username = user.Username;
email = user.Email;
displayname = user.DisplayName;
if (user.PhotoFileId != null)
{
photofileid = user.PhotoFileId.Value;
photo = await FileService.GetFileAsync(photofileid);
}
else
{
photofileid = -1;
photo = null;
}
isdeleted = user.IsDeleted.ToString();
lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn);
lastipaddress = user.LastIPAddress;
protected override async Task OnParametersSetAsync()
{
try
{
if (PageState.QueryString.ContainsKey("id"))
{
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
_togglepassword = SharedLocalizer["ShowPassword"];
profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
userid = Int32.Parse(PageState.QueryString["id"]);
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
if (user != null)
{
username = user.Username;
email = user.Email;
displayname = user.DisplayName;
if (user.PhotoFileId != null)
{
photofileid = user.PhotoFileId.Value;
photo = await FileService.GetFileAsync(photofileid);
}
else
{
photofileid = -1;
photo = null;
}
isdeleted = user.IsDeleted.ToString();
lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn);
lastipaddress = user.LastIPAddress;
settings = await SettingService.GetUserSettingsAsync(user.UserId);
createdby = user.CreatedBy;
createdon = user.CreatedOn;
modifiedby = user.ModifiedBy;
modifiedon = user.ModifiedOn;
deletedby = user.DeletedBy;
deletedon = user.DeletedOn;
}
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading User {UserId} {Error}", userid, ex.Message);
AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error);
}
}
settings = await SettingService.GetUserSettingsAsync(user.UserId);
createdby = user.CreatedBy;
createdon = user.CreatedOn;
modifiedby = user.ModifiedBy;
modifiedon = user.ModifiedOn;
deletedby = user.DeletedBy;
deletedon = user.DeletedOn;
}
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading User {UserId} {Error}", userid, ex.Message);
AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error);
}
}
private string GetProfileValue(string SettingName, string DefaultValue)
{
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
if (value.Contains("]"))
{
value = value.Substring(value.IndexOf("]") + 1);
}
return value;
}
private string GetProfileValue(string SettingName, string DefaultValue)
{
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
if (value.Contains("]"))
{
value = value.Substring(value.IndexOf("]") + 1);
}
return value;
}
private async Task SaveUser()
{
try
{
if (username != string.Empty && email != string.Empty && ValidateProfiles())
{
if (_password == confirm)
{
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
user.SiteId = PageState.Site.SiteId;
user.Username = username;
user.Password = _password;
user.Email = email;
user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname;
user.PhotoFileId = null;
user.PhotoFileId = filemanager.GetFileId();
if (user.PhotoFileId == -1)
{
user.PhotoFileId = null;
}
private async Task SaveUser()
{
try
{
if (username != string.Empty && email != string.Empty && ValidateProfiles())
{
if (_password == confirm)
{
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
user.SiteId = PageState.Site.SiteId;
user.Username = username;
user.Password = _password;
user.Email = email;
user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname;
user.PhotoFileId = null;
user.PhotoFileId = filemanager.GetFileId();
if (user.PhotoFileId == -1)
{
user.PhotoFileId = null;
}
user.IsDeleted = (isdeleted == null ? true : Boolean.Parse(isdeleted));
user.IsDeleted = (isdeleted == null ? true : Boolean.Parse(isdeleted));
user = await UserService.UpdateUserAsync(user);
if (user != null)
{
await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
await logger.LogInformation("User Saved {User}", user);
NavigationManager.NavigateTo(NavigateUrl());
}
else
{
AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error);
}
user = await UserService.UpdateUserAsync(user);
if (user != null)
{
await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
await logger.LogInformation("User Saved {User}", user);
NavigationManager.NavigateTo(NavigateUrl());
}
else
{
AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error);
}
}
else
{
@ -293,9 +297,17 @@ else
{
settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue);
}
if (profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)))
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
valid = false;
if (valid == true && profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)))
{
valid = false;
}
if (valid == true && !string.IsNullOrEmpty(profile.Validation))
{
Regex regex = new Regex(profile.Validation);
valid = regex.Match(SettingService.GetSetting(settings, profile.Name, string.Empty)).Success;
}
}
}
return valid;

View File

@ -20,8 +20,9 @@ else
<div class="container">
<div class="row mb-1 align-items-center">
<div class="col-sm-4">
<ActionLink Action="Add" Text="Add User" Security="SecurityAccessLevel.Edit" ResourceKey="AddUser" />
</div>
<ActionLink Action="Add" Text="Add User" Security="SecurityAccessLevel.Edit" ResourceKey="AddUser" />&nbsp;
<ActionLink Text="Import Users" Class="btn btn-secondary" Action="Users" Security="SecurityAccessLevel.Admin" ResourceKey="ImportUsers"/>
</div>
<div class="col-sm-4">
<input class="form-control" @bind="@_search" />
</div>
@ -35,9 +36,10 @@ else
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Username"]</th>
<th>@SharedLocalizer["Name"]</th>
<th>@Localizer["LastLoginOn"]</th>
<th class="app-sort-th link-primary text-decoration-underline" @onclick="@(() => SortTable("Username"))">@Localizer["Username"]<i class="@(SetSortIcon("Username"))"></i></th>
<th class="app-sort-th link-primary text-decoration-underline" @onclick="@(() => SortTable("DisplayName"))">@Localizer["Name"]<i class="@(SetSortIcon("DisplayName"))"></i></th>
<th class="app-sort-th link-primary text-decoration-underline" @onclick="@(() => SortTable("Email"))">@Localizer["Email"]<i class="@(SetSortIcon("Email"))"></i></th>
<th class="app-sort-th link-primary text-decoration-underline" @onclick="@(() => SortTable("LastLoginOn"))">@Localizer["LastLoginOn"]<i class="@(SetSortIcon("LastLoginOn"))"></i></th>
</Header>
<Row>
<td>
@ -50,11 +52,12 @@ else
<ActionLink Action="Roles" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="Roles" />
</td>
<td>@context.User.Username</td>
<td>@((MarkupString)string.Format("<a href=\"mailto:{0}\">{1}</a>", @context.User.Email, @context.User.DisplayName))</td>
<td>@context.User.DisplayName</td>
<td>@((MarkupString)string.Format("<a href=\"mailto:{0}\">{1}</a>", @context.User.Email, @context.User.Email))</td>
<td>@((context.User.LastLoginOn != DateTime.MinValue) ? string.Format("{0:dd-MMM-yyyy HH:mm:ss}", context.User.LastLoginOn) : "")</td>
</Row>
</Pager>
</TabPanel>
</TabPanel>
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings" Security="SecurityAccessLevel.Admin">
<div class="container">
<Section Name="User" Heading="User Settings" ResourceKey="UserSettings">
@ -360,126 +363,129 @@ else
</div>
<br />
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
</TabPanel>
</TabPanel>
</TabStrip>
}
@code {
private List<UserRole> allusers;
private List<UserRole> users;
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 _cookiename;
private string _allowregistration;
private string _allowsitelogin;
private string _twofactor;
private string _cookiename;
private string _minimumlength;
private string _uniquecharacters;
private string _requiredigit;
private string _requireupper;
private string _requirelower;
private string _requirepunctuation;
private string _maximumfailures;
private string _lockoutduration;
private string _minimumlength;
private string _uniquecharacters;
private string _requiredigit;
private string _requireupper;
private string _requirelower;
private string _requirepunctuation;
private string _maximumfailures;
private string _lockoutduration;
private string _providertype;
private string _providername;
private string _authority;
private string _metadataurl;
private string _authorizationurl;
private string _tokenurl;
private string _userinfourl;
private string _clientid;
private string _clientsecret;
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 _roleclaimtype;
private string _profileclaimtypes;
private string _domainfilter;
private string _createusers;
private string _providertype;
private string _providername;
private string _authority;
private string _metadataurl;
private string _authorizationurl;
private string _tokenurl;
private string _userinfourl;
private string _clientid;
private string _clientsecret;
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 _roleclaimtype;
private string _profileclaimtypes;
private string _domainfilter;
private string _createusers;
private string _secret;
private string _secrettype = "password";
private string _togglesecret = string.Empty;
private string _issuer;
private string _audience;
private string _lifetime;
private string _token;
private string _secret;
private string _secrettype = "password";
private string _togglesecret = string.Empty;
private string _issuer;
private string _audience;
private string _lifetime;
private string _token;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
private bool isSortedAscending;
private string activeSortColumn;
protected override async Task OnInitializedAsync()
{
await LoadUserSettingsAsync();
await LoadUsersAsync(true);
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
_allowregistration = PageState.Site.AllowRegistration.ToString();
_allowsitelogin = SettingService.GetSetting(settings, "LoginOptions:AllowSiteLogin", "true");
protected override async Task OnInitializedAsync()
{
await LoadUserSettingsAsync();
await LoadUsersAsync(true);
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_twofactor = SettingService.GetSetting(settings, "LoginOptions:TwoFactor", "false");
_cookiename = SettingService.GetSetting(settings, "LoginOptions:CookieName", ".AspNetCore.Identity.Application");
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
_allowregistration = PageState.Site.AllowRegistration.ToString();
_allowsitelogin = SettingService.GetSetting(settings, "LoginOptions:AllowSiteLogin", "true");
_minimumlength = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredLength", "6");
_uniquecharacters = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", "1");
_requiredigit = SettingService.GetSetting(settings, "IdentityOptions:Password:RequireDigit", "true");
_requireupper = SettingService.GetSetting(settings, "IdentityOptions:Password:RequireUppercase", "true");
_requirelower = SettingService.GetSetting(settings, "IdentityOptions:Password:RequireLowercase", "true");
_requirepunctuation = SettingService.GetSetting(settings, "IdentityOptions:Password:RequireNonAlphanumeric", "true");
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_twofactor = SettingService.GetSetting(settings, "LoginOptions:TwoFactor", "false");
_cookiename = SettingService.GetSetting(settings, "LoginOptions:CookieName", ".AspNetCore.Identity.Application");
_maximumfailures = SettingService.GetSetting(settings, "IdentityOptions:Lockout:MaxFailedAccessAttempts", "5");
_lockoutduration = TimeSpan.Parse(SettingService.GetSetting(settings, "IdentityOptions:Lockout:DefaultLockoutTimeSpan", "00:05:00")).TotalMinutes.ToString();
_minimumlength = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredLength", "6");
_uniquecharacters = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", "1");
_requiredigit = SettingService.GetSetting(settings, "IdentityOptions:Password:RequireDigit", "true");
_requireupper = SettingService.GetSetting(settings, "IdentityOptions:Password:RequireUppercase", "true");
_requirelower = SettingService.GetSetting(settings, "IdentityOptions:Password:RequireLowercase", "true");
_requirepunctuation = SettingService.GetSetting(settings, "IdentityOptions:Password:RequireNonAlphanumeric", "true");
_providertype = SettingService.GetSetting(settings, "ExternalLogin:ProviderType", "");
_providername = SettingService.GetSetting(settings, "ExternalLogin:ProviderName", "");
_authority = SettingService.GetSetting(settings, "ExternalLogin:Authority", "");
_metadataurl = SettingService.GetSetting(settings, "ExternalLogin:MetadataUrl", "");
_authorizationurl = SettingService.GetSetting(settings, "ExternalLogin:AuthorizationUrl", "");
_tokenurl = SettingService.GetSetting(settings, "ExternalLogin:TokenUrl", "");
_userinfourl = SettingService.GetSetting(settings, "ExternalLogin:UserInfoUrl", "");
_clientid = SettingService.GetSetting(settings, "ExternalLogin:ClientId", "");
_clientsecret = SettingService.GetSetting(settings, "ExternalLogin:ClientSecret", "");
_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", "sub");
_emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "email");
_roleclaimtype = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimType", "");
_profileclaimtypes = SettingService.GetSetting(settings, "ExternalLogin:ProfileClaimTypes", "");
_domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", "");
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
_maximumfailures = SettingService.GetSetting(settings, "IdentityOptions:Lockout:MaxFailedAccessAttempts", "5");
_lockoutduration = TimeSpan.Parse(SettingService.GetSetting(settings, "IdentityOptions:Lockout:DefaultLockoutTimeSpan", "00:05:00")).TotalMinutes.ToString();
_secret = SettingService.GetSetting(settings, "JwtOptions:Secret", "");
_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");
}
}
_providertype = SettingService.GetSetting(settings, "ExternalLogin:ProviderType", "");
_providername = SettingService.GetSetting(settings, "ExternalLogin:ProviderName", "");
_authority = SettingService.GetSetting(settings, "ExternalLogin:Authority", "");
_metadataurl = SettingService.GetSetting(settings, "ExternalLogin:MetadataUrl", "");
_authorizationurl = SettingService.GetSetting(settings, "ExternalLogin:AuthorizationUrl", "");
_tokenurl = SettingService.GetSetting(settings, "ExternalLogin:TokenUrl", "");
_userinfourl = SettingService.GetSetting(settings, "ExternalLogin:UserInfoUrl", "");
_clientid = SettingService.GetSetting(settings, "ExternalLogin:ClientId", "");
_clientsecret = SettingService.GetSetting(settings, "ExternalLogin:ClientSecret", "");
_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", "sub");
_emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "email");
_roleclaimtype = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimType", "");
_profileclaimtypes = SettingService.GetSetting(settings, "ExternalLogin:ProfileClaimTypes", "");
_domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", "");
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
private async Task LoadUsersAsync(bool load)
{
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();
}
}
_secret = SettingService.GetSetting(settings, "JwtOptions:Secret", "");
_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");
}
}
private async Task LoadUsersAsync(bool load)
{
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))
@ -654,4 +660,43 @@ else
_togglesecret = SharedLocalizer["ShowPassword"];
}
}
private void SortTable(string columnName)
{
if (columnName != activeSortColumn)
{
users = users.OrderBy(x => x.User.GetType().GetProperty(columnName)?.GetValue(x.User)).ToList();
isSortedAscending = true;
activeSortColumn = columnName;
}
else
{
if (isSortedAscending)
{
users = users.OrderByDescending(x => x.User.GetType().GetProperty(columnName)?.GetValue(x.User)).ToList();
}
else
{
users = users.OrderBy(x => x.User.GetType().GetProperty(columnName)?.GetValue(x.User)).ToList();
}
isSortedAscending = !isSortedAscending;
}
}
private string SetSortIcon(string columnName)
{
if (activeSortColumn != columnName)
{
return "app-fas pe-3 ";
}
if (isSortedAscending)
{
return "app-fas oi oi-sort-ascending";
}
else
{
return "app-fas oi oi-sort-descending";
}
}
}

View File

@ -34,13 +34,13 @@ else
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this role assignment is active" ResourceKey="EffectiveDate">Effective Date: </Label>
<div class="col-sm-9">
<input id="effectiveDate" class="form-control" @bind="@effectivedate" />
<input type="date" id="effectiveDate" class="form-control" @bind="@effectivedate" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this role assignment expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
<div class="col-sm-9">
<input id="expiryDate" class="form-control" @bind="@expirydate" />
<input type="date" id="expiryDate" class="form-control" @bind="@expirydate" />
</div>
</div>
@ -75,8 +75,8 @@ else
private string name = string.Empty;
private List<Role> roles;
private int roleid = -1;
private string effectivedate = string.Empty;
private string expirydate = string.Empty;
private DateTime? effectivedate = null;
private DateTime? expirydate = null;
private List<UserRole> userroles;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
@ -130,23 +130,8 @@ else
var userrole = userroles.Where(item => item.UserId == userid && item.RoleId == roleid).FirstOrDefault();
if (userrole != null)
{
if (string.IsNullOrEmpty(effectivedate))
{
userrole.EffectiveDate = null;
}
else
{
userrole.EffectiveDate = DateTime.Parse(effectivedate);
}
if (string.IsNullOrEmpty(expirydate))
{
userrole.ExpiryDate = null;
}
else
{
userrole.ExpiryDate = DateTime.Parse(expirydate);
}
userrole.EffectiveDate = effectivedate;
userrole.ExpiryDate = expirydate;
await UserRoleService.UpdateUserRoleAsync(userrole);
}
else
@ -154,25 +139,8 @@ else
userrole = new UserRole();
userrole.UserId = userid;
userrole.RoleId = roleid;
if (string.IsNullOrEmpty(effectivedate))
{
userrole.EffectiveDate = null;
}
else
{
userrole.EffectiveDate = DateTime.Parse(effectivedate);
}
if (string.IsNullOrEmpty(expirydate))
{
userrole.ExpiryDate = null;
}
else
{
userrole.ExpiryDate = DateTime.Parse(expirydate);
}
userrole.EffectiveDate = effectivedate;
userrole.ExpiryDate = expirydate;
await UserRoleService.AddUserRoleAsync(userrole);
}

View File

@ -0,0 +1,69 @@
@namespace Oqtane.Modules.Admin.Users
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IUserService UserService
@inject IStringLocalizer<Users> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="importfile" HelpText="Upload or select a tab delimited text file containing user information. The file must be in the Template format specified (Roles can be specified as a comma delimited list)." ResourceKey="ImportFile">Import File:</Label>
<div class="col-sm-9">
<FileManager Id="importfile" @ref="_filemanager" Filter="txt" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="notify" HelpText="Indicate if new users should receive an email notification" ResourceKey="Notify">Notify? </Label>
<div class="col-sm-9">
<select id="notify" class="form-select" @bind="@_notify" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
<br />
<button type="button" class="btn btn-success" @onclick="ImportUsers">@Localizer["Import"]</button>&nbsp;
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>&nbsp;
<a class="btn btn-info" href="/users.txt" target="_new">@Localizer["Template"]</a>
@code {
private FileManager _filemanager;
public override string Title => "Import Users";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
private string _notify = "True";
private async Task ImportUsers()
{
try
{
var fileid = _filemanager.GetFileId();
if (fileid != -1)
{
ShowProgressIndicator();
var results = await UserService.ImportUsersAsync(PageState.Site.SiteId, fileid, bool.Parse(_notify));
if (bool.Parse(results["Success"]))
{
AddModuleMessage(string.Format(Localizer["Message.Import.Success"], results["Users"]), MessageType.Success);
}
else
{
AddModuleMessage(Localizer["Message.Import.Failure"], MessageType.Error);
}
HideProgressIndicator();
}
else
{
AddModuleMessage(Localizer["Message.Import.Validation"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Importing Users {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Import"], MessageType.Error);
}
}
}

View File

@ -1,6 +1,7 @@
@namespace Oqtane.Modules.Controls
@using System.Text.Json
@inherits LocalizableComponent
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_visible)
{
@ -20,7 +21,7 @@
{
<button type="button" class="@Class" @onclick="Confirm">@((MarkupString)_iconSpan) @Text</button>
}
<button type="button" class="btn btn-secondary" @onclick="DisplayModal">@Localize("Cancel")</button>
<button type="button" class="btn btn-secondary" @onclick="DisplayModal">@SharedLocalizer["Cancel"]</button>
</div>
</div>
</div>

View File

@ -24,116 +24,135 @@
}
@code {
private string _text = string.Empty;
private string _parameters = string.Empty;
private string _url = string.Empty;
private List<Permission> _permissions;
private bool _editmode = false;
private bool _authorized = false;
private string _classname = "btn btn-primary";
private string _style = string.Empty;
private string _iconSpan = string.Empty;
private string _text = string.Empty;
private int _moduleId = -1;
private string _path = string.Empty;
private string _parameters = string.Empty;
private string _url = string.Empty;
private List<Permission> _permissions;
private bool _editmode = false;
private bool _authorized = false;
private string _classname = "btn btn-primary";
private string _style = string.Empty;
private string _iconSpan = string.Empty;
[Parameter]
public string Action { get; set; } // required
[Parameter]
public string Action { get; set; } // required
[Parameter]
public string Text { get; set; } // optional - defaults to Action if not specified
[Parameter]
public string Text { get; set; } // optional - defaults to Action if not specified
[Parameter]
public string Parameters { get; set; } // optional - querystring parameters should be in the form of "id=x&name=y"
[Parameter]
public int ModuleId { get; set; } = -1; // optional - allows the link to target a specific moduleid
[Parameter]
public int ModuleId { get; set; } = -1; // optional - allows the link to target a specific moduleid
[Parameter]
public string Path { get; set; } = null; // optional - allows the link to target a specific page
[Parameter]
public Action OnClick { get; set; } = null; // optional - executes a method in the calling component
[Parameter]
public string Parameters { get; set; } // optional - querystring parameters should be in the form of "id=x&name=y"
[Parameter]
public SecurityAccessLevel? Security { get; set; } // optional - can be used to explicitly specify SecurityAccessLevel
[Parameter]
public Action OnClick { get; set; } = null; // optional - executes a method in the calling component
[Parameter]
public string Permissions { get; set; } // deprecated - use PermissionList instead
[Parameter]
public SecurityAccessLevel? Security { get; set; } // optional - can be used to explicitly specify SecurityAccessLevel
[Parameter]
public List<Permission> PermissionList { get; set; } // optional - can be used to specify permissions
[Parameter]
public string Permissions { get; set; } // deprecated - use PermissionList instead
[Parameter]
public bool Disabled { get; set; } // optional
[Parameter]
public List<Permission> PermissionList { get; set; } // optional - can be used to specify permissions
[Parameter]
public string EditMode { get; set; } // optional - specifies if an authorized user must be in edit mode to see the action - default is false.
[Parameter]
public bool Disabled { get; set; } // optional
[Parameter]
public string Class { get; set; } // optional - defaults to primary if not specified
[Parameter]
public string EditMode { get; set; } // optional - specifies if an authorized user must be in edit mode to see the action - default is false.
[Parameter]
public string Style { get; set; } // optional
[Parameter]
public string Class { get; set; } // optional - defaults to primary if not specified
[Parameter]
public string IconName { get; set; } // optional - specifies an icon for the link - default is no icon
[Parameter]
public string Style { get; set; } // optional
[Parameter]
public bool IconOnly { get; set; } // optional - specifies only icon in link
[Parameter]
public string IconName { get; set; } // optional - specifies an icon for the link - default is no icon
[Parameter]
public string ReturnUrl { get; set; } // optional - used to set a url to redirect to
[Parameter]
public bool IconOnly { get; set; } // optional - specifies only icon in link
protected override void OnInitialized()
{
if (!string.IsNullOrEmpty(Permissions))
{
PermissionList = JsonSerializer.Deserialize<List<Permission>>(Permissions);
}
}
[Parameter]
public string ReturnUrl { get; set; } // optional - used to set a url to redirect to
protected override void OnParametersSet()
{
base.OnParametersSet();
_text = Action;
if (!string.IsNullOrEmpty(Text))
{
_text = Text;
}
protected override void OnInitialized()
{
if (!string.IsNullOrEmpty(Permissions))
{
PermissionList = JsonSerializer.Deserialize<List<Permission>>(Permissions);
}
}
if (IconOnly && !string.IsNullOrEmpty(IconName))
{
_text = string.Empty;
}
protected override void OnParametersSet()
{
base.OnParametersSet();
if (!string.IsNullOrEmpty(Parameters))
{
_parameters = Parameters;
}
_text = Action;
if (!string.IsNullOrEmpty(Text))
{
_text = Text;
}
if (!string.IsNullOrEmpty(Class))
{
_classname = Class;
}
if (IconOnly && !string.IsNullOrEmpty(IconName))
{
_text = string.Empty;
}
if (!string.IsNullOrEmpty(Style))
{
_style = Style;
}
_moduleId = ModuleState.ModuleId;
if (ModuleId != -1)
{
_moduleId = ModuleId;
}
if (!string.IsNullOrEmpty(EditMode))
{
_editmode = bool.Parse(EditMode);
}
_path = PageState.Page.Path;
if (Path != null)
{
_path = Path;
}
if (!string.IsNullOrEmpty(IconName))
{
if (!IconName.Contains(" "))
{
IconName = "oi oi-" + IconName;
}
_iconSpan = $"<span class=\"{IconName}\"></span>{(IconOnly ? "" : "&nbsp")}";
}
if (!string.IsNullOrEmpty(Parameters))
{
_parameters = Parameters;
}
if (!string.IsNullOrEmpty(Class))
{
_classname = Class;
}
if (!string.IsNullOrEmpty(Style))
{
_style = Style;
}
if (!string.IsNullOrEmpty(EditMode))
{
_editmode = bool.Parse(EditMode);
}
if (!string.IsNullOrEmpty(IconName))
{
if (!IconName.Contains(" "))
{
IconName = "oi oi-" + IconName;
}
_iconSpan = $"<span class=\"{IconName}\"></span>{(IconOnly ? "" : "&nbsp")}";
}
_permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList;
_text = Localize(nameof(Text), _text);
_url = (ModuleId == -1) ? EditUrl(Action, _parameters) : EditUrl(ModuleId, Action, _parameters);
_url = EditUrl(_path, _moduleId, Action, _parameters);
if (!string.IsNullOrEmpty(ReturnUrl))
{
_url += ((_url.Contains("?")) ? "&" : "?") + $"returnurl={WebUtility.UrlEncode(ReturnUrl)}";

View File

@ -64,12 +64,12 @@
if (!String.IsNullOrEmpty(ModifiedBy))
{
_text += $" {Localizer["by"]} <b>{ModifiedBy}</b>";
_text += $" {Localizer["By"]} <b>{ModifiedBy}</b>";
}
if (ModifiedOn != null)
{
_text += $" {Localizer["on"]} <b>{ModifiedOn.Value.ToString(DateTimeFormat)}</b>";
_text += $" {Localizer["On"]} <b>{ModifiedOn.Value.ToString(DateTimeFormat)}</b>";
}
_text += "</p>";

View File

@ -2,7 +2,7 @@
@inherits LocalizableComponent
<div class="app-autocomplete">
<input class="form-control" value="@Value" @oninput="OnInput" @onkeyup="OnKeyUp" placeholder="@Placeholder" autocomplete="off" />
<input class="form-control" value="@Value" @oninput="OnInput" @onkeyup="OnKeyUp" placeholder="@Placeholder" autocomplete="off" @attributes="InputAttributes" />
@if (_results != null)
{
<select class="form-select" style="position: relative;" value="@Value" size="@Rows" @onkeyup="OnKeyUp" @onchange="(e => OnChange(e))">
@ -29,27 +29,48 @@
</div>
@code {
Dictionary<string, string> _results;
Dictionary<string, string> _results;
Dictionary<string, object> InputAttributes { get; set; } = new();
[Parameter]
public Func<string, Task<Dictionary<string, string>>> OnSearch { get; set; } // required - an async delegate method which accepts a filter string parameter and returns a dictionary
[Parameter]
public Func<string, Task<Dictionary<string, string>>> OnSearch { get; set; } // required - an async delegate method which accepts a filter string parameter and returns a dictionary
[Parameter]
public int Characters { get; set; } = 3; // optional - number of characters before search is initiated
[Parameter]
public int Characters { get; set; } = 3; // optional - number of characters before search is initiated
[Parameter]
public int Rows { get; set; } = 3; // optional - number of result rows to display
[Parameter]
public int Rows { get; set; } = 3; // optional - number of result rows to display
[Parameter]
public string Placeholder { get; set; } // optional - placeholder input text
[Parameter]
public string Placeholder { get; set; } // optional - placeholder input text
[Parameter]
public string Value { get; set; } // value of item selected
[Parameter]
public string Value { get; set; } // value of item selected
[Parameter]
public string Key { get; set; } // key of item selected
[Parameter]
public string Key { get; set; } // key of item selected
private async Task OnInput(ChangeEventArgs e)
[Parameter]
public bool Required { get; set; } // optional - if the item is required
protected override void OnParametersSet()
{
if (Required)
{
if (!InputAttributes.ContainsKey(nameof(Required)))
{
InputAttributes.Add(nameof(Required), true);
}
}
else
{
if (InputAttributes.ContainsKey(nameof(Required)))
{
InputAttributes.Remove(nameof(Required));
}
}
}
private async Task OnInput(ChangeEventArgs e)
{
Value = e.Value?.ToString();
if (Value?.Length >= Characters)

View File

@ -55,16 +55,26 @@
</div>
<div class="col mt-2 text-end">
<button type="button" class="btn btn-success" @onclick="UploadFiles">@SharedLocalizer["Upload"]</button>
@if (ShowFiles && GetFileId() != -1)
@if (GetFileId() != -1)
{
<button type="button" class="btn btn-danger mx-1" @onclick="DeleteFile">@SharedLocalizer["Delete"]</button>
}
</div>
</div>
<div class="row">
<div class="col mt-1"><span id="@_progressinfoid" style="display: none;"></span></div>
<div class="col text-center mt-1"><progress id="@_progressbarid" class="mt-1" style="display: none;"></progress></div>
</div>
@if (ShowProgress)
{
<div class="row">
<div class="col mt-1"><span id="@_progressinfoid" style="display: none;"></span></div>
<div class="col mt-1"><progress id="@_progressbarid" class="mt-1" style="display: none;"></progress></div>
</div>
}
else
{
if (_uploading)
{
<div class="app-progress-indicator"></div>
}
}
}
</div>
</div>
@ -87,336 +97,400 @@
}
@code {
private bool _initialized = false;
private List<Folder> _folders;
private List<File> _files = new List<File>();
private string _fileinputid = string.Empty;
private string _progressinfoid = string.Empty;
private string _progressbarid = string.Empty;
private string _filter = "*";
private bool _haseditpermission = false;
private string _image = string.Empty;
private File _file = null;
private string _guid;
private string _message = string.Empty;
private MessageType _messagetype;
private bool _initialized = false;
private List<Folder> _folders;
private List<File> _files = new List<File>();
private string _fileinputid = string.Empty;
private string _progressinfoid = string.Empty;
private string _progressbarid = string.Empty;
private string _filter = "*";
private bool _haseditpermission = false;
private string _image = string.Empty;
private File _file = null;
private string _guid;
private string _message = string.Empty;
private MessageType _messagetype;
private bool _uploading = false;
[Parameter]
public string Id { get; set; } // optional - for setting the id of the FileManager component for accessibility
[Parameter]
public string Id { get; set; } // optional - for setting the id of the FileManager component for accessibility
[Parameter]
public int FolderId { get; set; } = -1; // optional - for setting a specific default folder by folderid
[Parameter]
public int FolderId { get; set; } = -1; // optional - for setting a specific default folder by folderid
[Parameter]
public string Folder { get; set; } = ""; // optional - for setting a specific default folder by folder path
[Parameter]
public string Folder { get; set; } = ""; // optional - for setting a specific default folder by folder path
[Parameter]
public int FileId { get; set; } = -1; // optional - for selecting a specific file by default
[Parameter]
public int FileId { get; set; } = -1; // optional - for selecting a specific file by default
[Parameter]
public string Filter { get; set; } // optional - comma delimited list of file types that can be selected or uploaded ie. "jpg,gif"
[Parameter]
public string Filter { get; set; } // optional - comma delimited list of file types that can be selected or uploaded ie. "jpg,gif"
[Parameter]
public bool ShowFiles { get; set; } = true; // optional - for indicating whether a list of files should be displayed - default is true
[Parameter]
public bool ShowFiles { get; set; } = true; // optional - for indicating whether a list of files should be displayed - default is true
[Parameter]
public bool ShowUpload { get; set; } = true; // optional - for indicating whether a Upload controls should be displayed - default is true
[Parameter]
public bool ShowUpload { get; set; } = true; // optional - for indicating whether a Upload controls should be displayed - default is true
[Parameter]
public bool ShowFolders { get; set; } = true; // optional - for indicating whether a list of folders should be displayed - default is true
[Parameter]
public bool ShowFolders { get; set; } = true; // optional - for indicating whether a list of folders should be displayed - default is true
[Parameter]
public bool ShowImage { get; set; } = true; // optional - for indicating whether an image thumbnail should be displayed - default is true
[Parameter]
public bool ShowImage { get; set; } = true; // optional - for indicating whether an image thumbnail should be displayed - default is true
[Parameter]
public bool ShowSuccess { get; set; } = false; // optional - for indicating whether a success message should be displayed upon successful upload - default is false
[Parameter]
public bool ShowProgress { get; set; } = true; // optional - for indicating whether progress info should be displayed during upload - default is true
[Parameter]
public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false
[Parameter]
public bool ShowSuccess { get; set; } = false; // optional - for indicating whether a success message should be displayed upon successful upload - default is false
[Parameter]
public EventCallback<int> OnUpload { get; set; } // optional - executes a method in the calling component when a file is uploaded
[Parameter]
public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false
[Parameter]
public EventCallback<int> OnSelect { get; set; } // optional - executes a method in the calling component when a file is selected
[Parameter]
public EventCallback<int> OnUpload { get; set; } // optional - executes a method in the calling component when a file is uploaded
[Parameter]
public EventCallback<int> OnDelete { get; set; } // optional - executes a method in the calling component when a file is deleted
[Parameter]
public EventCallback<int> OnSelect { get; set; } // optional - executes a method in the calling component when a file is selected
protected override async Task OnInitializedAsync()
{
// packages folder is a framework folder for uploading installable nuget packages
if (Folder == Constants.PackagesFolder)
{
ShowFiles = false;
ShowFolders = false;
Filter = "nupkg";
ShowSuccess = true;
}
[Parameter]
public EventCallback<int> OnDelete { get; set; } // optional - executes a method in the calling component when a file is deleted
if (!ShowFiles)
{
ShowImage = false;
}
protected override void OnInitialized()
{
// create unique id for component
_guid = Guid.NewGuid().ToString("N");
_fileinputid = "FileInput_" + _guid;
_progressinfoid = "ProgressInfo_" + _guid;
_progressbarid = "ProgressBar_" + _guid;
}
_folders = await FolderService.GetFoldersAsync(ModuleState.SiteId);
protected override async Task OnParametersSetAsync()
{
// packages folder is a framework folder for uploading installable nuget packages
if (Folder == Constants.PackagesFolder)
{
ShowFiles = false;
ShowFolders = false;
Filter = "nupkg";
ShowSuccess = true;
}
if (!string.IsNullOrEmpty(Folder) && Folder != Constants.PackagesFolder)
{
Folder folder = await FolderService.GetFolderAsync(ModuleState.SiteId, Folder);
if (folder != null)
{
FolderId = folder.FolderId;
}
else
{
FolderId = -1;
_message = "Folder Path " + Folder + "Does Not Exist";
_messagetype = MessageType.Error;
}
}
if (!string.IsNullOrEmpty(Folder) && Folder != Constants.PackagesFolder)
{
Folder folder = await FolderService.GetFolderAsync(ModuleState.SiteId, Folder);
if (folder != null)
{
FolderId = folder.FolderId;
}
else
{
FolderId = -1;
_message = "Folder Path " + Folder + "Does Not Exist";
_messagetype = MessageType.Error;
}
}
if (FileId != -1)
{
File file = await FileService.GetFileAsync(FileId);
if (file != null)
{
FolderId = file.FolderId;
await OnSelect.InvokeAsync(FileId);
}
else
{
FileId = -1; // file does not exist
_message = "FileId " + FileId.ToString() + "Does Not Exist";
_messagetype = MessageType.Error;
}
}
if (ShowFolders)
{
_folders = await FolderService.GetFoldersAsync(ModuleState.SiteId);
}
else
{
if (FolderId != -1)
{
var folder = await FolderService.GetFolderAsync(FolderId);
if (folder != null)
{
_folders = new List<Folder> { folder };
}
}
}
await SetImage();
if (FileId != -1)
{
File file = await FileService.GetFileAsync(FileId);
if (file != null)
{
FolderId = file.FolderId;
}
else
{
FileId = -1; // file does not exist
_message = "FileId " + FileId.ToString() + "Does Not Exist";
_messagetype = MessageType.Error;
}
}
if (!string.IsNullOrEmpty(Filter))
{
_filter = "." + Filter.Replace(",", ",.");
}
await SetImage();
await GetFiles();
if (!string.IsNullOrEmpty(Filter))
{
_filter = "." + Filter.Replace(",", ",.");
}
// create unique id for component
_guid = Guid.NewGuid().ToString("N");
_fileinputid = "FileInput_" + _guid;
_progressinfoid = "ProgressInfo_" + _guid;
_progressbarid = "ProgressBar_" + _guid;
await GetFiles();
_initialized = true;
}
_initialized = true;
}
private async Task GetFiles()
{
_haseditpermission = false;
if (Folder == Constants.PackagesFolder)
{
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, RoleNames.Host);
_files = new List<File>();
}
else
{
Folder folder = _folders.FirstOrDefault(item => item.FolderId == FolderId);
if (folder != null)
{
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, folder.PermissionList);
_files = await FileService.GetFilesAsync(FolderId);
}
else
{
_haseditpermission = false;
_files = new List<File>();
}
}
if (_filter != "*")
{
List<File> filtered = new List<File>();
foreach (File file in _files)
{
if (_filter.ToUpper().IndexOf("." + file.Extension.ToUpper()) != -1)
{
filtered.Add(file);
}
}
_files = filtered;
}
}
private async Task GetFiles()
{
_haseditpermission = false;
if (Folder == Constants.PackagesFolder)
{
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, RoleNames.Host);
_files = new List<File>();
}
else
{
Folder folder = _folders.FirstOrDefault(item => item.FolderId == FolderId);
if (folder != null)
{
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, folder.PermissionList);
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Browse, folder.PermissionList))
{
_files = await FileService.GetFilesAsync(FolderId);
}
else
{
_files = new List<File>();
}
}
else
{
_haseditpermission = false;
_files = new List<File>();
}
if (_filter != "*")
{
List<File> filtered = new List<File>();
foreach (File file in _files)
{
if (_filter.ToUpper().IndexOf("." + file.Extension.ToUpper()) != -1)
{
filtered.Add(file);
}
}
_files = filtered;
}
}
}
private async Task FolderChanged(ChangeEventArgs e)
{
_message = string.Empty;
try
{
FolderId = int.Parse((string)e.Value);
await GetFiles();
FileId = -1;
_file = null;
_image = string.Empty;
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Files {Error}", ex.Message);
_message = Localizer["Error.File.Load"];
_messagetype = MessageType.Error;
}
}
private async Task FolderChanged(ChangeEventArgs e)
{
_message = string.Empty;
try
{
FolderId = int.Parse((string)e.Value);
await GetFiles();
FileId = -1;
_file = null;
_image = string.Empty;
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Files {Error}", ex.Message);
_message = Localizer["Error.File.Load"];
_messagetype = MessageType.Error;
}
}
private async Task FileChanged(ChangeEventArgs e)
{
_message = string.Empty;
FileId = int.Parse((string)e.Value);
if (FileId != -1)
{
await OnSelect.InvokeAsync(FileId);
}
private async Task FileChanged(ChangeEventArgs e)
{
_message = string.Empty;
FileId = int.Parse((string)e.Value);
await SetImage();
await OnSelect.InvokeAsync(FileId);
StateHasChanged();
await SetImage();
StateHasChanged();
}
}
private async Task SetImage()
{
_image = string.Empty;
_file = null;
if (FileId != -1)
{
_file = await FileService.GetFileAsync(FileId);
if (_file != null && ShowImage && _file.ImageHeight != 0 && _file.ImageWidth != 0)
{
var maxwidth = 200;
var maxheight = 200;
private async Task SetImage()
{
_image = string.Empty;
_file = null;
if (FileId != -1)
{
_file = await FileService.GetFileAsync(FileId);
if (_file != null && ShowImage && _file.ImageHeight != 0 && _file.ImageWidth != 0)
{
var maxwidth = 200;
var maxheight = 200;
var ratioX = (double)maxwidth / (double)_file.ImageWidth;
var ratioY = (double)maxheight / (double)_file.ImageHeight;
var ratio = ratioX < ratioY ? ratioX : ratioY;
var ratioX = (double)maxwidth / (double)_file.ImageWidth;
var ratioY = (double)maxheight / (double)_file.ImageHeight;
var ratio = ratioX < ratioY ? ratioX : ratioY;
_image = "<img src=\"" + _file.Url + "\" alt=\"" + _file.Name +
"\" width=\"" + Convert.ToInt32(_file.ImageWidth * ratio).ToString() +
"\" height=\"" + Convert.ToInt32(_file.ImageHeight * ratio).ToString() + "\" />";
}
}
}
_image = "<img src=\"" + _file.Url + "\" alt=\"" + _file.Name +
"\" width=\"" + Convert.ToInt32(_file.ImageWidth * ratio).ToString() +
"\" height=\"" + Convert.ToInt32(_file.ImageHeight * ratio).ToString() + "\" />";
}
}
}
private async Task UploadFiles()
{
_message = string.Empty;
var interop = new Interop(JSRuntime);
var uploads = await interop.GetFiles(_fileinputid);
if (uploads.Length > 0)
{
string restricted = "";
foreach (var upload in uploads)
{
var extension = (upload.LastIndexOf(".") != -1) ? upload.Substring(upload.LastIndexOf(".") + 1) : "";
if (!Constants.UploadableFiles.Split(',').Contains(extension.ToLower()))
{
restricted += (restricted == "" ? "" : ",") + extension;
}
}
if (restricted == "")
{
try
{
// upload the files
var posturl = Utilities.TenantUrl(PageState.Alias, "/api/file/upload");
var folder = (Folder == Constants.PackagesFolder) ? Folder : FolderId.ToString();
await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken);
private async Task UploadFiles()
{
_message = string.Empty;
var interop = new Interop(JSRuntime);
var uploads = await interop.GetFiles(_fileinputid);
if (uploads.Length > 0)
{
string restricted = "";
foreach (var upload in uploads)
{
var filename = upload.Split(':')[0];
var extension = (filename.LastIndexOf(".") != -1) ? filename.Substring(filename.LastIndexOf(".") + 1) : "";
if (!Constants.UploadableFiles.Split(',').Contains(extension.ToLower()))
{
restricted += (restricted == "" ? "" : ",") + extension;
}
}
if (restricted == "")
{
if (!ShowProgress)
{
_uploading = true;
StateHasChanged();
}
// uploading is asynchronous so we need to wait for the uploads to complete
// note that this will only wait a maximum of 15 seconds which may not be long enough for very large file uploads
bool success = false;
int attempts = 0;
while (attempts < 5 && !success)
{
attempts += 1;
Thread.Sleep(1000 * attempts); // progressive retry
try
{
// upload the files
var posturl = Utilities.TenantUrl(PageState.Alias, "/api/file/upload");
var folder = (Folder == Constants.PackagesFolder) ? Folder : FolderId.ToString();
await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken);
success = true;
List<File> files = await FileService.GetFilesAsync(folder);
if (files.Count > 0)
{
foreach (string upload in uploads)
{
if (!files.Exists(item => item.Name == upload))
{
success = false;
}
}
}
}
// uploading is asynchronous so we need to poll to determine if uploads are completed
var success = true;
int upload = 0;
while (upload < uploads.Length && success)
{
success = false;
var filename = uploads[upload].Split(':')[0];
var size = Int64.Parse(uploads[upload].Split(':')[1]);
var maxattempts = (int)Math.Ceiling(size / 500000.0) + 1; // 30 MB takes 1 minute at 5 Mbps
// reset progress indicators
await interop.SetElementAttribute(_guid + "ProgressInfo", "style", "display: none;");
await interop.SetElementAttribute(_guid + "ProgressBar", "style", "display: none;");
int attempts = 0;
while (attempts < maxattempts && !success)
{
attempts += 1;
Thread.Sleep(1000);
if (success)
{
await logger.LogInformation("File Upload Succeeded {Files}", uploads);
if (ShowSuccess)
{
_message = Localizer["Success.File.Upload"];
_messagetype = MessageType.Success;
}
}
else
{
await logger.LogInformation("File Upload Failed Or Is Still In Progress {Files}", uploads);
_message = Localizer["Error.File.Upload"];
_messagetype = MessageType.Error;
}
if (Folder == Constants.PackagesFolder)
{
var files = await FileService.GetFilesAsync(folder);
if (files != null && files.Any(item => item.Name == filename))
{
success = true;
}
}
else
{
var file = await FileService.GetFileAsync(int.Parse(folder), filename);
if (file != null)
{
success = true;
}
}
}
if (success)
{
upload++;
}
}
// set FileId to first file in upload collection
await GetFiles();
var file = _files.Where(item => item.Name == uploads[0]).FirstOrDefault();
if (file != null)
{
FileId = file.FileId;
await SetImage();
await OnUpload.InvokeAsync(FileId);
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "File Upload Failed {Error}", ex.Message);
_message = Localizer["Error.File.Upload"];
_messagetype = MessageType.Error;
}
}
else
{
_message = string.Format(Localizer["Message.File.Restricted"], restricted);
_messagetype = MessageType.Warning;
}
}
else
{
_message = Localizer["Message.File.NotSelected"];
_messagetype = MessageType.Warning;
}
}
// reset progress indicators
if (ShowProgress)
{
await interop.SetElementAttribute(_progressinfoid, "style", "display: none;");
await interop.SetElementAttribute(_progressbarid, "style", "display: none;");
}
else
{
_uploading = false;
StateHasChanged();
}
private async Task DeleteFile()
{
_message = string.Empty;
try
{
await FileService.DeleteFileAsync(FileId);
await logger.LogInformation("File Deleted {File}", FileId);
await OnDelete.InvokeAsync(FileId);
if (success)
{
await logger.LogInformation("File Upload Succeeded {Files}", uploads);
if (ShowSuccess)
{
_message = Localizer["Success.File.Upload"];
_messagetype = MessageType.Success;
}
}
else
{
await logger.LogInformation("File Upload Failed Or Is Still In Progress {Files}", uploads);
_message = Localizer["Error.File.Upload"];
_messagetype = MessageType.Error;
}
_message = Localizer["Success.File.Delete"];
_messagetype = MessageType.Success;
if (Folder == Constants.PackagesFolder)
{
await OnUpload.InvokeAsync(-1);
}
else
{
// set FileId to first file in upload collection
var file = await FileService.GetFileAsync(int.Parse(folder), uploads[0].Split(":")[0]);
if (file != null)
{
FileId = file.FileId;
await SetImage();
await OnSelect.InvokeAsync(FileId);
await OnUpload.InvokeAsync(FileId);
}
await GetFiles();
StateHasChanged();
}
}
catch (Exception ex)
{
await logger.LogError(ex, "File Upload Failed {Error}", ex.Message);
_message = Localizer["Error.File.Upload"];
_messagetype = MessageType.Error;
_uploading = false;
}
}
else
{
_message = string.Format(Localizer["Message.File.Restricted"], restricted);
_messagetype = MessageType.Warning;
}
}
else
{
_message = Localizer["Message.File.NotSelected"];
_messagetype = MessageType.Warning;
}
}
private async Task DeleteFile()
{
_message = string.Empty;
try
{
await FileService.DeleteFileAsync(FileId);
await logger.LogInformation("File Deleted {File}", FileId);
await OnDelete.InvokeAsync(FileId);
if (ShowSuccess)
{
_message = Localizer["Success.File.Delete"];
_messagetype = MessageType.Success;
}
await GetFiles();
FileId = -1;
await SetImage();
StateHasChanged();
await OnSelect.InvokeAsync(FileId);
StateHasChanged();
}
catch (Exception ex)
{

View File

@ -0,0 +1,50 @@
@namespace Oqtane.Modules.Controls
@inherits LocalizableComponent
<input type="text" value="@Value" list="@_id" class="form-control" @onchange="(e => OnChange(e))" />
<datalist id="@_id" value="@Value">
@foreach(var kvp in DataList)
{
if (!string.IsNullOrEmpty(kvp.Value))
{
<option value="@kvp.Key">@Localize(kvp.Value, kvp.Value)</option>
}
else
{
<option value="@kvp.Key">@Localize(kvp.Key, kvp.Key)</option>
}
}
</datalist>
@code {
private string _id;
[Parameter]
public string Value { get; set; }
[EditorRequired]
[Parameter]
public Dictionary<string, string> DataList { get; set; }
[EditorRequired]
[Parameter]
public EventCallback<string> ValueChanged { get; set; }
protected override void OnInitialized()
{
// create unique id for component
_id = "DataList_" + Guid.NewGuid().ToString("N");
}
protected void OnChange(ChangeEventArgs e)
{
if (!string.IsNullOrEmpty(e.Value.ToString()))
{
Value = e.Value.ToString();
if (ValueChanged.HasDelegate)
{
ValueChanged.InvokeAsync(Value);
}
}
}
}

View File

@ -5,7 +5,7 @@
<div class="row" style="margin-bottom: 50px;">
<div class="col">
<TabStrip>
<TabPanel Name="Rich" Heading="Rich Text Editor">
<TabPanel Name="Rich" Heading="Rich Text Editor" ResourceKey="RichTextEditor">
@if (_richfilemanager)
{
<FileManager @ref="_fileManager" Filter="@Constants.ImageFiles" />

View File

@ -17,7 +17,10 @@
<hr class="app-rule" />
</div>
<div class="collapse @_show" id="@Name">
@ChildContent
@if (ChildContent != null)
{
@ChildContent
}
</div>
@code {
@ -26,7 +29,7 @@
private string _show = string.Empty;
[Parameter]
public RenderFragment ChildContent { get; set; }
public RenderFragment ChildContent { get; set; } = null;
[Parameter]
public string Name { get; set; } // required - the name of the section
@ -37,19 +40,12 @@
[Parameter]
public string Expanded { get; set; } // optional - will default to false if not provided
protected override void OnInitialized()
{
_heading = (!string.IsNullOrEmpty(Heading)) ? Heading : Name;
_expanded = (!string.IsNullOrEmpty(Expanded)) ? Expanded : "false";
if (_expanded == "true") { _show = "show"; }
}
protected override void OnParametersSet()
{
base.OnParametersSet();
base.OnParametersSet(); // must be included to call method in LocalizableComponent
_heading = !string.IsNullOrEmpty(Heading)
? Localize(nameof(Heading), Heading)
: Localize(nameof(Name), Name);
_heading = !string.IsNullOrEmpty(Heading) ? Localize(nameof(Heading), Heading) : Localize(nameof(Name), Name);
_expanded = (!string.IsNullOrEmpty(Expanded)) ? Expanded.ToLower() : "false";
if (_expanded == "true") { _show = "show"; }
}
}

View File

@ -8,7 +8,7 @@
@foreach (TabPanel tabPanel in _tabPanels)
{
<li class="nav-item" @key="tabPanel.Name">
@if (tabPanel.Name == ActiveTab)
@if (tabPanel.Name.ToLower() == ActiveTab.ToLower())
{
<a class="nav-link active" data-bs-toggle="tab" href="#@(Id + tabPanel.Name)" role="tab" @onclick:preventDefault="true">
@tabPanel.DisplayHeading()

View File

@ -53,7 +53,6 @@
public override List<Resource> Resources => new List<Resource>()
{
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill.bubble.css" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill.snow.css" }
};

View File

@ -15,11 +15,6 @@
}
@code {
public override List<Resource> Resources => new List<Resource>()
{
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }
};
private string content = "";
protected override async Task OnParametersSetAsync()

View File

@ -1,5 +1,7 @@
using System.Collections.Generic;
using Oqtane.Documentation;
using Oqtane.Models;
using Oqtane.Shared;
namespace Oqtane.Modules.HtmlText
{
@ -13,7 +15,11 @@ namespace Oqtane.Modules.HtmlText
Version = "1.0.1",
ServerManagerType = "Oqtane.Modules.HtmlText.Manager.HtmlTextManager, Oqtane.Server",
ReleaseVersions = "1.0.0,1.0.1",
SettingsType = "Oqtane.Modules.HtmlText.Settings, Oqtane.Client"
SettingsType = "Oqtane.Modules.HtmlText.Settings, Oqtane.Client",
Resources = new List<Resource>()
{
new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Module.css" }
}
};
}
}

View File

@ -9,6 +9,7 @@ using Oqtane.UI;
using System.Collections.Generic;
using Microsoft.JSInterop;
using System.Linq;
using System.Dynamic;
namespace Oqtane.Modules
{
@ -70,17 +71,33 @@ namespace Oqtane.Modules
{
if (firstRender)
{
if (Resources != null && Resources.Exists(item => item.ResourceType == ResourceType.Script))
List<Resource> resources = null;
var type = GetType();
if (type.BaseType == typeof(ModuleBase))
{
if (PageState.Page.Resources != null)
{
resources = PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level != ResourceLevel.Site && item.Namespace == type.Namespace).ToList();
}
}
else // modulecontrolbase
{
if (Resources != null)
{
resources = Resources.Where(item => item.ResourceType == ResourceType.Script).ToList();
}
}
if (resources != null &&resources.Any())
{
var interop = new Interop(JSRuntime);
var scripts = new List<object>();
var inline = 0;
foreach (Resource resource in Resources.Where(item => item.ResourceType == ResourceType.Script))
foreach (Resource resource in resources)
{
if (!string.IsNullOrEmpty(resource.Url))
{
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 });
scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module, location = resource.Location.ToString().ToLower() });
}
else
{
@ -104,6 +121,7 @@ namespace Oqtane.Modules
}
// url methods
public string NavigateUrl()
{
return NavigateUrl(PageState.Page.Path);
@ -263,16 +281,47 @@ namespace Oqtane.Modules
public void SetModuleTitle(string title)
{
var obj = new { PageModuleId = ModuleState.PageModuleId, Title = title };
dynamic obj = new ExpandoObject();
obj.PageModuleId = ModuleState.PageModuleId;
obj.Title = title;
SiteState.Properties.ModuleTitle = obj;
}
public void SetModuleVisibility(bool visible)
{
var obj = new { PageModuleId = ModuleState.PageModuleId, Visible = visible };
dynamic obj = new ExpandoObject();
obj.PageModuleId = ModuleState.PageModuleId;
obj.Visible = visible;
SiteState.Properties.ModuleVisibility = obj;
}
public void SetPageTitle(string title)
{
SiteState.Properties.PageTitle = title;
}
// note - only supports links and meta tags - not scripts
public void AddHeadContent(string content)
{
SiteState.AppendHeadContent(content);
}
public void AddScript(Resource resource)
{
resource.ResourceType = ResourceType.Script;
if (Resources == null) Resources = new List<Resource>();
if (!Resources.Any(item => (!string.IsNullOrEmpty(resource.Url) && item.Url == resource.Url) || (!string.IsNullOrEmpty(resource.Content) && item.Content == resource.Content)))
{
Resources.Add(resource);
}
}
public async Task ScrollToPageTop()
{
var interop = new Interop(JSRuntime);
await interop.ScrollTo(0, 0, "smooth");
}
// logging methods
public async Task Log(Alias alias, LogLevel level, string function, Exception exception, string message, params object[] args)
{

View File

@ -1,19 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<OutputType>Exe</OutputType>
<RazorLangVersion>3.0</RazorLangVersion>
<Configurations>Debug;Release</Configurations>
<Version>3.4.2</Version>
<Version>4.0.4</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
<Description>Modular Application Framework for Blazor and MAUI</Description>
<Description>CMS and Application Framework for Blazor and .NET 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.4.2</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.4</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace>
@ -22,13 +21,13 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.3" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="6.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.5" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="7.0.5" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="7.0.5" />
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
<PackageReference Include="System.Net.Http.Json" Version="7.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="6.0.3" />
<PackageReference Include="System.Net.Http.Json" Version="6.0.0" />
</ItemGroup>
<ItemGroup>

View File

@ -1,22 +1,25 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Runtime.Loader;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.JSInterop;
using Oqtane.Documentation;
using Oqtane.Models;
using Oqtane.Modules;
using Oqtane.Services;
using Oqtane.Shared;
using Oqtane.UI;
namespace Oqtane.Client
@ -66,6 +69,14 @@ namespace Oqtane.Client
private static async Task LoadClientAssemblies(HttpClient http, IServiceProvider serviceProvider)
{
// get alias
var navigationManager = serviceProvider.GetRequiredService<NavigationManager>();
var urlpath = GetUrlPath(navigationManager.Uri);
var json = await http.GetStringAsync($"api/Installation/installed/?path={WebUtility.UrlEncode(urlpath)}");
var installation = JsonSerializer.Deserialize<Installation>(json, new JsonSerializerOptions(JsonSerializerDefaults.Web));
urlpath = installation.Alias.Path;
urlpath = (!string.IsNullOrEmpty(urlpath)) ? urlpath + "/" : urlpath;
var dlls = new Dictionary<string, byte[]>();
var pdbs = new Dictionary<string, byte[]>();
var list = new List<string>();
@ -77,7 +88,7 @@ namespace Oqtane.Client
if (files.Count() != 0)
{
// get list of assemblies from server
var json = await http.GetStringAsync("/api/Installation/list");
json = await http.GetStringAsync($"{urlpath}api/Installation/list");
var assemblies = JsonSerializer.Deserialize<List<string>>(json);
// determine which assemblies need to be downloaded
@ -139,7 +150,7 @@ namespace Oqtane.Client
if (list.Count != 0)
{
// get assemblies from server and load into client app domain
var zip = await http.GetByteArrayAsync($"/api/Installation/load?list=" + string.Join(",", list));
var zip = await http.GetByteArrayAsync($"{urlpath}api/Installation/load?list=" + string.Join(",", list));
// asemblies and debug symbols are packaged in a zip file
using (ZipArchive archive = new ZipArchive(new MemoryStream(zip)))
@ -255,5 +266,10 @@ namespace Oqtane.Client
CultureInfo.DefaultThreadCurrentCulture = cultureInfo;
CultureInfo.DefaultThreadCurrentUICulture = cultureInfo;
}
private static string GetUrlPath(string url)
{
return new Uri(url).AbsolutePath.Substring(1);
}
}
}

View File

@ -0,0 +1,789 @@
<?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="Icon.AccountLogin" xml:space="preserve">
<value>Account Login</value>
</data>
<data name="Icon.AccountLogout" xml:space="preserve">
<value>Account Logout</value>
</data>
<data name="Icon.ActionRedo" xml:space="preserve">
<value>Action Redo</value>
</data>
<data name="Icon.ActionUndo" xml:space="preserve">
<value>Action Undo</value>
</data>
<data name="Icon.AlignCenter" xml:space="preserve">
<value>Align Center</value>
</data>
<data name="Icon.AlignLeft" xml:space="preserve">
<value>Align Left</value>
</data>
<data name="Icon.AlignRight" xml:space="preserve">
<value>Align Right</value>
</data>
<data name="Icon.Aperture" xml:space="preserve">
<value>Aperture</value>
</data>
<data name="Icon.ArrowBottom" xml:space="preserve">
<value>Arrow Bottom</value>
</data>
<data name="Icon.ArrowCircleBottom" xml:space="preserve">
<value>Arrow Circle Bottom</value>
</data>
<data name="Icon.ArrowCircleLeft" xml:space="preserve">
<value>Arrow Circle Left</value>
</data>
<data name="Icon.ArrowCircleRight" xml:space="preserve">
<value>Arrow Circle Right</value>
</data>
<data name="Icon.ArrowCircleTop" xml:space="preserve">
<value>Arrow Circle Top</value>
</data>
<data name="Icon.ArrowLeft" xml:space="preserve">
<value>Arrow Left</value>
</data>
<data name="Icon.ArrowRight" xml:space="preserve">
<value>Arrow Right</value>
</data>
<data name="Icon.ArrowThickBottom" xml:space="preserve">
<value>Arrow Thick Bottom</value>
</data>
<data name="Icon.ArrowThickLeft" xml:space="preserve">
<value>Arrow Thick Left</value>
</data>
<data name="Icon.ArrowThickRight" xml:space="preserve">
<value>Arrow Thick Right</value>
</data>
<data name="Icon.ArrowThickTop" xml:space="preserve">
<value>Arrow Thick Top</value>
</data>
<data name="Icon.ArrowTop" xml:space="preserve">
<value>Arrow Top</value>
</data>
<data name="Icon.AudioSpectrum" xml:space="preserve">
<value>Audio Spectrum</value>
</data>
<data name="Icon.Audio" xml:space="preserve">
<value>Audio</value>
</data>
<data name="Icon.Badge" xml:space="preserve">
<value>Badge</value>
</data>
<data name="Icon.Ban" xml:space="preserve">
<value>Ban</value>
</data>
<data name="Icon.BarChart" xml:space="preserve">
<value>Bar Chart</value>
</data>
<data name="Icon.Basket" xml:space="preserve">
<value>Basket</value>
</data>
<data name="Icon.BatteryEmpty" xml:space="preserve">
<value>Battery Empty</value>
</data>
<data name="Icon.BatteryFull" xml:space="preserve">
<value>Battery Full</value>
</data>
<data name="Icon.Beaker" xml:space="preserve">
<value>Beaker</value>
</data>
<data name="Icon.Bell" xml:space="preserve">
<value>Bell</value>
</data>
<data name="Icon.Bluetooth" xml:space="preserve">
<value>Bluetooth</value>
</data>
<data name="Icon.Bold" xml:space="preserve">
<value>Bold</value>
</data>
<data name="Icon.Bolt" xml:space="preserve">
<value>Bolt</value>
</data>
<data name="Icon.Book" xml:space="preserve">
<value>Book</value>
</data>
<data name="Icon.Bookmark" xml:space="preserve">
<value>Bookmark</value>
</data>
<data name="Icon.Box" xml:space="preserve">
<value>Box</value>
</data>
<data name="Icon.Briefcase" xml:space="preserve">
<value>Briefcase</value>
</data>
<data name="Icon.BritishPound" xml:space="preserve">
<value>British Pound</value>
</data>
<data name="Icon.Browser" xml:space="preserve">
<value>Browser</value>
</data>
<data name="Icon.Brush" xml:space="preserve">
<value>Brush</value>
</data>
<data name="Icon.Bug" xml:space="preserve">
<value>Bug</value>
</data>
<data name="Icon.Bullhorn" xml:space="preserve">
<value>Bullhorn</value>
</data>
<data name="Icon.Calculator" xml:space="preserve">
<value>Calculator</value>
</data>
<data name="Icon.Calendar" xml:space="preserve">
<value>Calendar</value>
</data>
<data name="Icon.CameraSlr" xml:space="preserve">
<value>Camera Slr</value>
</data>
<data name="Icon.CaretBottom" xml:space="preserve">
<value>Caret Bottom</value>
</data>
<data name="Icon.CaretLeft" xml:space="preserve">
<value>Caret Left</value>
</data>
<data name="Icon.CaretRight" xml:space="preserve">
<value>Caret Right</value>
</data>
<data name="Icon.CaretTop" xml:space="preserve">
<value>Caret Top</value>
</data>
<data name="Icon.Cart" xml:space="preserve">
<value>Cart</value>
</data>
<data name="Icon.Chat" xml:space="preserve">
<value>Chat</value>
</data>
<data name="Icon.Check" xml:space="preserve">
<value>Check</value>
</data>
<data name="Icon.ChevronBottom" xml:space="preserve">
<value>Chevron Bottom</value>
</data>
<data name="Icon.ChevronLeft" xml:space="preserve">
<value>Chevron Left</value>
</data>
<data name="Icon.ChevronRight" xml:space="preserve">
<value>Chevron Right</value>
</data>
<data name="Icon.ChevronTop" xml:space="preserve">
<value>Chevron Top</value>
</data>
<data name="Icon.CircleCheck" xml:space="preserve">
<value>Circle Check</value>
</data>
<data name="Icon.CircleX" xml:space="preserve">
<value>Circle X</value>
</data>
<data name="Icon.Clipboard" xml:space="preserve">
<value>Clipboard</value>
</data>
<data name="Icon.Clock" xml:space="preserve">
<value>Clock</value>
</data>
<data name="Icon.CloudDownload" xml:space="preserve">
<value>Cloud Download</value>
</data>
<data name="Icon.CloudUpload" xml:space="preserve">
<value>Cloud Upload</value>
</data>
<data name="Icon.Cloud" xml:space="preserve">
<value>Cloud</value>
</data>
<data name="Icon.Cloudy" xml:space="preserve">
<value>Cloudy</value>
</data>
<data name="Icon.Code" xml:space="preserve">
<value>Code</value>
</data>
<data name="Icon.Cog" xml:space="preserve">
<value>Cog</value>
</data>
<data name="Icon.CollapseDown" xml:space="preserve">
<value>Collapse Down</value>
</data>
<data name="Icon.CollapseLeft" xml:space="preserve">
<value>Collapse Left</value>
</data>
<data name="Icon.CollapseRight" xml:space="preserve">
<value>Collapse Right</value>
</data>
<data name="Icon.CollapseUp" xml:space="preserve">
<value>Collapse Up</value>
</data>
<data name="Icon.Command" xml:space="preserve">
<value>Command</value>
</data>
<data name="Icon.CommentSquare" xml:space="preserve">
<value>Comment Square</value>
</data>
<data name="Icon.Compass" xml:space="preserve">
<value>Compass</value>
</data>
<data name="Icon.Contrast" xml:space="preserve">
<value>Contrast</value>
</data>
<data name="Icon.Copywriting" xml:space="preserve">
<value>Copywriting</value>
</data>
<data name="Icon.CreditCard" xml:space="preserve">
<value>Credit Card</value>
</data>
<data name="Icon.Crop" xml:space="preserve">
<value>Crop</value>
</data>
<data name="Icon.Dashboard" xml:space="preserve">
<value>Dashboard</value>
</data>
<data name="Icon.DataTransferDownload" xml:space="preserve">
<value>Data Transfer Download</value>
</data>
<data name="Icon.DataTransferUpload" xml:space="preserve">
<value>Data Transfer Upload</value>
</data>
<data name="Icon.Delete" xml:space="preserve">
<value>Delete</value>
</data>
<data name="Icon.Dial" xml:space="preserve">
<value>Dial</value>
</data>
<data name="Icon.Document" xml:space="preserve">
<value>Document</value>
</data>
<data name="Icon.Dollar" xml:space="preserve">
<value>Dollar</value>
</data>
<data name="Icon.DoubleQuoteSansLeft" xml:space="preserve">
<value>Double Quote Sans Left</value>
</data>
<data name="Icon.DoubleQuoteSansRight" xml:space="preserve">
<value>Double Quote Sans Right</value>
</data>
<data name="Icon.DoubleQuoteSerifLeft" xml:space="preserve">
<value>Double Quote Serif Left</value>
</data>
<data name="Icon.DoubleQuoteSerifRight" xml:space="preserve">
<value>Double Quote Serif Right</value>
</data>
<data name="Icon.Droplet" xml:space="preserve">
<value>Droplet</value>
</data>
<data name="Icon.Eject" xml:space="preserve">
<value>Eject</value>
</data>
<data name="Icon.Elevator" xml:space="preserve">
<value>Elevator</value>
</data>
<data name="Icon.Ellipses" xml:space="preserve">
<value>Ellipses</value>
</data>
<data name="Icon.EnvelopeClosed" xml:space="preserve">
<value>Envelope Closed</value>
</data>
<data name="Icon.EnvelopeOpen" xml:space="preserve">
<value>Envelope Open</value>
</data>
<data name="Icon.Euro" xml:space="preserve">
<value>Euro</value>
</data>
<data name="Icon.Excerpt" xml:space="preserve">
<value>Excerpt</value>
</data>
<data name="Icon.ExpandDown" xml:space="preserve">
<value>Expand Down</value>
</data>
<data name="Icon.ExpandLeft" xml:space="preserve">
<value>Expand Left</value>
</data>
<data name="Icon.ExpandRight" xml:space="preserve">
<value>Expand Right</value>
</data>
<data name="Icon.ExpandUp" xml:space="preserve">
<value>Expand Up</value>
</data>
<data name="Icon.ExternalLink" xml:space="preserve">
<value>External Link</value>
</data>
<data name="Icon.Eye" xml:space="preserve">
<value>Eye</value>
</data>
<data name="Icon.Eyedropper" xml:space="preserve">
<value>Eyedropper</value>
</data>
<data name="Icon.File" xml:space="preserve">
<value>File</value>
</data>
<data name="Icon.Fire" xml:space="preserve">
<value>Fire</value>
</data>
<data name="Icon.Flag" xml:space="preserve">
<value>Flag</value>
</data>
<data name="Icon.Flash" xml:space="preserve">
<value>Flash</value>
</data>
<data name="Icon.Folder" xml:space="preserve">
<value>Folder</value>
</data>
<data name="Icon.Fork" xml:space="preserve">
<value>Fork</value>
</data>
<data name="Icon.FullscreenEnter" xml:space="preserve">
<value>Fullscreen Enter</value>
</data>
<data name="Icon.FullscreenExit" xml:space="preserve">
<value>Fullscreen Exit</value>
</data>
<data name="Icon.Globe" xml:space="preserve">
<value>Globe</value>
</data>
<data name="Icon.Graph" xml:space="preserve">
<value>Graph</value>
</data>
<data name="Icon.GridFourUp" xml:space="preserve">
<value>Grid Four Up</value>
</data>
<data name="Icon.GridThreeUp" xml:space="preserve">
<value>Grid Three Up</value>
</data>
<data name="Icon.GridTwoUp" xml:space="preserve">
<value>Grid Two Up</value>
</data>
<data name="Icon.HardDrive" xml:space="preserve">
<value>Hard Drive</value>
</data>
<data name="Icon.Header" xml:space="preserve">
<value>Header</value>
</data>
<data name="Icon.Headphones" xml:space="preserve">
<value>Headphones</value>
</data>
<data name="Icon.Heart" xml:space="preserve">
<value>Heart</value>
</data>
<data name="Icon.Home" xml:space="preserve">
<value>Home</value>
</data>
<data name="Icon.Image" xml:space="preserve">
<value>Image</value>
</data>
<data name="Icon.Inbox" xml:space="preserve">
<value>Inbox</value>
</data>
<data name="Icon.Infinity" xml:space="preserve">
<value>Infinity</value>
</data>
<data name="Icon.Info" xml:space="preserve">
<value>Info</value>
</data>
<data name="Icon.Italic" xml:space="preserve">
<value>Italic</value>
</data>
<data name="Icon.JustifyCenter" xml:space="preserve">
<value>Justify Center</value>
</data>
<data name="Icon.JustifyLeft" xml:space="preserve">
<value>Justify Left</value>
</data>
<data name="Icon.JustifyRight" xml:space="preserve">
<value>Justify Right</value>
</data>
<data name="Icon.Key" xml:space="preserve">
<value>Key</value>
</data>
<data name="Icon.Laptop" xml:space="preserve">
<value>Laptop</value>
</data>
<data name="Icon.Layers" xml:space="preserve">
<value>Layers</value>
</data>
<data name="Icon.Lightbulb" xml:space="preserve">
<value>Lightbulb</value>
</data>
<data name="Icon.LinkBroken" xml:space="preserve">
<value>Link Broken</value>
</data>
<data name="Icon.LinkIntact" xml:space="preserve">
<value>Link Intact</value>
</data>
<data name="Icon.ListRich" xml:space="preserve">
<value>List Rich</value>
</data>
<data name="Icon.List" xml:space="preserve">
<value>List</value>
</data>
<data name="Icon.Location" xml:space="preserve">
<value>Location</value>
</data>
<data name="Icon.LockLocked" xml:space="preserve">
<value>Lock Locked</value>
</data>
<data name="Icon.LockUnlocked" xml:space="preserve">
<value>Lock Unlocked</value>
</data>
<data name="Icon.LoopCircular" xml:space="preserve">
<value>Loop Circular</value>
</data>
<data name="Icon.LoopSquare" xml:space="preserve">
<value>Loop Square</value>
</data>
<data name="Icon.Loop" xml:space="preserve">
<value>Loop</value>
</data>
<data name="Icon.MagnifyingGlass" xml:space="preserve">
<value>Magnifying Glass</value>
</data>
<data name="Icon.MapMarker" xml:space="preserve">
<value>Map Marker</value>
</data>
<data name="Icon.Map" xml:space="preserve">
<value>Map</value>
</data>
<data name="Icon.MediaPause" xml:space="preserve">
<value>Media Pause</value>
</data>
<data name="Icon.MediaPlay" xml:space="preserve">
<value>Media Play</value>
</data>
<data name="Icon.MediaRecord" xml:space="preserve">
<value>Media Record</value>
</data>
<data name="Icon.MediaSkipBackward" xml:space="preserve">
<value>Media Skip Backward</value>
</data>
<data name="Icon.MediaSkipForward" xml:space="preserve">
<value>Media Skip Forward</value>
</data>
<data name="Icon.MediaStepBackward" xml:space="preserve">
<value>Media Step Backward</value>
</data>
<data name="Icon.MediaStepForward" xml:space="preserve">
<value>Media Step Forward</value>
</data>
<data name="Icon.MediaStop" xml:space="preserve">
<value>Media Stop</value>
</data>
<data name="Icon.MedicalCross" xml:space="preserve">
<value>Medical Cross</value>
</data>
<data name="Icon.Menu" xml:space="preserve">
<value>Menu</value>
</data>
<data name="Icon.Microphone" xml:space="preserve">
<value>Microphone</value>
</data>
<data name="Icon.Minus" xml:space="preserve">
<value>Minus</value>
</data>
<data name="Icon.Monitor" xml:space="preserve">
<value>Monitor</value>
</data>
<data name="Icon.Moon" xml:space="preserve">
<value>Moon</value>
</data>
<data name="Icon.Move" xml:space="preserve">
<value>Move</value>
</data>
<data name="Icon.MusicalNote" xml:space="preserve">
<value>Musical Note</value>
</data>
<data name="Icon.Paperclip" xml:space="preserve">
<value>Paperclip</value>
</data>
<data name="Icon.Pencil" xml:space="preserve">
<value>Pencil</value>
</data>
<data name="Icon.People" xml:space="preserve">
<value>People</value>
</data>
<data name="Icon.Person" xml:space="preserve">
<value>Person</value>
</data>
<data name="Icon.Phone" xml:space="preserve">
<value>Phone</value>
</data>
<data name="Icon.PieChart" xml:space="preserve">
<value>Pie Chart</value>
</data>
<data name="Icon.Pin" xml:space="preserve">
<value>Pin</value>
</data>
<data name="Icon.PlayCircle" xml:space="preserve">
<value>Play Circle</value>
</data>
<data name="Icon.Plus" xml:space="preserve">
<value>Plus</value>
</data>
<data name="Icon.PowerStandby" xml:space="preserve">
<value>Power Standby</value>
</data>
<data name="Icon.Print" xml:space="preserve">
<value>Print</value>
</data>
<data name="Icon.Project" xml:space="preserve">
<value>Project</value>
</data>
<data name="Icon.Pulse" xml:space="preserve">
<value>Pulse</value>
</data>
<data name="Icon.PuzzlePiece" xml:space="preserve">
<value>Puzzle Piece</value>
</data>
<data name="Icon.QuestionMark" xml:space="preserve">
<value>Question Mark</value>
</data>
<data name="Icon.Rain" xml:space="preserve">
<value>Rain</value>
</data>
<data name="Icon.Random" xml:space="preserve">
<value>Random</value>
</data>
<data name="Icon.Reload" xml:space="preserve">
<value>Reload</value>
</data>
<data name="Icon.ResizeBoth" xml:space="preserve">
<value>Resize Both</value>
</data>
<data name="Icon.ResizeHeight" xml:space="preserve">
<value>Resize Height</value>
</data>
<data name="Icon.ResizeWidth" xml:space="preserve">
<value>Resize Width</value>
</data>
<data name="Icon.RssAlt" xml:space="preserve">
<value>Rss Alt</value>
</data>
<data name="Icon.Rss" xml:space="preserve">
<value>Rss</value>
</data>
<data name="Icon.Script" xml:space="preserve">
<value>Script</value>
</data>
<data name="Icon.ShareBoxed" xml:space="preserve">
<value>Share Boxed</value>
</data>
<data name="Icon.Share" xml:space="preserve">
<value>Share</value>
</data>
<data name="Icon.Shield" xml:space="preserve">
<value>Shield</value>
</data>
<data name="Icon.Signal" xml:space="preserve">
<value>Signal</value>
</data>
<data name="Icon.Signpost" xml:space="preserve">
<value>Signpost</value>
</data>
<data name="Icon.SortAscending" xml:space="preserve">
<value>Sort Ascending</value>
</data>
<data name="Icon.SortDescending" xml:space="preserve">
<value>Sort Descending</value>
</data>
<data name="Icon.Spreadsheet" xml:space="preserve">
<value>Spreadsheet</value>
</data>
<data name="Icon.Star" xml:space="preserve">
<value>Star</value>
</data>
<data name="Icon.Sun" xml:space="preserve">
<value>Sun</value>
</data>
<data name="Icon.Tablet" xml:space="preserve">
<value>Tablet</value>
</data>
<data name="Icon.Tag" xml:space="preserve">
<value>Tag</value>
</data>
<data name="Icon.Tags" xml:space="preserve">
<value>Tags</value>
</data>
<data name="Icon.Target" xml:space="preserve">
<value>Target</value>
</data>
<data name="Icon.Task" xml:space="preserve">
<value>Task</value>
</data>
<data name="Icon.Terminal" xml:space="preserve">
<value>Terminal</value>
</data>
<data name="Icon.Text" xml:space="preserve">
<value>Text</value>
</data>
<data name="Icon.ThumbDown" xml:space="preserve">
<value>Thumb Down</value>
</data>
<data name="Icon.ThumbUp" xml:space="preserve">
<value>Thumb Up</value>
</data>
<data name="Icon.Timer" xml:space="preserve">
<value>Timer</value>
</data>
<data name="Icon.Transfer" xml:space="preserve">
<value>Transfer</value>
</data>
<data name="Icon.Trash" xml:space="preserve">
<value>Trash</value>
</data>
<data name="Icon.Underline" xml:space="preserve">
<value>Underline</value>
</data>
<data name="Icon.VerticalAlignBottom" xml:space="preserve">
<value>Vertical Align Bottom</value>
</data>
<data name="Icon.VerticalAlignCenter" xml:space="preserve">
<value>Vertical Align Center</value>
</data>
<data name="Icon.VerticalAlignTop" xml:space="preserve">
<value>Vertical Align Top</value>
</data>
<data name="Icon.Video" xml:space="preserve">
<value>Video</value>
</data>
<data name="Icon.VolumeHigh" xml:space="preserve">
<value>Volume High</value>
</data>
<data name="Icon.VolumeLow" xml:space="preserve">
<value>Volume Low</value>
</data>
<data name="Icon.VolumeOff" xml:space="preserve">
<value>Volume Off</value>
</data>
<data name="Icon.Warning" xml:space="preserve">
<value>Warning</value>
</data>
<data name="Icon.Wifi" xml:space="preserve">
<value>Wifi</value>
</data>
<data name="Icon.Wrench" xml:space="preserve">
<value>Wrench</value>
</data>
<data name="Icon.X" xml:space="preserve">
<value>X</value>
</data>
<data name="Icon.Yen" xml:space="preserve">
<value>Yen</value>
</data>
<data name="Icon.ZoomIn" xml:space="preserve">
<value>Zoom In</value>
</data>
<data name="Icon.ZoomOut" xml:space="preserve">
<value>Zoom Out</value>
</data>
</root>

View File

@ -124,7 +124,7 @@
<value>Database:</value>
</data>
<data name="ApplicationAdmin" xml:space="preserve">
<value>Application Administrator</value>
<value>Application Administration</value>
</data>
<data name="InstallNow" xml:space="preserve">
<value>Install Now</value>
@ -180,4 +180,7 @@
<data name="EnterConnectionString" xml:space="preserve">
<value>Enter Connection String</value>
</data>
<data name="Template" xml:space="preserve">
<value>Select a site template</value>
</data>
</root>

View File

@ -162,4 +162,7 @@
<data name="Name.Text" xml:space="preserve">
<value>Name:</value>
</data>
<data name="DownloadFiles.Heading" xml:space="preserve">
<value>Download Files</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
@ -150,4 +150,7 @@
<data name="Description.Text" xml:space="preserve">
<value>Description:</value>
</data>
<data name="File Management" xml:space="preserve">
<value>File Management</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
@ -178,9 +178,21 @@
<value>Capacity:</value>
</data>
<data name="ImageSizes.HelpText" xml:space="preserve">
<value>Enter a list of image sizes which can be generated dynamically from uploaded images (ie. 200x200,x200,200x)</value>
<value>Enter a list of image sizes which can be generated dynamically from uploaded images (ie. 200x200,400x400). Use * to indicate the folder supports all image sizes.</value>
</data>
<data name="ImageSizes.Text" xml:space="preserve">
<value>Image Sizes:</value>
</data>
<data name="FolderManagement.Title" xml:space="preserve">
<value>Folder Management!</value>
</data>
<data name="Private" xml:space="preserve">
<value>Private</value>
</data>
<data name="Public" xml:space="preserve">
<value>Public</value>
</data>
<data name="Folder Management" xml:space="preserve">
<value>Folder Management</value>
</data>
</root>

View File

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

View File

@ -132,11 +132,11 @@
<data name="IsDefault.Text" xml:space="preserve">
<value>Default?</value>
</data>
<data name="Success.Language.Install" xml:space="preserve">
<value>Translations Installed Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Apply These Changes.</value>
<data name="Success.Language.Download" xml:space="preserve">
<value>Translation Package Saved Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; To Complete The Installation.</value>
</data>
<data name="LanguageUpload.HelpText" xml:space="preserve">
<value>Upload one or more translation packages. Once they are uploaded click Install to complete the installation.</value>
<value>Upload one or more translation packages.</value>
</data>
<data name="LanguageUpload.Text" xml:space="preserve">
<value>Translation</value>

View File

@ -133,14 +133,11 @@
<value>Delete Language</value>
</data>
<data name="Success.Language.Download" xml:space="preserve">
<value>Translation Downloaded Successfully. Click Install To Complete Installation.</value>
<value>Translation Package Saved Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Complete The Installation.</value>
</data>
<data name="Error.Language.Download" xml:space="preserve">
<value>Error Downloading Translation</value>
</data>
<data name="Success.Language.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="Default" xml:space="preserve">
<value>Default</value>
</data>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@ -136,7 +136,7 @@
<value>The function that was performed</value>
</data>
<data name="Category.HelpText" xml:space="preserve">
<value>The categories that were affected</value>
<value>The fully qualified type type that was affected</value>
</data>
<data name="Page.HelpText" xml:space="preserve">
<value>The page that was affected</value>
@ -178,7 +178,7 @@
<value>Function: </value>
</data>
<data name="Category.Text" xml:space="preserve">
<value>Category: </value>
<value>Type Name: </value>
</data>
<data name="Page.Text" xml:space="preserve">
<value>Page: </value>

View File

@ -123,25 +123,25 @@
<data name="Error.Package.Load" xml:space="preserve">
<value>Error Loading Packages</value>
</data>
<data name="Success.Module.Install" xml:space="preserve">
<value>Module Installed Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Apply These Changes.</value>
</data>
<data name="Success.Module.Download" xml:space="preserve">
<value>Module Downloaded Successfully. Click Install To Complete Installation.</value>
<value>Module Package Downloaded Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Complete The Installation.</value>
</data>
<data name="Error.Module.Download" xml:space="preserve">
<value>Error Downloading Module</value>
</data>
<data name="Module.HelpText" xml:space="preserve">
<value>Upload one or more module packages. Once they are uploaded click Install to complete the installation.</value>
<value>Upload one or more module packages.</value>
</data>
<data name="Search.NoResults" xml:space="preserve">
<value>No Modules Match The Criteria Provided Or Package Service Is Disabled</value>
</data>
<data name="Download.Heading" xml:space="preserve">
<value>Download</value>
<value>Marketplace</value>
</data>
<data name="Upload.Heading" xml:space="preserve">
<value>Upload</value>
</data>
<data name="Product" xml:space="preserve">
<value>Product</value>
</data>
</root>

View File

@ -132,6 +132,9 @@
<data name="Message.Require.ValidName" xml:space="preserve">
<value>You Must Provide A Valid Owner Name And Module Name ( ie. No Punctuation Or Spaces And The Values Cannot Be The Same ) And Choose A Template</value>
</data>
<data name="Message.Require.ValidDescription" xml:space="preserve">
<value>You Must Provide A Valid Description (ie. No Punctuation)</value>
</data>
<data name="OwnerName.HelpText" xml:space="preserve">
<value>Enter the name of the organization who is developing this module. It should not contain spaces or punctuation.</value>
</data>

View File

@ -196,7 +196,7 @@
<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>
<value>The unique name of the package from which this module was installed. This value must be specified within the module's IModule interface specification.</value>
</data>
<data name="PackageName.Text" xml:space="preserve">
<value>Package Name:</value>
@ -211,12 +211,33 @@
<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>
<value>Translation Package Saved Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Complete The Installation.</value>
</data>
<data name="Translations.Heading" xml:space="preserve">
<value>Translations</value>
</data>
<data name="Message.DuplicateName" xml:space="preserve">
<value>A Module With The Name Specified Already Exists</value>
</data>
<data name="IsEnabled.HelpText" xml:space="preserve">
<value>Is module enabled for this site?</value>
</data>
<data name="IsEnabled.Text" xml:space="preserve">
<value>Enabled?</value>
</data>
<data name="View License" xml:space="preserve">
<value>View License</value>
</data>
<data name="Error.Validate" xml:space="preserve">
<value>Error Validating Package</value>
</data>
<data name="Message.Download" xml:space="preserve">
<value>Package Version Has Been Verified. Please Select The Download Button To Obtain The Package.</value>
</data>
<data name="Message.Validate" xml:space="preserve">
<value>This Package Version Has Not Been Registered In The Oqtane Marketplace Or You Do Not Have The Right To Use It From This Installation</value>
</data>
<data name="Validate" xml:space="preserve">
<value>Validate</value>
</data>
</root>

View File

@ -144,8 +144,11 @@
<data name="DeleteModule.Header" xml:space="preserve">
<value>Delete Module</value>
</data>
<data name="DeleteModule.Text" xml:space="preserve">
<value>Delete</value>
</data>
<data name="InUse" xml:space="preserve">
<value>In Use</value>
<value>In Use?</value>
</data>
<data name="EditModule.Text" xml:space="preserve">
<value>Edit</value>
@ -153,4 +156,7 @@
<data name="Modules" xml:space="preserve">
<value>Modules</value>
</data>
<data name="Enabled" xml:space="preserve">
<value>Enabled?</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
@ -132,4 +132,7 @@
<data name="Success.Content.Export" xml:space="preserve">
<value>Content Exported Successfully</value>
</data>
<data name="Export Content" xml:space="preserve">
<value>Export Content</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
@ -138,4 +138,7 @@
<data name="Message.Required.ImportContent" xml:space="preserve">
<value>You Must Enter Some Content To Import</value>
</data>
<data name="Import Content" xml:space="preserve">
<value>Import Content</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
@ -150,4 +150,13 @@
<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>
<data name="Module.HelpText" xml:space="preserve">
<value>The name of the module</value>
</data>
<data name="Module.Text" xml:space="preserve">
<value>Module:</value>
</data>
<data name="Module Settings" xml:space="preserve">
<value>Module Settings</value>
</data>
</root>

View File

@ -150,8 +150,8 @@
<data name="Container.Select" xml:space="preserve">
<value>Select Container</value>
</data>
<data name="Error.Page.Initialize" xml:space="preserve">
<value>Error Initializing Page</value>
<data name="Error.Page.Load" xml:space="preserve">
<value>Error Loading Page</value>
</data>
<data name="Error.ChildPage.Load" xml:space="preserve">
<value>Error Loading Child Pages For Parent</value>
@ -228,13 +228,31 @@
<data name="Appearance.Name" xml:space="preserve">
<value>Appearance</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 name="HeadContent.HelpText" xml:space="preserve">
<value>Optionally enter content to be included in the page head (ie. meta, link, or script tags)</value>
</data>
<data name="Meta.Text" xml:space="preserve">
<value>Meta:</value>
<data name="HeadContent.Text" xml:space="preserve">
<value>Head Content:</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>
<data name="BodyContent.HelpText" xml:space="preserve">
<value>Optionally enter content to be included in the page body (ie. script tags)</value>
</data>
<data name="BodyContent.Text" xml:space="preserve">
<value>Body Content:</value>
</data>
<data name="PageContent.Heading" xml:space="preserve">
<value>Page Content</value>
</data>
<data name="ThemeChanged.Message" xml:space="preserve">
<value>Please Note That Overriding The Default Site Theme With An Unrelated Page Theme May Result In Compatibility Issues For Your Site</value>
</data>
<data name="Permissions.Heading" xml:space="preserve">
<value>Permissions</value>
</data>
<data name="Theme.Heading" xml:space="preserve">
<value>Theme Settings</value>
</data>
</root>

View File

@ -264,13 +264,25 @@
<data name="Clickable.Text" xml:space="preserve">
<value>Clickable?</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 name="HeadContent.HelpText" xml:space="preserve">
<value>Optionally enter content to be included in the page head (ie. meta, link, or script tags)</value>
</data>
<data name="Meta.Text" xml:space="preserve">
<value>Meta:</value>
<data name="HeadContent.Text" xml:space="preserve">
<value>Head Content:</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>
<data name="BodyContent.HelpText" xml:space="preserve">
<value>Optionally enter content to be included in the page body (ie. script tags)</value>
</data>
<data name="BodyContent.Text" xml:space="preserve">
<value>Body Content:</value>
</data>
<data name="PageContent.Heading" xml:space="preserve">
<value>Page Content</value>
</data>
<data name="ThemeChanged.Message" xml:space="preserve">
<value>Please Note That Overriding The Default Site Theme With An Unrelated Page Theme May Result In Compatibility Issues For Your Site</value>
</data>
</root>

View File

@ -183,4 +183,10 @@
<data name="Private.Text" xml:space="preserve">
<value>Private? </value>
</data>
<data name="Validation.HelpText" xml:space="preserve">
<value>Optionally provide a regular expression (RegExp) for validating the value entered</value>
</data>
<data name="Validation.Text" xml:space="preserve">
<value>Validation:</value>
</data>
</root>

View File

@ -138,4 +138,13 @@
<data name="EditProfile.Text" xml:space="preserve">
<value>Edit</value>
</data>
<data name="Category" xml:space="preserve">
<value>Category</value>
</data>
<data name="Order" xml:space="preserve">
<value>Order</value>
</data>
<data name="Title" xml:space="preserve">
<value>Title</value>
</data>
</root>

View File

@ -120,9 +120,15 @@
<data name="DeleteModule.Header" xml:space="preserve">
<value>Delete Module</value>
</data>
<data name="DeleteModule.Text" xml:space="preserve">
<value>Delete</value>
</data>
<data name="DeletePage.Header" xml:space="preserve">
<value>Delete Page</value>
</data>
<data name="DeletePage.Text" xml:space="preserve">
<value>Delete</value>
</data>
<data name="NoPage.Deleted" xml:space="preserve">
<value>No Deleted Pages</value>
</data>

View File

@ -136,7 +136,7 @@
<value>User Account Created. Please Check Your Email For Verification Instructions.</value>
</data>
<data name="Error.User.AddInfo" xml:space="preserve">
<value>Error Adding User. Please Ensure Password Meets Complexity Requirements And Username Is Not Already In Use.</value>
<value>Error Adding User. Please Ensure Password Meets Complexity Requirements And Username And Email Is Not Already In Use.</value>
</data>
<data name="Message.Password.NoMatch" xml:space="preserve">
<value>Passwords Entered Do Not Match</value>
@ -177,19 +177,4 @@
<data name="Username.Text" xml:space="preserve">
<value>Username:</value>
</data>
<data name="Password.ValidationCriteria" xml:space="preserve">
<value>Passwords Must Have A Minimum Length Of {0} Characters, Including At Least {1} Unique Character(s), {2}{3}{4}{5} To Satisfy Password Compexity Requirements For This Site.</value>
</data>
<data name="Password.DigitRequirement" xml:space="preserve">
<value>At Least One Digit</value>
</data>
<data name="Password.LowercaseRequirement" xml:space="preserve">
<value>At Least One Lowercase Letter</value>
</data>
<data name="Password.PunctuationRequirement" xml:space="preserve">
<value>At Least One Punctuation Mark</value>
</data>
<data name="Password.UppercaseRequirement" xml:space="preserve">
<value>At Least One Uppercase Letter</value>
</data>
</root>

View File

@ -166,7 +166,7 @@
<value>The name of the database used 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).</value>
<value>The urls for the site. This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or virtual folders (ie. domain.com/folder).</value>
</data>
<data name="IsDeleted.HelpText" xml:space="preserve">
<value>Is this site deleted?</value>
@ -175,7 +175,7 @@
<value>Specify a logo for the site</value>
</data>
<data name="FavoriteIcon.HelpText" xml:space="preserve">
<value>Specify a Favicon</value>
<value>Specify a Favicon. The format for the image must be 16×16, 32×32, 48×48, or 64×64 pixels in size, and 8-bit, 24-bit, or 32-bit in color depth. The format of the image must be ICO, PNG, or GIF.</value>
</data>
<data name="DefaultTheme.HelpText" xml:space="preserve">
<value>Select the sites default theme</value>
@ -217,7 +217,7 @@
<value>Database: </value>
</data>
<data name="Aliases.Text" xml:space="preserve">
<value>Aliases: </value>
<value>Urls:</value>
</data>
<data name="IsDeleted.Text" xml:space="preserve">
<value>Deleted? </value>
@ -312,6 +312,9 @@
<data name="Database.HelpText" xml:space="preserve">
<value>The type of database</value>
</data>
<data name="DeleteSite.Header" xml:space="preserve">
<value>Delete Site</value>
</data>
<data name="DeleteSite.Text" xml:space="preserve">
<value>Delete Site</value>
</data>
@ -322,10 +325,10 @@
<value>Default Alias: </value>
</data>
<data name="Aliases.Heading" xml:space="preserve">
<value>Aliases</value>
<value>Urls</value>
</data>
<data name="AliasName" xml:space="preserve">
<value>Name</value>
<value>Url</value>
</data>
<data name="AliasDefault" xml:space="preserve">
<value>Default?</value>
@ -351,4 +354,52 @@
<data name="SiteMap.Text" xml:space="preserve">
<value>Site Map:</value>
</data>
<data name="Appearance.Heading" xml:space="preserve">
<value>Appearance</value>
</data>
<data name="HeadContent.HelpText" xml:space="preserve">
<value>Optionally enter content to be included in the page head (ie. meta, link, or script tags)</value>
</data>
<data name="HeadContent.Text" xml:space="preserve">
<value>Head Content:</value>
</data>
<data name="BodyContent.HelpText" xml:space="preserve">
<value>Optionally enter content to be included in the page body (ie. script tags)</value>
</data>
<data name="BodyContent.Text" xml:space="preserve">
<value>Body Content:</value>
</data>
<data name="PageContent.Heading" xml:space="preserve">
<value>Page Content</value>
</data>
<data name="SMTPEnabled.HelpText" xml:space="preserve">
<value>Specify if SMTP is enabled for this site</value>
</data>
<data name="SMTPEnabled.Text" xml:space="preserve">
<value>Enabled?</value>
</data>
<data name="Version.HelpText" xml:space="preserve">
<value>The site version (for site content migrations)</value>
</data>
<data name="Version.Text" xml:space="preserve">
<value>Version:</value>
</data>
<data name="DeleteAlias.Text" xml:space="preserve">
<value>Delete</value>
</data>
<data name="DeleteAlias.Header" xml:space="preserve">
<value>Delete Alias</value>
</data>
<data name="SiteGuid.HelpText" xml:space="preserve">
<value>The Unique Identifier For The Site</value>
</data>
<data name="SiteGuid.Text" xml:space="preserve">
<value>ID:</value>
</data>
<data name="Retention.HelpText" xml:space="preserve">
<value>Number of days of notifications to retain</value>
</data>
<data name="Retention.Text" xml:space="preserve">
<value>Retention (Days):</value>
</data>
</root>

View File

@ -136,7 +136,7 @@
<value>Default Admin Container</value>
</data>
<data name="Aliases.HelpText" xml:space="preserve">
<value>Enter the alias for the server</value>
<value>The urls for the site (comman delimited). This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or a virtual folder (ie. domain.com/folder).</value>
</data>
<data name="DefaultContainer.HelpText" xml:space="preserve">
<value>Select the default container for the site</value>
@ -145,7 +145,7 @@
<value>Database: </value>
</data>
<data name="Aliases.Text" xml:space="preserve">
<value>Aliases: </value>
<value>Urls: </value>
</data>
<data name="DefaultTheme.Text" xml:space="preserve">
<value>Default Theme: </value>

View File

@ -135,4 +135,7 @@
<data name="Browse" xml:space="preserve">
<value>Browse</value>
</data>
<data name="AliasName" xml:space="preserve">
<value>Url</value>
</data>
</root>

View File

@ -213,17 +213,17 @@
<data name="Log.Heading" xml:space="preserve">
<value>Log</value>
</data>
<data name="Register" xml:space="preserve">
<data name="Register" xml:space="preserve">
<value>Please Register Me For Major Product Updates And Security Bulletins</value>
</data>
<data name="Success.Register" xml:space="preserve">
<value>You Have Been Successfully Registered For Updates</value>
</data>
<data name="PackageService.HelpText" xml:space="preserve">
<value>Specify If The Package Service Is Enabled For Installing Modules, Themes, And Translations</value>
<data name="PackageManager.HelpText" xml:space="preserve">
<value>Specify The Package Manager Service For Installing Modules, Themes, And Translations. If This Field Is Blank It Means The Package Manager Service Is Disabled For This Installation.</value>
</data>
<data name="PackageService.Text" xml:space="preserve">
<value>Package Service Enabled?</value>
<data name="PackageManager.Text" xml:space="preserve">
<value>Package Manager:</value>
</data>
<data name="Swagger.HelpText" xml:space="preserve">
<value>Specify If Swagger Is Enabled For Your Server API</value>

View File

@ -123,19 +123,25 @@
<data name="Theme.Text" xml:space="preserve">
<value>Theme: </value>
</data>
<data name="Success.Theme.Install" xml:space="preserve">
<value>Theme Installed Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Apply These Changes.</value>
</data>
<data name="Success.Theme.Download" xml:space="preserve">
<value>Theme Downloaded Successfully. Click Install To Complete Installation.</value>
<value>Theme Package Downloaded Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Complete The Installation.</value>
</data>
<data name="Error.Theme.Download" xml:space="preserve">
<value>Error Downloading Theme</value>
</data>
<data name="Theme.HelpText" xml:space="preserve">
<value>Upload one or more theme packages. Once they are uploaded click Install to complete the installation.</value>
<value>Upload one or more theme packages.</value>
</data>
<data name="Search.NoResults" xml:space="preserve">
<value>No Themes Match The Criteria Provided Or Package Service Is Disabled</value>
</data>
<data name="Download.Heading" xml:space="preserve">
<value>Marketplace</value>
</data>
<data name="Upload.Heading" xml:space="preserve">
<value>Upload</value>
</data>
<data name="Product" xml:space="preserve">
<value>Product</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
@ -163,9 +163,33 @@
<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>
<value>The unique name of the package from which this theme was installed. This value must be specified within the themee's ITheme interface specification.</value>
</data>
<data name="PackageName.Text" xml:space="preserve">
<value>Package Name:</value>
</data>
<data name="Information.Heading" xml:space="preserve">
<value>Information</value>
</data>
<data name="IsEnabled.HelpText" xml:space="preserve">
<value>Is theme enabled for this site?</value>
</data>
<data name="IsEnabled.Text" xml:space="preserve">
<value>Enabled?</value>
</data>
<data name="View License" xml:space="preserve">
<value>View License</value>
</data>
<data name="Error.Validate" xml:space="preserve">
<value>Error Validating Package</value>
</data>
<data name="Message.Download" xml:space="preserve">
<value>Package Version Has Been Verified. Please Select The Download Button To Obtain The Package.</value>
</data>
<data name="Message.Validate" xml:space="preserve">
<value>This Package Version Has Not Been Registered In The Oqtane Marketplace Or You Do Not Have The Right To Use It From This Installation</value>
</data>
<data name="Validate" xml:space="preserve">
<value>Validate</value>
</data>
</root>

View File

@ -138,10 +138,22 @@
<data name="DeleteTheme.Header" xml:space="preserve">
<value>Delete Theme</value>
</data>
<data name="DeleteTheme.Text" xml:space="preserve">
<value>Delete</value>
</data>
<data name="CreateTheme.Text" xml:space="preserve">
<value>Create Theme</value>
</data>
<data name="InstallTheme.Text" xml:space="preserve">
<value>Install Theme</value>
</data>
<data name="ViewTheme.Text" xml:space="preserve">
<value>View</value>
</data>
<data name="EditTheme.Text" xml:space="preserve">
<value>Edit</value>
</data>
<data name="Enabled" xml:space="preserve">
<value>Enabled?</value>
</data>
</root>

View File

@ -141,4 +141,13 @@
<data name="Upload.Heading" xml:space="preserve">
<value>Upload</value>
</data>
<data name="Message.Text" xml:space="preserve">
<value>Framework Is Already Up To Date</value>
</data>
<data name="MessageUpgrade.Text" xml:space="preserve">
<value>Upload A Framework Package (Oqtane.Framework.version.nupkg) And Then Select Upgrade</value>
</data>
<data name="Localhost.Text" xml:space="preserve">
<value>You Cannot Perform A System Update In A Development Environment</value>
</data>
</root>

View File

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

View File

@ -228,4 +228,10 @@
<data name="Profile.Heading" xml:space="preserve">
<value>Profile</value>
</data>
<data name="ViewNotification.Text" xml:space="preserve">
<value>View</value>
</data>
<data name="DeleteNotification.Text" xml:space="preserve">
<value>Delete</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
@ -144,4 +144,7 @@
<data name="OriginalMessage" xml:space="preserve">
<value>Original Message</value>
</data>
<data name="View Notification" xml:space="preserve">
<value>View Notification</value>
</data>
</root>

View File

@ -118,7 +118,7 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Error.User.AddCheckPass" xml:space="preserve">
<value>Error Adding User. Please Ensure Password Meets Complexity Requirements And Username Is Not Already In Use.</value>
<value>Error Adding User. Please Ensure Password Meets Complexity Requirements And Username And Email Is Not Already In Use.</value>
</data>
<data name="Message.Password.NoMatch" xml:space="preserve">
<value>Passwords Entered Do Not Match</value>
@ -171,4 +171,10 @@
<data name="Password.Placeholder" xml:space="preserve">
<value>Password</value>
</data>
<data name="Notify.HelpText" xml:space="preserve">
<value>Indicate if new users should receive an email notification</value>
</data>
<data name="Notify.Text" xml:space="preserve">
<value>Notify?</value>
</data>
</root>

View File

@ -390,10 +390,22 @@
<data name="RoleClaimType.Text" xml:space="preserve">
<value>Role Claim:</value>
</data>
<data name="ProfileClaimTypes.HelpText" xml:space="preserve">
<data name="ProfileClaimTypes.HelpText" xml:space="preserve">
<value>Optionally provide a comma delimited list of user profile claims provided by the identity provider, as well as mappings to your user profile definition. For example if the identity provider includes a 'given_name' claim and you have a 'FirstName' user profile definition you should specify 'given_name:FirstName'.</value>
</data>
<data name="ProfileClaimTypes.Text" xml:space="preserve">
<value>User Profile Claims:</value>
</data>
<data name="Username" xml:space="preserve">
<value>User Name</value>
</data>
<data name="Name" xml:space="preserve">
<value>Name</value>
</data>
<data name="Email" xml:space="preserve">
<value>Email</value>
</data>
<data name="ImportUsers.Text" xml:space="preserve">
<value>Import Users</value>
</data>
</root>

View File

@ -117,61 +117,34 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Template.Text" xml:space="preserve">
<value>Template: </value>
<data name="ImportFile.HelpText" xml:space="preserve">
<value>Upload or select a tab delimited text file containing user information. The file must be in the Template format specified (Roles can be specified as a comma delimited list).</value>
</data>
<data name="Template.Select" xml:space="preserve">
<value>Select Template</value>
<data name="ImportFile.Text" xml:space="preserve">
<value>Import File:</value>
</data>
<data name="Module.Create" xml:space="preserve">
<value>Create Module</value>
<data name="Error.Import" xml:space="preserve">
<value>Error Importing Users</value>
</data>
<data name="Module.Activate" xml:space="preserve">
<value>Activate Module</value>
<data name="Import" xml:space="preserve">
<value>Import</value>
</data>
<data name="Info.Module.Creator" xml:space="preserve">
<value>Please Note That The Module Creator Is Only Intended To Be Used In A Development Environment</value>
<data name="Message.Import.Failure" xml:space="preserve">
<value>User Import Failed. Please Review Your Event Log For More Detailed Information.</value>
</data>
<data name="Info.Module.Activate" xml:space="preserve">
<value>Once You Have Compiled The Module And Restarted The Application You Can Activate The Module Below</value>
<data name="Message.Import.Success" xml:space="preserve">
<value>User Import Successful. {0} Users Imported.</value>
</data>
<data name="Success.Module.Create" xml:space="preserve">
<value>The Source Code For Your Module Has Been Created At The Location Specified Below And Must Be Compiled In Order To Make It Functional. Once It Has Been Compiled You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Apply These Changes.</value>
<data name="Message.Import.Validation" xml:space="preserve">
<value>You Must Specify A User File For Import</value>
</data>
<data name="Message.Require.ValidName" xml:space="preserve">
<value>You Must Provide A Valid Owner Name And Module Name ( ie. No Punctuation Or Spaces And The Values Cannot Be The Same ) And Choose A Template</value>
<data name="Template" xml:space="preserve">
<value>Template</value>
</data>
<data name="OwnerName.HelpText" xml:space="preserve">
<value>Enter the name of the organization who is developing this module. It should not contain spaces or punctuation.</value>
<data name="Notify.HelpText" xml:space="preserve">
<value>Indicate if new users should receive an email notification</value>
</data>
<data name="ModuleName.HelpText" xml:space="preserve">
<value>Enter a name for this module. It should not contain spaces or punctuation.</value>
</data>
<data name="Description.HelpText" xml:space="preserve">
<value>Enter a short description for the module</value>
</data>
<data name="Template.HelpText" xml:space="preserve">
<value>Select a module template. Templates are located in the wwwroot/Modules/Templates folder on the server.</value>
</data>
<data name="FrameworkReference.HelpText" xml:space="preserve">
<value>Select a framework reference version</value>
</data>
<data name="Location.HelpText" xml:space="preserve">
<value>Location where the module will be created</value>
</data>
<data name="OwnerName.Text" xml:space="preserve">
<value>Owner Name: </value>
</data>
<data name="ModuleName.Text" xml:space="preserve">
<value>Module Name: </value>
</data>
<data name="Description.Text" xml:space="preserve">
<value>Description: </value>
</data>
<data name="FrameworkReference.Text" xml:space="preserve">
<value>Framework Reference: </value>
</data>
<data name="Location.Text" xml:space="preserve">
<value>Location: </value>
<data name="Notify.Text" xml:space="preserve">
<value>Notify?</value>
</data>
</root>

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