Compare commits
469 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6a43bcf458 | |||
| ecec06b616 | |||
| 368c9e60ea | |||
| f5b4e52526 | |||
| a939a286ae | |||
| 88acb2a665 | |||
| e7c2ad5965 | |||
| 071cceb7f8 | |||
| 377465e361 | |||
| 0f738113af | |||
| 534a6147a8 | |||
| 87313c8082 | |||
| b315f09640 | |||
| 3025f11ea8 | |||
| 8fb391717f | |||
| 583ccf9811 | |||
| cd6ec49cc8 | |||
| 2b9c5b1728 | |||
| 0e772974a6 | |||
| 62e181cfc3 | |||
| 68233951cb | |||
| d8531899b6 | |||
| bc2e7915cc | |||
| b18d47afa3 | |||
| 297f91da00 | |||
| 2bdc7e1bc3 | |||
| dc0a5c8bb0 | |||
| 2a302a187a | |||
| 407a3a19b6 | |||
| d462fc7afd | |||
| 0b425e3bd9 | |||
| 3efc12fabc | |||
| 852385a192 | |||
| 8077025fe8 | |||
| 6f0da0c002 | |||
| 5dbea610c1 | |||
| 92496f4369 | |||
| ec00b1162f | |||
| e638aee1ac | |||
| 5420f625b4 | |||
| 77fa7f4a79 | |||
| 2db1fe0890 | |||
| 63bb70785a | |||
| 8d23d9aba3 | |||
| bebe70f46b | |||
| 34f2db5985 | |||
| 8e75c09e3f | |||
| b5d4eaa36e | |||
| 116d163b9d | |||
| 2cb568773c | |||
| 916019f015 | |||
| e83d7e9d57 | |||
| 151af30259 | |||
| 7fed6bb93a | |||
| 382a8eb8f3 | |||
| 9508ff68db | |||
| 9684e6e1a8 | |||
| 52745b1946 | |||
| 3db2d03a37 | |||
| 9052d6abb6 | |||
| 3c528f0b93 | |||
| 3322297eaa | |||
| 3c1167d359 | |||
| 9e35a520cc | |||
| beb4919d97 | |||
| 6895d16a20 | |||
| 05b37080c1 | |||
| 51894de708 | |||
| 442ec291a1 | |||
| 6ef106be31 | |||
| 70551f9d27 | |||
| fe422ed5aa | |||
| 1995a96a98 | |||
| 4abcc6e58f | |||
| a6f4921055 | |||
| 23e83a5e30 | |||
| 6abd2cf7fc | |||
| 085f137942 | |||
| 62d99d33bd | |||
| 57375eaab9 | |||
| 30b7e71cd8 | |||
| e26bb66405 | |||
| 6263bd3a60 | |||
| 8bee8d2f3f | |||
| 4ffe8fac3a | |||
| 09a7457c01 | |||
| 15a8f0a4ac | |||
| 503134d38c | |||
| e19b8ffed9 | |||
| c38dc69d3b | |||
| ff16fd8b9c | |||
| 880a6e43d1 | |||
| 188d3b42d8 | |||
| c526e01534 | |||
| dae906d52f | |||
| e620bba0da | |||
| fae22595aa | |||
| a528e5eab2 | |||
| 0991925090 | |||
| f9741a82bd | |||
| f0067d86a6 | |||
| d7aa999f25 | |||
| ea87497e6d | |||
| 2dc8cabc80 | |||
| 4e53dcd8d5 | |||
| dd447e802e | |||
| c7a86aa49c | |||
| c6e7638e8b | |||
| 166969bc35 | |||
| 61d231801a | |||
| cc51f5bb0f | |||
| dcd99695e7 | |||
| 1c78683f4c | |||
| f2124c5ae0 | |||
| 868aca9fdb | |||
| d12f7b79d2 | |||
| 600bbdfd0d | |||
| 04bc68de55 | |||
| 82b4f7b611 | |||
| 4adba1ab5f | |||
| 6d2ac670af | |||
| 64a03b6e91 | |||
| c88958ae7e | |||
| 4278b9992b | |||
| ebac6d51b0 | |||
| fba4f23f71 | |||
| 7231d2f49e | |||
| d871bffdd5 | |||
| 5630b4842c | |||
| b19141b361 | |||
| 732e279605 | |||
| fa173d492c | |||
| 3259494d45 | |||
| 0a03eb620a | |||
| 701d8c9a57 | |||
| fdca8a2890 | |||
| 22e2a4da1e | |||
| 409523912b | |||
| d5c68444c3 | |||
| ffa93e0ee7 | |||
| 3f4f1a8278 | |||
| 8e70949880 | |||
| be8436d237 | |||
| 876f13be5e | |||
| dfca6640da | |||
| a2e57bc54c | |||
| dcc2e59e46 | |||
| 90e721b172 | |||
| 94391875d5 | |||
| 43d06c042d | |||
| 3e12910fbd | |||
| ba70ebe23c | |||
| b739841495 | |||
| acabc75aa6 | |||
| 27041f464f | |||
| 9f923ae968 | |||
| 9c7d832357 | |||
| e913c10d5b | |||
| c698188901 | |||
| 8fd67621ac | |||
| 0c60085e09 | |||
| 1826316c80 | |||
| 07341aeebe | |||
| 9f6945dda2 | |||
| b39b568b4c | |||
| e59d5fd339 | |||
| b7bc527d6c | |||
| 1ea76d06d1 | |||
| b049be9d83 | |||
| 966fc55594 | |||
| ca9ddbd90f | |||
| 0d04926d9f | |||
| 2b500d41ca | |||
| 5c67eeea58 | |||
| 09daf3f6cc | |||
| 9a06a3311e | |||
| 304694fbf9 | |||
| 96ba42df96 | |||
| e7bc11d026 | |||
| 1272305355 | |||
| 30c6da13c2 | |||
| 5aacb2b877 | |||
| b5fdf42c37 | |||
| c81d677c5c | |||
| 6daf675e52 | |||
| 3f7a7f3340 | |||
| 1ebf3c4077 | |||
| 1f1173ae03 | |||
| efa466e1d6 | |||
| cefe349b4e | |||
| a9bc356f37 | |||
| 6fc791020c | |||
| 713ec1b373 | |||
| e3fa781122 | |||
| e4b6d0ff29 | |||
| cd2a328560 | |||
| d2d88d4b5e | |||
| 0067cc4266 | |||
| da3afefa8d | |||
| ab534d07f3 | |||
| 49c513ac9b | |||
| 6f7a18674e | |||
| 0f559ba42d | |||
| 2af02fae95 | |||
| 006423e32e | |||
| 23f29ca55d | |||
| 68a7571741 | |||
| 10e60e352a | |||
| 3b16ae8cc0 | |||
| 66c4737021 | |||
| 8684e03af1 | |||
| edad9e6b3c | |||
| 66b89752d3 | |||
| 9a6195edf1 | |||
| 2bd07b54b6 | |||
| 7cf9d9ad65 | |||
| 4dff30ec8c | |||
| 581f14e661 | |||
| 8ccdc37b64 | |||
| 9e85b35498 | |||
| fff408a5bf | |||
| 4d5168c998 | |||
| bf2c978f1d | |||
| ec06c1cdf1 | |||
| f451cfce09 | |||
| 91e55aeb9b | |||
| 919fb5012f | |||
| 2bb6226e78 | |||
| 6a0c47f7b1 | |||
| 31b688cbf6 | |||
| 7f1fed2fb1 | |||
| aa6c876b12 | |||
| 4e33aeef89 | |||
| e2601dcf05 | |||
| 247baa375d | |||
| a4adba846e | |||
| 52799c7cb0 | |||
| a8635dc555 | |||
| cca0f2219e | |||
| d2f8c3c2bb | |||
| 0f38df053f | |||
| 5c926a10a7 | |||
| 036bbb418e | |||
| 93d224fa37 | |||
| 5b45e3e417 | |||
| c2f2dfd837 | |||
| 2f2baf12fb | |||
| 052c339d0d | |||
| 96192e2e06 | |||
| ea9fa30358 | |||
| 78f8e2f484 | |||
| 0fe2a3fb80 | |||
| a340f52973 | |||
| bd94b715ba | |||
| b9a97ffa4c | |||
| 5a37ab1b89 | |||
| 67a6ac2240 | |||
| 7b42845ecc | |||
| 3ef39896d1 | |||
| b01f3b505d | |||
| 84c5e4c30b | |||
| abc0f3943e | |||
| c7b71db015 | |||
| f5a8a953bb | |||
| 8e965912aa | |||
| 6c3cfb0c7a | |||
| 85d162aa9d | |||
| 67c460dfa5 | |||
| 83d35dbc65 | |||
| 86735a5afd | |||
| 6ecbb89469 | |||
| 2ca0508030 | |||
| 8fbd50dcef | |||
| 2143660345 | |||
| 8c903fbfdd | |||
| 33be372348 | |||
| 447ec3f5e6 | |||
| a4aed69887 | |||
| bbbd6e9e3e | |||
| 06712faee9 | |||
| 48a90072ee | |||
| 0344f4d60b | |||
| 6a4affd5a6 | |||
| d73e2288bb | |||
| 7d7500ba05 | |||
| 247fc5248b | |||
| 85fcd1ed33 | |||
| 4ab8f8cc25 | |||
| ccdfe9bc26 | |||
| dc47961cc2 | |||
| 87394cd330 | |||
| b5a9c32c3e | |||
| d16521f037 | |||
| b553b16049 | |||
| 784548be57 | |||
| cf96a80ead | |||
| ede6babeaf | |||
| 9a57cae4bd | |||
| 1a296bf58c | |||
| e900d2f35a | |||
| 69d2d3d942 | |||
| b7ff49bdb2 | |||
| 3284e0f60a | |||
| 8cec847188 | |||
| 2d44644a3d | |||
| e32f55e433 | |||
| 362c4ae272 | |||
| eb8ad04557 | |||
| d1455596c6 | |||
| 6142bfc5db | |||
| dbda0be53b | |||
| bf932719b2 | |||
| 60e6e33805 | |||
| 64b8b5d3c8 | |||
| 8bce40c2b8 | |||
| b3f6194fda | |||
| fdbf2ab0a7 | |||
| d7eb0dc509 | |||
| 1a34bf4460 | |||
| 4cf1b5c0e7 | |||
| 764b883579 | |||
| 3bd6767138 | |||
| bef9025b6c | |||
| a37f07d20b | |||
| 638946b1f5 | |||
| 30c869ff2a | |||
| 2c3fda9cb5 | |||
| b11a7a678c | |||
| 02011f9ce5 | |||
| 39ae6a76cd | |||
| 31684bf7ca | |||
| 7b36f8d122 | |||
| f2a0be4f57 | |||
| 2cefab1c64 | |||
| 5b4b96f065 | |||
| 77949331e2 | |||
| 4f8c4f47e2 | |||
| 334137454e | |||
| af7ea3efa8 | |||
| 6119417331 | |||
| 580397a82d | |||
| 23c3c47db4 | |||
| df3073fb12 | |||
| aa9664e187 | |||
| 44f4aee55d | |||
| 02861b8e01 | |||
| 9607110381 | |||
| 9ae12ff678 | |||
| 2bcb8636ca | |||
| 4c2960eeae | |||
| 7e2c76e872 | |||
| 30fcde7157 | |||
| 4971d3317d | |||
| 85ae7b01b8 | |||
| 9f566624fe | |||
| 50fa95dff9 | |||
| 752083e9eb | |||
| 582c7f83f7 | |||
| d95104cb92 | |||
| 6c58ab4554 | |||
| 085187cfac | |||
| 3d0f0a5adc | |||
| eae8b431ee | |||
| e3a34446c0 | |||
| bfe57c3ac7 | |||
| d4001be716 | |||
| 662a1817f2 | |||
| 2c99ef412d | |||
| f53ed5b13b | |||
| b5d51838c6 | |||
| 92fd70198a | |||
| 7f1990f851 | |||
| 797d7afc3e | |||
| c5a23cdfa0 | |||
| 906358f1f8 | |||
| 638f2a59c5 | |||
| cf9b4b869c | |||
| 671c52fbbb | |||
| 6c0e2a62e7 | |||
| 1b78c9ad81 | |||
| 7a4b98aec9 | |||
| 9ef6c15014 | |||
| f4cea3fe03 | |||
| 5dd9b1ec91 | |||
| 658059806b | |||
| 4f8a18451c | |||
| b1770ebb76 | |||
| 6923065d86 | |||
| 9f097521f6 | |||
| 235e5c1d3a | |||
| e179976fe8 | |||
| 082726b405 | |||
| 91c5309855 | |||
| 92be1e7a5c | |||
| cceda1db1e | |||
| a59191cea7 | |||
| b0dee4a60c | |||
| 3f33f2b9df | |||
| 97116b4e0c | |||
| a5f51ff9a1 | |||
| 962488fd34 | |||
| 190d973b77 | |||
| 397e0b3f71 | |||
| 83ba9ca73e | |||
| 3d08138686 | |||
| 262fa6b99b | |||
| 372db9dcfa | |||
| e9dc52919c | |||
| a981dd0e97 | |||
| 7c2775119b | |||
| 0be7f1bdb5 | |||
| 8446b9e8d5 | |||
| ce404668d3 | |||
| 948fab50ee | |||
| 9690f1df48 | |||
| 5a24f87293 | |||
| d2ff49fe73 | |||
| e9035df9d2 | |||
| 1ddf21f4fc | |||
| 63d2ded038 | |||
| 7b8e0e48c0 | |||
| bb52402a17 | |||
| 13d9cb461b | |||
| 0a994afd67 | |||
| 57a1257750 | |||
| b0c1d36bab | |||
| 0621751968 | |||
| 461330773a | |||
| 818a97cc2c | |||
| 17045073c8 | |||
| 7a818ee698 | |||
| 668e0cb4eb | |||
| 741b16ca4e | |||
| 85a376b17d | |||
| df86cd909c | |||
| ac236607f5 | |||
| 19813b7eb6 | |||
| cb5e4e076f | |||
| 48fca77f59 | |||
| 76372451aa | |||
| 34cd197122 | |||
| 6b567364f9 | |||
| 711de49571 | |||
| 9a0f7ad83f | |||
| 0d3d693799 | |||
| b63590d6c7 | |||
| 5f3a3d4d54 | |||
| b1a8c28283 | |||
| 1412737036 | |||
| ffb3f4fa50 | |||
| ff450ca43a | |||
| d4f0805108 | |||
| 64ce69d1c7 | |||
| ca3cb48091 | |||
| 85085bf4c7 | |||
| 873af6b598 | |||
| c423895f31 | |||
| 4418e27c29 | |||
| f776977af8 | |||
| c13ce3d0f1 | |||
| 2c4c669ea2 | |||
| 29fe3dfd0b | |||
| 985e50d415 | |||
| 11150b6a10 | |||
| c499acdc4a | |||
| 018737c42a | |||
| 3811b8f0c0 | |||
| d81514e9be | |||
| 14b0d7abf0 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -35,4 +35,4 @@ Oqtane.Server/wwwroot/Themes/*
|
|||||||
!Oqtane.Server/wwwroot/Themes/Oqtane.Themes.*
|
!Oqtane.Server/wwwroot/Themes/Oqtane.Themes.*
|
||||||
!Oqtane.Server/wwwroot/Themes/Templates
|
!Oqtane.Server/wwwroot/Themes/Templates
|
||||||
Oqtane.Server/wwwroot/Themes/Templates/*
|
Oqtane.Server/wwwroot/Themes/Templates/*
|
||||||
Oqtane.Server/wwwroot/Themes/Templates/External
|
!Oqtane.Server/wwwroot/Themes/Templates/External
|
||||||
|
|||||||
17
Directory.Build.props
Normal file
17
Directory.Build.props
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<Configurations>Debug;Release</Configurations>
|
||||||
|
<Version>6.2.1</Version>
|
||||||
|
<Product>Oqtane</Product>
|
||||||
|
<Authors>Shaun Walker</Authors>
|
||||||
|
<Company>.NET Foundation</Company>
|
||||||
|
<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/v6.2.1</PackageReleaseNotes>
|
||||||
|
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||||
|
<RepositoryType>Git</RepositoryType>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
9
Oqtane.Application/.gitignore
vendored
Normal file
9
Oqtane.Application/.gitignore
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
.vs/
|
||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
*.user
|
||||||
|
artifacts/
|
||||||
|
msbuild.binlog
|
||||||
|
.vscode/
|
||||||
|
*.binlog
|
||||||
|
*.nupkg
|
||||||
86
Oqtane.Application/.template.config/template.json
Normal file
86
Oqtane.Application/.template.config/template.json
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json.schemastore.org/template",
|
||||||
|
"author": "Shaun Walker",
|
||||||
|
"classifications": [
|
||||||
|
"Web",
|
||||||
|
"ASP.NET",
|
||||||
|
"Blazor",
|
||||||
|
"Oqtane"
|
||||||
|
],
|
||||||
|
"name": "Oqtane Application Template",
|
||||||
|
"shortName": "oqtane-app",
|
||||||
|
"defaultName": "MyCompany.MyProject",
|
||||||
|
"identity": "Oqtane.Application.Template",
|
||||||
|
"tags": {
|
||||||
|
"language": "C#",
|
||||||
|
"type": "solution",
|
||||||
|
"editorTreatAs":"solution"
|
||||||
|
},
|
||||||
|
"sourceName": "Oqtane.Application",
|
||||||
|
"preferNameDirectory": true,
|
||||||
|
"guids": [
|
||||||
|
"04B05448-788F-433D-92C0-FED35122D45A",
|
||||||
|
"AA8E58A1-CD09-4208-BF66-A8BB341FD669",
|
||||||
|
"18D73F73-D7BE-4388-85BA-FBD9AC96FCA2"
|
||||||
|
],
|
||||||
|
"symbols": {
|
||||||
|
"Framework": {
|
||||||
|
"type": "parameter",
|
||||||
|
"description": "The target framework for the project",
|
||||||
|
"datatype": "choice",
|
||||||
|
"choices": [
|
||||||
|
{
|
||||||
|
"choice": "net9.0",
|
||||||
|
"description": "Target net9.0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"replaces": "net9.0",
|
||||||
|
"defaultValue": "net9.0"
|
||||||
|
},
|
||||||
|
"HttpPort": {
|
||||||
|
"type": "parameter",
|
||||||
|
"datatype": "integer",
|
||||||
|
"description": "Port number to use for the HTTP endpoint in launchSettings.json."
|
||||||
|
},
|
||||||
|
"HttpPortGenerated": {
|
||||||
|
"type": "generated",
|
||||||
|
"generator": "port"
|
||||||
|
},
|
||||||
|
"HttpPortReplacer": {
|
||||||
|
"type": "generated",
|
||||||
|
"generator": "coalesce",
|
||||||
|
"parameters": {
|
||||||
|
"sourceVariableName": "HttpPort",
|
||||||
|
"fallbackVariableName": "HttpPortGenerated"
|
||||||
|
},
|
||||||
|
"replaces": "44358"
|
||||||
|
},
|
||||||
|
"HttpsPort": {
|
||||||
|
"type": "parameter",
|
||||||
|
"datatype": "integer",
|
||||||
|
"description": "Port number to use for the HTTPS endpoint in launchSettings.json."
|
||||||
|
},
|
||||||
|
"HttpsPortGenerated": {
|
||||||
|
"type": "generated",
|
||||||
|
"generator": "port",
|
||||||
|
"parameters": {
|
||||||
|
"low": 44300,
|
||||||
|
"high": 44399
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"HttpsPortReplacer": {
|
||||||
|
"type": "generated",
|
||||||
|
"generator": "coalesce",
|
||||||
|
"parameters": {
|
||||||
|
"sourceVariableName": "HttpsPort",
|
||||||
|
"fallbackVariableName": "HttpsPortGenerated"
|
||||||
|
},
|
||||||
|
"replaces": "44359"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"primaryOutputs": [
|
||||||
|
{
|
||||||
|
"path": "Oqtane.Application.sln"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
3
Oqtane.Application/Client/AssemblyInfo.cs
Normal file
3
Oqtane.Application/Client/AssemblyInfo.cs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
|
[assembly: RootNamespace("Oqtane.Application.Client")]
|
||||||
15
Oqtane.Application/Client/Interop.cs
Normal file
15
Oqtane.Application/Client/Interop.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using Microsoft.JSInterop;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Oqtane.Application
|
||||||
|
{
|
||||||
|
public class Interop
|
||||||
|
{
|
||||||
|
private readonly IJSRuntime _jsRuntime;
|
||||||
|
|
||||||
|
public Interop(IJSRuntime jsRuntime)
|
||||||
|
{
|
||||||
|
_jsRuntime = jsRuntime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
112
Oqtane.Application/Client/Modules/MyModule/Edit.razor
Normal file
112
Oqtane.Application/Client/Modules/MyModule/Edit.razor
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
@using Oqtane.Modules.Controls
|
||||||
|
@using Oqtane.Application.Services
|
||||||
|
@using Oqtane.Application.Models
|
||||||
|
|
||||||
|
@namespace Oqtane.Application.MyModule
|
||||||
|
@inherits ModuleBase
|
||||||
|
@inject IMyModuleService MyModuleService
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject IStringLocalizer<Edit> Localizer
|
||||||
|
|
||||||
|
<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="Enter a name" ResourceKey="Name">Name: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="name" class="form-control" @bind="@_name" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-success" @onclick="Save">@Localizer["Save"]</button>
|
||||||
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
|
||||||
|
<br /><br />
|
||||||
|
@if (PageState.Action == "Edit")
|
||||||
|
{
|
||||||
|
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
|
||||||
|
}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||||
|
|
||||||
|
public override string Actions => "Add,Edit";
|
||||||
|
|
||||||
|
public override string Title => "Manage MyModule";
|
||||||
|
|
||||||
|
public override List<Resource> Resources => new List<Resource>()
|
||||||
|
{
|
||||||
|
new Stylesheet(ModulePath() + "Module.css")
|
||||||
|
};
|
||||||
|
|
||||||
|
private ElementReference form;
|
||||||
|
private bool validated = false;
|
||||||
|
|
||||||
|
private int _id;
|
||||||
|
private string _name;
|
||||||
|
private string _createdby;
|
||||||
|
private DateTime _createdon;
|
||||||
|
private string _modifiedby;
|
||||||
|
private DateTime _modifiedon;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (PageState.Action == "Edit")
|
||||||
|
{
|
||||||
|
_id = Int32.Parse(PageState.QueryString["id"]);
|
||||||
|
MyModule MyModule = await MyModuleService.GetMyModuleAsync(_id, ModuleState.ModuleId);
|
||||||
|
if (MyModule != null)
|
||||||
|
{
|
||||||
|
_name = MyModule.Name;
|
||||||
|
_createdby = MyModule.CreatedBy;
|
||||||
|
_createdon = MyModule.CreatedOn;
|
||||||
|
_modifiedby = MyModule.ModifiedBy;
|
||||||
|
_modifiedon = MyModule.ModifiedOn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Loading MyModule {MyModuleId} {Error}", _id, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Message.LoadError"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Save()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
validated = true;
|
||||||
|
var interop = new Oqtane.UI.Interop(JSRuntime);
|
||||||
|
if (await interop.FormValid(form))
|
||||||
|
{
|
||||||
|
if (PageState.Action == "Add")
|
||||||
|
{
|
||||||
|
MyModule MyModule = new MyModule();
|
||||||
|
MyModule.ModuleId = ModuleState.ModuleId;
|
||||||
|
MyModule.Name = _name;
|
||||||
|
MyModule = await MyModuleService.AddMyModuleAsync(MyModule);
|
||||||
|
await logger.LogInformation("MyModule Added {MyModule}", MyModule);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MyModule MyModule = await MyModuleService.GetMyModuleAsync(_id, ModuleState.ModuleId);
|
||||||
|
MyModule.Name = _name;
|
||||||
|
await MyModuleService.UpdateMyModuleAsync(MyModule);
|
||||||
|
await logger.LogInformation("MyModule Updated {MyModule}", MyModule);
|
||||||
|
}
|
||||||
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.SaveValidation"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Saving MyModule {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Message.SaveError"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
77
Oqtane.Application/Client/Modules/MyModule/Index.razor
Normal file
77
Oqtane.Application/Client/Modules/MyModule/Index.razor
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
@using Oqtane.Application.Services
|
||||||
|
@using Oqtane.Application.Models
|
||||||
|
|
||||||
|
@namespace Oqtane.Application.MyModule
|
||||||
|
@inherits ModuleBase
|
||||||
|
@inject IMyModuleService MyModuleService
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject IStringLocalizer<Index> Localizer
|
||||||
|
|
||||||
|
@if (_MyModules == null)
|
||||||
|
{
|
||||||
|
<p><em>Loading...</em></p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<ActionLink Action="Add" Security="SecurityAccessLevel.Edit" Text="Add MyModule" ResourceKey="Add" />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
@if (@_MyModules.Count != 0)
|
||||||
|
{
|
||||||
|
<Pager Items="@_MyModules">
|
||||||
|
<Header>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
|
<th>@Localizer["Name"]</th>
|
||||||
|
</Header>
|
||||||
|
<Row>
|
||||||
|
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.MyModuleId.ToString())" ResourceKey="Edit" /></td>
|
||||||
|
<td><ActionDialog Header="Delete MyModule" Message="Are You Sure You Wish To Delete This MyModule?" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" ResourceKey="Delete" Id="@context.MyModuleId.ToString()" /></td>
|
||||||
|
<td>@context.Name</td>
|
||||||
|
</Row>
|
||||||
|
</Pager>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p>@Localizer["Message.DisplayNone"]</p>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@code {
|
||||||
|
public override List<Resource> Resources => new List<Resource>()
|
||||||
|
{
|
||||||
|
new Stylesheet(ModulePath() + "Module.css"),
|
||||||
|
new Script(ModulePath() + "Module.js")
|
||||||
|
};
|
||||||
|
|
||||||
|
List<Models.MyModule> _MyModules;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_MyModules = await MyModuleService.GetMyModulesAsync(ModuleState.ModuleId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Loading MyModule {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Message.LoadError"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Delete(MyModule MyModule)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await MyModuleService.DeleteMyModuleAsync(MyModule.MyModuleId, ModuleState.ModuleId);
|
||||||
|
await logger.LogInformation("MyModule Deleted {MyModule}", MyModule);
|
||||||
|
_MyModules = await MyModuleService.GetMyModulesAsync(ModuleState.ModuleId);
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Deleting MyModule {MyModule} {Error}", MyModule, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Message.DeleteError"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
Oqtane.Application/Client/Modules/MyModule/ModuleInfo.cs
Normal file
19
Oqtane.Application/Client/Modules/MyModule/ModuleInfo.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using Oqtane.Models;
|
||||||
|
using Oqtane.Modules;
|
||||||
|
|
||||||
|
namespace Oqtane.Application.MyModule
|
||||||
|
{
|
||||||
|
public class ModuleInfo : IModule
|
||||||
|
{
|
||||||
|
public ModuleDefinition ModuleDefinition => new ModuleDefinition
|
||||||
|
{
|
||||||
|
Name = "MyModule",
|
||||||
|
Description = "Example module",
|
||||||
|
Version = "1.0.0",
|
||||||
|
ServerManagerType = "Oqtane.Application.Manager.MyModuleManager, Oqtane.Application.Server.Oqtane",
|
||||||
|
ReleaseVersions = "1.0.0",
|
||||||
|
Dependencies = "Oqtane.Application.Shared.Oqtane",
|
||||||
|
PackageName = "Oqtane.Application"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
47
Oqtane.Application/Client/Modules/MyModule/Settings.razor
Normal file
47
Oqtane.Application/Client/Modules/MyModule/Settings.razor
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
@namespace Oqtane.Application.MyModule
|
||||||
|
@inherits ModuleBase
|
||||||
|
@inject ISettingService SettingService
|
||||||
|
@inject IStringLocalizer<Settings> Localizer
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="value" HelpText="Enter a value" ResourceKey="SettingName" ResourceType="@resourceType">Name: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="value" type="text" class="form-control" @bind="@_value" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private string resourceType = "Oqtane.Application.MyModule.Settings, Oqtane.Application.Client.Oqtane"; // for localization
|
||||||
|
public override string Title => "MyModdule Settings";
|
||||||
|
|
||||||
|
string _value;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Dictionary<string, string> settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
|
||||||
|
_value = SettingService.GetSetting(settings, "SettingName", "");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AddModuleMessage(ex.Message, MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateSettings()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Dictionary<string, string> settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
|
||||||
|
SettingService.SetSetting(settings, "SettingName", _value);
|
||||||
|
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AddModuleMessage(ex.Message, MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
Oqtane.Application/Client/Oqtane.Application.Client.csproj
Normal file
30
Oqtane.Application/Client/Oqtane.Application.Client.csproj
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<Version>1.0.0</Version>
|
||||||
|
<AssemblyName>Oqtane.Application.Client.Oqtane</AssemblyName>
|
||||||
|
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
|
||||||
|
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
|
||||||
|
<PublishTrimmed>false</PublishTrimmed>
|
||||||
|
<BlazorEnableCompression>false</BlazorEnableCompression>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.9" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.9" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.9" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.9" />
|
||||||
|
<PackageReference Include="System.Net.Http.Json" Version="9.0.9" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Shared\Oqtane.Application.Shared.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Oqtane.Client" Version="6.2.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
13
Oqtane.Application/Client/Program.cs
Normal file
13
Oqtane.Application/Client/Program.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Oqtane.Application.Client
|
||||||
|
{
|
||||||
|
internal class Program
|
||||||
|
{
|
||||||
|
static async Task Main(string[] args)
|
||||||
|
{
|
||||||
|
// defer client startup to Oqtane - do not modify
|
||||||
|
await Oqtane.Client.Program.Main(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,141 @@
|
|||||||
|
<?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="Name.Text" xml:space="preserve">
|
||||||
|
<value>Name: </value>
|
||||||
|
</data>
|
||||||
|
<data name="Name.HelpText" xml:space="preserve">
|
||||||
|
<value>Enter the name</value>
|
||||||
|
</data>
|
||||||
|
<data name="Save" xml:space="preserve">
|
||||||
|
<value>Save</value>
|
||||||
|
</data>
|
||||||
|
<data name="Cancel" xml:space="preserve">
|
||||||
|
<value>Cancel</value>
|
||||||
|
</data>
|
||||||
|
<data name="Message.SaveValidation" xml:space="preserve">
|
||||||
|
<value>Please Provide All Required Information</value>
|
||||||
|
</data>
|
||||||
|
<data name="Message.SaveError" xml:space="preserve">
|
||||||
|
<value>Error Saving MyModule</value>
|
||||||
|
</data>
|
||||||
|
<data name="Message.LoadError" xml:space="preserve">
|
||||||
|
<value>Error Loading MyModule</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
@ -0,0 +1,147 @@
|
|||||||
|
<?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="Name" xml:space="preserve">
|
||||||
|
<value>Name</value>
|
||||||
|
</data>
|
||||||
|
<data name="Add.Text" xml:space="preserve">
|
||||||
|
<value>Add MyModule</value>
|
||||||
|
</data>
|
||||||
|
<data name="Edit.Text" xml:space="preserve">
|
||||||
|
<value>Edit</value>
|
||||||
|
</data>
|
||||||
|
<data name="Delete.Text" xml:space="preserve">
|
||||||
|
<value>Delete</value>
|
||||||
|
</data>
|
||||||
|
<data name="Delete.Header" xml:space="preserve">
|
||||||
|
<value>Delete MyModule</value>
|
||||||
|
</data>
|
||||||
|
<data name="Delete.Message" xml:space="preserve">
|
||||||
|
<value>Are You Sure You Wish To Delete This MyModule?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Message.DisplayNone" xml:space="preserve">
|
||||||
|
<value>No MyModules To Display</value>
|
||||||
|
</data>
|
||||||
|
<data name="Message.LoadError" xml:space="preserve">
|
||||||
|
<value>Error Loading MyModule</value>
|
||||||
|
</data>
|
||||||
|
<data name="Message.DeleteError" xml:space="preserve">
|
||||||
|
<value>Error Deleting MyModule</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
@ -0,0 +1,126 @@
|
|||||||
|
<?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 xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd: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="SettingName.Text" xml:space="preserve">
|
||||||
|
<value>Name: </value>
|
||||||
|
</data>
|
||||||
|
<data name="SettingName.HelpText" xml:space="preserve">
|
||||||
|
<value>Enter a value</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
@ -0,0 +1,126 @@
|
|||||||
|
<?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="Title.HelpText" xml:space="preserve">
|
||||||
|
<value>Specify If The Module Title Should Be Displayed</value>
|
||||||
|
</data>
|
||||||
|
<data name="Title.Text" xml:space="preserve">
|
||||||
|
<value>Display Title</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
@ -0,0 +1,138 @@
|
|||||||
|
<?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="Login.HelpText" xml:space="preserve">
|
||||||
|
<value>Specify if a Login option should be displayed. Note that this option does not prevent the login page from being accessible via a direct url.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Login.Text" xml:space="preserve">
|
||||||
|
<value>Show Login?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Register.HelpText" xml:space="preserve">
|
||||||
|
<value>Specify if a Register option should be displayed. Note that this option is also dependent on the Allow Registration option in Site Settings.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Register.Text" xml:space="preserve">
|
||||||
|
<value>Show Register?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Scope.HelpText" xml:space="preserve">
|
||||||
|
<value>Specify if the settings are applicable to this page or the entire site.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Scope.Text" xml:space="preserve">
|
||||||
|
<value>Setting Scope:</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
55
Oqtane.Application/Client/Services/MyModuleService.cs
Normal file
55
Oqtane.Application/Client/Services/MyModuleService.cs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Oqtane.Services;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
|
||||||
|
namespace Oqtane.Application.Services
|
||||||
|
{
|
||||||
|
public interface IMyModuleService
|
||||||
|
{
|
||||||
|
Task<List<Models.MyModule>> GetMyModulesAsync(int ModuleId);
|
||||||
|
|
||||||
|
Task<Models.MyModule> GetMyModuleAsync(int MyModuleId, int ModuleId);
|
||||||
|
|
||||||
|
Task<Models.MyModule> AddMyModuleAsync(Models.MyModule MyModule);
|
||||||
|
|
||||||
|
Task<Models.MyModule> UpdateMyModuleAsync(Models.MyModule MyModule);
|
||||||
|
|
||||||
|
Task DeleteMyModuleAsync(int MyModuleId, int ModuleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MyModuleService : ServiceBase, IMyModuleService
|
||||||
|
{
|
||||||
|
public MyModuleService(HttpClient http, SiteState siteState) : base(http, siteState) { }
|
||||||
|
|
||||||
|
private string Apiurl => CreateApiUrl("MyModule");
|
||||||
|
|
||||||
|
public async Task<List<Models.MyModule>> GetMyModulesAsync(int ModuleId)
|
||||||
|
{
|
||||||
|
List<Models.MyModule> Tasks = await GetJsonAsync<List<Models.MyModule>>(CreateAuthorizationPolicyUrl($"{Apiurl}?moduleid={ModuleId}", EntityNames.Module, ModuleId), Enumerable.Empty<Models.MyModule>().ToList());
|
||||||
|
return Tasks.OrderBy(item => item.Name).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Models.MyModule> GetMyModuleAsync(int MyModuleId, int ModuleId)
|
||||||
|
{
|
||||||
|
return await GetJsonAsync<Models.MyModule>(CreateAuthorizationPolicyUrl($"{Apiurl}/{MyModuleId}/{ModuleId}", EntityNames.Module, ModuleId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Models.MyModule> AddMyModuleAsync(Models.MyModule MyModule)
|
||||||
|
{
|
||||||
|
return await PostJsonAsync<Models.MyModule>(CreateAuthorizationPolicyUrl($"{Apiurl}", EntityNames.Module, MyModule.ModuleId), MyModule);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Models.MyModule> UpdateMyModuleAsync(Models.MyModule MyModule)
|
||||||
|
{
|
||||||
|
return await PutJsonAsync<Models.MyModule>(CreateAuthorizationPolicyUrl($"{Apiurl}/{MyModule.MyModuleId}", EntityNames.Module, MyModule.ModuleId), MyModule);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteMyModuleAsync(int MyModuleId, int ModuleId)
|
||||||
|
{
|
||||||
|
await DeleteAsync(CreateAuthorizationPolicyUrl($"{Apiurl}/{MyModuleId}/{ModuleId}", EntityNames.Module, ModuleId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
Oqtane.Application/Client/Startup/ClientStartup.cs
Normal file
18
Oqtane.Application/Client/Startup/ClientStartup.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using System.Linq;
|
||||||
|
using Oqtane.Services;
|
||||||
|
using Oqtane.Application.Services;
|
||||||
|
|
||||||
|
namespace Oqtane.Application.Startup
|
||||||
|
{
|
||||||
|
public class ClientStartup : IClientStartup
|
||||||
|
{
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
if (!services.Any(s => s.ServiceType == typeof(IMyModuleService)))
|
||||||
|
{
|
||||||
|
services.AddScoped<IMyModuleService, MyModuleService>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
@namespace Oqtane.Application.MyTheme
|
||||||
|
@inherits ContainerBase
|
||||||
|
@inject ISettingService SettingService
|
||||||
|
|
||||||
|
<div class="@_classes">
|
||||||
|
@if (_title && ModuleState.Title != "-")
|
||||||
|
{
|
||||||
|
<div class="row px-4">
|
||||||
|
<div class="d-flex flex-nowrap">
|
||||||
|
<ModuleActions /><h2><ModuleTitle /></h2>
|
||||||
|
</div>
|
||||||
|
<hr class="app-rule" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<ModuleActions />
|
||||||
|
}
|
||||||
|
<div class="row px-4">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<ModuleInstance />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
public override string Name => "Container";
|
||||||
|
|
||||||
|
private bool _title = true;
|
||||||
|
private string _classes = "container-fluid";
|
||||||
|
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
_title = bool.Parse(SettingService.GetSetting(ModuleState.Settings, GetType().Namespace + ":Title", "true"));
|
||||||
|
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// error loading container settings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
@namespace Oqtane.Application.MyTheme
|
||||||
|
@inherits ModuleBase
|
||||||
|
@implements Oqtane.Interfaces.ISettingsControl
|
||||||
|
@inject ISettingService SettingService
|
||||||
|
@attribute [OqtaneIgnore]
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="title" ResourceKey="Title" ResourceType="@resourceType" HelpText="Specify If The Module Title Should Be Displayed">Display Title?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="title" class="form-select" @bind="@_title">
|
||||||
|
<option value="true">Yes</option>
|
||||||
|
<option value="false">No</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private string resourceType = "Oqtane.Application.MyTheme.ContainerSettings, Oqtane.Application.Client.Oqtane"; // for localization
|
||||||
|
private string _title = "true";
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
_title = SettingService.GetSetting(ModuleState.Settings, GetType().Namespace + ":Title", "true");
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AddModuleMessage(ex.Message, MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateSettings()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
|
||||||
|
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Title", _title);
|
||||||
|
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AddModuleMessage(ex.Message, MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
Oqtane.Application/Client/Themes/MyTheme/ThemeInfo.cs
Normal file
25
Oqtane.Application/Client/Themes/MyTheme/ThemeInfo.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Oqtane.Models;
|
||||||
|
using Oqtane.Themes;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
|
||||||
|
namespace Oqtane.Application.MyTheme
|
||||||
|
{
|
||||||
|
public class ThemeInfo : ITheme
|
||||||
|
{
|
||||||
|
public Oqtane.Models.Theme Theme => new Oqtane.Models.Theme
|
||||||
|
{
|
||||||
|
Name = "MyTheme",
|
||||||
|
Version = "1.0.0",
|
||||||
|
PackageName = "Oqtane.Application",
|
||||||
|
ThemeSettingsType = "Oqtane.Application.MyTheme.ThemeSettings, Oqtane.Application.Client.Oqtane",
|
||||||
|
ContainerSettingsType = "Oqtane.Application.MyTheme.ContainerSettings, Oqtane.Application.Client.Oqtane",
|
||||||
|
Resources = new List<Resource>()
|
||||||
|
{
|
||||||
|
new Stylesheet(Constants.BootstrapStylesheetUrl, Constants.BootstrapStylesheetIntegrity, "anonymous"),
|
||||||
|
new Stylesheet("~/Theme.css"),
|
||||||
|
new Script(Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
118
Oqtane.Application/Client/Themes/MyTheme/Themes/Theme.razor
Normal file
118
Oqtane.Application/Client/Themes/MyTheme/Themes/Theme.razor
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
@namespace Oqtane.Application.MyTheme
|
||||||
|
@inherits ThemeBase
|
||||||
|
@inject ISettingService SettingService
|
||||||
|
|
||||||
|
<main role="main">
|
||||||
|
<nav class="navbar navbar-dark bg-primary fixed-top">
|
||||||
|
<Logo /><Menu Orientation="Horizontal" />
|
||||||
|
<div class="controls ms-auto">
|
||||||
|
<div class="controls-group"><UserProfile ShowRegister="@_register" /> <Login ShowLogin="@_login" /> <ControlPanel ButtonClass="btn-outline-light" /></div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="content">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<Pane Name="@PaneNames.Admin" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Pane Name="Top Full Width" />
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<Pane Name="Top 100%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<Pane Name="Left 50%" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<Pane Name="Right 50%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<Pane Name="Left 33%" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<Pane Name="Center 33%" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<Pane Name="Right 33%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<Pane Name="Left Outer 25%" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<Pane Name="Left Inner 25%" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<Pane Name="Right Inner 25%" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<Pane Name="Right Outer 25%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<Pane Name="Left 25%" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<Pane Name="Center 50%" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<Pane Name="Right 25%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<Pane Name="Left Sidebar 66%" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<Pane Name="Right Sidebar 33%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<Pane Name="Left Sidebar 33%" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8">
|
||||||
|
<Pane Name="Right Sidebar 66%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<Pane Name="Bottom 100%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Pane Name="Bottom Full Width" />
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
public override string Name => "MyTheme";
|
||||||
|
|
||||||
|
public override string Panes => PaneNames.Admin + ",Top Full Width,Top 100%,Left 50%,Right 50%,Left 33%,Center 33%,Right 33%,Left Outer 25%,Left Inner 25%,Right Inner 25%,Right Outer 25%,Left 25%,Center 50%,Right 25%,Left Sidebar 66%,Right Sidebar 33%,Left Sidebar 33%,Right Sidebar 66%,Bottom 100%,Bottom Full Width";
|
||||||
|
|
||||||
|
private bool _login = true;
|
||||||
|
private bool _register = true;
|
||||||
|
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var settings = SettingService.MergeSettings(PageState.Site.Settings, PageState.Page.Settings);
|
||||||
|
_login = bool.Parse(SettingService.GetSetting(settings, GetType().Namespace + ":Login", "true"));
|
||||||
|
_register = bool.Parse(SettingService.GetSetting(settings, GetType().Namespace + ":Register", "true"));
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// error loading theme settings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,140 @@
|
|||||||
|
@namespace Oqtane.Application.MyTheme
|
||||||
|
@inherits ModuleBase
|
||||||
|
@implements Oqtane.Interfaces.ISettingsControl
|
||||||
|
@inject ISettingService SettingService
|
||||||
|
@inject IStringLocalizer<ThemeSettings> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
@attribute [OqtaneIgnore]
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="scope" ResourceKey="Scope" ResourceType="@resourceType" HelpText="Specify if the settings are applicable to this page or the entire site.">Setting Scope:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="scope" class="form-select" value="@_scope" @onchange="(e => ScopeChanged(e))">
|
||||||
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
|
{
|
||||||
|
<option value="site">@Localizer["Site"]</option>
|
||||||
|
}
|
||||||
|
<option value="page">@Localizer["Page"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="login" ResourceKey="Login" ResourceType="@resourceType" HelpText="Specify if a Login option should be displayed. Note that this option does not prevent the login page from being accessible via a direct url.">Show Login?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="login" class="form-select" @bind="@_login">
|
||||||
|
<option value="-"><@SharedLocalizer["Not Specified"]></option>
|
||||||
|
<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="register" ResourceKey="Register" ResourceType="@resourceType" HelpText="Specify if a Register option should be displayed. Note that this option is also dependent on the Allow Registration option in Site Settings.">Show Register?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="register" class="form-select" @bind="@_register">
|
||||||
|
<option value="-"><@SharedLocalizer["Not Specified"]></option>
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private int pageId = -1;
|
||||||
|
private string resourceType = "Oqtane.Application.MyTheme.ThemeSettings, Oqtane.Application.Client.Oqtane"; // for localization
|
||||||
|
private string _scope = "page";
|
||||||
|
private string _login = "-";
|
||||||
|
private string _register = "-";
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
if (PageState.QueryString.ContainsKey("id"))
|
||||||
|
{
|
||||||
|
pageId = int.Parse(PageState.QueryString["id"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await LoadSettings();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Loading Settings {Error}", ex.Message);
|
||||||
|
AddModuleMessage("Error Loading Settings", MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadSettings()
|
||||||
|
{
|
||||||
|
if (_scope == "site")
|
||||||
|
{
|
||||||
|
var settings = PageState.Site.Settings;
|
||||||
|
_login = SettingService.GetSetting(settings, GetType().Namespace + ":Login", "true");
|
||||||
|
_register = SettingService.GetSetting(settings, GetType().Namespace + ":Register", "true");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var settings = await SettingService.GetPageSettingsAsync(pageId);
|
||||||
|
settings = SettingService.MergeSettings(PageState.Site.Settings, settings);
|
||||||
|
_login = SettingService.GetSetting(settings, GetType().Namespace + ":Login", "-");
|
||||||
|
_register = SettingService.GetSetting(settings, GetType().Namespace + ":Register", "-");
|
||||||
|
}
|
||||||
|
await Task.Yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ScopeChanged(ChangeEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_scope = (string)eventArgs.Value;
|
||||||
|
await LoadSettings();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Loading Settings {Error}", ex.Message);
|
||||||
|
AddModuleMessage("Error Loading Settings", MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateSettings()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_scope == "site")
|
||||||
|
{
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
if (_login != "-")
|
||||||
|
{
|
||||||
|
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Login", _login);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_register != "-")
|
||||||
|
{
|
||||||
|
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Register", _register);
|
||||||
|
}
|
||||||
|
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var settings = await SettingService.GetPageSettingsAsync(pageId);
|
||||||
|
if (_login != "-")
|
||||||
|
{
|
||||||
|
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Login", _login);
|
||||||
|
}
|
||||||
|
if (_register != "-")
|
||||||
|
{
|
||||||
|
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Register", _register);
|
||||||
|
}
|
||||||
|
await SettingService.UpdatePageSettingsAsync(settings, pageId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Saving Settings {Error}", ex.Message);
|
||||||
|
AddModuleMessage("Error Saving Settings", MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
Oqtane.Application/Client/_Imports.razor
Normal file
25
Oqtane.Application/Client/_Imports.razor
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
@using System
|
||||||
|
@using System.Linq
|
||||||
|
@using System.Collections.Generic
|
||||||
|
@using System.Net.Http
|
||||||
|
@using System.Net.Http.Json
|
||||||
|
|
||||||
|
@using Microsoft.AspNetCore.Components.Authorization
|
||||||
|
@using Microsoft.AspNetCore.Components.Routing
|
||||||
|
@using Microsoft.AspNetCore.Components.Web
|
||||||
|
@using Microsoft.Extensions.Localization
|
||||||
|
@using Microsoft.JSInterop
|
||||||
|
|
||||||
|
@using Oqtane
|
||||||
|
@using Oqtane.Models
|
||||||
|
@using Oqtane.Modules
|
||||||
|
@using Oqtane.Modules.Controls
|
||||||
|
@using Oqtane.Providers
|
||||||
|
@using Oqtane.Security
|
||||||
|
@using Oqtane.Services
|
||||||
|
@using Oqtane.Shared
|
||||||
|
@using Oqtane.Themes
|
||||||
|
@using Oqtane.Themes.Controls
|
||||||
|
@using Oqtane.UI
|
||||||
|
@using Oqtane.Enums
|
||||||
|
@using Oqtane.Interfaces
|
||||||
21
Oqtane.Application/Oqtane.Application.Template.nuspec
Normal file
21
Oqtane.Application/Oqtane.Application.Template.nuspec
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
|
||||||
|
<metadata>
|
||||||
|
<id>Oqtane.Application.Template</id>
|
||||||
|
<version>6.2.1</version>
|
||||||
|
<title>Oqtane Application Template For Blazor</title>
|
||||||
|
<authors>Shaun Walker</authors>
|
||||||
|
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||||
|
<license type="expression">MIT</license>
|
||||||
|
<licenseUrl>https://licenses.nuget.org/MIT</licenseUrl>
|
||||||
|
<icon>icon.png</icon>
|
||||||
|
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||||
|
<description>Oqtane is an open source CMS and Application Framework that provides advanced functionality for developing web, mobile, and desktop applications on .NET. It leverages Blazor to compose a fully dynamic digital experience which can be hosted on Static Blazor, Blazor Server, Blazor WebAssembly, or Blazor Hybrid (via .NET MAUI).</description>
|
||||||
|
<language>en-US</language>
|
||||||
|
<tags>Web ASP.NET Blazor Oqtane Modular Multi-Tenant "Open Source" "SQL Server" MySQL PostgreSQL SQLite</tags>
|
||||||
|
<readme>README.md</readme>
|
||||||
|
<packageTypes>
|
||||||
|
<packageType name="Template" />
|
||||||
|
</packageTypes>
|
||||||
|
</metadata>
|
||||||
|
</package>
|
||||||
33
Oqtane.Application/Oqtane.Application.sln
Normal file
33
Oqtane.Application/Oqtane.Application.sln
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.12.35506.116 d17.12
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Application.Server", "Server\Oqtane.Application.Server.csproj", "{04B05448-788F-433D-92C0-FED35122D45A}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Application.Client", "Client\Oqtane.Application.Client.csproj", "{AA8E58A1-CD09-4208-BF66-A8BB341FD669}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Application.Shared", "Shared\Oqtane.Application.Shared.csproj", "{18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{04B05448-788F-433D-92C0-FED35122D45A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{04B05448-788F-433D-92C0-FED35122D45A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{04B05448-788F-433D-92C0-FED35122D45A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{04B05448-788F-433D-92C0-FED35122D45A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
22
Oqtane.Application/README.md
Normal file
22
Oqtane.Application/README.md
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# Oqtane Application Template
|
||||||
|
|
||||||
|
This is a Visual Studio Project Template designed for Oqtane development projects. This template relies on the native templating capabilities of the .NET Command Line Interface (CLI):
|
||||||
|
|
||||||
|
```
|
||||||
|
dotnet new install Oqtane.Application.Template
|
||||||
|
dotnet new oqtane-app -o MyCompany.MyProject
|
||||||
|
cd MyCompany.MyProject
|
||||||
|
dotnet build
|
||||||
|
cd Server
|
||||||
|
dotnet run
|
||||||
|
browse to Url
|
||||||
|
```
|
||||||
|
|
||||||
|
When using this approach you do not need to have a local copy of the oqtane.framework source code - you simply utilize Oqtane as a standard application dependency.
|
||||||
|
|
||||||
|
The solution also contains Client, Server, and Shared folders which is where you you would implement your custom functionality. An example module and theme are included for reference, and you can add additional modules and themes within the same projects by following the standard Oqtane folder/namespace conventions.
|
||||||
|
|
||||||
|
*Known Issues*
|
||||||
|
|
||||||
|
- do not use the term "Oqtane" or "Module" in your output name or else you will experience namespace conflicts
|
||||||
|
|
||||||
3
Oqtane.Application/Server/AssemblyInfo.cs
Normal file
3
Oqtane.Application/Server/AssemblyInfo.cs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
|
[assembly: RootNamespace("Oqtane.Application.Server")]
|
||||||
114
Oqtane.Application/Server/Controllers/MyModuleController.cs
Normal file
114
Oqtane.Application/Server/Controllers/MyModuleController.cs
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
using Oqtane.Enums;
|
||||||
|
using Oqtane.Infrastructure;
|
||||||
|
using Oqtane.Application.Services;
|
||||||
|
using Oqtane.Controllers;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Oqtane.Application.Controllers
|
||||||
|
{
|
||||||
|
[Route(ControllerRoutes.ApiRoute)]
|
||||||
|
public class MyModuleController : ModuleControllerBase
|
||||||
|
{
|
||||||
|
private readonly IMyModuleService _MyModuleService;
|
||||||
|
|
||||||
|
public MyModuleController(IMyModuleService MyModuleService, ILogManager logger, IHttpContextAccessor accessor) : base(logger, accessor)
|
||||||
|
{
|
||||||
|
_MyModuleService = MyModuleService;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/<controller>?moduleid=x
|
||||||
|
[HttpGet]
|
||||||
|
[Authorize(Policy = PolicyNames.ViewModule)]
|
||||||
|
public async Task<IEnumerable<Models.MyModule>> Get(string moduleid)
|
||||||
|
{
|
||||||
|
int ModuleId;
|
||||||
|
if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId))
|
||||||
|
{
|
||||||
|
return await _MyModuleService.GetMyModulesAsync(ModuleId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized MyModule Get Attempt {ModuleId}", moduleid);
|
||||||
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET api/<controller>/5
|
||||||
|
[HttpGet("{id}/{moduleid}")]
|
||||||
|
[Authorize(Policy = PolicyNames.ViewModule)]
|
||||||
|
public async Task<Models.MyModule> Get(int id, int moduleid)
|
||||||
|
{
|
||||||
|
Models.MyModule MyModule = await _MyModuleService.GetMyModuleAsync(id, moduleid);
|
||||||
|
if (MyModule != null && IsAuthorizedEntityId(EntityNames.Module, MyModule.ModuleId))
|
||||||
|
{
|
||||||
|
return MyModule;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized MyModule Get Attempt {MyModuleId} {ModuleId}", id, moduleid);
|
||||||
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST api/<controller>
|
||||||
|
[HttpPost]
|
||||||
|
[Authorize(Policy = PolicyNames.EditModule)]
|
||||||
|
public async Task<Models.MyModule> Post([FromBody] Models.MyModule MyModule)
|
||||||
|
{
|
||||||
|
if (ModelState.IsValid && IsAuthorizedEntityId(EntityNames.Module, MyModule.ModuleId))
|
||||||
|
{
|
||||||
|
MyModule = await _MyModuleService.AddMyModuleAsync(MyModule);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized MyModule Post Attempt {MyModule}", MyModule);
|
||||||
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
|
MyModule = null;
|
||||||
|
}
|
||||||
|
return MyModule;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT api/<controller>/5
|
||||||
|
[HttpPut("{id}")]
|
||||||
|
[Authorize(Policy = PolicyNames.EditModule)]
|
||||||
|
public async Task<Models.MyModule> Put(int id, [FromBody] Models.MyModule MyModule)
|
||||||
|
{
|
||||||
|
if (ModelState.IsValid && MyModule.MyModuleId == id && IsAuthorizedEntityId(EntityNames.Module, MyModule.ModuleId))
|
||||||
|
{
|
||||||
|
MyModule = await _MyModuleService.UpdateMyModuleAsync(MyModule);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized MyModule Put Attempt {MyModule}", MyModule);
|
||||||
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
|
MyModule = null;
|
||||||
|
}
|
||||||
|
return MyModule;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE api/<controller>/5
|
||||||
|
[HttpDelete("{id}/{moduleid}")]
|
||||||
|
[Authorize(Policy = PolicyNames.EditModule)]
|
||||||
|
public async Task Delete(int id, int moduleid)
|
||||||
|
{
|
||||||
|
Models.MyModule MyModule = await _MyModuleService.GetMyModuleAsync(id, moduleid);
|
||||||
|
if (MyModule != null && IsAuthorizedEntityId(EntityNames.Module, MyModule.ModuleId))
|
||||||
|
{
|
||||||
|
await _MyModuleService.DeleteMyModuleAsync(id, MyModule.ModuleId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized v Delete Attempt {MyModuleId} {ModuleId}", id, moduleid);
|
||||||
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
87
Oqtane.Application/Server/Manager/MyModuleManager.cs
Normal file
87
Oqtane.Application/Server/Manager/MyModuleManager.cs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Oqtane.Modules;
|
||||||
|
using Oqtane.Models;
|
||||||
|
using Oqtane.Infrastructure;
|
||||||
|
using Oqtane.Interfaces;
|
||||||
|
using Oqtane.Enums;
|
||||||
|
using Oqtane.Repository;
|
||||||
|
using Oqtane.Application.Repository;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Oqtane.Application.Manager
|
||||||
|
{
|
||||||
|
public class MyModuleManager : MigratableModuleBase, IInstallable, IPortable, ISearchable
|
||||||
|
{
|
||||||
|
private readonly IMyModuleRepository _MyModuleRepository;
|
||||||
|
private readonly IDBContextDependencies _DBContextDependencies;
|
||||||
|
|
||||||
|
public MyModuleManager(IMyModuleRepository MyModuleRepository, IDBContextDependencies DBContextDependencies)
|
||||||
|
{
|
||||||
|
_MyModuleRepository = MyModuleRepository;
|
||||||
|
_DBContextDependencies = DBContextDependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Install(Tenant tenant, string version)
|
||||||
|
{
|
||||||
|
return Migrate(new Context(_DBContextDependencies), tenant, MigrationType.Up);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Uninstall(Tenant tenant)
|
||||||
|
{
|
||||||
|
return Migrate(new Context(_DBContextDependencies), tenant, MigrationType.Down);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ExportModule(Module module)
|
||||||
|
{
|
||||||
|
string content = "";
|
||||||
|
List<Models.MyModule> MyModules = _MyModuleRepository.GetMyModules(module.ModuleId).ToList();
|
||||||
|
if (MyModules != null)
|
||||||
|
{
|
||||||
|
content = JsonSerializer.Serialize(MyModules);
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ImportModule(Module module, string content, string version)
|
||||||
|
{
|
||||||
|
List<Models.MyModule> MyModules = null;
|
||||||
|
if (!string.IsNullOrEmpty(content))
|
||||||
|
{
|
||||||
|
MyModules = JsonSerializer.Deserialize<List<Models.MyModule>>(content);
|
||||||
|
}
|
||||||
|
if (MyModules != null)
|
||||||
|
{
|
||||||
|
foreach(var Task in MyModules)
|
||||||
|
{
|
||||||
|
_MyModuleRepository.AddMyModule(new Models.MyModule { ModuleId = module.ModuleId, Name = Task.Name });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<List<SearchContent>> GetSearchContentsAsync(PageModule pageModule, DateTime lastIndexedOn)
|
||||||
|
{
|
||||||
|
var searchContentList = new List<SearchContent>();
|
||||||
|
|
||||||
|
foreach (var MyModule in _MyModuleRepository.GetMyModules(pageModule.ModuleId))
|
||||||
|
{
|
||||||
|
if (MyModule.ModifiedOn >= lastIndexedOn)
|
||||||
|
{
|
||||||
|
searchContentList.Add(new SearchContent
|
||||||
|
{
|
||||||
|
EntityName = "MyModule",
|
||||||
|
EntityId = MyModule.MyModuleId.ToString(),
|
||||||
|
Title = MyModule.Name,
|
||||||
|
Body = MyModule.Name,
|
||||||
|
ContentModifiedBy = MyModule.ModifiedBy,
|
||||||
|
ContentModifiedOn = MyModule.ModifiedOn
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(searchContentList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Oqtane.Databases.Interfaces;
|
||||||
|
using Oqtane.Migrations;
|
||||||
|
using Oqtane.Application.Migrations.EntityBuilders;
|
||||||
|
using Oqtane.Application.Repository;
|
||||||
|
|
||||||
|
namespace Oqtane.Application.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(Context))]
|
||||||
|
[Migration("Oqtane.Application.01.00.00.00")]
|
||||||
|
public class InitializeModule : MultiDatabaseMigration
|
||||||
|
{
|
||||||
|
public InitializeModule(IDatabase database) : base(database)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
var myModuleEntityBuilder = new MyModuleEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||||
|
myModuleEntityBuilder.Create();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
var myModuleEntityBuilder = new MyModuleEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||||
|
myModuleEntityBuilder.Drop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations.Operations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders;
|
||||||
|
using Oqtane.Databases.Interfaces;
|
||||||
|
using Oqtane.Migrations;
|
||||||
|
using Oqtane.Migrations.EntityBuilders;
|
||||||
|
|
||||||
|
namespace Oqtane.Application.Migrations.EntityBuilders
|
||||||
|
{
|
||||||
|
public class MyModuleEntityBuilder : AuditableBaseEntityBuilder<MyModuleEntityBuilder>
|
||||||
|
{
|
||||||
|
private const string _entityTableName = "MyModule";
|
||||||
|
private readonly PrimaryKey<MyModuleEntityBuilder> _primaryKey = new("PK_MyModule", x => x.MyModuleId);
|
||||||
|
private readonly ForeignKey<MyModuleEntityBuilder> _moduleForeignKey = new("FK_MyModule_Module", x => x.ModuleId, "Module", "ModuleId", ReferentialAction.Cascade);
|
||||||
|
|
||||||
|
public MyModuleEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database)
|
||||||
|
{
|
||||||
|
EntityTableName = _entityTableName;
|
||||||
|
PrimaryKey = _primaryKey;
|
||||||
|
ForeignKeys.Add(_moduleForeignKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override MyModuleEntityBuilder BuildTable(ColumnsBuilder table)
|
||||||
|
{
|
||||||
|
MyModuleId = AddAutoIncrementColumn(table, "MyModuleId");
|
||||||
|
ModuleId = AddIntegerColumn(table,"ModuleId");
|
||||||
|
Name = AddMaxStringColumn(table,"Name");
|
||||||
|
AddAuditableColumns(table);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OperationBuilder<AddColumnOperation> MyModuleId { get; set; }
|
||||||
|
public OperationBuilder<AddColumnOperation> ModuleId { get; set; }
|
||||||
|
public OperationBuilder<AddColumnOperation> Name { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
29
Oqtane.Application/Server/Oqtane.Application.Server.csproj
Normal file
29
Oqtane.Application/Server/Oqtane.Application.Server.csproj
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<Version>1.0.0</Version>
|
||||||
|
<AssemblyName>Oqtane.Application.Server.Oqtane</AssemblyName>
|
||||||
|
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||||
|
<SatelliteResourceLanguages>none</SatelliteResourceLanguages>
|
||||||
|
<CompressionEnabled>false</CompressionEnabled>
|
||||||
|
<StaticWebAssetsFingerprintContent>false</StaticWebAssetsFingerprintContent>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.9" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.9" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.9" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.9" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Client\Oqtane.Application.Client.csproj" />
|
||||||
|
<ProjectReference Include="..\Shared\Oqtane.Application.Shared.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Oqtane.Server" Version="6.2.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
42
Oqtane.Application/Server/Program.cs
Normal file
42
Oqtane.Application/Server/Program.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Oqtane.Infrastructure;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Oqtane.Application.Server
|
||||||
|
{
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
// defer server startup to Oqtane - do not modify
|
||||||
|
var host = BuildWebHost(args);
|
||||||
|
var databaseManager = host.Services.GetService<IDatabaseManager>();
|
||||||
|
var install = databaseManager.Install();
|
||||||
|
if (!string.IsNullOrEmpty(install.Message))
|
||||||
|
{
|
||||||
|
var filelogger = host.Services.GetRequiredService<ILogger<Program>>();
|
||||||
|
if (filelogger != null)
|
||||||
|
{
|
||||||
|
filelogger.LogError($"[Oqtane.Application.Server.Program.Main] {install.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
host.Run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IWebHost BuildWebHost(string[] args) =>
|
||||||
|
WebHost.CreateDefaultBuilder(args)
|
||||||
|
.UseConfiguration(new ConfigurationBuilder()
|
||||||
|
.AddCommandLine(args)
|
||||||
|
.AddEnvironmentVariables()
|
||||||
|
.Build())
|
||||||
|
.UseStartup<Startup>()
|
||||||
|
.ConfigureLocalizationSettings()
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
}
|
||||||
25
Oqtane.Application/Server/Properties/launchSettings.json
Normal file
25
Oqtane.Application/Server/Properties/launchSettings.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||||
|
"profiles": {
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": true,
|
||||||
|
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||||
|
"applicationUrl": "http://localhost:44358",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"https": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": true,
|
||||||
|
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||||
|
"applicationUrl": "https://localhost:44359;http://localhost:44358",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
Oqtane.Application/Server/Repository/Context.cs
Normal file
24
Oqtane.Application/Server/Repository/Context.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Oqtane.Modules;
|
||||||
|
using Oqtane.Repository;
|
||||||
|
using Oqtane.Repository.Databases.Interfaces;
|
||||||
|
|
||||||
|
namespace Oqtane.Application.Repository
|
||||||
|
{
|
||||||
|
public class Context : DBContextBase, ITransientService, IMultiDatabase
|
||||||
|
{
|
||||||
|
public virtual DbSet<Models.MyModule> MyModule { get; set; }
|
||||||
|
|
||||||
|
public Context(IDBContextDependencies DBContextDependencies) : base(DBContextDependencies)
|
||||||
|
{
|
||||||
|
// ContextBase handles multi-tenant database connections
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnModelCreating(ModelBuilder builder)
|
||||||
|
{
|
||||||
|
base.OnModelCreating(builder);
|
||||||
|
|
||||||
|
builder.Entity<Models.MyModule>().ToTable(ActiveDatabase.RewriteName("MyModule"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
75
Oqtane.Application/Server/Repository/MyModuleRepository.cs
Normal file
75
Oqtane.Application/Server/Repository/MyModuleRepository.cs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Oqtane.Modules;
|
||||||
|
|
||||||
|
namespace Oqtane.Application.Repository
|
||||||
|
{
|
||||||
|
public interface IMyModuleRepository
|
||||||
|
{
|
||||||
|
IEnumerable<Models.MyModule> GetMyModules(int ModuleId);
|
||||||
|
Models.MyModule GetMyModule(int MyModuleId);
|
||||||
|
Models.MyModule GetMyModule(int MyModuleId, bool tracking);
|
||||||
|
Models.MyModule AddMyModule(Models.MyModule MyModule);
|
||||||
|
Models.MyModule UpdateMyModule(Models.MyModule MyModule);
|
||||||
|
void DeleteMyModule(int MyModuleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MyModuleRepository : IMyModuleRepository, ITransientService
|
||||||
|
{
|
||||||
|
private readonly IDbContextFactory<Context> _factory;
|
||||||
|
|
||||||
|
public MyModuleRepository(IDbContextFactory<Context> factory)
|
||||||
|
{
|
||||||
|
_factory = factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Models.MyModule> GetMyModules(int ModuleId)
|
||||||
|
{
|
||||||
|
using var db = _factory.CreateDbContext();
|
||||||
|
return db.MyModule.Where(item => item.ModuleId == ModuleId).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Models.MyModule GetMyModule(int MyModuleId)
|
||||||
|
{
|
||||||
|
return GetMyModule(MyModuleId, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Models.MyModule GetMyModule(int MyModuleId, bool tracking)
|
||||||
|
{
|
||||||
|
using var db = _factory.CreateDbContext();
|
||||||
|
if (tracking)
|
||||||
|
{
|
||||||
|
return db.MyModule.Find(MyModuleId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return db.MyModule.AsNoTracking().FirstOrDefault(item => item.MyModuleId == MyModuleId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Models.MyModule AddMyModule(Models.MyModule MyModule)
|
||||||
|
{
|
||||||
|
using var db = _factory.CreateDbContext();
|
||||||
|
db.MyModule.Add(MyModule);
|
||||||
|
db.SaveChanges();
|
||||||
|
return MyModule;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Models.MyModule UpdateMyModule(Models.MyModule MyModule)
|
||||||
|
{
|
||||||
|
using var db = _factory.CreateDbContext();
|
||||||
|
db.Entry(MyModule).State = EntityState.Modified;
|
||||||
|
db.SaveChanges();
|
||||||
|
return MyModule;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteMyModule(int MyModuleId)
|
||||||
|
{
|
||||||
|
using var db = _factory.CreateDbContext();
|
||||||
|
Models.MyModule MyModule = db.MyModule.Find(MyModuleId);
|
||||||
|
db.MyModule.Remove(MyModule);
|
||||||
|
db.SaveChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
101
Oqtane.Application/Server/Services/MyModuleService.cs
Normal file
101
Oqtane.Application/Server/Services/MyModuleService.cs
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Oqtane.Enums;
|
||||||
|
using Oqtane.Infrastructure;
|
||||||
|
using Oqtane.Models;
|
||||||
|
using Oqtane.Security;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
using Oqtane.Application.Repository;
|
||||||
|
|
||||||
|
namespace Oqtane.Application.Services
|
||||||
|
{
|
||||||
|
public class ServerMyModuleService : IMyModuleService
|
||||||
|
{
|
||||||
|
private readonly IMyModuleRepository _MyModuleRepository;
|
||||||
|
private readonly IUserPermissions _userPermissions;
|
||||||
|
private readonly ILogManager _logger;
|
||||||
|
private readonly IHttpContextAccessor _accessor;
|
||||||
|
private readonly Alias _alias;
|
||||||
|
|
||||||
|
public ServerMyModuleService(IMyModuleRepository MyModuleRepository, IUserPermissions userPermissions, ITenantManager tenantManager, ILogManager logger, IHttpContextAccessor accessor)
|
||||||
|
{
|
||||||
|
_MyModuleRepository = MyModuleRepository;
|
||||||
|
_userPermissions = userPermissions;
|
||||||
|
_logger = logger;
|
||||||
|
_accessor = accessor;
|
||||||
|
_alias = tenantManager.GetAlias();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<List<Models.MyModule>> GetMyModulesAsync(int ModuleId)
|
||||||
|
{
|
||||||
|
if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, ModuleId, PermissionNames.View))
|
||||||
|
{
|
||||||
|
return Task.FromResult(_MyModuleRepository.GetMyModules(ModuleId).ToList());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized MyModule Get Attempt {ModuleId}", ModuleId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Models.MyModule> GetMyModuleAsync(int MyModuleId, int ModuleId)
|
||||||
|
{
|
||||||
|
if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, ModuleId, PermissionNames.View))
|
||||||
|
{
|
||||||
|
return Task.FromResult(_MyModuleRepository.GetMyModule(MyModuleId));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized MyModule Get Attempt {TaskId} {ModuleId}", MyModuleId, ModuleId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Models.MyModule> AddMyModuleAsync(Models.MyModule MyModule)
|
||||||
|
{
|
||||||
|
if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, MyModule.ModuleId, PermissionNames.Edit))
|
||||||
|
{
|
||||||
|
MyModule = _MyModuleRepository.AddMyModule(MyModule);
|
||||||
|
_logger.Log(LogLevel.Information, this, LogFunction.Create, "MyModule Added {MyModule}", MyModule);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized MyModule Add Attempt {MyModule}", MyModule);
|
||||||
|
MyModule = null;
|
||||||
|
}
|
||||||
|
return Task.FromResult(MyModule);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Models.MyModule> UpdateMyModuleAsync(Models.MyModule MyModule)
|
||||||
|
{
|
||||||
|
if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, MyModule.ModuleId, PermissionNames.Edit))
|
||||||
|
{
|
||||||
|
MyModule = _MyModuleRepository.UpdateMyModule(MyModule);
|
||||||
|
_logger.Log(LogLevel.Information, this, LogFunction.Update, "MyModule Updated {MyModule}", MyModule);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized MyModule Update Attempt {MyModule}", MyModule);
|
||||||
|
MyModule = null;
|
||||||
|
}
|
||||||
|
return Task.FromResult(MyModule);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task DeleteMyModuleAsync(int MyModuleId, int ModuleId)
|
||||||
|
{
|
||||||
|
if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, ModuleId, PermissionNames.Edit))
|
||||||
|
{
|
||||||
|
_MyModuleRepository.DeleteMyModule(MyModuleId);
|
||||||
|
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "MyModule Deleted {MyModuleId}", MyModuleId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized MyModule Delete Attempt {MyModuleId} {ModuleId}", MyModuleId, ModuleId);
|
||||||
|
}
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
45
Oqtane.Application/Server/Startup.cs
Normal file
45
Oqtane.Application/Server/Startup.cs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Oqtane.Extensions;
|
||||||
|
using Oqtane.Infrastructure;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
using Microsoft.AspNetCore.Cors.Infrastructure;
|
||||||
|
|
||||||
|
namespace Oqtane.Application.Server
|
||||||
|
{
|
||||||
|
public class Startup
|
||||||
|
{
|
||||||
|
private readonly IConfigurationRoot _configuration;
|
||||||
|
private readonly IWebHostEnvironment _environment;
|
||||||
|
|
||||||
|
public Startup(IWebHostEnvironment environment)
|
||||||
|
{
|
||||||
|
AppDomain.CurrentDomain.SetData(Constants.DataDirectory, Path.Combine(environment.ContentRootPath, "Data"));
|
||||||
|
|
||||||
|
var builder = new ConfigurationBuilder()
|
||||||
|
.SetBasePath(environment.ContentRootPath)
|
||||||
|
.AddJsonFile("appsettings.json", false, true)
|
||||||
|
.AddJsonFile($"appsettings.{environment.EnvironmentName}.json", true, true)
|
||||||
|
.AddEnvironmentVariables();
|
||||||
|
|
||||||
|
_configuration = builder.Build();
|
||||||
|
_environment = environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
// defer server startup to Oqtane - do not modify
|
||||||
|
services.AddOqtane(_configuration, _environment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Configure(IApplicationBuilder app, IConfigurationRoot configuration, IWebHostEnvironment environment, ICorsService corsService, ICorsPolicyProvider corsPolicyProvider, ISyncManager sync)
|
||||||
|
{
|
||||||
|
// defer server startup to Oqtane - do not modify
|
||||||
|
app.UseOqtane(configuration, environment, corsService, corsPolicyProvider, sync);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Oqtane.Application/Server/Startup/ServerStartup.cs
Normal file
28
Oqtane.Application/Server/Startup/ServerStartup.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Oqtane.Infrastructure;
|
||||||
|
using Oqtane.Application.Repository;
|
||||||
|
using Oqtane.Application.Services;
|
||||||
|
|
||||||
|
namespace Oqtane.Application.Startup
|
||||||
|
{
|
||||||
|
public class ServerStartup : IServerStartup
|
||||||
|
{
|
||||||
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||||
|
{
|
||||||
|
// not implemented
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ConfigureMvc(IMvcBuilder mvcBuilder)
|
||||||
|
{
|
||||||
|
// not implemented
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddTransient<IMyModuleService, ServerMyModuleService>();
|
||||||
|
services.AddDbContextFactory<Context>(opt => { }, ServiceLifetime.Transient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
63
Oqtane.Application/Server/appsettings.json
Normal file
63
Oqtane.Application/Server/appsettings.json
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
{
|
||||||
|
"RenderMode": "Static",
|
||||||
|
"Runtime": "Server",
|
||||||
|
"Database": {
|
||||||
|
"DefaultDBType": ""
|
||||||
|
},
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"DefaultConnection": ""
|
||||||
|
},
|
||||||
|
"Installation": {
|
||||||
|
"DefaultAlias": "",
|
||||||
|
"HostPassword": "",
|
||||||
|
"HostEmail": "",
|
||||||
|
"SiteTemplate": "",
|
||||||
|
"DefaultTheme": "",
|
||||||
|
"DefaultContainer": ""
|
||||||
|
},
|
||||||
|
"Localization": {
|
||||||
|
"DefaultCulture": "en"
|
||||||
|
},
|
||||||
|
"AvailableDatabases": [
|
||||||
|
{
|
||||||
|
"Name": "LocalDB",
|
||||||
|
"ControlType": "Oqtane.Installer.Controls.LocalDBConfig, Oqtane.Client",
|
||||||
|
"DBType": "Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "SQL Server",
|
||||||
|
"ControlType": "Oqtane.Installer.Controls.SqlServerConfig, Oqtane.Client",
|
||||||
|
"DBType": "Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "SQLite",
|
||||||
|
"ControlType": "Oqtane.Installer.Controls.SqliteConfig, Oqtane.Client",
|
||||||
|
"DBType": "Oqtane.Database.Sqlite.SqliteDatabase, Oqtane.Server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "MySQL",
|
||||||
|
"ControlType": "Oqtane.Installer.Controls.MySQLConfig, Oqtane.Client",
|
||||||
|
"DBType": "Oqtane.Database.MySQL.MySQLDatabase, Oqtane.Server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "PostgreSQL",
|
||||||
|
"ControlType": "Oqtane.Installer.Controls.PostgreSQLConfig, Oqtane.Client",
|
||||||
|
"DBType": "Oqtane.Database.PostgreSQL.PostgreSQLDatabase, Oqtane.Server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Azure SQL",
|
||||||
|
"ControlType": "Oqtane.Installer.Controls.AzureSqlConfig, Oqtane.Client",
|
||||||
|
"DBType": "Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Server"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Logging": {
|
||||||
|
"FileLogger": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
/* Module Script */
|
||||||
|
var App = App || {};
|
||||||
|
|
||||||
|
App.MyModule = {
|
||||||
|
};
|
||||||
19
Oqtane.Application/Shared/Models/MyModule.cs
Normal file
19
Oqtane.Application/Shared/Models/MyModule.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Oqtane.Models;
|
||||||
|
|
||||||
|
namespace Oqtane.Application.Models
|
||||||
|
{
|
||||||
|
public class MyModule : IAuditable
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public int MyModuleId { get; set; }
|
||||||
|
public int ModuleId { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public string CreatedBy { get; set; }
|
||||||
|
public DateTime CreatedOn { get; set; }
|
||||||
|
public string ModifiedBy { get; set; }
|
||||||
|
public DateTime ModifiedOn { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
17
Oqtane.Application/Shared/Oqtane.Application.Shared.csproj
Normal file
17
Oqtane.Application/Shared/Oqtane.Application.Shared.csproj
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<Version>1.0.0</Version>
|
||||||
|
<AssemblyName>Oqtane.Application.Shared.Oqtane</AssemblyName>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Oqtane.Shared" Version="6.2.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
BIN
Oqtane.Application/icon.png
Normal file
BIN
Oqtane.Application/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
@ -1,8 +1,10 @@
|
|||||||
using Microsoft.AspNetCore.Components.Authorization;
|
using Microsoft.AspNetCore.Components.Authorization;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
using Oqtane.Interfaces;
|
using Oqtane.Interfaces;
|
||||||
using Oqtane.Providers;
|
using Oqtane.Providers;
|
||||||
using Oqtane.Services;
|
using Oqtane.Services;
|
||||||
using Oqtane.Shared;
|
using Oqtane.Shared;
|
||||||
|
using Radzen;
|
||||||
|
|
||||||
namespace Microsoft.Extensions.DependencyInjection
|
namespace Microsoft.Extensions.DependencyInjection
|
||||||
{
|
{
|
||||||
@ -23,7 +25,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||||||
services.AddScoped<SiteState>();
|
services.AddScoped<SiteState>();
|
||||||
services.AddScoped<IInstallationService, InstallationService>();
|
services.AddScoped<IInstallationService, InstallationService>();
|
||||||
services.AddScoped<IModuleDefinitionService, ModuleDefinitionService>();
|
services.AddScoped<IModuleDefinitionService, ModuleDefinitionService>();
|
||||||
services.AddScoped<IThemeService, ThemeService>();
|
services.AddScoped<IThemeService, Oqtane.Services.ThemeService>();
|
||||||
services.AddScoped<IAliasService, AliasService>();
|
services.AddScoped<IAliasService, AliasService>();
|
||||||
services.AddScoped<ITenantService, TenantService>();
|
services.AddScoped<ITenantService, TenantService>();
|
||||||
services.AddScoped<ISiteService, SiteService>();
|
services.AddScoped<ISiteService, SiteService>();
|
||||||
@ -39,7 +41,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||||||
services.AddScoped<ILogService, LogService>();
|
services.AddScoped<ILogService, LogService>();
|
||||||
services.AddScoped<IJobService, JobService>();
|
services.AddScoped<IJobService, JobService>();
|
||||||
services.AddScoped<IJobLogService, JobLogService>();
|
services.AddScoped<IJobLogService, JobLogService>();
|
||||||
services.AddScoped<INotificationService, NotificationService>();
|
services.AddScoped<INotificationService, Oqtane.Services.NotificationService>();
|
||||||
services.AddScoped<IFolderService, FolderService>();
|
services.AddScoped<IFolderService, FolderService>();
|
||||||
services.AddScoped<IFileService, FileService>();
|
services.AddScoped<IFileService, FileService>();
|
||||||
services.AddScoped<ISiteTemplateService, SiteTemplateService>();
|
services.AddScoped<ISiteTemplateService, SiteTemplateService>();
|
||||||
@ -53,12 +55,19 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||||||
services.AddScoped<ISyncService, SyncService>();
|
services.AddScoped<ISyncService, SyncService>();
|
||||||
services.AddScoped<ILocalizationCookieService, LocalizationCookieService>();
|
services.AddScoped<ILocalizationCookieService, LocalizationCookieService>();
|
||||||
services.AddScoped<ICookieConsentService, CookieConsentService>();
|
services.AddScoped<ICookieConsentService, CookieConsentService>();
|
||||||
services.AddScoped<IOutputCacheService, OutputCacheService>();
|
|
||||||
services.AddScoped<ITimeZoneService, TimeZoneService>();
|
services.AddScoped<ITimeZoneService, TimeZoneService>();
|
||||||
|
services.AddScoped<IMigrationHistoryService, MigrationHistoryService>();
|
||||||
|
services.AddScoped<IOutputCacheService, OutputCacheService>();
|
||||||
|
|
||||||
// providers
|
// providers
|
||||||
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.QuillJSTextEditor>();
|
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.QuillJSTextEditor>();
|
||||||
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.TextAreaTextEditor>();
|
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.TextAreaTextEditor>();
|
||||||
|
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.RadzenTextEditor>();
|
||||||
|
|
||||||
|
services.AddRadzenComponents();
|
||||||
|
|
||||||
|
var localizer = services.BuildServiceProvider().GetService<IStringLocalizer<Oqtane.Modules.Controls.RadzenTextEditor>>();
|
||||||
|
Oqtane.Modules.Controls.RadzenEditorDefinitions.Localizer = localizer;
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="mx-auto text-center">
|
<div class="mx-auto text-center">
|
||||||
<img src="oqtane-black.png" />
|
<img src="installer-logo.png" />
|
||||||
<div style="font-weight: bold">@SharedLocalizer["Version"] @Constants.Version (.NET @Environment.Version.Major)</div>
|
<div style="font-weight: bold">@SharedLocalizer["Version"] @Constants.Version (.NET @Environment.Version.Major)</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -182,7 +182,7 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_databaseName = "LocalDB";
|
_databaseName = Constants.DefaultDBName;
|
||||||
}
|
}
|
||||||
LoadDatabaseConfigComponent();
|
LoadDatabaseConfigComponent();
|
||||||
|
|
||||||
@ -269,8 +269,8 @@
|
|||||||
SiteName = Constants.DefaultSite,
|
SiteName = Constants.DefaultSite,
|
||||||
Register = _register,
|
Register = _register,
|
||||||
SiteTemplate = _template,
|
SiteTemplate = _template,
|
||||||
RenderMode = RenderModes.Static,
|
RenderMode = "", // provided by appsettings.json
|
||||||
Runtime = Runtimes.Server
|
Runtime = "" // provided by appsettings.json
|
||||||
};
|
};
|
||||||
|
|
||||||
var installation = await InstallationService.Install(config);
|
var installation = await InstallationService.Install(config);
|
||||||
|
|||||||
@ -56,7 +56,7 @@
|
|||||||
<input id="starting" type="date" class="form-control" @bind="@_startDate" />
|
<input id="starting" type="date" class="form-control" @bind="@_startDate" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<input id="starting" type="time" class="form-control" placeholder="hh:mm" @bind="@_startTime" />
|
<input id="starting" type="time" class="form-control" @bind="@_startTime" placeholder="hh:mm" required="@(_startDate.HasValue)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -69,7 +69,7 @@
|
|||||||
<input id="ending" type="date" class="form-control" @bind="@_endDate" />
|
<input id="ending" type="date" class="form-control" @bind="@_endDate" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<input id="ending" type="time" class="form-control" placeholder="hh:mm" @bind="@_endTime" />
|
<input id="ending" type="time" class="form-control" placeholder="hh:mm" @bind="@_endTime" required="@(_endDate.HasValue)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -82,7 +82,7 @@
|
|||||||
<input id="next" type="date" class="form-control" @bind="@_nextDate" />
|
<input id="next" type="date" class="form-control" @bind="@_nextDate" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<input id="next" type="time" class="form-control" placeholder="hh:mm" @bind="@_nextTime" />
|
<input id="next" type="time" class="form-control" placeholder="hh:mm" @bind="@_nextTime" required="@(_nextDate.HasValue)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -176,10 +176,18 @@
|
|||||||
{
|
{
|
||||||
job.Interval = int.Parse(_interval);
|
job.Interval = int.Parse(_interval);
|
||||||
}
|
}
|
||||||
job.StartDate = LocalToUtc(_startDate.Value.Date.Add(_startTime.Value.TimeOfDay));
|
job.StartDate = _startDate.HasValue && _startTime.HasValue
|
||||||
job.EndDate = LocalToUtc(_endDate.Value.Date.Add(_endTime.Value.TimeOfDay));
|
? LocalToUtc(_startDate.GetValueOrDefault().Date.Add(_startTime.GetValueOrDefault().TimeOfDay))
|
||||||
|
: null;
|
||||||
|
|
||||||
|
job.EndDate = _endDate.HasValue && _endTime.HasValue
|
||||||
|
? LocalToUtc(_endDate.GetValueOrDefault().Date.Add(_endTime.GetValueOrDefault().TimeOfDay))
|
||||||
|
: null;
|
||||||
|
|
||||||
|
job.NextExecution = _nextDate.HasValue && _nextTime.HasValue
|
||||||
|
? LocalToUtc(_nextDate.GetValueOrDefault().Date.Add(_nextTime.GetValueOrDefault().TimeOfDay))
|
||||||
|
: null;
|
||||||
job.RetentionHistory = int.Parse(_retentionHistory);
|
job.RetentionHistory = int.Parse(_retentionHistory);
|
||||||
job.NextExecution = LocalToUtc(_nextDate.Value.Date.Add(_nextTime.Value.TimeOfDay));
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -198,5 +206,4 @@
|
|||||||
AddModuleMessage(Localizer["Message.Required.JobInfo"], MessageType.Warning);
|
AddModuleMessage(Localizer["Message.Required.JobInfo"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -115,6 +115,13 @@ else
|
|||||||
private async Task StartJob(int jobId)
|
private async Task StartJob(int jobId)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
Job _job = await JobService.GetJobAsync(jobId);
|
||||||
|
if (!_job.IsEnabled)
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.Job.Disabled"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
await JobService.StartJobAsync(jobId);
|
await JobService.StartJobAsync(jobId);
|
||||||
await logger.LogInformation("Job Started {JobId}", jobId);
|
await logger.LogInformation("Job Started {JobId}", jobId);
|
||||||
@ -122,6 +129,7 @@ else
|
|||||||
_jobs = await JobService.GetJobsAsync();
|
_jobs = await JobService.GetJobsAsync();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Starting Job {JobId} {Error}", jobId, ex.Message);
|
await logger.LogError(ex, "Error Starting Job {JobId} {Error}", jobId, ex.Message);
|
||||||
|
|||||||
@ -21,9 +21,7 @@ else
|
|||||||
@if (_allowexternallogin)
|
@if (_allowexternallogin)
|
||||||
{
|
{
|
||||||
<button type="button" class="btn btn-primary" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
|
<button type="button" class="btn btn-primary" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
|
||||||
<br />
|
<br /><br />
|
||||||
|
|
||||||
<br />
|
|
||||||
}
|
}
|
||||||
@if (_allowsitelogin)
|
@if (_allowsitelogin)
|
||||||
{
|
{
|
||||||
@ -49,15 +47,11 @@ else
|
|||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
|
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
<br />
|
<br /><br />
|
||||||
|
|
||||||
<br />
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["ForgotPassword"]</button>
|
<button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["ForgotPassword"]</button>
|
||||||
@if (PageState.Site.AllowRegistration)
|
@if (PageState.Site.AllowRegistration)
|
||||||
{
|
{
|
||||||
<br />
|
<br /><br />
|
||||||
|
|
||||||
<br />
|
|
||||||
<NavLink href="@NavigateUrl("register")">@Localizer["Register"]</NavLink>
|
<NavLink href="@NavigateUrl("register")">@Localizer["Register"]</NavLink>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,7 +94,7 @@ else
|
|||||||
|
|
||||||
public override List<Resource> Resources => new List<Resource>()
|
public override List<Resource> Resources => new List<Resource>()
|
||||||
{
|
{
|
||||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }
|
new Stylesheet(ModulePath() + "Module.css")
|
||||||
};
|
};
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
|
|||||||
@ -101,6 +101,12 @@
|
|||||||
<small>@SharedLocalizer["Search.By"]:</small> <strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><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 />
|
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
|
||||||
<br />
|
<br />
|
||||||
|
@if (_moduledefinitions.Exists(item => item.PackageName == context.PackageId))
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-info">@SharedLocalizer["Installed"]</button>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
@if (!string.IsNullOrEmpty(context.PackageUrl))
|
@if (!string.IsNullOrEmpty(context.PackageUrl))
|
||||||
{
|
{
|
||||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
|
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
|
||||||
@ -109,6 +115,7 @@
|
|||||||
{
|
{
|
||||||
<a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
|
<a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
<br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -171,6 +178,7 @@
|
|||||||
|
|
||||||
@code {
|
@code {
|
||||||
private bool _initialized = false;
|
private bool _initialized = false;
|
||||||
|
private List<ModuleDefinition> _moduledefinitions;
|
||||||
private int _page = 1;
|
private int _page = 1;
|
||||||
private List<Package> _packages;
|
private List<Package> _packages;
|
||||||
private string _price = "free";
|
private string _price = "free";
|
||||||
@ -187,7 +195,8 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await LoadModuleDefinitions();
|
_moduledefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
|
||||||
|
await LoadPackages();
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -197,24 +206,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadModuleDefinitions()
|
private async Task LoadPackages()
|
||||||
{
|
{
|
||||||
ShowProgressIndicator();
|
ShowProgressIndicator();
|
||||||
|
|
||||||
var moduledefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
|
|
||||||
_packages = await PackageService.GetPackagesAsync("module", _search, _price, "", _sort);
|
_packages = await PackageService.GetPackagesAsync("module", _search, _price, "", _sort);
|
||||||
|
|
||||||
if (_packages != null)
|
|
||||||
{
|
|
||||||
foreach (Package package in _packages.ToArray())
|
|
||||||
{
|
|
||||||
if (moduledefinitions.Exists(item => item.PackageName == package.PackageId))
|
|
||||||
{
|
|
||||||
_packages.Remove(package);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HideProgressIndicator();
|
HideProgressIndicator();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,25 +217,25 @@
|
|||||||
{
|
{
|
||||||
_price = price;
|
_price = price;
|
||||||
_sort = "popularity";
|
_sort = "popularity";
|
||||||
await LoadModuleDefinitions();
|
await LoadPackages();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Search()
|
private async Task Search()
|
||||||
{
|
{
|
||||||
await LoadModuleDefinitions();
|
await LoadPackages();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Reset()
|
private async Task Reset()
|
||||||
{
|
{
|
||||||
_page = 1;
|
_page = 1;
|
||||||
_search = "";
|
_search = "";
|
||||||
await LoadModuleDefinitions();
|
await LoadPackages();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Refresh()
|
private async Task Refresh()
|
||||||
{
|
{
|
||||||
await LoadModuleDefinitions();
|
await LoadPackages();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPageChange(int page)
|
private void OnPageChange(int page)
|
||||||
@ -251,7 +246,7 @@
|
|||||||
private async void SortChanged(ChangeEventArgs e)
|
private async void SortChanged(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
_sort = (string)e.Value;
|
_sort = (string)e.Value;
|
||||||
await LoadModuleDefinitions();
|
await LoadPackages();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HideModal()
|
private void HideModal()
|
||||||
@ -310,6 +305,6 @@
|
|||||||
|
|
||||||
private void OnUpload()
|
private void OnUpload()
|
||||||
{
|
{
|
||||||
AddModuleMessage(string.Format(Localizer["Success.Module.Download"], NavigateUrl("admin/system")), MessageType.Success);
|
AddModuleMessage(string.Format(Localizer["Success.Module.Upload"], NavigateUrl("admin/system")), MessageType.Success);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
@if (_initialized)
|
@if (_initialized)
|
||||||
{
|
{
|
||||||
<TabStrip>
|
<TabStrip>
|
||||||
<TabPanel Name="Definition" ResourceKey="Definition" Heading="Definition">
|
<TabPanel Name="Module" ResourceKey="Module" Heading="Module">
|
||||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@ -236,11 +236,10 @@
|
|||||||
private DateTime _createdon;
|
private DateTime _createdon;
|
||||||
private string _modifiedby;
|
private string _modifiedby;
|
||||||
private DateTime _modifiedon;
|
private DateTime _modifiedon;
|
||||||
private List<Page> _pagesWithModules;
|
|
||||||
|
|
||||||
#pragma warning disable 649
|
|
||||||
private PermissionGrid _permissionGrid;
|
private PermissionGrid _permissionGrid;
|
||||||
#pragma warning restore 649
|
|
||||||
|
private List<Page> _pagesWithModules;
|
||||||
|
|
||||||
private List<Package> _packages;
|
private List<Package> _packages;
|
||||||
private List<Language> _languages;
|
private List<Language> _languages;
|
||||||
|
|||||||
@ -17,8 +17,8 @@ else
|
|||||||
<div class="row mb-3 align-items-center">
|
<div class="row mb-3 align-items-center">
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<ActionLink Action="Add" Text="Install Module" ResourceKey="InstallModule" />
|
<ActionLink Action="Add" Text="Install Module" ResourceKey="InstallModule" />
|
||||||
<ActionLink Action="Create" Text="Create Module" ResourceKey="CreateModule" Class="btn btn-secondary ps-2" />
|
<ActionLink Action="Create" Text="Create Module" ResourceKey="CreateModule" Class="btn btn-secondary ms-1" />
|
||||||
<button type="button" class="btn btn-secondary pw-2" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
|
<button type="button" class="btn btn-secondary ms-1" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<select class="form-select" @onchange="(e => CategoryChanged(e))">
|
<select class="form-select" @onchange="(e => CategoryChanged(e))">
|
||||||
|
|||||||
@ -269,8 +269,16 @@
|
|||||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) || (_parent != null && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, _parent.PermissionList)))
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) || (_parent != null && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, _parent.PermissionList)))
|
||||||
{
|
{
|
||||||
_themetype = PageState.Site.DefaultThemeType;
|
_themetype = PageState.Site.DefaultThemeType;
|
||||||
_themes = ThemeService.GetThemeControls(PageState.Site.Themes);
|
var themes = new List<Theme>();
|
||||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
foreach (var theme in PageState.Site.Themes)
|
||||||
|
{
|
||||||
|
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Utilize, theme.PermissionList))
|
||||||
|
{
|
||||||
|
themes.Add(theme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_themes = ThemeService.GetThemeControls(themes);
|
||||||
|
_containers = ThemeService.GetContainerControls(themes, _themetype);
|
||||||
_containertype = PageState.Site.DefaultContainerType;
|
_containertype = PageState.Site.DefaultContainerType;
|
||||||
_children = new List<Page>();
|
_children = new List<Page>();
|
||||||
foreach (Page p in _pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
|
foreach (Page p in _pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
|
||||||
|
|||||||
@ -443,8 +443,16 @@
|
|||||||
{
|
{
|
||||||
_themetype = PageState.Site.DefaultThemeType;
|
_themetype = PageState.Site.DefaultThemeType;
|
||||||
}
|
}
|
||||||
_themes = ThemeService.GetThemeControls(PageState.Site.Themes);
|
var themes = new List<Theme>();
|
||||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
foreach (var theme in PageState.Site.Themes)
|
||||||
|
{
|
||||||
|
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Utilize, theme.PermissionList))
|
||||||
|
{
|
||||||
|
themes.Add(theme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_themes = ThemeService.GetThemeControls(themes);
|
||||||
|
_containers = ThemeService.GetContainerControls(themes, _themetype);
|
||||||
_containertype = _page.DefaultContainerType;
|
_containertype = _page.DefaultContainerType;
|
||||||
if (string.IsNullOrEmpty(_containertype))
|
if (string.IsNullOrEmpty(_containertype))
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IProfileService ProfileService
|
@inject IProfileService ProfileService
|
||||||
|
@inject ISettingService SettingService
|
||||||
@inject IStringLocalizer<Edit> Localizer
|
@inject IStringLocalizer<Edit> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@ -56,9 +57,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="options" HelpText="A comma delimited list of options the user can select from" ResourceKey="Options">Options: </Label>
|
<Label Class="col-sm-3" For="options" HelpText="A comma delimited list of options. Options can contain a key and value if they are seperated by a colon (ie. key:value). You can also dynamically load your options from Settings." ResourceKey="Options">Options: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
@if (_optiontype == "Settings")
|
||||||
|
{
|
||||||
<input id="options" class="form-control" @bind="@_options" maxlength="2000" />
|
<input id="options" class="form-control" @bind="@_options" maxlength="2000" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<select id="entityName" class="form-select" @bind="@_options">
|
||||||
|
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||||
|
@foreach (var entityname in _entitynames)
|
||||||
|
{
|
||||||
|
<option value="@entityname">@entityname</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
}
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="ToggleOptionType">@Localizer[_optiontype]</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@ -116,6 +133,8 @@
|
|||||||
private string _rows = "1";
|
private string _rows = "1";
|
||||||
private string _defaultvalue = string.Empty;
|
private string _defaultvalue = string.Empty;
|
||||||
private string _options = string.Empty;
|
private string _options = string.Empty;
|
||||||
|
private string _optiontype = "Settings";
|
||||||
|
private List<string> _entitynames;
|
||||||
private string _validation = string.Empty;
|
private string _validation = string.Empty;
|
||||||
private string _autocomplete = string.Empty;
|
private string _autocomplete = string.Empty;
|
||||||
private string _isrequired = "False";
|
private string _isrequired = "False";
|
||||||
@ -133,6 +152,8 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
_entitynames = await SettingService.GetEntityNamesAsync();
|
||||||
|
|
||||||
if (PageState.QueryString.ContainsKey("id"))
|
if (PageState.QueryString.ContainsKey("id"))
|
||||||
{
|
{
|
||||||
_profileid = Int32.Parse(PageState.QueryString["id"]);
|
_profileid = Int32.Parse(PageState.QueryString["id"]);
|
||||||
@ -148,6 +169,11 @@
|
|||||||
_rows = profile.Rows.ToString();
|
_rows = profile.Rows.ToString();
|
||||||
_defaultvalue = profile.DefaultValue;
|
_defaultvalue = profile.DefaultValue;
|
||||||
_options = profile.Options;
|
_options = profile.Options;
|
||||||
|
if (_options.StartsWith("EntityName:"))
|
||||||
|
{
|
||||||
|
_optiontype = "Options";
|
||||||
|
_options = _options.Substring(11);
|
||||||
|
}
|
||||||
_validation = profile.Validation;
|
_validation = profile.Validation;
|
||||||
_autocomplete = profile.Autocomplete;
|
_autocomplete = profile.Autocomplete;
|
||||||
_isrequired = profile.IsRequired.ToString();
|
_isrequired = profile.IsRequired.ToString();
|
||||||
@ -166,6 +192,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ToggleOptionType()
|
||||||
|
{
|
||||||
|
if (_optiontype == "Options")
|
||||||
|
{
|
||||||
|
_optiontype = "Settings";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_optiontype = "Options";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task SaveProfile()
|
private async Task SaveProfile()
|
||||||
{
|
{
|
||||||
validated = true;
|
validated = true;
|
||||||
@ -193,7 +231,14 @@
|
|||||||
profile.MaxLength = int.Parse(_maxlength);
|
profile.MaxLength = int.Parse(_maxlength);
|
||||||
profile.Rows = int.Parse(_rows);
|
profile.Rows = int.Parse(_rows);
|
||||||
profile.DefaultValue = _defaultvalue;
|
profile.DefaultValue = _defaultvalue;
|
||||||
|
if (_optiontype == "Options" && !string.IsNullOrEmpty(_options))
|
||||||
|
{
|
||||||
|
profile.Options = "EntityName:" + _options;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
profile.Options = _options;
|
profile.Options = _options;
|
||||||
|
}
|
||||||
profile.Validation = _validation;
|
profile.Validation = _validation;
|
||||||
profile.Autocomplete = _autocomplete;
|
profile.Autocomplete = _autocomplete;
|
||||||
profile.IsRequired = (_isrequired == null ? false : Boolean.Parse(_isrequired));
|
profile.IsRequired = (_isrequired == null ? false : Boolean.Parse(_isrequired));
|
||||||
|
|||||||
@ -3,10 +3,10 @@
|
|||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IUserService UserService
|
@inject IUserService UserService
|
||||||
@inject ITimeZoneService TimeZoneService
|
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
@inject ISettingService SettingService
|
@inject ISettingService SettingService
|
||||||
|
@inject ITimeZoneService TimeZoneService
|
||||||
|
|
||||||
@if (_initialized)
|
@if (_initialized)
|
||||||
{
|
{
|
||||||
@ -115,7 +115,7 @@
|
|||||||
{
|
{
|
||||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||||
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
|
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
|
||||||
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
_timezones = TimeZoneService.GetTimeZones();
|
||||||
_timezoneid = PageState.Site.TimeZoneId;
|
_timezoneid = PageState.Site.TimeZoneId;
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -40,13 +40,16 @@
|
|||||||
{
|
{
|
||||||
<Pager Items="@_searchResults?.Results"
|
<Pager Items="@_searchResults?.Results"
|
||||||
Format="Grid"
|
Format="Grid"
|
||||||
|
PageSize="@_pageSize"
|
||||||
|
DisplayPages="@_displayPages"
|
||||||
|
CurrentPage="@_currentPage"
|
||||||
Columns="1"
|
Columns="1"
|
||||||
Toolbar="Bottom"
|
Toolbar="Bottom"
|
||||||
Parameters="@($"q={_keywords}")">
|
Parameters="@($"q={_keywords}")">
|
||||||
<Row>
|
<Row>
|
||||||
<div class="search-item mb-2">
|
<div class="search-item mb-2">
|
||||||
<h4 class="mb-1"><a href="@context.Url">@context.Title</a></h4>
|
<h4 class="mb-1"><a href="@context.Url">@context.Title</a></h4>
|
||||||
<p class="mb-0 text-muted">@((MarkupString)context.Snippet)</p>
|
<p class="mb-0 text-body-secondary">@((MarkupString)context.Snippet)</p>
|
||||||
</div>
|
</div>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
@ -66,6 +69,7 @@
|
|||||||
|
|
||||||
@code {
|
@code {
|
||||||
public override string RenderMode => RenderModes.Static;
|
public override string RenderMode => RenderModes.Static;
|
||||||
|
private const string SearchDefaultPageSize = "10";
|
||||||
|
|
||||||
private string _includeEntities;
|
private string _includeEntities;
|
||||||
private string _excludeEntities;
|
private string _excludeEntities;
|
||||||
@ -75,6 +79,8 @@
|
|||||||
private string _sortField;
|
private string _sortField;
|
||||||
private string _sortOrder;
|
private string _sortOrder;
|
||||||
private string _bodyLength;
|
private string _bodyLength;
|
||||||
|
private string _currentPage = "0";
|
||||||
|
private string _displayPages = "7";
|
||||||
|
|
||||||
private string _keywords;
|
private string _keywords;
|
||||||
private SearchResults _searchResults;
|
private SearchResults _searchResults;
|
||||||
@ -89,11 +95,16 @@
|
|||||||
_excludeEntities = SettingService.GetSetting(ModuleState.Settings, "SearchResults_ExcludeEntities", "");
|
_excludeEntities = SettingService.GetSetting(ModuleState.Settings, "SearchResults_ExcludeEntities", "");
|
||||||
_fromDate = SettingService.GetSetting(ModuleState.Settings, "SearchResults_FromDate", DateTime.MinValue.ToString());
|
_fromDate = SettingService.GetSetting(ModuleState.Settings, "SearchResults_FromDate", DateTime.MinValue.ToString());
|
||||||
_toDate = SettingService.GetSetting(ModuleState.Settings, "SearchResults_ToDate", DateTime.MaxValue.ToString());
|
_toDate = SettingService.GetSetting(ModuleState.Settings, "SearchResults_ToDate", DateTime.MaxValue.ToString());
|
||||||
_pageSize = SettingService.GetSetting(ModuleState.Settings, "SearchResults_PageSize", int.MaxValue.ToString());
|
_pageSize = SettingService.GetSetting(ModuleState.Settings, "SearchResults_PageSize", SearchDefaultPageSize);
|
||||||
_sortField = SettingService.GetSetting(ModuleState.Settings, "SearchResults_SortField", "Relevance");
|
_sortField = SettingService.GetSetting(ModuleState.Settings, "SearchResults_SortField", "Relevance");
|
||||||
_sortOrder = SettingService.GetSetting(ModuleState.Settings, "SearchResults_SortOrder", "Descending");
|
_sortOrder = SettingService.GetSetting(ModuleState.Settings, "SearchResults_SortOrder", "Descending");
|
||||||
_bodyLength = SettingService.GetSetting(ModuleState.Settings, "SearchResults_BodyLength", "255");
|
_bodyLength = SettingService.GetSetting(ModuleState.Settings, "SearchResults_BodyLength", "255");
|
||||||
|
|
||||||
|
if (PageState.QueryString.ContainsKey("p"))
|
||||||
|
{
|
||||||
|
_currentPage = PageState.QueryString["p"];
|
||||||
|
}
|
||||||
|
|
||||||
if (_keywords == null && PageState.QueryString.ContainsKey("q"))
|
if (_keywords == null && PageState.QueryString.ContainsKey("q"))
|
||||||
{
|
{
|
||||||
_keywords = WebUtility.UrlDecode(PageState.QueryString["q"]);
|
_keywords = WebUtility.UrlDecode(PageState.QueryString["q"]);
|
||||||
@ -122,7 +133,7 @@
|
|||||||
ExcludeEntities = _excludeEntities,
|
ExcludeEntities = _excludeEntities,
|
||||||
FromDate = (!string.IsNullOrEmpty(_fromDate)) ? DateTime.Parse(_fromDate) : DateTime.MinValue,
|
FromDate = (!string.IsNullOrEmpty(_fromDate)) ? DateTime.Parse(_fromDate) : DateTime.MinValue,
|
||||||
ToDate = (!string.IsNullOrEmpty(_toDate)) ? DateTime.Parse(_toDate) : DateTime.MaxValue,
|
ToDate = (!string.IsNullOrEmpty(_toDate)) ? DateTime.Parse(_toDate) : DateTime.MaxValue,
|
||||||
PageSize = (!string.IsNullOrEmpty(_pageSize)) ? int.Parse(_pageSize) : int.MaxValue,
|
PageSize = int.MaxValue,
|
||||||
PageIndex = 0,
|
PageIndex = 0,
|
||||||
SortField = (!string.IsNullOrEmpty(_sortField)) ? (SearchSortField)Enum.Parse(typeof(SearchSortField), _sortField) : SearchSortField.Relevance,
|
SortField = (!string.IsNullOrEmpty(_sortField)) ? (SearchSortField)Enum.Parse(typeof(SearchSortField), _sortField) : SearchSortField.Relevance,
|
||||||
SortOrder = (!string.IsNullOrEmpty(_sortOrder)) ? (SearchSortOrder)Enum.Parse(typeof(SearchSortOrder), _sortOrder) : SearchSortOrder.Descending,
|
SortOrder = (!string.IsNullOrEmpty(_sortOrder)) ? (SearchSortOrder)Enum.Parse(typeof(SearchSortOrder), _sortOrder) : SearchSortOrder.Descending,
|
||||||
|
|||||||
226
Oqtane.Client/Modules/Admin/Settings/Add.razor
Normal file
226
Oqtane.Client/Modules/Admin/Settings/Add.razor
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
@namespace Oqtane.Modules.Admin.Settings
|
||||||
|
@inherits ModuleBase
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject ISettingService SettingService
|
||||||
|
@inject IStringLocalizer<Edit> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
<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="entityName" HelpText="Entity Name" ResourceKey="EntityName">Entity:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
@if (_entityNameElement == "input")
|
||||||
|
{
|
||||||
|
<input id="entityName" class="form-control" @bind="@_entityName" maxlength="256" required />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<select class="form-select custom-select" value="@_entityName" @onchange="(e => EntityNameChanged(e))">
|
||||||
|
<option value="-"><@Localizer["Select Entity"]></option>
|
||||||
|
@foreach (var entityName in _entityNames)
|
||||||
|
{
|
||||||
|
<option value="@entityName">@entityName</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
}
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@EntityNameClicked" tabindex="-1">@_entityNameTitle</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="entityId" HelpText="Entity Id" ResourceKey="EntityId">Id:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
@if (_entityIdElement == "input")
|
||||||
|
{
|
||||||
|
<input id="entityId" class="form-control" @bind="@_entityId" maxlength="256" required />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<select class="form-select custom-select" @bind="@_entityId">
|
||||||
|
<option value="-"><@Localizer["Select Id"]></option>
|
||||||
|
@foreach (var entityId in _entityIds)
|
||||||
|
{
|
||||||
|
<option value="@entityId">@entityId</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
}
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@EntityIdClicked" tabindex="-1">@_entityIdTitle</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="settingName" HelpText="Setting Name" ResourceKey="SettingName">Name:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="settingName" class="form-control" @bind="@_settingName" maxlength="256" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="settingValue" HelpText="Setting Value" ResourceKey="SettingValue">Value:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="SettingValue" class="form-control" @bind="@_settingValue" maxlength="256" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="isPrivate" HelpText="Private" ResourceKey="IsPrivate">Private?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="isPrivate" class="form-select" @bind="@_isPrivate">
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br /><br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SaveSetting">@SharedLocalizer["Save"]</button>
|
||||||
|
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private ElementReference form;
|
||||||
|
private bool validated = false;
|
||||||
|
|
||||||
|
private string _entityName = "-";
|
||||||
|
private List<string> _entityNames = new List<string>();
|
||||||
|
private string _entityNameElement = "select";
|
||||||
|
private string _entityNameTitle = "";
|
||||||
|
private string _entityId = "-";
|
||||||
|
private List<int> _entityIds = new List<int>();
|
||||||
|
private string _entityIdElement = "select";
|
||||||
|
private string _entityIdTitle = "";
|
||||||
|
private string _settingName = "";
|
||||||
|
private string _settingValue = "";
|
||||||
|
private string _isPrivate = "True";
|
||||||
|
|
||||||
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_entityNameTitle = Localizer["Input"];
|
||||||
|
_entityIdTitle = Localizer["Input"];
|
||||||
|
|
||||||
|
// default entity names
|
||||||
|
_entityNames.Add(EntityNames.Host);
|
||||||
|
_entityNames.Add(EntityNames.Job);
|
||||||
|
_entityNames.Add(EntityNames.ModuleDefinition);
|
||||||
|
_entityNames.Add(EntityNames.Theme);
|
||||||
|
_entityNames.Add(EntityNames.Tenant);
|
||||||
|
_entityNames.Add(EntityNames.Site);
|
||||||
|
_entityNames.Add(EntityNames.Role);
|
||||||
|
_entityNames.Add(EntityNames.Page);
|
||||||
|
_entityNames.Add(EntityNames.Module);
|
||||||
|
_entityNames.Add(EntityNames.Folder);
|
||||||
|
_entityNames.Add(EntityNames.User);
|
||||||
|
_entityNames.Add(EntityNames.Visitor);
|
||||||
|
|
||||||
|
// custom entity names
|
||||||
|
var entityNames = await SettingService.GetEntityNamesAsync();
|
||||||
|
foreach (var entityName in entityNames)
|
||||||
|
{
|
||||||
|
if (!_entityNames.Contains(entityName))
|
||||||
|
{
|
||||||
|
_entityNames.Add(entityName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Loading Setting {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.LoadSetting"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EntityNameClicked()
|
||||||
|
{
|
||||||
|
if (_entityNameElement == "select")
|
||||||
|
{
|
||||||
|
_entityName = "";
|
||||||
|
_entityNameElement = "input";
|
||||||
|
_entityNameTitle = Localizer["Select"];
|
||||||
|
_entityId = "";
|
||||||
|
_entityIdElement = "input";
|
||||||
|
_entityIdTitle = Localizer["Select"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_entityName = "-";
|
||||||
|
_entityNameElement = "select";
|
||||||
|
_entityNameTitle = Localizer["Input"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EntityIdClicked()
|
||||||
|
{
|
||||||
|
if (_entityIdElement == "select")
|
||||||
|
{
|
||||||
|
_entityId = "";
|
||||||
|
_entityIdElement = "input";
|
||||||
|
_entityIdTitle = Localizer["Select"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_entityId = "-";
|
||||||
|
_entityIdElement = "select";
|
||||||
|
_entityIdTitle = Localizer["Input"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void EntityNameChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_entityName = e.Value.ToString();
|
||||||
|
_entityId = "-";
|
||||||
|
_entityIdElement = "select";
|
||||||
|
_entityIdTitle = Localizer["Input"];
|
||||||
|
if (_entityName != "-")
|
||||||
|
{
|
||||||
|
_entityIds = await SettingService.GetEntityIdsAsync(_entityName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_entityIds = new List<int>();
|
||||||
|
}
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error On EntityNameChanged");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveSetting()
|
||||||
|
{
|
||||||
|
validated = true;
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
if (await interop.FormValid(form) && _entityName != "-" && int.TryParse(_entityId, out int entityId))
|
||||||
|
{
|
||||||
|
var setting = new Setting();
|
||||||
|
setting.EntityName = _entityName;
|
||||||
|
setting.EntityId = entityId;
|
||||||
|
setting.SettingName = _settingName;
|
||||||
|
setting.SettingValue = _settingValue;
|
||||||
|
setting.IsPrivate = (bool.Parse(_isPrivate));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
setting = await SettingService.AddSettingAsync(setting);
|
||||||
|
await logger.LogInformation("Setting Saved {Setting}", setting);
|
||||||
|
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Saving Setting {Setting} {Error}", setting, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.SaveSetting"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
122
Oqtane.Client/Modules/Admin/Settings/Edit.razor
Normal file
122
Oqtane.Client/Modules/Admin/Settings/Edit.razor
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
@namespace Oqtane.Modules.Admin.Settings
|
||||||
|
@inherits ModuleBase
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject ISettingService SettingService
|
||||||
|
@inject IStringLocalizer<Edit> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
<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="entityName" HelpText="Entity Name" ResourceKey="EntityName">Entity:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="entityName" class="form-control" @bind="@_entityName" maxlength="256" disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="entityId" HelpText="Entity Id" ResourceKey="EntityId">Id:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="entityId" class="form-control" @bind="@_entityId" maxlength="256" disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="settingName" HelpText="Setting Name" ResourceKey="SettingName">Name:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="settingName" class="form-control" @bind="@_settingName" maxlength="256" disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="settingValue" HelpText="Setting Value" ResourceKey="SettingValue">Value:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="SettingValue" class="form-control" @bind="@_settingValue" maxlength="256" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="isPrivate" HelpText="Private" ResourceKey="IsPrivate">Private?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="isPrivate" class="form-select" @bind="@_isPrivate">
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br /><br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SaveSetting">@SharedLocalizer["Save"]</button>
|
||||||
|
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
<br /><br />
|
||||||
|
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private ElementReference form;
|
||||||
|
private bool validated = false;
|
||||||
|
|
||||||
|
private int _settingId;
|
||||||
|
private string _entityName;
|
||||||
|
private string _entityId;
|
||||||
|
private string _settingName;
|
||||||
|
private string _settingValue;
|
||||||
|
private string _isPrivate;
|
||||||
|
private string _createdby;
|
||||||
|
private DateTime _createdon;
|
||||||
|
private string _modifiedby;
|
||||||
|
private DateTime _modifiedon;
|
||||||
|
|
||||||
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
_settingId = int.Parse(PageState.QueryString["id"]);
|
||||||
|
_entityName = PageState.QueryString["entity"];
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var setting = await SettingService.GetSettingAsync(_entityName, _settingId);
|
||||||
|
if (setting != null)
|
||||||
|
{
|
||||||
|
_entityId = setting.EntityId.ToString();
|
||||||
|
_settingName = setting.SettingName;
|
||||||
|
_settingValue = setting.SettingValue;
|
||||||
|
_isPrivate = setting.IsPrivate.ToString();
|
||||||
|
_createdby = setting.CreatedBy;
|
||||||
|
_createdon = setting.CreatedOn;
|
||||||
|
_modifiedby = setting.ModifiedBy;
|
||||||
|
_modifiedon = setting.ModifiedOn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Loading Setting {SettingId} {Error}", _settingId, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.LoadSetting"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveSetting()
|
||||||
|
{
|
||||||
|
validated = true;
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
if (await interop.FormValid(form))
|
||||||
|
{
|
||||||
|
var setting = await SettingService.GetSettingAsync(_entityName, _settingId);
|
||||||
|
setting.SettingValue = _settingValue;
|
||||||
|
setting.IsPrivate = (_isPrivate != null && Boolean.Parse(_isPrivate));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
setting = await SettingService.UpdateSettingAsync(setting);
|
||||||
|
await logger.LogInformation("Setting Saved {Setting}", setting);
|
||||||
|
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Saving Setting {Setting} {Error}", setting, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.SaveSetting"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
56
Oqtane.Client/Modules/Admin/Settings/ImportSettings.razor
Normal file
56
Oqtane.Client/Modules/Admin/Settings/ImportSettings.razor
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
@namespace Oqtane.Modules.Admin.Settings
|
||||||
|
@inherits ModuleBase
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject ISettingService SettingService
|
||||||
|
@inject IStringLocalizer<ImportSettings> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="settings" HelpText="Provide settings in comma delimited format using the column template specified" ResourceKey="Settings">Settings:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea id="settings" class="form-control" @bind="@_settings" rows="5" required></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="Import">@Localizer["Import"]</button>
|
||||||
|
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private string _settings = "Entity,Id,Name,Value,Private\n";
|
||||||
|
|
||||||
|
public override string Title => "Import Settings";
|
||||||
|
|
||||||
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
|
private async Task Import()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(_settings))
|
||||||
|
{
|
||||||
|
ShowProgressIndicator();
|
||||||
|
var result = await SettingService.ImportSettingsAsync(new Result { Message = _settings });
|
||||||
|
if (result.Success)
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.Import.Success"], 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 Settings {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.Import"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
146
Oqtane.Client/Modules/Admin/Settings/Index.razor
Normal file
146
Oqtane.Client/Modules/Admin/Settings/Index.razor
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
@namespace Oqtane.Modules.Admin.Settings
|
||||||
|
@inherits ModuleBase
|
||||||
|
@inject ISettingService SettingService
|
||||||
|
@inject IStringLocalizer<Index> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<ActionLink Action="Add" Text="Add Setting" Security="SecurityAccessLevel.Host" ResourceKey="AddSetting" ReturnUrl="@(NavigateUrl(PageState.Page.Path, AddUrlParameters(_entityName, _entityId)))" />
|
||||||
|
<ActionLink Action="ImportSettings" Text="Import" Class="btn btn-secondary ms-1" Security="SecurityAccessLevel.Host" ResourceKey="ImportSettings" ReturnUrl="@(NavigateUrl(PageState.Page.Path, AddUrlParameters(_entityName, _entityId)))" />
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<select class="form-select custom-select" value="@_entityName" @onchange="(e => EntityNameChanged(e))">
|
||||||
|
<option value="-"><@Localizer["Select Entity"]></option>
|
||||||
|
@foreach (var entityName in _entityNames)
|
||||||
|
{
|
||||||
|
<option value="@entityName">@entityName</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<select class="form-select custom-select" value="@_entityId" @onchange="(e => EntityIdChanged(e))">
|
||||||
|
<option value="-"><@Localizer["Select Id"]></option>
|
||||||
|
@foreach (var entityId in _entityIds)
|
||||||
|
{
|
||||||
|
<option value="@entityId">@entityId</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<Pager Items="@_settings" SearchProperties="SettingName,SettingValue">
|
||||||
|
<Header>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
|
<th>@Localizer["Name"]</th>
|
||||||
|
<th>@Localizer["Value"]</th>
|
||||||
|
</Header>
|
||||||
|
<Row>
|
||||||
|
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"entity={context.EntityName}&id={context.SettingId}")" Security="SecurityAccessLevel.Host" ResourceKey="EditSetting" ReturnUrl="@(NavigateUrl(PageState.Page.Path, AddUrlParameters(_entityName, _entityId)))" /></td>
|
||||||
|
<td><ActionDialog Header="Delete Setting" Message="@string.Format(Localizer["Confirm.DeleteSetting"], context.SettingName)" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteSetting(context))" ResourceKey="DeleteSetting" /></td>
|
||||||
|
<td>@context.SettingName</td>
|
||||||
|
<td>@context.SettingValue</td>
|
||||||
|
</Row>
|
||||||
|
</Pager>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private string _entityName = "-";
|
||||||
|
private List<string> _entityNames = new List<string>();
|
||||||
|
private string _entityId = "-";
|
||||||
|
private List<int> _entityIds = new List<int>();
|
||||||
|
private List<Setting> _settings = new List<Setting>();
|
||||||
|
|
||||||
|
public override string UrlParametersTemplate => "/{entityname}/{entityid}";
|
||||||
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
|
protected override async Task OnParametersSetAsync()
|
||||||
|
{
|
||||||
|
_entityNames = await SettingService.GetEntityNamesAsync();
|
||||||
|
|
||||||
|
if (UrlParameters.ContainsKey("entityname"))
|
||||||
|
{
|
||||||
|
_entityName = UrlParameters["entityname"];
|
||||||
|
await GetEntityIds();
|
||||||
|
}
|
||||||
|
if (UrlParameters.ContainsKey("entityid"))
|
||||||
|
{
|
||||||
|
_entityId = UrlParameters["entityid"];
|
||||||
|
await GetSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GetEntityIds()
|
||||||
|
{
|
||||||
|
if (_entityName != "-")
|
||||||
|
{
|
||||||
|
_entityIds = await SettingService.GetEntityIdsAsync(_entityName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_entityIds = new List<int>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GetSettings()
|
||||||
|
{
|
||||||
|
if (_entityName != "-" && _entityId != "-")
|
||||||
|
{
|
||||||
|
_settings = await SettingService.GetSettingsAsync(_entityName, int.Parse(_entityId), "");
|
||||||
|
_settings = _settings.OrderBy(item => item.SettingName).ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_settings = new List<Setting>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void EntityNameChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_entityName = e.Value.ToString();
|
||||||
|
_entityId = "-";
|
||||||
|
await GetEntityIds();
|
||||||
|
await GetSettings();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error On EntityNameChanged");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void EntityIdChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_entityId = e.Value.ToString();
|
||||||
|
await GetSettings();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error On EntityIdChanged");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DeleteSetting(Setting setting)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await SettingService.DeleteSettingAsync(setting.EntityName, setting.EntityId, setting.SettingName);
|
||||||
|
await logger.LogInformation("Setting Deleted {Setting}", setting);
|
||||||
|
await GetSettings();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Deleting Setting {Setting} {Error}", setting, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.DeleteSetting"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -54,6 +54,8 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
|
{
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="isDeleted" HelpText="Is this site deleted?" ResourceKey="IsDeleted">Deleted? </Label>
|
<Label Class="col-sm-3" For="isDeleted" HelpText="Is this site deleted?" ResourceKey="IsDeleted">Deleted? </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@ -63,6 +65,7 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="sitemap" HelpText="The site map url for this site which can be submitted to search engines for indexing. The sitemap is cached for 5 minutes and the cache can be manually cleared." ResourceKey="SiteMap">Site Map: </Label>
|
<Label Class="col-sm-3" For="sitemap" HelpText="The site map url for this site which can be submitted to search engines for indexing. The sitemap is cached for 5 minutes and the cache can be manually cleared." ResourceKey="SiteMap">Site Map: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@ -193,6 +196,17 @@
|
|||||||
</Section>
|
</Section>
|
||||||
<Section Name="SMTP" Heading="SMTP Settings" ResourceKey="SMTPSettings">
|
<Section Name="SMTP" Heading="SMTP Settings" ResourceKey="SMTPSettings">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<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" value="@_smtpenabled" @onchange="(e => SMTPEnabledChanged(e))">
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (_smtpenabled == "True" && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
|
{
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<div class="col-sm-3">
|
<div class="col-sm-3">
|
||||||
</div>
|
</div>
|
||||||
@ -213,37 +227,45 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="smtpssl" HelpText="Specify if SSL is required for your SMTP server" ResourceKey="UseSsl">SSL Enabled: </Label>
|
<Label Class="col-sm-3" For="smtpssl" HelpText="Specify the type of SSL connection for your SMTP server" ResourceKey="SmtpSSL">SSL Options: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="smtpssl" class="form-select" @bind="@_smtpssl" >
|
<select id="smtpssl" class="form-select" @bind="@_smtpssl">
|
||||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
<option value="None">@Localizer["None"]</option>
|
||||||
<option value="False">@SharedLocalizer["No"]</option>
|
<option value="Auto">@Localizer["Auto"]</option>
|
||||||
|
<option value="StartTls">@Localizer["StartTls"]</option>
|
||||||
|
<option value="SslOnConnect">@Localizer["SslOnConnect"]</option>
|
||||||
|
<option value="StartTlsWhenAvailable">@Localizer["StartTlsWhenAvailable"]</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="smtpauthentication" HelpText="Specify the SMTP authentication type" ResourceKey="SMTPAuthentication">Authentication: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="smtpauthentication" class="form-select" value="@_smtpauthentication" @onchange="(e => SMTPAuthenticationChanged(e))">
|
||||||
|
<option value="Basic">@Localizer["Basic"]</option>
|
||||||
|
<option value="OAuth2">@Localizer["OAuth2"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (_smtpauthentication == "Basic")
|
||||||
|
{
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="username" HelpText="Enter the username for your SMTP account" ResourceKey="SmtpUsername">Username: </Label>
|
<Label Class="col-sm-3" For="username" HelpText="Enter the username for your SMTP account" ResourceKey="SmtpUsername">Username: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="username" class="form-control" @bind="@_smtpusername" autocomplete="off"/>
|
<input id="username" class="form-control" @bind="@_smtpusername" autocomplete="off" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="password" HelpText="Enter the password for your SMTP account" ResourceKey="SmtpPassword">Password: </Label>
|
<Label Class="col-sm-3" For="password" HelpText="Enter the password for your SMTP account" ResourceKey="SmtpPassword">Password: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="password" type="@_smtppasswordtype" class="form-control" @bind="@_smtppassword" autocomplete="off"/>
|
<input id="password" type="@_smtppasswordtype" class="form-control" @bind="@_smtppassword" autocomplete="off" />
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@ToggleSMTPPassword" tabindex="-1">@_togglesmtppassword</button>
|
<button type="button" class="btn btn-secondary" @onclick="@ToggleSMTPPassword" tabindex="-1">@_togglesmtppassword</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="sender" HelpText="Enter the email which emails will be sent from. Please note that this email address may need to be authorized with the SMTP server." ResourceKey="SmtpSender">Email Sender: </Label>
|
<Label Class="col-sm-3" For="relay" HelpText="Only specify this option if you have properly configured an SMTP Relay Service to route your outgoing mail. This option will send notifications from the user's email rather than from the Email Sender specified below." ResourceKey="SmtpRelay">Relay Configured? </Label>
|
||||||
<div class="col-sm-9">
|
|
||||||
<input id="sender" class="form-control" @bind="@_smtpsender" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="relay" HelpText="Only specify this option if you have properly configured an SMTP Relay Service to route your outgoing mail. This option will send notifications from the user's email rather than from the Email Sender specified above." ResourceKey="SmtpRelay">Relay Configured? </Label>
|
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="relay" class="form-select" @bind="@_smtprelay" required>
|
<select id="relay" class="form-select" @bind="@_smtprelay" required>
|
||||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
@ -251,13 +273,41 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
<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>
|
<Label Class="col-sm-3" For="smtpauthority" HelpText="The Authority Url for the SMTP provider" ResourceKey="SmtpAuthority">Authority Url:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="smtpenabled" class="form-select" @bind="@_smtpenabled">
|
<input id="smtpauthority" class="form-control" @bind="@_smtpauthority" />
|
||||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
</div>
|
||||||
<option value="False">@SharedLocalizer["No"]</option>
|
</div>
|
||||||
</select>
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="smtpclientid" HelpText="The Client ID for the SMTP provider" ResourceKey="SmtpClientID">Client ID:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="smtpclientid" class="form-control" @bind="@_smtpclientid" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="smtpclientsecret" HelpText="The Client Secret for the SMTP provider" ResourceKey="SmtpClientSecret">Client Secret:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="@_smtpclientsecrettype" id="smtpclientsecret" class="form-control" @bind="@_smtpclientsecret" />
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@ToggleSmtpClientSecret">@_togglesmtpclientsecret</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="smtpscopes" HelpText="A list of Scopes for the SMTP provider (separated by commas)" ResourceKey="SmtpScopes">Scopes:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="smtpscopes" class="form-control" @bind="@_smtpscopes" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="sender" HelpText="Enter the email address which emails will be sent from. Please note that this email address usually needs to be authorized with the SMTP provider." ResourceKey="SmtpSender">Email Sender: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="sender" class="form-control" @bind="@_smtpsender" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@ -268,6 +318,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-secondary" @onclick="SendEmail">@Localizer["Smtp.TestConfig"]</button>
|
<button type="button" class="btn btn-secondary" @onclick="SendEmail">@Localizer["Smtp.TestConfig"]</button>
|
||||||
<br /><br />
|
<br /><br />
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
<Section Name="PWA" Heading="Progressive Web Application Settings" ResourceKey="PWASettings">
|
<Section Name="PWA" Heading="Progressive Web Application Settings" ResourceKey="PWASettings">
|
||||||
@ -455,16 +506,23 @@
|
|||||||
private string _headcontent = string.Empty;
|
private string _headcontent = string.Empty;
|
||||||
private string _bodycontent = string.Empty;
|
private string _bodycontent = string.Empty;
|
||||||
|
|
||||||
|
private string _smtpenabled = "False";
|
||||||
|
private string _smtpauthentication = "Basic";
|
||||||
private string _smtphost = string.Empty;
|
private string _smtphost = string.Empty;
|
||||||
private string _smtpport = string.Empty;
|
private string _smtpport = string.Empty;
|
||||||
private string _smtpssl = "False";
|
private string _smtpssl = "Auto";
|
||||||
private string _smtpusername = string.Empty;
|
private string _smtpusername = string.Empty;
|
||||||
private string _smtppassword = string.Empty;
|
private string _smtppassword = string.Empty;
|
||||||
private string _smtppasswordtype = "password";
|
private string _smtppasswordtype = "password";
|
||||||
private string _togglesmtppassword = string.Empty;
|
private string _togglesmtppassword = string.Empty;
|
||||||
|
private string _smtpauthority = string.Empty;
|
||||||
|
private string _smtpclientid = string.Empty;
|
||||||
|
private string _smtpclientsecret = string.Empty;
|
||||||
|
private string _smtpclientsecrettype = "password";
|
||||||
|
private string _togglesmtpclientsecret = string.Empty;
|
||||||
|
private string _smtpscopes = string.Empty;
|
||||||
private string _smtpsender = string.Empty;
|
private string _smtpsender = string.Empty;
|
||||||
private string _smtprelay = "False";
|
private string _smtprelay = "False";
|
||||||
private string _smtpenabled = "True";
|
|
||||||
private int _retention = 30;
|
private int _retention = 30;
|
||||||
|
|
||||||
private string _pwaisenabled;
|
private string _pwaisenabled;
|
||||||
@ -508,7 +566,7 @@
|
|||||||
Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
|
Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
|
||||||
if (site != null)
|
if (site != null)
|
||||||
{
|
{
|
||||||
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
_timezones = TimeZoneService.GetTimeZones();
|
||||||
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
||||||
|
|
||||||
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||||
@ -534,9 +592,17 @@
|
|||||||
{
|
{
|
||||||
_faviconfileid = site.FaviconFileId.Value;
|
_faviconfileid = site.FaviconFileId.Value;
|
||||||
}
|
}
|
||||||
_themes = ThemeService.GetThemeControls(PageState.Site.Themes);
|
var themes = new List<Theme>();
|
||||||
|
foreach (var theme in PageState.Site.Themes)
|
||||||
|
{
|
||||||
|
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Utilize, theme.PermissionList))
|
||||||
|
{
|
||||||
|
themes.Add(theme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_themes = ThemeService.GetThemeControls(themes);
|
||||||
_themetype = (!string.IsNullOrEmpty(site.DefaultThemeType)) ? site.DefaultThemeType : Constants.DefaultTheme;
|
_themetype = (!string.IsNullOrEmpty(site.DefaultThemeType)) ? site.DefaultThemeType : Constants.DefaultTheme;
|
||||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
_containers = ThemeService.GetContainerControls(themes, _themetype);
|
||||||
_containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer;
|
_containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer;
|
||||||
_admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer;
|
_admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer;
|
||||||
_cookieconsent = SettingService.GetSetting(settings, "CookieConsent", string.Empty);
|
_cookieconsent = SettingService.GetSetting(settings, "CookieConsent", string.Empty);
|
||||||
@ -556,16 +622,27 @@
|
|||||||
_bodycontent = site.BodyContent;
|
_bodycontent = site.BodyContent;
|
||||||
|
|
||||||
// SMTP
|
// SMTP
|
||||||
|
_smtpenabled = SettingService.GetSetting(settings, "SMTPEnabled", "False");
|
||||||
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
|
{
|
||||||
_smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty);
|
_smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty);
|
||||||
_smtpport = SettingService.GetSetting(settings, "SMTPPort", string.Empty);
|
_smtpport = SettingService.GetSetting(settings, "SMTPPort", string.Empty);
|
||||||
_smtpssl = SettingService.GetSetting(settings, "SMTPSSL", "False");
|
_smtpssl = SettingService.GetSetting(settings, "SMTPSSL", "Auto");
|
||||||
|
if (_smtpssl == "True") _smtpssl = "SslOnConnect";
|
||||||
|
if (_smtpssl == "False") _smtpssl = "StartTlsWhenAvailable";
|
||||||
|
_smtpauthentication = SettingService.GetSetting(settings, "SMTPAuthentication", "Basic");
|
||||||
_smtpusername = SettingService.GetSetting(settings, "SMTPUsername", string.Empty);
|
_smtpusername = SettingService.GetSetting(settings, "SMTPUsername", string.Empty);
|
||||||
_smtppassword = SettingService.GetSetting(settings, "SMTPPassword", string.Empty);
|
_smtppassword = SettingService.GetSetting(settings, "SMTPPassword", string.Empty);
|
||||||
_togglesmtppassword = SharedLocalizer["ShowPassword"];
|
_togglesmtppassword = SharedLocalizer["ShowPassword"];
|
||||||
|
_smtpauthority = SettingService.GetSetting(settings, "SMTPAuthority", string.Empty);
|
||||||
|
_smtpclientid = SettingService.GetSetting(settings, "SMTPClientId", string.Empty);
|
||||||
|
_smtpclientsecret = SettingService.GetSetting(settings, "SMTPClientSecret", string.Empty);
|
||||||
|
_togglesmtpclientsecret = SharedLocalizer["ShowPassword"];
|
||||||
|
_smtpscopes = SettingService.GetSetting(settings, "SMTPScopes", string.Empty);
|
||||||
_smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty);
|
_smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty);
|
||||||
_smtprelay = SettingService.GetSetting(settings, "SMTPRelay", "False");
|
_smtprelay = SettingService.GetSetting(settings, "SMTPRelay", "False");
|
||||||
_smtpenabled = SettingService.GetSetting(settings, "SMTPEnabled", "True");
|
|
||||||
_retention = int.Parse(SettingService.GetSetting(settings, "NotificationRetention", "30"));
|
_retention = int.Parse(SettingService.GetSetting(settings, "NotificationRetention", "30"));
|
||||||
|
}
|
||||||
|
|
||||||
// PWA
|
// PWA
|
||||||
_pwaisenabled = site.PwaIsEnabled.ToString();
|
_pwaisenabled = site.PwaIsEnabled.ToString();
|
||||||
@ -596,7 +673,8 @@
|
|||||||
if (tenant != null)
|
if (tenant != null)
|
||||||
{
|
{
|
||||||
_tenant = tenant.Name;
|
_tenant = tenant.Name;
|
||||||
_database = _databases.Find(item => item.DBType == tenant.DBType && item.Name != "LocalDB")?.Name;
|
// hack - there are 3 providers with SqlServerDatabase DBTypes - so we are choosing the last one in alphabetical order
|
||||||
|
_database = _databases.Where(item => item.DBType == tenant.DBType).OrderBy(item => item.Name).Last()?.Name;
|
||||||
_connectionstring = tenant.DBConnectionString;
|
_connectionstring = tenant.DBConnectionString;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -742,16 +820,23 @@
|
|||||||
|
|
||||||
// SMTP
|
// SMTP
|
||||||
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
||||||
|
settings = SettingService.SetSetting(settings, "SMTPEnabled", _smtpenabled, true);
|
||||||
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
|
{
|
||||||
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, "SMTPPort", _smtpport, true);
|
||||||
settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true);
|
settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "SMTPAuthentication", _smtpauthentication, true);
|
||||||
settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true);
|
settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true);
|
||||||
settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true);
|
settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "SMTPAuthority", _smtpauthority, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "SMTPClientId", _smtpclientid, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "SMTPClientSecret", _smtpclientsecret, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "SMTPScopes", _smtpscopes, true);
|
||||||
settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
|
settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
|
||||||
settings = SettingService.SetSetting(settings, "SMTPRelay", _smtprelay, true);
|
settings = SettingService.SetSetting(settings, "SMTPRelay", _smtprelay, true);
|
||||||
settings = SettingService.SetSetting(settings, "SMTPEnabled", _smtpenabled, true);
|
|
||||||
settings = SettingService.SetSetting(settings, "SiteGuid", _siteguid, true);
|
|
||||||
settings = SettingService.SetSetting(settings, "NotificationRetention", _retention.ToString(), true);
|
settings = SettingService.SetSetting(settings, "NotificationRetention", _retention.ToString(), true);
|
||||||
|
}
|
||||||
|
|
||||||
//cookie consent
|
//cookie consent
|
||||||
settings = SettingService.SetSetting(settings, "CookieConsent", _cookieconsent);
|
settings = SettingService.SetSetting(settings, "CookieConsent", _cookieconsent);
|
||||||
@ -759,6 +844,7 @@
|
|||||||
// functionality
|
// functionality
|
||||||
settings = SettingService.SetSetting(settings, "TextEditor", _textEditor);
|
settings = SettingService.SetSetting(settings, "TextEditor", _textEditor);
|
||||||
|
|
||||||
|
settings = SettingService.SetSetting(settings, "SiteGuid", _siteguid, true);
|
||||||
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
|
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
|
||||||
|
|
||||||
await logger.LogInformation("Site Settings Saved {Site}", site);
|
await logger.LogInformation("Site Settings Saved {Site}", site);
|
||||||
@ -813,6 +899,46 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SMTPAuthenticationChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
_smtpauthentication = (string)e.Value;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SMTPEnabledChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
_smtpenabled = (string)e.Value;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleSMTPPassword()
|
||||||
|
{
|
||||||
|
if (_smtppasswordtype == "password")
|
||||||
|
{
|
||||||
|
_smtppasswordtype = "text";
|
||||||
|
_togglesmtppassword = SharedLocalizer["HidePassword"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_smtppasswordtype = "password";
|
||||||
|
_togglesmtppassword = SharedLocalizer["ShowPassword"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleSmtpClientSecret()
|
||||||
|
{
|
||||||
|
if (_smtpclientsecrettype == "password")
|
||||||
|
{
|
||||||
|
_smtpclientsecrettype = "text";
|
||||||
|
_togglesmtpclientsecret = SharedLocalizer["HidePassword"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_smtpclientsecrettype = "password";
|
||||||
|
_togglesmtpclientsecret = SharedLocalizer["ShowPassword"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task SendEmail()
|
private async Task SendEmail()
|
||||||
{
|
{
|
||||||
if (_smtphost != "" && _smtpport != "" && _smtpsender != "")
|
if (_smtphost != "" && _smtpport != "" && _smtpsender != "")
|
||||||
@ -823,8 +949,13 @@
|
|||||||
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, "SMTPPort", _smtpport, true);
|
||||||
settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true);
|
settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "SMTPAuthentication", _smtpauthentication, true);
|
||||||
settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true);
|
settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true);
|
||||||
settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true);
|
settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "SMTPAuthority", _smtpauthority, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "SMTPClientId", _smtpclientid, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "SMTPClientSecret", _smtpclientsecret, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "SMTPScopes", _smtpscopes, true);
|
||||||
settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
|
settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
|
||||||
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||||
await logger.LogInformation("Site SMTP Settings Saved");
|
await logger.LogInformation("Site SMTP Settings Saved");
|
||||||
@ -845,20 +976,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ToggleSMTPPassword()
|
|
||||||
{
|
|
||||||
if (_smtppasswordtype == "password")
|
|
||||||
{
|
|
||||||
_smtppasswordtype = "text";
|
|
||||||
_togglesmtppassword = SharedLocalizer["HidePassword"];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_smtppasswordtype = "password";
|
|
||||||
_togglesmtppassword = SharedLocalizer["ShowPassword"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task GetAliases()
|
private async Task GetAliases()
|
||||||
{
|
{
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
|
|||||||
@ -216,7 +216,7 @@ else
|
|||||||
_tenantid = _tenants.First(item => item.Name == TenantNames.Master).TenantId.ToString();
|
_tenantid = _tenants.First(item => item.Name == TenantNames.Master).TenantId.ToString();
|
||||||
}
|
}
|
||||||
_urls = PageState.Alias.Name;
|
_urls = PageState.Alias.Name;
|
||||||
_themeList = await ThemeService.GetThemesAsync();
|
_themeList = await ThemeService.GetThemesAsync(PageState.Site.SiteId);
|
||||||
_themes = ThemeService.GetThemeControls(_themeList);
|
_themes = ThemeService.GetThemeControls(_themeList);
|
||||||
if (_themes.Any(item => item.TypeName == Constants.DefaultTheme))
|
if (_themes.Any(item => item.TypeName == Constants.DefaultTheme))
|
||||||
{
|
{
|
||||||
@ -237,7 +237,7 @@ else
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_databaseName = "LocalDB";
|
_databaseName = Constants.DefaultDBName;
|
||||||
}
|
}
|
||||||
LoadDatabaseConfigComponent();
|
LoadDatabaseConfigComponent();
|
||||||
}
|
}
|
||||||
@ -390,6 +390,8 @@ else
|
|||||||
config.DatabaseType = tenant.DBType;
|
config.DatabaseType = tenant.DBType;
|
||||||
config.ConnectionString = tenant.DBConnectionString;
|
config.ConnectionString = tenant.DBConnectionString;
|
||||||
config.IsNewTenant = false;
|
config.IsNewTenant = false;
|
||||||
|
config.HostEmail = PageState.User.Email;
|
||||||
|
config.HostName = PageState.User.DisplayName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -403,6 +405,7 @@ else
|
|||||||
config.SiteTemplate = _sitetemplatetype;
|
config.SiteTemplate = _sitetemplatetype;
|
||||||
config.RenderMode = _rendermode;
|
config.RenderMode = _rendermode;
|
||||||
config.Runtime = _runtime;
|
config.Runtime = _runtime;
|
||||||
|
config.Register = false;
|
||||||
|
|
||||||
ShowProgressIndicator();
|
ShowProgressIndicator();
|
||||||
|
|
||||||
|
|||||||
@ -200,7 +200,8 @@ else
|
|||||||
if (tenant != null)
|
if (tenant != null)
|
||||||
{
|
{
|
||||||
_tenant = tenant.Name;
|
_tenant = tenant.Name;
|
||||||
_databasetype = _databases.FirstOrDefault(item => item.DBType == tenant.DBType && item.Name != "LocalDB").Name;
|
// hack - there are 3 providers with SqlServerDatabase DBTypes - so we are choosing the last one in alphabetical order
|
||||||
|
_databasetype = _databases.Where(item => item.DBType == tenant.DBType).OrderBy(item => item.Name).Last()?.Name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -211,7 +212,7 @@ else
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_databasetype = "LocalDB";
|
_databasetype = Constants.DefaultDBName;
|
||||||
}
|
}
|
||||||
_showConnectionString = false;
|
_showConnectionString = false;
|
||||||
LoadDatabaseConfigComponent();
|
LoadDatabaseConfigComponent();
|
||||||
|
|||||||
@ -2,10 +2,14 @@
|
|||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject ISystemService SystemService
|
@inject ISystemService SystemService
|
||||||
@inject IInstallationService InstallationService
|
@inject IInstallationService InstallationService
|
||||||
|
@inject IMigrationHistoryService MigrationHistoryService
|
||||||
|
@inject ITenantService TenantService
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<TabStrip>
|
@if (_initialized)
|
||||||
|
{
|
||||||
|
<TabStrip>
|
||||||
<TabPanel Name="Info" Heading="Info" ResourceKey="Info">
|
<TabPanel Name="Info" Heading="Info" ResourceKey="Info">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@ -170,12 +174,38 @@
|
|||||||
<br /><br />
|
<br /><br />
|
||||||
<button type="button" class="btn btn-danger" @onclick="ClearLog">@Localizer["Clear"]</button>
|
<button type="button" class="btn btn-danger" @onclick="ClearLog">@Localizer["Clear"]</button>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabStrip>
|
<TabPanel Name="Migrations" Heading="Migrations" ResourceKey="Migrations">
|
||||||
<br /><br />
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="tenant" HelpText="The name of the current database. Note that this is not the physical database name but rather the tenant name which is used within the framework to identify a database." ResourceKey="Tenant">Database: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="tenant" class="form-control" @bind="@_tenant" readonly />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<Pager Items="@_history" SearchProperties="MigrationId">
|
||||||
|
<Header>
|
||||||
|
<th>@Localizer["Migration"]</th>
|
||||||
|
<th>@Localizer["Date"]</th>
|
||||||
|
<th>@Localizer["Version"]</th>
|
||||||
|
</Header>
|
||||||
|
<Row>
|
||||||
|
<td>@context.MigrationId</td>
|
||||||
|
<td>@UtcToLocal(context.AppliedDate)</td>
|
||||||
|
<td>@context.AppliedVersion</td>
|
||||||
|
</Row>
|
||||||
|
</Pager>
|
||||||
|
</TabPanel>
|
||||||
|
</TabStrip>
|
||||||
|
<br /><br />
|
||||||
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
|
private bool _initialized = false;
|
||||||
|
|
||||||
private string _version = string.Empty;
|
private string _version = string.Empty;
|
||||||
private string _clrversion = string.Empty;
|
private string _clrversion = string.Empty;
|
||||||
private string _osversion = string.Empty;
|
private string _osversion = string.Empty;
|
||||||
@ -199,6 +229,9 @@
|
|||||||
|
|
||||||
private string _log = string.Empty;
|
private string _log = string.Empty;
|
||||||
|
|
||||||
|
private string _tenant = string.Empty;
|
||||||
|
private List<MigrationHistory> _history;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
_version = Constants.Version;
|
_version = Constants.Version;
|
||||||
@ -236,6 +269,12 @@
|
|||||||
{
|
{
|
||||||
_log = systeminfo["Log"].ToString();
|
_log = systeminfo["Log"].ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tenants = await TenantService.GetTenantsAsync();
|
||||||
|
_tenant = tenants.Find(item => item.TenantId == PageState.Site.TenantId).Name;
|
||||||
|
_history = await MigrationHistoryService.GetMigrationHistoryAsync();
|
||||||
|
|
||||||
|
_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SaveConfig()
|
private async Task SaveConfig()
|
||||||
|
|||||||
@ -101,6 +101,12 @@
|
|||||||
<small>@SharedLocalizer["Search.By"]:</small> <strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><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 />
|
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
|
||||||
<br />
|
<br />
|
||||||
|
@if (_themes.Exists(item => item.PackageName == context.PackageId))
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-info">@SharedLocalizer["Installed"]</button>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
@if (!string.IsNullOrEmpty(context.PackageUrl))
|
@if (!string.IsNullOrEmpty(context.PackageUrl))
|
||||||
{
|
{
|
||||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
|
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
|
||||||
@ -109,6 +115,7 @@
|
|||||||
{
|
{
|
||||||
<a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
|
<a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
<br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -171,6 +178,7 @@
|
|||||||
|
|
||||||
@code {
|
@code {
|
||||||
private bool _initialized = false;
|
private bool _initialized = false;
|
||||||
|
private List<Theme> _themes;
|
||||||
private int _page = 1;
|
private int _page = 1;
|
||||||
private List<Package> _packages;
|
private List<Package> _packages;
|
||||||
private string _price = "free";
|
private string _price = "free";
|
||||||
@ -187,7 +195,8 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await LoadThemes();
|
_themes = await ThemeService.GetThemesAsync(PageState.Site.SiteId);
|
||||||
|
await LoadPackages();
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -197,24 +206,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadThemes()
|
private async Task LoadPackages()
|
||||||
{
|
{
|
||||||
ShowProgressIndicator();
|
ShowProgressIndicator();
|
||||||
|
|
||||||
var themes = await ThemeService.GetThemesAsync();
|
|
||||||
_packages = await PackageService.GetPackagesAsync("theme", _search, _price, "", _sort);
|
_packages = await PackageService.GetPackagesAsync("theme", _search, _price, "", _sort);
|
||||||
|
|
||||||
if (_packages != null)
|
|
||||||
{
|
|
||||||
foreach (Package package in _packages.ToArray())
|
|
||||||
{
|
|
||||||
if (themes.Exists(item => item.PackageName == package.PackageId))
|
|
||||||
{
|
|
||||||
_packages.Remove(package);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HideProgressIndicator();
|
HideProgressIndicator();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,25 +217,25 @@
|
|||||||
{
|
{
|
||||||
_price = price;
|
_price = price;
|
||||||
_sort = "popularity";
|
_sort = "popularity";
|
||||||
await LoadThemes();
|
await LoadPackages();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Search()
|
private async Task Search()
|
||||||
{
|
{
|
||||||
await LoadThemes();
|
await LoadPackages();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Reset()
|
private async Task Reset()
|
||||||
{
|
{
|
||||||
_page = 1;
|
_page = 1;
|
||||||
_search = "";
|
_search = "";
|
||||||
await LoadThemes();
|
await LoadPackages();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Refresh()
|
private async Task Refresh()
|
||||||
{
|
{
|
||||||
await LoadThemes();
|
await LoadPackages();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPageChange(int page)
|
private void OnPageChange(int page)
|
||||||
@ -251,7 +246,7 @@
|
|||||||
private async void SortChanged(ChangeEventArgs e)
|
private async void SortChanged(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
_sort = (string)e.Value;
|
_sort = (string)e.Value;
|
||||||
await LoadThemes();
|
await LoadPackages();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HideModal()
|
private void HideModal()
|
||||||
@ -310,6 +305,6 @@
|
|||||||
|
|
||||||
private void OnUpload()
|
private void OnUpload()
|
||||||
{
|
{
|
||||||
AddModuleMessage(string.Format(Localizer["Success.Theme.Download"], NavigateUrl("admin/system")), MessageType.Success);
|
AddModuleMessage(string.Format(Localizer["Success.Theme.Upload"], NavigateUrl("admin/system")), MessageType.Success);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,10 +9,12 @@
|
|||||||
|
|
||||||
@if (_initialized)
|
@if (_initialized)
|
||||||
{
|
{
|
||||||
|
<TabStrip>
|
||||||
|
<TabPanel Name="Theme" ResourceKey="Theme" Heading="Theme">
|
||||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<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>
|
<Label Class="col-sm-3" For="name" HelpText="The name of the theme" ResourceKey="Name">Name: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="name" class="form-control" @bind="@_name" />
|
<input id="name" class="form-control" @bind="@_name" />
|
||||||
</div>
|
</div>
|
||||||
@ -87,6 +89,18 @@
|
|||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
|
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel Name="Permissions" ResourceKey="Permissions" Heading="Permissions">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<PermissionGrid EntityName="@EntityNames.Theme" PermissionNames="@PermissionNames.Utilize" PermissionList="@_permissions" @ref="_permissionGrid" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SaveTheme">@SharedLocalizer["Save"]</button>
|
||||||
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
</TabPanel>
|
||||||
|
</TabStrip>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@ -103,11 +117,14 @@
|
|||||||
private string _url = "";
|
private string _url = "";
|
||||||
private string _contact = "";
|
private string _contact = "";
|
||||||
private string _license = "";
|
private string _license = "";
|
||||||
|
private List<Permission> _permissions = null;
|
||||||
private string _createdby;
|
private string _createdby;
|
||||||
private DateTime _createdon;
|
private DateTime _createdon;
|
||||||
private string _modifiedby;
|
private string _modifiedby;
|
||||||
private DateTime _modifiedon;
|
private DateTime _modifiedon;
|
||||||
|
|
||||||
|
private PermissionGrid _permissionGrid;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
@ -126,6 +143,7 @@
|
|||||||
_url = theme.Url;
|
_url = theme.Url;
|
||||||
_contact = theme.Contact;
|
_contact = theme.Contact;
|
||||||
_license = theme.License;
|
_license = theme.License;
|
||||||
|
_permissions = theme.PermissionList;
|
||||||
_createdby = theme.CreatedBy;
|
_createdby = theme.CreatedBy;
|
||||||
_createdon = theme.CreatedOn;
|
_createdon = theme.CreatedOn;
|
||||||
_modifiedby = theme.ModifiedBy;
|
_modifiedby = theme.ModifiedBy;
|
||||||
@ -152,6 +170,7 @@
|
|||||||
var theme = await ThemeService.GetThemeAsync(_themeId, ModuleState.SiteId);
|
var theme = await ThemeService.GetThemeAsync(_themeId, ModuleState.SiteId);
|
||||||
theme.Name = _name;
|
theme.Name = _name;
|
||||||
theme.IsEnabled = (_isenabled == null ? true : bool.Parse(_isenabled));
|
theme.IsEnabled = (_isenabled == null ? true : bool.Parse(_isenabled));
|
||||||
|
theme.PermissionList = _permissionGrid.GetPermissionList();
|
||||||
await ThemeService.UpdateThemeAsync(theme);
|
await ThemeService.UpdateThemeAsync(theme);
|
||||||
await logger.LogInformation("Theme Saved {Theme}", theme);
|
await logger.LogInformation("Theme Saved {Theme}", theme);
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
|
|||||||
@ -15,12 +15,11 @@
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
<ActionLink Action="Add" Text="Install Theme" ResourceKey="InstallTheme" />
|
<ActionLink Action="Add" Text="Install Theme" ResourceKey="InstallTheme" />
|
||||||
<ActionLink Action="Create" Text="Create Theme" ResourceKey="CreateTheme" Class="btn btn-secondary ps-2" />
|
<ActionLink Action="Create" Text="Create Theme" ResourceKey="CreateTheme" Class="btn btn-secondary ms-1" />
|
||||||
<button type="button" class="btn btn-secondary pw-2" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
|
<button type="button" class="btn btn-secondary ms-1" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
|
||||||
|
|
||||||
<Pager Items="@_themes">
|
<Pager Items="@_themes">
|
||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th>@SharedLocalizer["Name"]</th>
|
<th>@SharedLocalizer["Name"]</th>
|
||||||
@ -38,7 +37,6 @@ else
|
|||||||
<ActionDialog Header="Delete Theme" Message="@string.Format(Localizer["Confirm.Theme.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteTheme(context))" ResourceKey="DeleteTheme" />
|
<ActionDialog Header="Delete Theme" Message="@string.Format(Localizer["Confirm.Theme.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteTheme(context))" ResourceKey="DeleteTheme" />
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td><NavLink class="btn btn-secondary" href="@NavigateUrl("admin/site")">@Localizer["Assign"]</NavLink></td>
|
|
||||||
<td>@context.Name</td>
|
<td>@context.Name</td>
|
||||||
<td>@context.Version</td>
|
<td>@context.Version</td>
|
||||||
<td>
|
<td>
|
||||||
@ -80,7 +78,7 @@ else
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_themes = await ThemeService.GetThemesAsync();
|
_themes = await ThemeService.GetThemesAsync(PageState.Site.SiteId);
|
||||||
_packages = await PackageService.GetPackageUpdatesAsync("theme");
|
_packages = await PackageService.GetPackageUpdatesAsync("theme");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -163,7 +161,7 @@ else
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ThemeService.DeleteThemeAsync(Theme.ThemeName);
|
await ThemeService.DeleteThemeAsync(Theme.ThemeId, PageState.Site.SiteId);
|
||||||
AddModuleMessage(Localizer["Success.Theme.Delete"], MessageType.Success);
|
AddModuleMessage(Localizer["Success.Theme.Delete"], MessageType.Success);
|
||||||
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
|
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -126,13 +126,16 @@
|
|||||||
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete">
|
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete">
|
||||||
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||||
{
|
{
|
||||||
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
|
var values = option.Split(':');
|
||||||
|
var name = values[0];
|
||||||
|
var value = values.Length > 1 ? values[1] : values[0];
|
||||||
|
@if (GetProfileValue(p.Name, "") == name || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == name))
|
||||||
{
|
{
|
||||||
<option value="@option" selected>@option</option>
|
<option value="@name" selected>@value</option>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<option value="@option">@option</option>
|
<option value="@name">@value</option>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
@ -142,13 +145,16 @@
|
|||||||
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
|
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
|
||||||
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||||
{
|
{
|
||||||
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
|
var values = option.Split(':');
|
||||||
|
var name = values[0];
|
||||||
|
var value = values.Length > 1 ? values[1] : values[0];
|
||||||
|
@if (GetProfileValue(p.Name, "") == name || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == name))
|
||||||
{
|
{
|
||||||
<option value="@option" selected>@option</option>
|
<option value="@name" selected>@value</option>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<option value="@option">@option</option>
|
<option value="@name">@value</option>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
@ -367,7 +373,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private List<Models.TimeZone> _timezones;
|
|
||||||
private bool _initialized = false;
|
private bool _initialized = false;
|
||||||
private string _passwordrequirements;
|
private string _passwordrequirements;
|
||||||
private string _username = string.Empty;
|
private string _username = string.Empty;
|
||||||
@ -381,6 +386,7 @@
|
|||||||
private string _displayname = string.Empty;
|
private string _displayname = string.Empty;
|
||||||
private FileManager _filemanager;
|
private FileManager _filemanager;
|
||||||
private int _folderid = -1;
|
private int _folderid = -1;
|
||||||
|
private List<Models.TimeZone> _timezones;
|
||||||
private string _timezoneid = string.Empty;
|
private string _timezoneid = string.Empty;
|
||||||
private int _photofileid = -1;
|
private int _photofileid = -1;
|
||||||
private File _photo = null;
|
private File _photo = null;
|
||||||
@ -404,7 +410,16 @@
|
|||||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
_allowtwofactor = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "true");
|
_allowtwofactor = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "true");
|
||||||
_profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
_profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
||||||
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
foreach (var profile in _profiles)
|
||||||
|
{
|
||||||
|
if (profile.Options.ToLower().StartsWith("entityname:"))
|
||||||
|
{
|
||||||
|
var options = await SettingService.GetSettingsAsync(profile.Options.Substring(11), -1);
|
||||||
|
options.Add("", $"<{SharedLocalizer["Not Specified"]}>");
|
||||||
|
profile.Options = string.Join(",", options.OrderBy(item => item.Value).Select(kvp => $"{kvp.Key}:{kvp.Value}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_timezones = TimeZoneService.GetTimeZones();
|
||||||
|
|
||||||
if (PageState.User != null)
|
if (PageState.User != null)
|
||||||
{
|
{
|
||||||
@ -414,11 +429,6 @@
|
|||||||
_displayname = PageState.User.DisplayName;
|
_displayname = PageState.User.DisplayName;
|
||||||
_timezoneid = PageState.User.TimeZoneId;
|
_timezoneid = PageState.User.TimeZoneId;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(_email))
|
|
||||||
{
|
|
||||||
AddModuleMessage(Localizer["Message.User.NoEmail"], MessageType.Warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get user folder
|
// get user folder
|
||||||
var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath);
|
var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath);
|
||||||
if (folder != null)
|
if (folder != null)
|
||||||
|
|||||||
@ -28,6 +28,15 @@
|
|||||||
<input id="email" class="form-control" @bind="@_email" />
|
<input id="email" class="form-control" @bind="@_email" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="confirmed" HelpText="Indicates if the user's email is verified" ResourceKey="Confirmed">Verified?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="confirmed" class="form-select" @bind="@_confirmed">
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="displayname" HelpText="The full name of the user" ResourceKey="DisplayName"></Label>
|
<Label Class="col-sm-3" For="displayname" HelpText="The full name of the user" ResourceKey="DisplayName"></Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@ -79,13 +88,16 @@
|
|||||||
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
|
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
|
||||||
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||||
{
|
{
|
||||||
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
|
var values = option.Split(':');
|
||||||
|
var name = values[0];
|
||||||
|
var value = values.Length > 1 ? values[1] : values[0];
|
||||||
|
@if (GetProfileValue(p.Name, "") == name || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == name))
|
||||||
{
|
{
|
||||||
<option value="@option" selected>@option</option>
|
<option value="@name" selected>@value</option>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<option value="@option">@option</option>
|
<option value="@name">@value</option>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
@ -120,6 +132,7 @@
|
|||||||
private bool _initialized = false;
|
private bool _initialized = false;
|
||||||
private string _username = string.Empty;
|
private string _username = string.Empty;
|
||||||
private string _email = string.Empty;
|
private string _email = string.Empty;
|
||||||
|
private string _confirmed = "True";
|
||||||
private string _displayname = string.Empty;
|
private string _displayname = string.Empty;
|
||||||
private string _timezoneid = string.Empty;
|
private string _timezoneid = string.Empty;
|
||||||
private string _notify = "True";
|
private string _notify = "True";
|
||||||
@ -133,8 +146,17 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
_timezones = TimeZoneService.GetTimeZones();
|
||||||
_profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
_profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
||||||
|
foreach (var profile in _profiles)
|
||||||
|
{
|
||||||
|
if (profile.Options.ToLower().StartsWith("entityname:"))
|
||||||
|
{
|
||||||
|
var options = await SettingService.GetSettingsAsync(profile.Options.Substring(11), -1);
|
||||||
|
options.Add("", $"<{SharedLocalizer["Not Specified"]}>");
|
||||||
|
profile.Options = string.Join(",", options.OrderBy(item => item.Value).Select(kvp => $"{kvp.Key}:{kvp.Value}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
_settings = new Dictionary<string, string>();
|
_settings = new Dictionary<string, string>();
|
||||||
_timezoneid = PageState.Site.TimeZoneId;
|
_timezoneid = PageState.Site.TimeZoneId;
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
@ -169,6 +191,7 @@
|
|||||||
user.Username = _username;
|
user.Username = _username;
|
||||||
user.Password = ""; // will be auto generated
|
user.Password = ""; // will be auto generated
|
||||||
user.Email = _email;
|
user.Email = _email;
|
||||||
|
user.EmailConfirmed = bool.Parse(_confirmed);
|
||||||
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
||||||
user.TimeZoneId = _timezoneid;
|
user.TimeZoneId = _timezoneid;
|
||||||
user.PhotoFileId = null;
|
user.PhotoFileId = null;
|
||||||
|
|||||||
@ -48,7 +48,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="confirmed" HelpText="Indicates if the user's email is verified" ResourceKey="Confirmed">Confirmed?</Label>
|
<Label Class="col-sm-3" For="confirmed" HelpText="Indicates if the user's email is verified" ResourceKey="Confirmed">Verified?</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="confirmed" class="form-select" @bind="@_confirmed">
|
<select id="confirmed" class="form-select" @bind="@_confirmed">
|
||||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
@ -121,13 +121,16 @@
|
|||||||
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
|
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
|
||||||
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||||
{
|
{
|
||||||
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
|
var values = option.Split(':');
|
||||||
|
var name = values[0];
|
||||||
|
var value = values.Length > 1 ? values[1] : values[0];
|
||||||
|
@if (GetProfileValue(p.Name, "") == name || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == name))
|
||||||
{
|
{
|
||||||
<option value="@option" selected>@option</option>
|
<option value="@name" selected>@value</option>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<option value="@option">@option</option>
|
<option value="@name">@value</option>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
@ -153,13 +156,13 @@
|
|||||||
<br />
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) && PageState.Runtime != Shared.Runtime.Hybrid && !_ishost)
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) && PageState.Runtime != Shared.Runtime.Hybrid && !_ishost && _isdeleted != "True")
|
||||||
{
|
{
|
||||||
<button type="button" class="btn btn-primary ms-1" @onclick="ImpersonateUser">@Localizer["Impersonate"]</button>
|
<button type="button" class="btn btn-primary ms-1" @onclick="ImpersonateUser">@Localizer["Impersonate"]</button>
|
||||||
}
|
}
|
||||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && _isdeleted == "True")
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && _isdeleted == "True")
|
||||||
{
|
{
|
||||||
<ActionDialog Header="Delete User" Message="Are You Sure You Wish To Permanently Delete This User?" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteUser())" ResourceKey="DeleteUser" />
|
<ActionDialog Header="Delete User" Message="Are You Sure You Wish To Permanently Delete This User?" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger ms-1" OnClick="@(async () => await DeleteUser())" ResourceKey="DeleteUser" />
|
||||||
}
|
}
|
||||||
<br /><br />
|
<br /><br />
|
||||||
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
|
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
|
||||||
@ -204,7 +207,16 @@
|
|||||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
_profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
|
_profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
|
||||||
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
foreach (var profile in _profiles)
|
||||||
|
{
|
||||||
|
if (profile.Options.ToLower().StartsWith("entityname:"))
|
||||||
|
{
|
||||||
|
var options = await SettingService.GetSettingsAsync(profile.Options.Substring(11), -1);
|
||||||
|
options.Add("", $"<{SharedLocalizer["Not Specified"]}>");
|
||||||
|
profile.Options = string.Join(",", options.OrderBy(item => item.Value).Select(kvp => $"{kvp.Key}:{kvp.Value}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_timezones = TimeZoneService.GetTimeZones();
|
||||||
|
|
||||||
if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int UserId))
|
if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int UserId))
|
||||||
{
|
{
|
||||||
|
|||||||
@ -17,8 +17,21 @@ else
|
|||||||
{
|
{
|
||||||
<TabStrip>
|
<TabStrip>
|
||||||
<TabPanel Name="Users" Heading="Users" ResourceKey="Users">
|
<TabPanel Name="Users" Heading="Users" ResourceKey="Users">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<div class="col-sm-6">
|
||||||
<ActionLink Action="Add" Text="Add User" Security="SecurityAccessLevel.Edit" ResourceKey="AddUser" />
|
<ActionLink Action="Add" Text="Add User" Security="SecurityAccessLevel.Edit" ResourceKey="AddUser" />
|
||||||
<ActionLink Text="Import Users" Class="btn btn-secondary ms-2" Action="Users" Security="SecurityAccessLevel.Admin" ResourceKey="ImportUsers"/>
|
<ActionLink Text="Import Users" Class="btn btn-secondary ms-2" Action="Users" Security="SecurityAccessLevel.Admin" ResourceKey="ImportUsers" />
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<select id="deleted" class="form-select custom-select" value="@_deleted" @onchange="(e => DeletedChanged(e))">
|
||||||
|
<option value="false">@Localizer["Active Users"]</option>
|
||||||
|
<option value="true">@Localizer["Deleted Users"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
|
||||||
<Pager Items="@users" RowClass="align-middle" SearchProperties="User.Username,User.Email,User.DisplayName">
|
<Pager Items="@users" RowClass="align-middle" SearchProperties="User.Username,User.Email,User.DisplayName">
|
||||||
<Header>
|
<Header>
|
||||||
@ -74,10 +87,19 @@ else
|
|||||||
<input id="profileurl" class="form-control" @bind="@_profileurl" />
|
<input id="profileurl" class="form-control" @bind="@_profileurl" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="requireconfirmedemail" HelpText="Do you want to require registered users to verify their email address before they are allowed to log in?" ResourceKey="RequireConfirmedEmail">Require Verified Email?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="requireconfirmedemail" class="form-select" @bind="@_requireconfirmedemail">
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="twofactor" HelpText="Do you want users to use two factor authentication? Note that you should use the Disabled option until you have successfully verified that the Notification Job in Scheduled Jobs is enabled and your SMTP options in Site Settings are configured or else you will lock yourself out." ResourceKey="TwoFactor">Two Factor?</Label>
|
<Label Class="col-sm-3" For="twofactor" HelpText="Do you want users to use two factor authentication? Note that you should use the Disabled option until you have successfully verified that the Notification Job in Scheduled Jobs is enabled and your SMTP options in Site Settings are configured or else you will lock yourself out." ResourceKey="TwoFactor">Two Factor Authentication?</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="twofactor" class="form-select" @bind="@_twofactor">
|
<select id="twofactor" class="form-select" @bind="@_twofactor">
|
||||||
<option value="false">@Localizer["Disabled"]</option>
|
<option value="false">@Localizer["Disabled"]</option>
|
||||||
@ -92,6 +114,12 @@ else
|
|||||||
<input id="cookiename" class="form-control" @bind="@_cookiename" />
|
<input id="cookiename" class="form-control" @bind="@_cookiename" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="cookiedomain" HelpText="If you would like to share cookies across subdomains you will need to specify a root domain with a leading dot (ie. '.example.com')" ResourceKey="CookieDomain">Cookie Domain:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="cookiedomain" class="form-control" @bind="@_cookiedomain" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="cookieexpiration" HelpText="You can choose to use a custom authentication cookie expiration timespan for each site (e.g. '08:00:00' for 8 hours). The default is 14 days if not specified." ResourceKey="CookieExpiration">Cookie Expiration Timespan:</Label>
|
<Label Class="col-sm-3" For="cookieexpiration" HelpText="You can choose to use a custom authentication cookie expiration timespan for each site (e.g. '08:00:00' for 8 hours). The default is 14 days if not specified." ResourceKey="CookieExpiration">Cookie Expiration Timespan:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@ -292,6 +320,24 @@ else
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="requirenonce" HelpText="Specify if Nonce validation is required for the ID token (the default is true)" ResourceKey="RequireNonce">Require Nonce?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="requirenonce" class="form-select" @bind="@_requirenonce" required>
|
||||||
|
<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="singlelogout" HelpText="Specify if users should be logged out of both the application and provider (the default is false indicating they will only be logged out of the application)" ResourceKey="SingleLogout">Use Single Logout?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="singlelogout" class="form-select" @bind="@_singlelogout" required>
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="scopes" HelpText="A list of Scopes to request from the provider (separated by commas). If none are specified, standard Scopes will be used by default." ResourceKey="Scopes">Scopes:</Label>
|
<Label Class="col-sm-3" For="scopes" HelpText="A list of Scopes to request from the provider (separated by commas). If none are specified, standard Scopes will be used by default." ResourceKey="Scopes">Scopes:</Label>
|
||||||
@ -486,12 +532,15 @@ else
|
|||||||
|
|
||||||
@code {
|
@code {
|
||||||
private List<UserRole> users;
|
private List<UserRole> users;
|
||||||
|
private string _deleted = "false";
|
||||||
|
|
||||||
private string _allowregistration;
|
private string _allowregistration;
|
||||||
private string _registerurl;
|
private string _registerurl;
|
||||||
private string _profileurl;
|
private string _profileurl;
|
||||||
|
private string _requireconfirmedemail;
|
||||||
private string _twofactor;
|
private string _twofactor;
|
||||||
private string _cookiename;
|
private string _cookiename;
|
||||||
|
private string _cookiedomain;
|
||||||
private string _cookieexpiration;
|
private string _cookieexpiration;
|
||||||
private string _alwaysremember;
|
private string _alwaysremember;
|
||||||
private string _logouteverywhere;
|
private string _logouteverywhere;
|
||||||
@ -519,6 +568,8 @@ else
|
|||||||
private string _clientsecrettype = "password";
|
private string _clientsecrettype = "password";
|
||||||
private string _toggleclientsecret = string.Empty;
|
private string _toggleclientsecret = string.Empty;
|
||||||
private string _authresponsetype;
|
private string _authresponsetype;
|
||||||
|
private string _requirenonce;
|
||||||
|
private string _singlelogout;
|
||||||
private string _scopes;
|
private string _scopes;
|
||||||
private string _parameters;
|
private string _parameters;
|
||||||
private string _pkce;
|
private string _pkce;
|
||||||
@ -554,17 +605,19 @@ else
|
|||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
await LoadUsersAsync(true);
|
await LoadUsersAsync();
|
||||||
|
|
||||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
_allowregistration = PageState.Site.AllowRegistration.ToString().ToLower();
|
_allowregistration = PageState.Site.AllowRegistration.ToString().ToLower();
|
||||||
_registerurl = SettingService.GetSetting(settings, "LoginOptions:RegisterUrl", "");
|
_registerurl = SettingService.GetSetting(settings, "LoginOptions:RegisterUrl", "");
|
||||||
_profileurl = SettingService.GetSetting(settings, "LoginOptions:ProfileUrl", "");
|
_profileurl = SettingService.GetSetting(settings, "LoginOptions:ProfileUrl", "");
|
||||||
|
_requireconfirmedemail = SettingService.GetSetting(settings, "LoginOptions:RequireConfirmedEmail", "true");
|
||||||
|
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
_twofactor = SettingService.GetSetting(settings, "LoginOptions:TwoFactor", "false");
|
_twofactor = SettingService.GetSetting(settings, "LoginOptions:TwoFactor", "false");
|
||||||
_cookiename = SettingService.GetSetting(settings, "LoginOptions:CookieName", ".AspNetCore.Identity.Application");
|
_cookiename = SettingService.GetSetting(settings, "LoginOptions:CookieName", ".AspNetCore.Identity.Application");
|
||||||
|
_cookiedomain = SettingService.GetSetting(settings, "LoginOptions:CookieDomain", "");
|
||||||
_cookieexpiration = SettingService.GetSetting(settings, "LoginOptions:CookieExpiration", "");
|
_cookieexpiration = SettingService.GetSetting(settings, "LoginOptions:CookieExpiration", "");
|
||||||
_alwaysremember = SettingService.GetSetting(settings, "LoginOptions:AlwaysRemember", "false");
|
_alwaysremember = SettingService.GetSetting(settings, "LoginOptions:AlwaysRemember", "false");
|
||||||
_logouteverywhere = SettingService.GetSetting(settings, "LoginOptions:LogoutEverywhere", "false");
|
_logouteverywhere = SettingService.GetSetting(settings, "LoginOptions:LogoutEverywhere", "false");
|
||||||
@ -604,6 +657,8 @@ else
|
|||||||
_clientsecret = SettingService.GetSetting(settings, "ExternalLogin:ClientSecret", "");
|
_clientsecret = SettingService.GetSetting(settings, "ExternalLogin:ClientSecret", "");
|
||||||
_toggleclientsecret = SharedLocalizer["ShowPassword"];
|
_toggleclientsecret = SharedLocalizer["ShowPassword"];
|
||||||
_authresponsetype = SettingService.GetSetting(settings, "ExternalLogin:AuthResponseType", "code");
|
_authresponsetype = SettingService.GetSetting(settings, "ExternalLogin:AuthResponseType", "code");
|
||||||
|
_requirenonce = SettingService.GetSetting(settings, "ExternalLogin:RequireNonce", "true");
|
||||||
|
_singlelogout = SettingService.GetSetting(settings, "ExternalLogin:SingleLogout", "false");
|
||||||
_scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", "");
|
_scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", "");
|
||||||
_parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", "");
|
_parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", "");
|
||||||
_pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false");
|
_pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false");
|
||||||
@ -625,9 +680,7 @@ else
|
|||||||
_allowsitelogin = SettingService.GetSetting(settings, "LoginOptions:AllowSiteLogin", "true");
|
_allowsitelogin = SettingService.GetSetting(settings, "LoginOptions:AllowSiteLogin", "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadUsersAsync(bool load)
|
private async Task LoadUsersAsync()
|
||||||
{
|
|
||||||
if (load)
|
|
||||||
{
|
{
|
||||||
users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
|
users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
@ -636,6 +689,20 @@ else
|
|||||||
users.AddRange(hosts);
|
users.AddRange(hosts);
|
||||||
users = users.OrderBy(u => u.User.DisplayName).ToList();
|
users = users.OrderBy(u => u.User.DisplayName).ToList();
|
||||||
}
|
}
|
||||||
|
users = users.Where(item => item.User.IsDeleted == bool.Parse(_deleted)).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void DeletedChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_deleted = e.Value.ToString();
|
||||||
|
await LoadUsersAsync();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error On DeletedChanged");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -661,7 +728,7 @@ else
|
|||||||
await logger.LogInformation("User {Username} Expired From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
await logger.LogInformation("User {Username} Expired From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
||||||
}
|
}
|
||||||
AddModuleMessage(Localizer["Success.DeleteUser"], MessageType.Success);
|
AddModuleMessage(Localizer["Success.DeleteUser"], MessageType.Success);
|
||||||
await LoadUsersAsync(true);
|
await LoadUsersAsync();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -685,8 +752,10 @@ else
|
|||||||
{
|
{
|
||||||
settings = SettingService.SetSetting(settings, "LoginOptions:RegisterUrl", _registerurl, false);
|
settings = SettingService.SetSetting(settings, "LoginOptions:RegisterUrl", _registerurl, false);
|
||||||
settings = SettingService.SetSetting(settings, "LoginOptions:ProfileUrl", _profileurl, false);
|
settings = SettingService.SetSetting(settings, "LoginOptions:ProfileUrl", _profileurl, false);
|
||||||
|
settings = SettingService.SetSetting(settings, "LoginOptions:RequireConfirmedEmail", _requireconfirmedemail, false);
|
||||||
settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false);
|
settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false);
|
||||||
settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true);
|
settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "LoginOptions:CookieDomain", _cookiedomain, true);
|
||||||
settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true);
|
settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true);
|
||||||
settings = SettingService.SetSetting(settings, "LoginOptions:AlwaysRemember", _alwaysremember, false);
|
settings = SettingService.SetSetting(settings, "LoginOptions:AlwaysRemember", _alwaysremember, false);
|
||||||
settings = SettingService.SetSetting(settings, "LoginOptions:LogoutEverywhere", _logouteverywhere, false);
|
settings = SettingService.SetSetting(settings, "LoginOptions:LogoutEverywhere", _logouteverywhere, false);
|
||||||
@ -712,6 +781,8 @@ else
|
|||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientId", _clientid, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientId", _clientid, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientSecret", _clientsecret, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientSecret", _clientsecret, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:AuthResponseType", _authresponsetype, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:AuthResponseType", _authresponsetype, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:RequireNonce", _requirenonce, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:SingleLogout", _singlelogout, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:Scopes", _scopes, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:Scopes", _scopes, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:Parameters", _parameters, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:Parameters", _parameters, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true);
|
||||||
|
|||||||
@ -52,7 +52,7 @@
|
|||||||
|
|
||||||
if (CreatedOn != null)
|
if (CreatedOn != null)
|
||||||
{
|
{
|
||||||
_text += $" {Localizer["On"]} <b>{UtcToLocal(CreatedOn).Value.ToString(DateTimeFormat)}</ b >";
|
_text += $" {Localizer["On"]} <b>{UtcToLocal(CreatedOn).Value.ToString(DateTimeFormat)}</b>";
|
||||||
}
|
}
|
||||||
|
|
||||||
_text += "</p>";
|
_text += "</p>";
|
||||||
@ -69,7 +69,7 @@
|
|||||||
|
|
||||||
if (ModifiedOn != null)
|
if (ModifiedOn != null)
|
||||||
{
|
{
|
||||||
_text += $" {Localizer["On"]} <b>{UtcToLocal(ModifiedOn).Value.ToString(DateTimeFormat)}</ b >";
|
_text += $" {Localizer["On"]} <b>{UtcToLocal(ModifiedOn).Value.ToString(DateTimeFormat)}</b>";
|
||||||
}
|
}
|
||||||
|
|
||||||
_text += "</p>";
|
_text += "</p>";
|
||||||
@ -86,7 +86,7 @@
|
|||||||
|
|
||||||
if (DeletedOn != null)
|
if (DeletedOn != null)
|
||||||
{
|
{
|
||||||
_text += $" {Localizer["On"]} <b>{UtcToLocal(DeletedOn).Value.ToString(DateTimeFormat)}</ b >";
|
_text += $" {Localizer["On"]} <b>{UtcToLocal(DeletedOn).Value.ToString(DateTimeFormat)}</b>";
|
||||||
}
|
}
|
||||||
|
|
||||||
_text += "</p>";
|
_text += "</p>";
|
||||||
|
|||||||
@ -107,7 +107,7 @@
|
|||||||
|
|
||||||
@code {
|
@code {
|
||||||
private bool _initialized = false;
|
private bool _initialized = false;
|
||||||
private List<Folder> _folders;
|
private List<Folder> _folders = new List<Folder>();
|
||||||
private List<File> _files = new List<File>();
|
private List<File> _files = new List<File>();
|
||||||
private string _fileinputid = string.Empty;
|
private string _fileinputid = string.Empty;
|
||||||
private string _progressinfoid = string.Empty;
|
private string _progressinfoid = string.Empty;
|
||||||
@ -157,6 +157,9 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false
|
public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public bool AnonymizeUploadFilenames { get; set; } = false; // optional - indicate if file names should be anonymized on upload - default false
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public int ChunkSize { get; set; } = 1; // optional - size of file chunks to upload in MB
|
public int ChunkSize { get; set; } = 1; // optional - size of file chunks to upload in MB
|
||||||
|
|
||||||
@ -195,8 +198,10 @@
|
|||||||
Filter = "nupkg";
|
Filter = "nupkg";
|
||||||
ShowSuccess = true;
|
ShowSuccess = true;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if (!string.IsNullOrEmpty(Folder) && Folder != Constants.PackagesFolder)
|
{
|
||||||
|
// folder path specified rather than folderid
|
||||||
|
if (!string.IsNullOrEmpty(Folder))
|
||||||
{
|
{
|
||||||
Folder folder = await FolderService.GetFolderAsync(ModuleState.SiteId, Folder);
|
Folder folder = await FolderService.GetFolderAsync(ModuleState.SiteId, Folder);
|
||||||
if (folder != null)
|
if (folder != null)
|
||||||
@ -210,6 +215,7 @@
|
|||||||
_messagetype = MessageType.Error;
|
_messagetype = MessageType.Error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (ShowFolders)
|
if (ShowFolders)
|
||||||
{
|
{
|
||||||
@ -242,25 +248,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await SetImage();
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(Filter))
|
if (!string.IsNullOrEmpty(Filter))
|
||||||
{
|
{
|
||||||
_filter = "." + Filter.Replace(",", ",.");
|
_filter = "." + Filter.Replace(",", ",.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GetFolderPermission();
|
||||||
|
await SetImage();
|
||||||
await GetFiles();
|
await GetFiles();
|
||||||
|
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task GetFiles()
|
private void GetFolderPermission()
|
||||||
{
|
{
|
||||||
_haseditpermission = false;
|
_haseditpermission = false;
|
||||||
if (Folder == Constants.PackagesFolder)
|
if (Folder == Constants.PackagesFolder)
|
||||||
{
|
{
|
||||||
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, RoleNames.Host);
|
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, RoleNames.Host);
|
||||||
_files = new List<File>();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -268,67 +273,12 @@
|
|||||||
if (folder != null)
|
if (folder != null)
|
||||||
{
|
{
|
||||||
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, folder.PermissionList);
|
_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
|
else
|
||||||
{
|
{
|
||||||
_haseditpermission = false;
|
_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;
|
|
||||||
|
|
||||||
await OnSelectFolder.InvokeAsync(FolderId);
|
|
||||||
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);
|
|
||||||
await SetImage();
|
|
||||||
#pragma warning disable CS0618
|
|
||||||
await OnSelect.InvokeAsync(FileId);
|
|
||||||
#pragma warning restore CS0618
|
|
||||||
await OnSelectFile.InvokeAsync(FileId);
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SetImage()
|
private async Task SetImage()
|
||||||
@ -354,6 +304,74 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task GetFiles()
|
||||||
|
{
|
||||||
|
if (ShowFiles)
|
||||||
|
{
|
||||||
|
Folder folder = _folders.FirstOrDefault(item => item.FolderId == FolderId);
|
||||||
|
if (folder != null)
|
||||||
|
{
|
||||||
|
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Browse, folder.PermissionList))
|
||||||
|
{
|
||||||
|
_files = await FileService.GetFilesAsync(FolderId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_files = new List<File>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_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);
|
||||||
|
FileId = -1;
|
||||||
|
GetFolderPermission();
|
||||||
|
await SetImage();
|
||||||
|
await GetFiles();
|
||||||
|
await OnSelectFolder.InvokeAsync(FolderId);
|
||||||
|
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);
|
||||||
|
await SetImage();
|
||||||
|
#pragma warning disable CS0618
|
||||||
|
await OnSelect.InvokeAsync(FileId);
|
||||||
|
#pragma warning restore CS0618
|
||||||
|
await OnSelectFile.InvokeAsync(FileId);
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task UploadFiles()
|
private async Task UploadFiles()
|
||||||
{
|
{
|
||||||
_message = string.Empty;
|
_message = string.Empty;
|
||||||
@ -408,7 +426,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// upload files
|
// upload files
|
||||||
var success = await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken, jwt, chunksize, tokenSource.Token);
|
var success = await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken, jwt, chunksize, AnonymizeUploadFilenames, tokenSource.Token);
|
||||||
|
|
||||||
// reset progress indicators
|
// reset progress indicators
|
||||||
if (ShowProgress)
|
if (ShowProgress)
|
||||||
@ -430,35 +448,35 @@
|
|||||||
_message = Localizer["Success.File.Upload"];
|
_message = Localizer["Success.File.Upload"];
|
||||||
_messagetype = MessageType.Success;
|
_messagetype = MessageType.Success;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await logger.LogError("File Upload Failed {Files}", uploads);
|
|
||||||
_message = Localizer["Error.File.Upload"];
|
|
||||||
_messagetype = MessageType.Error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Folder == Constants.PackagesFolder)
|
FileId = -1;
|
||||||
{
|
if (Folder != Constants.PackagesFolder && !AnonymizeUploadFilenames)
|
||||||
await OnUpload.InvokeAsync(-1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
// set FileId to first file in upload collection
|
// set FileId to first file in upload collection
|
||||||
var file = await FileService.GetFileAsync(int.Parse(folder), uploads[0].Split(":")[0]);
|
var file = await FileService.GetFileAsync(int.Parse(folder), uploads[0].Split(":")[0]);
|
||||||
if (file != null)
|
if (file != null)
|
||||||
{
|
{
|
||||||
FileId = file.FileId;
|
FileId = file.FileId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await SetImage();
|
await SetImage();
|
||||||
|
|
||||||
|
await OnUpload.InvokeAsync(FileId);
|
||||||
#pragma warning disable CS0618
|
#pragma warning disable CS0618
|
||||||
await OnSelect.InvokeAsync(FileId);
|
await OnSelect.InvokeAsync(FileId);
|
||||||
#pragma warning restore CS0618
|
#pragma warning restore CS0618
|
||||||
await OnSelectFile.InvokeAsync(FileId);
|
await OnSelectFile.InvokeAsync(FileId);
|
||||||
await OnUpload.InvokeAsync(FileId);
|
|
||||||
}
|
|
||||||
await GetFiles();
|
await GetFiles();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await logger.LogError("File Upload Failed {Files}", uploads);
|
||||||
|
_message = Localizer["Error.File.Upload"];
|
||||||
|
_messagetype = MessageType.Error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -471,7 +489,6 @@
|
|||||||
finally {
|
finally {
|
||||||
tokenSource.Dispose();
|
tokenSource.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -501,13 +518,15 @@
|
|||||||
_messagetype = MessageType.Success;
|
_messagetype = MessageType.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
await GetFiles();
|
|
||||||
FileId = -1;
|
FileId = -1;
|
||||||
await SetImage();
|
await SetImage();
|
||||||
|
|
||||||
#pragma warning disable CS0618
|
#pragma warning disable CS0618
|
||||||
await OnSelect.InvokeAsync(FileId);
|
await OnSelect.InvokeAsync(FileId);
|
||||||
#pragma warning restore CS0618
|
#pragma warning restore CS0618
|
||||||
await OnSelectFile.InvokeAsync(FileId);
|
await OnSelectFile.InvokeAsync(FileId);
|
||||||
|
|
||||||
|
await GetFiles();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -533,15 +552,16 @@
|
|||||||
public async Task Refresh(int fileId)
|
public async Task Refresh(int fileId)
|
||||||
{
|
{
|
||||||
await GetFiles();
|
await GetFiles();
|
||||||
|
FileId = -1;
|
||||||
if (fileId != -1)
|
if (fileId != -1)
|
||||||
{
|
{
|
||||||
var file = _files.Where(item => item.FileId == fileId).FirstOrDefault();
|
var file = _files.Where(item => item.FileId == fileId).FirstOrDefault();
|
||||||
if (file != null)
|
if (file != null)
|
||||||
{
|
{
|
||||||
FileId = file.FileId;
|
FileId = file.FileId;
|
||||||
|
}
|
||||||
|
}
|
||||||
await SetImage();
|
await SetImage();
|
||||||
}
|
|
||||||
}
|
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,29 +4,43 @@
|
|||||||
|
|
||||||
@if (!string.IsNullOrEmpty(Message))
|
@if (!string.IsNullOrEmpty(Message))
|
||||||
{
|
{
|
||||||
|
@if (_style == MessageStyle.Alert)
|
||||||
|
{
|
||||||
<div class="@_classname alert-dismissible fade show mb-3" role="alert">
|
<div class="@_classname alert-dismissible fade show mb-3" role="alert">
|
||||||
@((MarkupString)Message)
|
@((MarkupString)Message)
|
||||||
@if (Type == MessageType.Error && PageState != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
@if (Type == MessageType.Error && PageState != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
<NavLink class="ms-2" href="@NavigateUrl("admin/log")">View Details</NavLink>
|
<NavLink class="ms-2" href="@NavigateUrl("admin/log")">View Details</NavLink>
|
||||||
}
|
}
|
||||||
@if (ModuleState != null)
|
<form method="post" class="app-form-inline" @formname="ModuleMessageForm" @onsubmit="CloseMessage" data-enhance>
|
||||||
{
|
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||||
@if (ModuleState.RenderMode == RenderModes.Static)
|
<button type="submit" class="btn-close" data-dismiss="alert" aria-label="close"></button>
|
||||||
{
|
</form>
|
||||||
<a href="@NavigationManager.Uri" class="btn-close" data-dismiss="alert" aria-label="close"></a>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<button type="button" class="btn-close" data-dismiss="alert" aria-label="close" @onclick="CloseMessage"></button>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (_style == MessageStyle.Toast)
|
||||||
|
{
|
||||||
|
<div class="app-modulemessage-toast bottom-0 end-0">
|
||||||
|
<div class="@_classname alert-dismissible fade show mb-3" role="alert">
|
||||||
|
@((MarkupString)Message)
|
||||||
|
@if (Type == MessageType.Error && PageState != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
|
{
|
||||||
|
<NavLink class="ms-2" href="@NavigateUrl("admin/log")">View Details</NavLink>
|
||||||
|
}
|
||||||
|
<form method="post" class="app-form-inline" @formname="ModuleMessageForm" @onsubmit="CloseMessage" data-enhance>
|
||||||
|
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||||
|
<button type="submit" class="btn-close" data-dismiss="alert" aria-label="close"></button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private string _message = string.Empty;
|
private string _message = string.Empty;
|
||||||
private string _classname = string.Empty;
|
private string _classname = string.Empty;
|
||||||
|
private MessageStyle _style;
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Message { get; set; }
|
public string Message { get; set; }
|
||||||
@ -34,6 +48,9 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public MessageType Type { get; set; }
|
public MessageType Type { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public MessageStyle Style { get; set; } = MessageStyle.Alert;
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public RenderModeBoundary Parent { get; set; }
|
public RenderModeBoundary Parent { get; set; }
|
||||||
|
|
||||||
@ -43,6 +60,11 @@
|
|||||||
if (!string.IsNullOrEmpty(_message))
|
if (!string.IsNullOrEmpty(_message))
|
||||||
{
|
{
|
||||||
_classname = GetMessageType(Type);
|
_classname = GetMessageType(Type);
|
||||||
|
_style = Style;
|
||||||
|
if (Type == MessageType.Error)
|
||||||
|
{
|
||||||
|
_style = MessageStyle.Alert; // errors should always be displayed as alerts
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,9 +89,10 @@
|
|||||||
|
|
||||||
return classname;
|
return classname;
|
||||||
}
|
}
|
||||||
private void CloseMessage(MouseEventArgs e)
|
|
||||||
|
private void CloseMessage()
|
||||||
{
|
{
|
||||||
if(Parent != null)
|
if (Parent != null)
|
||||||
{
|
{
|
||||||
Parent.DismissMessage();
|
Parent.DismissMessage();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
@if (_permissions != null)
|
@if (_permissions != null)
|
||||||
{
|
{
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<table class="table table-borderless">
|
<table class="table table-borderless">
|
||||||
@ -28,7 +28,7 @@
|
|||||||
@foreach (var permissionname in _permissionnames)
|
@foreach (var permissionname in _permissionnames)
|
||||||
{
|
{
|
||||||
<td style="text-align: center;">
|
<td style="text-align: center;">
|
||||||
<TriStateCheckBox Value=@GetPermissionValue(permissionname, role.Name, -1) Disabled="@GetPermissionDisabled(permissionname, role.Name)" OnChange="@(e => PermissionChanged(e, permissionname, role.Name, -1))" />
|
<TriStateCheckBox Value="@GetPermissionValue(permissionname, role.Name, -1)" Disabled="@GetPermissionDisabled(permissionname, role.Name)" OnChange="@(e => PermissionChanged(e, permissionname, role.Name, -1))" />
|
||||||
</td>
|
</td>
|
||||||
}
|
}
|
||||||
</tr>
|
</tr>
|
||||||
@ -64,7 +64,7 @@
|
|||||||
@foreach (var permissionname in _permissionnames)
|
@foreach (var permissionname in _permissionnames)
|
||||||
{
|
{
|
||||||
<td style="text-align: center; width: 1px;">
|
<td style="text-align: center; width: 1px;">
|
||||||
<TriStateCheckBox Value=@GetPermissionValue(permissionname, "", user.UserId) Disabled="@GetPermissionDisabled(permissionname, "")" OnChange="@(e => PermissionChanged(e, permissionname, "", user.UserId))" />
|
<TriStateCheckBox Value="@GetPermissionValue(permissionname, "", user.UserId)" Disabled="@GetPermissionDisabled(permissionname, "")" OnChange="@(e => PermissionChanged(e, permissionname, "", user.UserId))" />
|
||||||
</td>
|
</td>
|
||||||
}
|
}
|
||||||
</tr>
|
</tr>
|
||||||
@ -88,7 +88,7 @@
|
|||||||
<ModuleMessage Type="MessageType.Warning" Message="@_message" />
|
<ModuleMessage Type="MessageType.Warning" Message="@_message" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@ -119,10 +119,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
_roles = await RoleService.GetRolesAsync(ModuleState.SiteId, true);
|
_roles = await RoleService.GetRolesAsync(ModuleState.SiteId, true);
|
||||||
if (!UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
_roles.RemoveAll(item => item.Name == RoleNames.Host); // remove host role
|
||||||
{
|
|
||||||
_roles.RemoveAll(item => item.Name == RoleNames.Host);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get permission names
|
// get permission names
|
||||||
if (string.IsNullOrEmpty(PermissionNames))
|
if (string.IsNullOrEmpty(PermissionNames))
|
||||||
@ -222,24 +219,24 @@
|
|||||||
|
|
||||||
private bool GetPermissionDisabled(string permissionName, string roleName)
|
private bool GetPermissionDisabled(string permissionName, string roleName)
|
||||||
{
|
{
|
||||||
|
var disabled = false;
|
||||||
|
|
||||||
|
// administrator role permissions can only be changed by a host
|
||||||
if (roleName == RoleNames.Admin && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
if (roleName == RoleNames.Admin && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
return true;
|
disabled = true;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (GetEntityName(permissionName) != EntityName && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PermissionChanged(bool? value, string permissionName, string roleName, int userId)
|
// API permissions can only be changed by an administrator
|
||||||
|
if (GetEntityName(permissionName) != EntityName && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
|
{
|
||||||
|
disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool? PermissionChanged(bool? value, string permissionName, string roleName, int userId)
|
||||||
{
|
{
|
||||||
if (roleName != "")
|
if (roleName != "")
|
||||||
{
|
{
|
||||||
@ -248,6 +245,14 @@
|
|||||||
{
|
{
|
||||||
_permissions.Remove(permission);
|
_permissions.Remove(permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// system roles cannot be denied - only custom roles can be denied
|
||||||
|
var role = _roles.FirstOrDefault(item => item.Name == roleName);
|
||||||
|
if (value != null && !value.Value && role.IsSystem)
|
||||||
|
{
|
||||||
|
value = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionName), GetPermissionName(permissionName), roleName, null, value.Value));
|
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionName), GetPermissionName(permissionName), roleName, null, value.Value));
|
||||||
@ -265,6 +270,7 @@
|
|||||||
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionName), GetPermissionName(permissionName), null, userId, value.Value));
|
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionName), GetPermissionName(permissionName), null, userId, value.Value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Dictionary<string, string>> GetUsers(string filter)
|
private async Task<Dictionary<string, string>> GetUsers(string filter)
|
||||||
@ -305,29 +311,20 @@
|
|||||||
|
|
||||||
private void ValidatePermissions()
|
private void ValidatePermissions()
|
||||||
{
|
{
|
||||||
// remove deny all users, unauthenticated, and registered users
|
|
||||||
var permissions = _permissions.Where(item => !item.IsAuthorized &&
|
|
||||||
(item.RoleName == RoleNames.Everyone || item.RoleName == RoleNames.Unauthenticated || item.RoleName == RoleNames.Registered)).ToList();
|
|
||||||
foreach (var permission in permissions)
|
|
||||||
{
|
|
||||||
_permissions.Remove(permission);
|
|
||||||
}
|
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
// remove deny administrators and host users
|
// remove host role permissions
|
||||||
permissions = _permissions.Where(item => !item.IsAuthorized &&
|
var permissions = _permissions.Where(item => item.RoleName == RoleNames.Host).ToList();
|
||||||
(item.RoleName == RoleNames.Admin || item.RoleName == RoleNames.Host)).ToList();
|
|
||||||
foreach (var permission in permissions)
|
foreach (var permission in permissions)
|
||||||
{
|
{
|
||||||
_permissions.Remove(permission);
|
_permissions.Remove(permission);
|
||||||
}
|
}
|
||||||
|
// add host role permissions if administrator role is not assigned (to prevent lockout)
|
||||||
foreach (var permissionname in _permissionnames)
|
foreach (var permissionname in _permissionnames)
|
||||||
{
|
{
|
||||||
// add administrators role if neither host or administrator is assigned
|
if (!_permissions.Any(item => item.EntityName == GetEntityName(permissionname) && item.PermissionName == GetPermissionName(permissionname) && item.RoleName == RoleNames.Admin))
|
||||||
if (!_permissions.Any(item => item.EntityName == GetEntityName(permissionname) && item.PermissionName == GetPermissionName(permissionname) &&
|
|
||||||
(item.RoleName == RoleNames.Admin || item.RoleName == RoleNames.Host)))
|
|
||||||
{
|
{
|
||||||
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionname), GetPermissionName(permissionname), RoleNames.Admin, null, true));
|
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionname), GetPermissionName(permissionname), RoleNames.Host, null, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -177,14 +177,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
public string Name => "QuillJS";
|
public string Name => "QuillJS Text Editor";
|
||||||
|
|
||||||
private string resourceType = "Oqtane.Modules.Controls.QuillJSTextEditor, Oqtane.Client";
|
private string resourceType = "Oqtane.Modules.Controls.QuillJSTextEditor, Oqtane.Client";
|
||||||
|
|
||||||
private bool _settingsLoaded;
|
private bool _settingsLoaded;
|
||||||
private bool _initialized = false;
|
private bool _initialized = false;
|
||||||
|
|
||||||
private QuillEditorInterop _interop;
|
private QuillJSTextEditorInterop _interop;
|
||||||
private FileManager _fileManager;
|
private FileManager _fileManager;
|
||||||
private string _activetab = "Rich";
|
private string _activetab = "Rich";
|
||||||
private bool _allowSettings = false;
|
private bool _allowSettings = false;
|
||||||
@ -246,14 +246,14 @@
|
|||||||
|
|
||||||
public override List<Resource> Resources { get; set; } = new List<Resource>()
|
public override List<Resource> Resources { get; set; } = new List<Resource>()
|
||||||
{
|
{
|
||||||
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill.min.js", Location = ResourceLocation.Body },
|
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/texteditors/quilljs/quill.min.js", Location = ResourceLocation.Body },
|
||||||
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-blot-formatter.min.js", Location = ResourceLocation.Body },
|
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/texteditors/quilljs/quill-blot-formatter.min.js", Location = ResourceLocation.Body },
|
||||||
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-interop.js", Location = ResourceLocation.Body }
|
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/texteditors/quilljs/quill-interop.js", Location = ResourceLocation.Body }
|
||||||
};
|
};
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
_interop = new QuillEditorInterop(JSRuntime);
|
_interop = new QuillJSTextEditorInterop(JSRuntime);
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(Placeholder))
|
if (string.IsNullOrEmpty(Placeholder))
|
||||||
{
|
{
|
||||||
@ -277,7 +277,7 @@
|
|||||||
{
|
{
|
||||||
// include CSS theme
|
// include CSS theme
|
||||||
var interop = new Interop(JSRuntime);
|
var interop = new Interop(JSRuntime);
|
||||||
await interop.IncludeLink("", "stylesheet", $"{PageState?.Alias.BaseUrl}/css/quill/quill.{_theme}.css", "text/css", "", "", "");
|
await interop.IncludeLink("", "stylesheet", $"{PageState?.Alias.BaseUrl}/css/texteditors/quilljs/quill.{_theme}.css", "text/css", "", "", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
await base.OnAfterRenderAsync(firstRender);
|
await base.OnAfterRenderAsync(firstRender);
|
||||||
@ -4,11 +4,11 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Oqtane.Modules.Controls
|
namespace Oqtane.Modules.Controls
|
||||||
{
|
{
|
||||||
public class QuillEditorInterop
|
public class QuillJSTextEditorInterop
|
||||||
{
|
{
|
||||||
private readonly IJSRuntime _jsRuntime;
|
private readonly IJSRuntime _jsRuntime;
|
||||||
|
|
||||||
public QuillEditorInterop(IJSRuntime jsRuntime)
|
public QuillJSTextEditorInterop(IJSRuntime jsRuntime)
|
||||||
{
|
{
|
||||||
_jsRuntime = jsRuntime;
|
_jsRuntime = jsRuntime;
|
||||||
}
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
@namespace Oqtane.Modules.Controls
|
||||||
|
@using Radzen
|
||||||
|
@using Radzen.Blazor
|
||||||
|
@inject DialogService DialogService
|
||||||
|
@inject IStringLocalizer<Oqtane.Modules.Controls.RadzenTextEditor> Localizer
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(_message))
|
||||||
|
{
|
||||||
|
<div class="rz-html-editor-dialog-item">
|
||||||
|
<div class="alert alert-warning alert-dismissible fade show mb-3" role="alert">
|
||||||
|
@((MarkupString)_message)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div class="rz-html-editor-dialog-item">
|
||||||
|
<FileManager @ref="_fileManager" Filter="@Filters" />
|
||||||
|
</div>
|
||||||
|
<div class="rz-html-editor-dialog-buttons">
|
||||||
|
<RadzenButton Text="@Localizer["InsertImage"]" Click="InsertImage" />
|
||||||
|
<RadzenButton Text="@Localizer["Cancel"]" Click="() => DialogService.Close()" ButtonStyle="ButtonStyle.Secondary" />
|
||||||
|
</div>
|
||||||
|
@code {
|
||||||
|
private FileManager _fileManager;
|
||||||
|
private string _message = string.Empty;
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string Filters { get; set; }
|
||||||
|
|
||||||
|
private void InsertImage()
|
||||||
|
{
|
||||||
|
_message = string.Empty;
|
||||||
|
var file = _fileManager.GetFile();
|
||||||
|
if (file != null)
|
||||||
|
{
|
||||||
|
var result = $"<img src=\"{file.Url}\" style=\"max-width: 100%\" alt=\"{file.Name}\" />";
|
||||||
|
DialogService.Close(result);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_message = Localizer["Message.Require.Image"];
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,148 @@
|
|||||||
|
@namespace Oqtane.Modules.Controls
|
||||||
|
@using Radzen
|
||||||
|
@using Radzen.Blazor
|
||||||
|
@using System.Text
|
||||||
|
@inject DialogService DialogService
|
||||||
|
@inject IStringLocalizer<Oqtane.Modules.Controls.RadzenTextEditor> Localizer
|
||||||
|
|
||||||
|
@if (_linkAttributes != null)
|
||||||
|
{
|
||||||
|
@if (!string.IsNullOrWhiteSpace(_message))
|
||||||
|
{
|
||||||
|
<div class="rz-html-editor-dialog-item">
|
||||||
|
<div class="alert alert-warning alert-dismissible fade show mb-3" role="alert">
|
||||||
|
@((MarkupString)_message)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div class="rz-html-editor-dialog-item">
|
||||||
|
<RadzenDropDown TValue="int" class="form-control" PopupStyle="color: var(--rz-input-value-color);" @bind-Value="_linkType" Data="_linkTypes" TextProperty="Value" ValueProperty="Key" />
|
||||||
|
</div>
|
||||||
|
@if (_linkType == 0)
|
||||||
|
{
|
||||||
|
<div class="rz-html-editor-dialog-item">
|
||||||
|
<RadzenTextBox class="form-control" @bind-Value="@_linkAttributes.Href" Placeholder="@Localizer["WebAddress"]" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="rz-html-editor-dialog-item">
|
||||||
|
<FileManager @ref="_fileManager" OnSelectFile="SelectFile" OnSelectFolder="SelectFile" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (_linkTextEditable)
|
||||||
|
{
|
||||||
|
<div class="rz-html-editor-dialog-item">
|
||||||
|
<RadzenTextBox class="form-control" @bind-Value="@_linkAttributes.InnerText" Placeholder="@Localizer["LinkText"]" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div class="rz-html-editor-dialog-item">
|
||||||
|
<RadzenDropDown TValue="bool" class="form-control" PopupStyle="color: var(--rz-input-value-color);" @bind-Value="_blank" Data="_linkTargets" TextProperty="Value" ValueProperty="Key" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div class="rz-html-editor-dialog-buttons">
|
||||||
|
<RadzenButton Text=@Localizer["InsertLink"] Click="InsertLink" />
|
||||||
|
<RadzenButton Text=@Localizer["Cancel"] Click="() => DialogService.Close()" ButtonStyle="ButtonStyle.Secondary" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
class LinkAttributes
|
||||||
|
{
|
||||||
|
public string InnerText { get; set; }
|
||||||
|
public string InnerHtml { get; set; }
|
||||||
|
public string Href { get; set; }
|
||||||
|
public string Target { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public RadzenHtmlEditor Editor { get; set; }
|
||||||
|
|
||||||
|
private IDictionary<int, string> _linkTypes;
|
||||||
|
private IDictionary<bool, string> _linkTargets;
|
||||||
|
private LinkAttributes _linkAttributes;
|
||||||
|
private bool _blank;
|
||||||
|
private int _linkType;
|
||||||
|
private string _message;
|
||||||
|
private bool _linkTextEditable;
|
||||||
|
private FileManager _fileManager;
|
||||||
|
private File _previousFile;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await base.OnInitializedAsync();
|
||||||
|
_linkAttributes = await Editor.GetSelectionAttributes<LinkAttributes>("a", new[] { "innerText", "href", "target" });
|
||||||
|
if (_linkAttributes.Target == "_blank")
|
||||||
|
{
|
||||||
|
_blank = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_linkTextEditable = string.IsNullOrWhiteSpace(_linkAttributes.InnerHtml) || _linkAttributes.InnerHtml == "<br>";
|
||||||
|
|
||||||
|
_linkTypes = new Dictionary<int, string>
|
||||||
|
{
|
||||||
|
{ 0, Localizer["WebLink"] },
|
||||||
|
{ 1, Localizer["FileLink"] }
|
||||||
|
};
|
||||||
|
|
||||||
|
_linkTargets = new Dictionary<bool, string>
|
||||||
|
{
|
||||||
|
{ false, Localizer["OpenInCurrentWindow"] },
|
||||||
|
{ true, Localizer["OpenInNewWindow"] }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectFile()
|
||||||
|
{
|
||||||
|
var file = _fileManager.GetFile();
|
||||||
|
if(file != null)
|
||||||
|
{
|
||||||
|
_linkAttributes.Href = file.Url;
|
||||||
|
if ((string.IsNullOrWhiteSpace(_linkAttributes.InnerText) || _linkAttributes.InnerText == _previousFile?.Name) && _linkTextEditable)
|
||||||
|
{
|
||||||
|
_linkAttributes.InnerText = file.Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_linkAttributes.Href = string.Empty;
|
||||||
|
if (_linkAttributes.InnerText == _previousFile?.Name)
|
||||||
|
{
|
||||||
|
_linkAttributes.InnerText = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_previousFile = file;
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InsertLink()
|
||||||
|
{
|
||||||
|
_message = string.Empty;
|
||||||
|
if (string.IsNullOrWhiteSpace(_linkAttributes.Href))
|
||||||
|
{
|
||||||
|
_message = _linkType == 1 ? Localizer["Message.Require.File"] : Localizer["Message.Require.WebAddress"];
|
||||||
|
}
|
||||||
|
else if (string.IsNullOrWhiteSpace(_linkAttributes.InnerText) && _linkTextEditable)
|
||||||
|
{
|
||||||
|
_message = Localizer["Message.Require.LinkText"];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(_message))
|
||||||
|
{
|
||||||
|
var html = new StringBuilder();
|
||||||
|
html.AppendFormat("<a href=\"{0}\"", _linkAttributes.Href);
|
||||||
|
if (_blank)
|
||||||
|
{
|
||||||
|
html.Append(" target=\"_blank\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
html.AppendFormat(">{0}</a>", string.IsNullOrWhiteSpace(_linkAttributes.InnerText) ? _linkAttributes.InnerHtml : _linkAttributes.InnerText);
|
||||||
|
|
||||||
|
DialogService.Close(html.ToString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
// This is just a placeholder file
|
||||||
|
// It is necessary for the documentation to successfully build this project.
|
||||||
|
// Reason is that docfx will run the .net compiler and find references
|
||||||
|
// to this class in the project.
|
||||||
|
// But since the real class is just a .razor file, ATM docfx will fail.
|
||||||
|
//
|
||||||
|
// Note added 2025-09-23 by @tvatavuk.
|
||||||
|
// We hope that as .net and docfx improve, the razor-compiler will work in that scenario
|
||||||
|
// as well, and this file can be removed.
|
||||||
|
|
||||||
|
namespace Oqtane.Modules.Controls;
|
||||||
|
public partial class RadzenTextEditor;
|
||||||
@ -0,0 +1,215 @@
|
|||||||
|
@using Microsoft.Extensions.Configuration
|
||||||
|
@using Oqtane.Interfaces
|
||||||
|
@using System.Text.RegularExpressions
|
||||||
|
@using Radzen
|
||||||
|
@using Radzen.Blazor
|
||||||
|
|
||||||
|
@namespace Oqtane.Modules.Controls
|
||||||
|
@inherits ModuleControlBase
|
||||||
|
@implements ITextEditor
|
||||||
|
@implements IDisposable
|
||||||
|
@inject Radzen.ThemeService ThemeService
|
||||||
|
@inject IRadzenEditorSettingService EditorSettingService
|
||||||
|
@inject DialogService DialogService
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject IStringLocalizer<Oqtane.Modules.Controls.RadzenTextEditor> Localizer
|
||||||
|
|
||||||
|
<RadzenTheme Theme="@RadzenEditorDefinitions.DefaultTheme" />
|
||||||
|
<RadzenComponents />
|
||||||
|
<RadzenHtmlEditor @ref="_editor" Visible="_visible" Placeholder="@Placeholder" style="@($"height: {Height}px;")"
|
||||||
|
@bind-Value="_value" Execute="OnExecute" class="rz-text-editor">
|
||||||
|
<ChildContent>
|
||||||
|
@_toolbar
|
||||||
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
|
{
|
||||||
|
<RadzenHtmlEditorCustomTool CommandName="Settings" Icon="settings" Title="@Localizer["Settings"]" />
|
||||||
|
}
|
||||||
|
</ChildContent>
|
||||||
|
|
||||||
|
</RadzenHtmlEditor>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private Oqtane.Modules.Controls.RadzenTextEditorInterop _interop;
|
||||||
|
private RadzenHtmlEditor _editor;
|
||||||
|
private string _value;
|
||||||
|
private bool _visible = false;
|
||||||
|
private string _theme;
|
||||||
|
private string _background;
|
||||||
|
private IList<string> _toolbarItems;
|
||||||
|
private RenderFragment _toolbar;
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string Placeholder { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public bool ReadOnly { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public int Height { get; set; } = 450;
|
||||||
|
|
||||||
|
public string Name => "Radzen HTML Editor";
|
||||||
|
|
||||||
|
public override List<Resource> Resources { get; set; } = new List<Resource>()
|
||||||
|
{
|
||||||
|
new Script("_content/Radzen.Blazor/Radzen.Blazor.js"),
|
||||||
|
new Script("js/texteditors/radzen/radzen-interop.js")
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
_interop = new Oqtane.Modules.Controls.RadzenTextEditorInterop(JSRuntime);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
await base.OnAfterRenderAsync(firstRender);
|
||||||
|
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
await interop.IncludeLink("", "stylesheet", $"{PageState?.Alias.BaseUrl}/css/texteditors/radzen/radzentexteditor.css", "text/css", "", "", "");
|
||||||
|
await LoadSettings();
|
||||||
|
_visible = true;
|
||||||
|
StateHasChanged();
|
||||||
|
|
||||||
|
await _interop.Initialize(_editor.Element);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(_theme))
|
||||||
|
{
|
||||||
|
ThemeService.SetTheme(_theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(_background))
|
||||||
|
{
|
||||||
|
var backgroundColor = RadzenEditorDefinitions.TransparentBackgroundColor;
|
||||||
|
switch (_background)
|
||||||
|
{
|
||||||
|
case "Light":
|
||||||
|
backgroundColor = RadzenEditorDefinitions.LightBackgroundColor;
|
||||||
|
break;
|
||||||
|
case "Dark":
|
||||||
|
backgroundColor = RadzenEditorDefinitions.DarkBackgroundColor;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
await _interop.SetBackgroundColor(_editor.Element, backgroundColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize(string content)
|
||||||
|
{
|
||||||
|
_value = !string.IsNullOrEmpty(content) ? content : string.Empty;
|
||||||
|
DialogService.OnOpen += OnDialogOpened;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (DialogService != null)
|
||||||
|
{
|
||||||
|
DialogService.OnOpen -= OnDialogOpened;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetContent()
|
||||||
|
{
|
||||||
|
await Task.CompletedTask;
|
||||||
|
|
||||||
|
return _value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadSettings()
|
||||||
|
{
|
||||||
|
var scope = await EditorSettingService.GetSettingScopeAsync(ModuleState.ModuleId);
|
||||||
|
var editorSetting = scope == 1
|
||||||
|
? await EditorSettingService.LoadSettingsFromModuleAsync(ModuleState.ModuleId)
|
||||||
|
: await EditorSettingService.LoadSettingsFromSiteAsync(PageState.Site.SiteId);
|
||||||
|
|
||||||
|
_theme = editorSetting.Theme;
|
||||||
|
_background = editorSetting.Background;
|
||||||
|
_toolbarItems = editorSetting.ToolbarItems.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToList();
|
||||||
|
_toolbar = SetupToolbarItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
private RenderFragment SetupToolbarItems()
|
||||||
|
{
|
||||||
|
return builder =>
|
||||||
|
{
|
||||||
|
var sequence = 0;
|
||||||
|
foreach (var item in _toolbarItems)
|
||||||
|
{
|
||||||
|
if (RadzenEditorDefinitions.ToolbarItems.ContainsKey(item))
|
||||||
|
{
|
||||||
|
sequence = RadzenEditorDefinitions.ToolbarItems[item](builder, sequence);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnExecute(HtmlEditorExecuteEventArgs args)
|
||||||
|
{
|
||||||
|
switch(args.CommandName)
|
||||||
|
{
|
||||||
|
case "InsertImage":
|
||||||
|
await InsertImage(args.Editor);
|
||||||
|
break;
|
||||||
|
case "InsertLink":
|
||||||
|
await InsertLink(args.Editor);
|
||||||
|
break;
|
||||||
|
case "Settings":
|
||||||
|
await UpdateSettings(args.Editor);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task InsertImage(RadzenHtmlEditor editor)
|
||||||
|
{
|
||||||
|
await editor.SaveSelectionAsync();
|
||||||
|
|
||||||
|
var result = await DialogService.OpenAsync<RadzenFileManagerDialog>(Localizer["DialogTitle.SelectImage"], new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "Filters", PageState.Site.ImageFiles }
|
||||||
|
}, new DialogOptions { CssClass = "rz-text-editor-dialog" });
|
||||||
|
|
||||||
|
await editor.RestoreSelectionAsync();
|
||||||
|
|
||||||
|
if (result != null)
|
||||||
|
{
|
||||||
|
await editor.ExecuteCommandAsync(HtmlEditorCommands.InsertHtml, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task InsertLink(RadzenHtmlEditor editor)
|
||||||
|
{
|
||||||
|
await editor.SaveSelectionAsync();
|
||||||
|
|
||||||
|
var result = await DialogService.OpenAsync<RadzenInsertLinkDialog>(Localizer["DialogTitle.InsertLink"], new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "Editor", editor }
|
||||||
|
}, new DialogOptions { CssClass = "rz-text-editor-dialog" });
|
||||||
|
|
||||||
|
await editor.RestoreSelectionAsync();
|
||||||
|
|
||||||
|
if (result != null)
|
||||||
|
{
|
||||||
|
await editor.ExecuteCommandAsync(HtmlEditorCommands.InsertHtml, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UpdateSettings(RadzenHtmlEditor editor)
|
||||||
|
{
|
||||||
|
await editor.SaveSelectionAsync();
|
||||||
|
|
||||||
|
var result = await DialogService.OpenAsync<RadzenTextEditorSettingsDialog>(Localizer["Settings"], null, new DialogOptions { Width = "650px" });
|
||||||
|
if (result == true)
|
||||||
|
{
|
||||||
|
NavigationManager.NavigateTo(NavigationManager.Uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
await editor.RestoreSelectionAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnDialogOpened(string title, Type componentType, Dictionary<string, object> parameters, DialogOptions options)
|
||||||
|
{
|
||||||
|
await _interop.UpdateDialogLayout(_editor.Element);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,85 @@
|
|||||||
|
using Microsoft.AspNetCore.Components.Rendering;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Oqtane.Modules.Controls
|
||||||
|
{
|
||||||
|
public sealed class RadzenEditorDefinitions
|
||||||
|
{
|
||||||
|
public static IStringLocalizer<Oqtane.Modules.Controls.RadzenTextEditor> Localizer { get; internal set; }
|
||||||
|
|
||||||
|
public const string TransparentBackgroundColor = "rgba(0, 0, 0, 0)";
|
||||||
|
|
||||||
|
public const string LightBackgroundColor = "rgba(255, 255, 255, 1)";
|
||||||
|
|
||||||
|
public const string DarkBackgroundColor = "rgba(0, 0, 0, 1)";
|
||||||
|
|
||||||
|
public const string DefaultTheme = "default";
|
||||||
|
|
||||||
|
public const string DefaultBackground = "Default";
|
||||||
|
|
||||||
|
public static readonly IDictionary<string, Func<RenderTreeBuilder, int, int>> ToolbarItems = new Dictionary<string, Func<RenderTreeBuilder, int, int>>()
|
||||||
|
{
|
||||||
|
{ "AlignCenter", (builder, sequence) => CreateFragment(builder, sequence, "AlignCenter", "RadzenHtmlEditorAlignCenter") },
|
||||||
|
{ "AlignLeft", (builder, sequence) => CreateFragment(builder, sequence, "AlignLeft", "RadzenHtmlEditorAlignLeft") },
|
||||||
|
{ "AlignRight", (builder, sequence) => CreateFragment(builder, sequence, "AlignRight", "RadzenHtmlEditorAlignRight") },
|
||||||
|
{ "Background", (builder, sequence) => CreateFragment(builder, sequence, "Background", "RadzenHtmlEditorBackground") },
|
||||||
|
{ "Color", (builder, sequence) => CreateFragment(builder, sequence, "Color", "RadzenHtmlEditorColor") },
|
||||||
|
{ "FontName", (builder, sequence) => CreateFragment(builder, sequence, "FontName", "RadzenHtmlEditorFontName") },
|
||||||
|
{ "FontSize", (builder, sequence) => CreateFragment(builder, sequence, "FontSize", "RadzenHtmlEditorFontSize") },
|
||||||
|
{ "FormatBlock", (builder, sequence) => CreateFragment(builder, sequence, "FormatBlock", "RadzenHtmlEditorFormatBlock") },
|
||||||
|
{ "Indent", (builder, sequence) => CreateFragment(builder, sequence, "Indent", "RadzenHtmlEditorIndent") },
|
||||||
|
{ "InsertImage", (builder, sequence) => CreateFragment(builder, sequence, "InsertImage", "RadzenHtmlEditorCustomTool", "InsertImage", "image") },
|
||||||
|
{ "Italic", (builder, sequence) => CreateFragment(builder, sequence, "Italic", "RadzenHtmlEditorItalic") },
|
||||||
|
{ "Justify", (builder, sequence) => CreateFragment(builder, sequence, "Justify", "RadzenHtmlEditorJustify") },
|
||||||
|
{ "Link", (builder, sequence) => CreateFragment(builder, sequence, "InsertLink", "RadzenHtmlEditorCustomTool", "InsertLink", "insert_link") },
|
||||||
|
{ "OrderedList", (builder, sequence) => CreateFragment(builder, sequence, "OrderedList", "RadzenHtmlEditorOrderedList") },
|
||||||
|
{ "Outdent", (builder, sequence) => CreateFragment(builder, sequence, "Outdent", "RadzenHtmlEditorOutdent") },
|
||||||
|
{ "Redo", (builder, sequence) => CreateFragment(builder, sequence, "Redo", "RadzenHtmlEditorRedo") },
|
||||||
|
{ "RemoveFormat", (builder, sequence) => CreateFragment(builder, sequence, "RemoveFormat", "RadzenHtmlEditorRemoveFormat") },
|
||||||
|
{ "Separator", (builder, sequence) => CreateFragment(builder, sequence, "Separator", "RadzenHtmlEditorSeparator") },
|
||||||
|
{ "Source", (builder, sequence) => CreateFragment(builder, sequence, "Source", "RadzenHtmlEditorSource") },
|
||||||
|
{ "StrikeThrough", (builder, sequence) => CreateFragment(builder, sequence, "StrikeThrough", "RadzenHtmlEditorStrikeThrough") },
|
||||||
|
{ "Subscript", (builder, sequence) => CreateFragment(builder, sequence, "Subscript", "RadzenHtmlEditorSubscript") },
|
||||||
|
{ "Superscript", (builder, sequence) => CreateFragment(builder, sequence, "Superscript", "RadzenHtmlEditorSuperscript") },
|
||||||
|
{ "Underline", (builder, sequence) => CreateFragment(builder, sequence, "Underline", "RadzenHtmlEditorUnderline") },
|
||||||
|
{ "Undo", (builder, sequence) => CreateFragment(builder, sequence, "Undo", "RadzenHtmlEditorUndo") },
|
||||||
|
{ "Unlink", (builder, sequence) => CreateFragment(builder, sequence, "Unlink", "RadzenHtmlEditorUnlink") },
|
||||||
|
{ "UnorderedList", (builder, sequence) => CreateFragment(builder, sequence, "UnorderedList", "RadzenHtmlEditorUnorderedList") },
|
||||||
|
};
|
||||||
|
|
||||||
|
public static readonly string DefaultToolbarItems = "Undo,Redo,Separator,FontName,FontSize,FormatBlock,Bold,Italic,Underline,StrikeThrough,Separator,AlignLeft,AlignCenter,AlignRight,Justify,Separator,Indent,Outdent,UnorderedList,OrderedList,Separator,Color,Background,RemoveFormat,Separator,Subscript,Superscript,Separator,Link,Unlink,InsertImage,Separator,Source";
|
||||||
|
|
||||||
|
private static int CreateFragment(RenderTreeBuilder builder, int sequence, string name, string typeName, string commaneName = "", string icon = "")
|
||||||
|
{
|
||||||
|
var fullTypeName = $"Radzen.Blazor.{typeName}, Radzen.Blazor";
|
||||||
|
var type = Type.GetType(fullTypeName);
|
||||||
|
if (type != null)
|
||||||
|
{
|
||||||
|
var title = Localizer[$"{name}.Title"];
|
||||||
|
var placeholder = Localizer[$"{name}.Placeholder"];
|
||||||
|
builder.OpenComponent(sequence++, type);
|
||||||
|
if (!string.IsNullOrEmpty(title) && title != $"{name}.Title" && type.GetProperty("Title") != null)
|
||||||
|
{
|
||||||
|
builder.AddAttribute(sequence++, "Title", title);
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(placeholder) && placeholder != $"{name}.Placeholder" && type.GetProperty("Placeholder") != null)
|
||||||
|
{
|
||||||
|
builder.AddAttribute(sequence++, "Placeholder", placeholder);
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(commaneName) && type.GetProperty("CommandName") != null)
|
||||||
|
{
|
||||||
|
builder.AddAttribute(sequence++, "CommandName", commaneName);
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(icon) && type.GetProperty("Icon") != null)
|
||||||
|
{
|
||||||
|
builder.AddAttribute(sequence++, "Icon", icon);
|
||||||
|
}
|
||||||
|
builder.CloseComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
return sequence;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,60 @@
|
|||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Oqtane.Modules.Controls
|
||||||
|
{
|
||||||
|
public class RadzenTextEditorInterop
|
||||||
|
{
|
||||||
|
private readonly IJSRuntime _jsRuntime;
|
||||||
|
|
||||||
|
public RadzenTextEditorInterop(IJSRuntime jsRuntime)
|
||||||
|
{
|
||||||
|
_jsRuntime = jsRuntime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Initialize(ElementReference editor)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_jsRuntime.InvokeVoidAsync("Oqtane.RadzenTextEditor.initialize", editor);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SetBackgroundColor(ElementReference editor, string color)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_jsRuntime.InvokeVoidAsync(
|
||||||
|
"Oqtane.RadzenTextEditor.setBackgroundColor",
|
||||||
|
editor, color);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task UpdateDialogLayout(ElementReference editor)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_jsRuntime.InvokeVoidAsync("Oqtane.RadzenTextEditor.updateDialogLayout", editor);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
namespace Oqtane.Modules.Controls
|
||||||
|
{
|
||||||
|
public class RadzenEditorSetting
|
||||||
|
{
|
||||||
|
public string Theme { get; set; }
|
||||||
|
|
||||||
|
public string Background { get; set; }
|
||||||
|
|
||||||
|
public string ToolbarItems { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,222 @@
|
|||||||
|
@namespace Oqtane.Modules.Controls
|
||||||
|
@using System.IO
|
||||||
|
@using Radzen
|
||||||
|
@using Radzen.Blazor
|
||||||
|
@inherits ModuleControlBase
|
||||||
|
@inject DialogService DialogService
|
||||||
|
@inject Radzen.ThemeService ThemeService
|
||||||
|
@inject ISettingService SettingService
|
||||||
|
@inject IRadzenEditorSettingService EditorSettingService
|
||||||
|
@inject IStringLocalizer<Oqtane.Modules.Controls.RadzenTextEditor> Localizer
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-sm-3">
|
||||||
|
@Localizer["Scope"]
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-9">
|
||||||
|
<RadzenRadioButtonList @bind-Value="@_settingScope" TValue="int" Change="OnScopeChanged">
|
||||||
|
<Items>
|
||||||
|
<RadzenRadioButtonListItem Text="@Localizer["Site"]" Value="0" />
|
||||||
|
<RadzenRadioButtonListItem Text="@Localizer["Module"]" Value="1" />
|
||||||
|
</Items>
|
||||||
|
</RadzenRadioButtonList>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-2">
|
||||||
|
<div class="col-12 col-sm-3">
|
||||||
|
@Localizer["Theme"]
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-9">
|
||||||
|
<RadzenDropDown @bind-Value="_theme" TValue="string" Data="@_themes" Style="width: 100%;">
|
||||||
|
<Template>
|
||||||
|
<span>@Localizer[$"theme.{context}"]</span>
|
||||||
|
</Template>
|
||||||
|
</RadzenDropDown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-2">
|
||||||
|
<div class="col-12 col-sm-3">
|
||||||
|
@Localizer["Background"]
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-9">
|
||||||
|
<RadzenDropDown @bind-Value="_background" TValue="string" Data="_backgroundColors" Style="width: 100%;">
|
||||||
|
<Template>
|
||||||
|
<span>@Localizer[context]</span>
|
||||||
|
</Template>
|
||||||
|
</RadzenDropDown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-2">
|
||||||
|
<div class="col-12 col-sm-3">
|
||||||
|
@Localizer["Toolbar"]
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-9">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-sm-7">
|
||||||
|
<RadzenDropDown TValue="string" @bind-Value="_addToolbarItem" Data="@RadzenEditorDefinitions.ToolbarItems.Keys" Style="width: 100%;">
|
||||||
|
</RadzenDropDown>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-5 text-end">
|
||||||
|
<button type="button" class="btn btn-primary" @onclick="AddToolbarItem">@Localizer["Add"]</button>
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="ResetToolbarItem">@Localizer["Reset"]</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-2" style="max-height: 500px; overflow-y: scroll;">
|
||||||
|
<div class="col">
|
||||||
|
<RadzenDropZoneContainer TItem="ToolbarItem" Data="_toolbarItems"
|
||||||
|
ItemSelector="@((i, z) => true)"
|
||||||
|
CanDrop="@((i) => true)"
|
||||||
|
Drop="OnToolbarItemDrop"
|
||||||
|
ItemRender="OnToolbarItemRender">
|
||||||
|
<ChildContent>
|
||||||
|
<RadzenDropZone TItem="ToolbarItem" class="rounded">
|
||||||
|
</RadzenDropZone>
|
||||||
|
</ChildContent>
|
||||||
|
<Template>
|
||||||
|
<div>
|
||||||
|
<strong>@context.Name</strong>
|
||||||
|
<RadzenButton Icon="delete" Click="@((e) => DeleteToolbarItem(context))" Size="ButtonSize.ExtraSmall" ButtonStyle="ButtonStyle.Light" />
|
||||||
|
</div>
|
||||||
|
</Template>
|
||||||
|
</RadzenDropZoneContainer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2 text-end">
|
||||||
|
<RadzenButton Text="OK" Click=@OnOkClick />
|
||||||
|
<RadzenButton Text="Cancel" Click=@OnCancelClick ButtonStyle="ButtonStyle.Secondary" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private readonly IList<string> _themes = new List<string>
|
||||||
|
{
|
||||||
|
"default",
|
||||||
|
"dark",
|
||||||
|
"material",
|
||||||
|
"material-dark",
|
||||||
|
"standard",
|
||||||
|
"standard-dark",
|
||||||
|
"humanistic",
|
||||||
|
"humanistic-dark",
|
||||||
|
"software",
|
||||||
|
"software-dark"
|
||||||
|
};
|
||||||
|
private readonly IList<string> _backgroundColors = new List<string> { "Default", "Light", "Dark" };
|
||||||
|
|
||||||
|
private int _settingScope;
|
||||||
|
private string _theme;
|
||||||
|
private string _background;
|
||||||
|
private IList<ToolbarItem> _toolbarItems = new List<ToolbarItem>();
|
||||||
|
private string _addToolbarItem;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
_settingScope = await EditorSettingService.GetSettingScopeAsync(ModuleState.ModuleId);
|
||||||
|
|
||||||
|
await LoadSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<RadzenEditorSetting> LoadSettingsFromModule()
|
||||||
|
{
|
||||||
|
return await EditorSettingService.LoadSettingsFromModuleAsync(ModuleState.ModuleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<RadzenEditorSetting> LoadSettingsFromSite()
|
||||||
|
{
|
||||||
|
return await EditorSettingService.LoadSettingsFromSiteAsync(PageState.Site.SiteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadSettings()
|
||||||
|
{
|
||||||
|
var editorSetting = _settingScope == 1 ? await LoadSettingsFromModule() : await LoadSettingsFromSite();
|
||||||
|
_theme = editorSetting.Theme;
|
||||||
|
_background = editorSetting.Background;
|
||||||
|
_toolbarItems = editorSetting.ToolbarItems.Split(',').Select((v, i) =>
|
||||||
|
{
|
||||||
|
return new ToolbarItem { Key = i, Name = v };
|
||||||
|
}).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnScopeChanged()
|
||||||
|
{
|
||||||
|
await LoadSettings();
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddToolbarItem()
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(_addToolbarItem))
|
||||||
|
{
|
||||||
|
_toolbarItems.Add(new ToolbarItem { Key = _toolbarItems.Count, Name = _addToolbarItem });
|
||||||
|
_addToolbarItem = string.Empty;
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResetToolbarItem()
|
||||||
|
{
|
||||||
|
_toolbarItems = RadzenEditorDefinitions.DefaultToolbarItems.Split(',').Select((v, i) =>
|
||||||
|
{
|
||||||
|
return new ToolbarItem { Key = i, Name = v };
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeleteToolbarItem(ToolbarItem item)
|
||||||
|
{
|
||||||
|
_toolbarItems.Remove(item);
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCancelClick()
|
||||||
|
{
|
||||||
|
DialogService.Close(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnOkClick()
|
||||||
|
{
|
||||||
|
var editorSetting = new RadzenEditorSetting
|
||||||
|
{
|
||||||
|
Theme = _theme,
|
||||||
|
Background = _background,
|
||||||
|
ToolbarItems = string.Join(",", _toolbarItems.Select(i => i.Name))
|
||||||
|
};
|
||||||
|
await EditorSettingService.UpdateSettingScopeAsync(ModuleState.ModuleId, _settingScope);
|
||||||
|
if (_settingScope == 1)
|
||||||
|
{
|
||||||
|
await EditorSettingService.SaveModuleSettingsAsync(ModuleState.ModuleId, editorSetting);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await EditorSettingService.SaveSiteSettingsAsync(PageState.Site.SiteId, editorSetting);
|
||||||
|
}
|
||||||
|
|
||||||
|
DialogService.Close(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnToolbarItemDrop(RadzenDropZoneItemEventArgs<ToolbarItem> args)
|
||||||
|
{
|
||||||
|
if (args.ToItem != null && args.ToItem.Key != args.Item.Key)
|
||||||
|
{
|
||||||
|
_toolbarItems.Remove(args.Item);
|
||||||
|
_toolbarItems.Insert(_toolbarItems.IndexOf(args.ToItem), args.Item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnToolbarItemRender(RadzenDropZoneItemRenderEventArgs<ToolbarItem> args)
|
||||||
|
{
|
||||||
|
args.Attributes.Add("class", "rz-card rz-variant-flat rz-background-color-primary-lighter rz-color-on-primary-lighter rz-p-2 d-inline-block ms-1 mt-1");
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ToolbarItem
|
||||||
|
{
|
||||||
|
public int Key { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,7 +7,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
public string Name => "TextArea";
|
public string Name => "Basic Text Editor";
|
||||||
|
|
||||||
private ElementReference _editor;
|
private ElementReference _editor;
|
||||||
private string _content;
|
private string _content;
|
||||||
@ -16,7 +16,7 @@
|
|||||||
public bool Disabled { get; set; }
|
public bool Disabled { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public Action<bool?> OnChange { get; set; }
|
public Func<bool?, bool?> OnChange { get; set; }
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
@ -41,12 +41,14 @@
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_value = OnChange(_value);
|
||||||
SetImage();
|
SetImage();
|
||||||
OnChange(_value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetImage()
|
private void SetImage()
|
||||||
|
{
|
||||||
|
if (!Disabled)
|
||||||
{
|
{
|
||||||
switch (_value)
|
switch (_value)
|
||||||
{
|
{
|
||||||
@ -63,6 +65,12 @@
|
|||||||
_title = string.Empty;
|
_title = string.Empty;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_src = "images/disabled.png";
|
||||||
|
_title = Localizer["PermissionDisabled"];
|
||||||
|
}
|
||||||
|
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|||||||
8
Oqtane.Client/Modules/Enums/MessageStyle.cs
Normal file
8
Oqtane.Client/Modules/Enums/MessageStyle.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace Oqtane.Modules
|
||||||
|
{
|
||||||
|
public enum MessageStyle
|
||||||
|
{
|
||||||
|
Alert,
|
||||||
|
Toast
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -18,7 +18,7 @@ namespace Oqtane.Modules.HtmlText
|
|||||||
SettingsType = "Oqtane.Modules.HtmlText.Settings, Oqtane.Client",
|
SettingsType = "Oqtane.Modules.HtmlText.Settings, Oqtane.Client",
|
||||||
Resources = new List<Resource>()
|
Resources = new List<Resource>()
|
||||||
{
|
{
|
||||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Module.css" }
|
new Stylesheet("~/Module.css")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Components;
|
|||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
using Oqtane.Enums;
|
using Oqtane.Enums;
|
||||||
using Oqtane.Models;
|
using Oqtane.Models;
|
||||||
|
using Oqtane.Security;
|
||||||
using Oqtane.Services;
|
using Oqtane.Services;
|
||||||
using Oqtane.Shared;
|
using Oqtane.Shared;
|
||||||
using Oqtane.UI;
|
using Oqtane.UI;
|
||||||
@ -139,14 +140,24 @@ namespace Oqtane.Modules
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// path method
|
// path methods
|
||||||
|
|
||||||
public string ModulePath()
|
public string ModulePath()
|
||||||
{
|
{
|
||||||
return PageState?.Alias.BaseUrl + "/Modules/" + GetType().Namespace + "/";
|
return PageState?.Alias.BaseUrl + "/Modules/" + GetType().Namespace + "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string StaticAssetPath
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// requires module to have implemented IModule
|
||||||
|
return PageState?.Alias.BaseUrl + "_content/" + ModuleState.ModuleDefinition?.PackageName + "/";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// fingerprint hash code for static assets
|
// fingerprint hash code for static assets
|
||||||
|
|
||||||
public string Fingerprint
|
public string Fingerprint
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@ -155,6 +166,18 @@ namespace Oqtane.Modules
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// authorization methods
|
||||||
|
|
||||||
|
public bool IsAuthorizedRole(string roleName)
|
||||||
|
{
|
||||||
|
return UserSecurity.IsAuthorized(PageState.User, roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsAuthorizedPermission(string permissionName)
|
||||||
|
{
|
||||||
|
return UserSecurity.IsAuthorized(PageState.User, permissionName, ModuleState.PermissionList);
|
||||||
|
}
|
||||||
|
|
||||||
// url methods
|
// url methods
|
||||||
|
|
||||||
// navigate url
|
// navigate url
|
||||||
@ -356,7 +379,17 @@ namespace Oqtane.Modules
|
|||||||
|
|
||||||
public void AddModuleMessage(string message, MessageType type, string position)
|
public void AddModuleMessage(string message, MessageType type, string position)
|
||||||
{
|
{
|
||||||
RenderModeBoundary.AddModuleMessage(message, type, position);
|
AddModuleMessage(message, type, position, MessageStyle.Alert);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddModuleMessage(string message, MessageType type, MessageStyle style)
|
||||||
|
{
|
||||||
|
AddModuleMessage(message, type, "top", style);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddModuleMessage(string message, MessageType type, string position, MessageStyle style)
|
||||||
|
{
|
||||||
|
RenderModeBoundary.AddModuleMessage(message, type, position, style);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClearModuleMessage()
|
public void ClearModuleMessage()
|
||||||
@ -417,6 +450,9 @@ namespace Oqtane.Modules
|
|||||||
await interop.ScrollTo(0, 0, "smooth");
|
await interop.ScrollTo(0, 0, "smooth");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// token replace methods
|
||||||
|
|
||||||
public string ReplaceTokens(string content)
|
public string ReplaceTokens(string content)
|
||||||
{
|
{
|
||||||
return ReplaceTokens(content, null);
|
return ReplaceTokens(content, null);
|
||||||
@ -500,33 +536,46 @@ namespace Oqtane.Modules
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// date methods
|
// date conversion methods
|
||||||
|
|
||||||
public DateTime? UtcToLocal(DateTime? datetime)
|
public DateTime? UtcToLocal(DateTime? datetime)
|
||||||
{
|
{
|
||||||
TimeZoneInfo timezone = null;
|
// Early return if input is null
|
||||||
|
if (datetime == null || datetime.Value == DateTime.MinValue || datetime.Value == DateTime.MaxValue)
|
||||||
|
return datetime;
|
||||||
|
|
||||||
|
string timezoneId = null;
|
||||||
|
|
||||||
if (PageState.User != null && !string.IsNullOrEmpty(PageState.User.TimeZoneId))
|
if (PageState.User != null && !string.IsNullOrEmpty(PageState.User.TimeZoneId))
|
||||||
{
|
{
|
||||||
timezone = TimeZoneInfo.FindSystemTimeZoneById(PageState.User.TimeZoneId);
|
timezoneId = PageState.User.TimeZoneId;
|
||||||
}
|
}
|
||||||
else if (!string.IsNullOrEmpty(PageState.Site.TimeZoneId))
|
else if (!string.IsNullOrEmpty(PageState.Site.TimeZoneId))
|
||||||
{
|
{
|
||||||
timezone = TimeZoneInfo.FindSystemTimeZoneById(PageState.Site.TimeZoneId);
|
timezoneId = PageState.Site.TimeZoneId;
|
||||||
}
|
}
|
||||||
return Utilities.UtcAsLocalDateTime(datetime, timezone);
|
|
||||||
|
return Utilities.UtcAsLocalDateTime(datetime, timezoneId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DateTime? LocalToUtc(DateTime? datetime)
|
public DateTime? LocalToUtc(DateTime? datetime)
|
||||||
{
|
{
|
||||||
TimeZoneInfo timezone = null;
|
// Early return if input is null
|
||||||
|
if (datetime == null || datetime.Value == DateTime.MinValue || datetime.Value == DateTime.MaxValue)
|
||||||
|
return datetime;
|
||||||
|
|
||||||
|
string timezoneId = null;
|
||||||
|
|
||||||
if (PageState.User != null && !string.IsNullOrEmpty(PageState.User.TimeZoneId))
|
if (PageState.User != null && !string.IsNullOrEmpty(PageState.User.TimeZoneId))
|
||||||
{
|
{
|
||||||
timezone = TimeZoneInfo.FindSystemTimeZoneById(PageState.User.TimeZoneId);
|
timezoneId = PageState.User.TimeZoneId;
|
||||||
}
|
}
|
||||||
else if (!string.IsNullOrEmpty(PageState.Site.TimeZoneId))
|
else if (!string.IsNullOrEmpty(PageState.Site.TimeZoneId))
|
||||||
{
|
{
|
||||||
timezone = TimeZoneInfo.FindSystemTimeZoneById(PageState.Site.TimeZoneId);
|
timezoneId = PageState.Site.TimeZoneId;
|
||||||
}
|
}
|
||||||
return Utilities.LocalDateAndTimeAsUtc(datetime, timezone);
|
|
||||||
|
return Utilities.LocalDateAndTimeAsUtc(datetime, timezoneId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// logging methods
|
// logging methods
|
||||||
|
|||||||
@ -1,43 +1,24 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<Configurations>Debug;Release</Configurations>
|
|
||||||
<Version>6.1.3</Version>
|
|
||||||
<Product>Oqtane</Product>
|
|
||||||
<Authors>Shaun Walker</Authors>
|
|
||||||
<Company>.NET Foundation</Company>
|
|
||||||
<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/v6.1.3</PackageReleaseNotes>
|
|
||||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
|
||||||
<RepositoryType>Git</RepositoryType>
|
|
||||||
<RootNamespace>Oqtane</RootNamespace>
|
<RootNamespace>Oqtane</RootNamespace>
|
||||||
<IsPackable>true</IsPackable>
|
|
||||||
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
|
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
|
||||||
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
|
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.5" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.9" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.5" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.9" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.5" />
|
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.9" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.5" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.9" />
|
||||||
|
<PackageReference Include="Radzen.Blazor" Version="7.4.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Oqtane.Shared\Oqtane.Shared.csproj" />
|
<ProjectReference Include="..\Oqtane.Shared\Oqtane.Shared.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Content Update="Installer\Controls\AzureSqlConfig.razor">
|
|
||||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
|
||||||
</Content>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<PublishTrimmed>false</PublishTrimmed>
|
<PublishTrimmed>false</PublishTrimmed>
|
||||||
<BlazorEnableCompression>false</BlazorEnableCompression>
|
<BlazorEnableCompression>false</BlazorEnableCompression>
|
||||||
|
|||||||
@ -183,4 +183,7 @@
|
|||||||
<data name="Refresh.Text" xml:space="preserve">
|
<data name="Refresh.Text" xml:space="preserve">
|
||||||
<value>Refresh</value>
|
<value>Refresh</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Message.Job.Disabled" xml:space="preserve">
|
||||||
|
<value>The job cannot be started while in the disabled state.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@ -133,7 +133,7 @@
|
|||||||
<value>External Login Could Not Be Linked. Please Contact Your Administrator For Further Instructions.</value>
|
<value>External Login Could Not Be Linked. Please Contact Your Administrator For Further Instructions.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Error.Login.Fail" xml:space="preserve">
|
<data name="Error.Login.Fail" xml:space="preserve">
|
||||||
<value>Login Failed. Please Remember That Passwords Are Case Sensitive. If You Have Attempted To Sign In Multiple Times Unsuccessfully, Your Account Will Be Locked Out For A Period Of Time. Note That User Accounts Often Require Email Address Verification So You May Wish To Check Your Email For A Notification.</value>
|
<value>Login Failed. Please Remember That Passwords Are Case Sensitive. If You Have Attempted To Sign In Multiple Times Unsuccessfully, Your Account Will Be Locked Out For A Period Of Time. Note That New User Accounts Often Require Email Address Verification So You May Wish To Check Your Email For A Notification Containing Further Instructions.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Message.Required.UserInfo" xml:space="preserve">
|
<data name="Message.Required.UserInfo" xml:space="preserve">
|
||||||
<value>Please Provide All Required Fields</value>
|
<value>Please Provide All Required Fields</value>
|
||||||
|
|||||||
@ -126,6 +126,9 @@
|
|||||||
<data name="Success.Module.Download" xml:space="preserve">
|
<data name="Success.Module.Download" xml:space="preserve">
|
||||||
<value>Module Package Downloaded Successfully. You Must <a href={0}>Restart</a> Your Application To Complete The Installation.</value>
|
<value>Module Package Downloaded Successfully. You Must <a href={0}>Restart</a> Your Application To Complete The Installation.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Success.Module.Upload" xml:space="preserve">
|
||||||
|
<value>Module Package Uploaded Successfully. You Must <a href={0}>Restart</a> Your Application To Complete The Installation.</value>
|
||||||
|
</data>
|
||||||
<data name="Error.Module.Download" xml:space="preserve">
|
<data name="Error.Module.Download" xml:space="preserve">
|
||||||
<value>Error Downloading Module</value>
|
<value>Error Downloading Module</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user