Compare commits
1084 Commits
revert-487
...
v6.2.1
| 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 | |||
| b24e3252d9 | |||
| e15787b1e4 | |||
| d5f19d97e2 | |||
| 5543a4aeed | |||
| 9c333232e2 | |||
| d52b95ea23 | |||
| ef4fbcbb8a | |||
| aa454b411f | |||
| 543e9339c7 | |||
| 7fff5c0d18 | |||
| fa79f3f6fa | |||
| c098839881 | |||
| 8d0d88c1b9 | |||
| 2b6ba0f410 | |||
| 11235009c0 | |||
| 4b05f7fdad | |||
| 338b0ae509 | |||
| a437082952 | |||
| ca9aba7b3b | |||
| fe9f189734 | |||
| 018ac612f4 | |||
| 5bde40ec2b | |||
| 4d5780c192 | |||
| ff6a810ad5 | |||
| feec01ba00 | |||
| 1f05d12ef5 | |||
| 31aba14507 | |||
| bbd6f13f36 | |||
| 68edbbbdb9 | |||
| eb5a0dc1c9 | |||
| 7cea4f1792 | |||
| c57c6abb1b | |||
| a25b706c7b | |||
| 5d077e843d | |||
| 65bf3e9899 | |||
| 51ba3a01f5 | |||
| f9ca611b8b | |||
| a49b8728fd | |||
| 018737c42a | |||
| 9234b91089 | |||
| f3fcef52dd | |||
| 7f8b741981 | |||
| f1791a709c | |||
| 30307fb05e | |||
| 57d443be8d | |||
| 84844c5043 | |||
| 9000f05961 | |||
| 6bef9b5c6e | |||
| ffef1f4820 | |||
| 10c7bdbcaa | |||
| e8f9888a41 | |||
| 8bac702be6 | |||
| 128bcecfe3 | |||
| 1390b3c489 | |||
| a0f41341ac | |||
| 8ffa7ef7ff | |||
| deb4607081 | |||
| 49c62f80e8 | |||
| 139793f3c0 | |||
| f237cb9655 | |||
| 9f18c460d8 | |||
| 306a41b442 | |||
| b53f54295d | |||
| 7e4f066694 | |||
| 90d72489d9 | |||
| 045c455324 | |||
| 60da903360 | |||
| 6d2a71f37e | |||
| c8f60a12a4 | |||
| 11b2d3aa43 | |||
| 3d9c81d850 | |||
| 8ccb1a24f8 | |||
| 48c6796128 | |||
| fc403f920b | |||
| ca19496b5c | |||
| 0ae38f8a40 | |||
| ad868ba841 | |||
| f2aa39aa85 | |||
| e76e0fc351 | |||
| a348913888 | |||
| 5507006c53 | |||
| 994429f098 | |||
| 8efdcb9c49 | |||
| db9a40db2b | |||
| 25667499e6 | |||
| a728cd2d91 | |||
| 3f5f3ef10b | |||
| 3811b8f0c0 | |||
| 0d708124c2 | |||
| d31f73df14 | |||
| 3fed45438b | |||
| 8aa967fa1b | |||
| 6eaa3e342c | |||
| 6fc9e60f62 | |||
| d81514e9be | |||
| 14b0d7abf0 | |||
| c39ffcf51c | |||
| 6f60a91f4c | |||
| 8031df6f28 | |||
| da1e859fda | |||
| 8d4d25f1d1 | |||
| 6aff27778d | |||
| ca2dcbfec0 | |||
| 24b666a382 | |||
| 9e34295529 | |||
| 10c55d056b | |||
| 753ab3bdd7 | |||
| feee8def6f | |||
| c208f12f8c | |||
| dc926bf838 | |||
| 55a76b5204 | |||
| 53b837e763 | |||
| 3a551cdf25 | |||
| c0c7f87dc9 | |||
| ac77fd138b | |||
| 30d6e9d67c | |||
| 47db4334a1 | |||
| fce97179e1 | |||
| 4f16cd2d01 | |||
| fa587691b1 | |||
| e0044658f9 | |||
| 53de1ddb36 | |||
| da7b046092 | |||
| d888d83a98 | |||
| 430f83e8e9 | |||
| e7acd14faa | |||
| 1b00fa74bc | |||
| 4d572d8173 | |||
| dbda85d8d9 | |||
| 95cb5dd66c | |||
| f64e1c3a6a | |||
| 26a686c412 | |||
| 2505383f53 | |||
| 1a1e9ac6be | |||
| 7f20a3179e | |||
| 46431f0187 | |||
| 1ff8ec78c9 | |||
| 7840230c62 | |||
| 523db0a005 | |||
| cc906d49ba | |||
| fc23af89d3 | |||
| 5d8829ba63 | |||
| 31ccd80894 | |||
| bac2234616 | |||
| bd61db76a3 | |||
| bc99e3b992 | |||
| b7314b0813 | |||
| 4759bd569f | |||
| b88c28f864 | |||
| 774ccb05f8 | |||
| 0ac48cba34 | |||
| e36880fe3a | |||
| 713cf5de2c | |||
| 0fa336411f | |||
| 8ebdb09d68 | |||
| 40bc53001e | |||
| b1656d1eea | |||
| 7aa54bf979 | |||
| 231f9bca84 | |||
| e4f8596c19 | |||
| 020b7233d0 | |||
| 85fc0b3e2f | |||
| 5dcc7c14f3 | |||
| 7993d27b11 | |||
| 1f8c54ce74 | |||
| 73a414a34b | |||
| 8fa19c4a51 | |||
| 0667ae3e15 | |||
| db1d00cd07 | |||
| b27f092bef | |||
| 4eaea8e586 | |||
| 89cd7d3bbb | |||
| 2fff1d8d21 | |||
| 850631f00e | |||
| 1cea8846cf | |||
| af48a48559 | |||
| 655c1762aa | |||
| f706ccfd87 | |||
| 71e4c7f117 | |||
| ad6182f4bd | |||
| 86bf0f65b0 | |||
| 7742f7747d | |||
| eb998c41f2 | |||
| 657bd7c97c | |||
| c8286148c1 | |||
| e6ba2cce62 | |||
| 6105ff44b4 | |||
| 72da77be01 | |||
| 4c29b31f1b | |||
| 6e640108ed | |||
| 157322441d | |||
| 61d967e6af | |||
| 99f2158e55 | |||
| 1cba78cc4e | |||
| 1770c1ee11 | |||
| a57fbea0cc | |||
| f0c27c83f1 | |||
| 7873ca564c | |||
| 5ebc1fec24 | |||
| f2559b7d4d | |||
| 1ee92a248e | |||
| 8376f98f21 | |||
| 810a3e0171 | |||
| 2eac9c3795 | |||
| 75f2425668 | |||
| 2dd1d7e926 | |||
| 5bb05a0a51 | |||
| bc2c5b00c6 | |||
| 09f5e158dd | |||
| 4656471a0a | |||
| 69d58a4273 | |||
| 53a27677d4 | |||
| f243ad0348 | |||
| b4ce6bbb42 | |||
| fa32937045 | |||
| 812e5f3c8e | |||
| e2981e802c | |||
| 4ae4705c73 | |||
| fbf4b12713 | |||
| e4ece3e0dc | |||
| 9f231421be | |||
| b4fdbb5e48 | |||
| fbf62ca30d | |||
| 05d2096fb8 | |||
| 7683af81bc | |||
| bad10b3812 | |||
| 9f9522c2ed | |||
| 62879c3e52 | |||
| 45610f8dd7 | |||
| 18102cbd78 | |||
| 262d6a1529 | |||
| 1124ddaf90 | |||
| 981add3872 | |||
| 8d4b30140e | |||
| b9c59137a8 | |||
| 0b1c7e06ca | |||
| fcaf80cba6 | |||
| 6358b9eabb | |||
| 70a3fab1ff | |||
| bdf86ace86 | |||
| d57132d1e4 | |||
| a6e87abf99 | |||
| f1771610fe | |||
| a88ea9780f | |||
| bca0866d72 | |||
| cebed93abf | |||
| ee2b2e3569 | |||
| cb8e9ee244 | |||
| 486184b16c | |||
| 9f9bd1988f | |||
| ba1bfd1bc0 | |||
| e12926e971 | |||
| 5b4db0de3b | |||
| 70ff55faa6 | |||
| f2bd47d8bc | |||
| e2b9c9e98e | |||
| b0791a594f | |||
| ea5eaa6ed2 | |||
| ec3fd1d585 | |||
| d76de22977 | |||
| c0e3483cc7 | |||
| 0994cdf3b6 | |||
| a76fd82262 | |||
| 2f919c7d69 | |||
| f12592731b | |||
| 4a20e1a25d | |||
| cc7111c3ff | |||
| 81972aed62 | |||
| 5e2092c6d4 | |||
| d136f8ac91 | |||
| 5b23917940 | |||
| 2cda0a3798 | |||
| f315ad1ce9 | |||
| 49f1c273c2 | |||
| 8518476c87 | |||
| a34ed756db | |||
| a48232c4e3 | |||
| ac65e38390 | |||
| eab3a753f5 | |||
| 9e5922e121 | |||
| bf57b23776 | |||
| 8f4a20fd46 | |||
| b3716da5ac | |||
| 2c129fd800 | |||
| 6fb18e7a25 | |||
| 38d28d6944 | |||
| a187e1a7a2 | |||
| 7d7a19c7c2 | |||
| 6a2ae2153a | |||
| f50ba1a91e | |||
| b09575dbd6 | |||
| c52ee3d91d | |||
| 1ced5c0425 | |||
| e399a5c9b1 | |||
| 08dff5fb67 | |||
| 912760f2a7 | |||
| 4b62fdbf93 | |||
| df593d43a7 | |||
| 89b1fba771 | |||
| 5505c91ae0 | |||
| cc720ff399 | |||
| 29f07f6c56 | |||
| a69e197a1f | |||
| 6dddd8eff8 | |||
| 51aada8922 | |||
| b47bf40e8f | |||
| 48151bf365 | |||
| 659950996d | |||
| 6e656a4d0a | |||
| bf308dd13d | |||
| 982f3b1943 | |||
| 7a4ea8cf1b | |||
| 7c0482a87c | |||
| 101ededd89 | |||
| a8cbc0040e | |||
| ed91bb445b | |||
| f158a222f4 | |||
| 46bcad1fca | |||
| 5e147afb9f | |||
| b061d4593f | |||
| 3fa520b4ef | |||
| 2df05b4afd | |||
| e0569a6748 | |||
| 2e6ab398d9 | |||
| 94b03d2a6b | |||
| f84fe30bb6 | |||
| 049ddef531 | |||
| a1a214c742 | |||
| c40a483ffa | |||
| aff99acfae | |||
| 628129c08d | |||
| 679d34dfdf | |||
| b2f65903ae | |||
| 2daefe0382 | |||
| 1214a11704 | |||
| e55e0118c2 | |||
| a1ac81e907 | |||
| 14ad68bf69 | |||
| e3118c6e99 | |||
| b41aeab8f8 | |||
| 1a738b358e | |||
| f4b1e8035b | |||
| 324e985247 | |||
| 60faacd7d0 | |||
| d4a4d7c346 | |||
| 189f8f1d27 | |||
| ed353461da | |||
| e9330d6c62 | |||
| f53e7cc3f6 | |||
| 4f4258d532 | |||
| c80910f355 | |||
| 12470ab178 | |||
| 704e091e9b | |||
| f30f1e5c1f | |||
| 0741ce2197 | |||
| fc81bae9b7 | |||
| 3fa68e4f96 | |||
| 05a767c7be | |||
| 8c1e8f6377 | |||
| 0fbae8d7da | |||
| cec4b339f5 | |||
| 1a7656d8ee | |||
| e173815810 | |||
| 620c768e05 | |||
| 7740679077 | |||
| 1ebc8ebff3 | |||
| fa4fac70d5 | |||
| 8c83a18f93 | |||
| a151ecfda0 | |||
| dec0c0649c | |||
| a356f893ac | |||
| e2af4f74c3 | |||
| 99022b76e5 | |||
| 9dd6dc7523 | |||
| 6f588200d7 | |||
| f3dbeae28e | |||
| 9f70361298 | |||
| 534353ce13 | |||
| 3f391a7354 | |||
| 0dd0752710 | |||
| 710fae4b0e | |||
| de6c57a7ee | |||
| 7eee1fcd6a | |||
| 1fd2aedf96 | |||
| ffdd7c063b | |||
| a87af264eb | |||
| 3640cd2fdd | |||
| f7cf25c4bb | |||
| 77dbd0d4c7 | |||
| 5a77c83e68 | |||
| 0a6763e08c | |||
| b5aa206670 | |||
| dbb4d9b64b | |||
| 6775edfd66 | |||
| b06750ed65 | |||
| 57879c1891 | |||
| 65b55a76f2 | |||
| 8562a68306 | |||
| 160da46b5a | |||
| ae5f70a739 | |||
| 4456e57466 | |||
| 24cd090c61 | |||
| 4f849f5d5f | |||
| db2e86e84c | |||
| 14748ce2b3 | |||
| 527509732c | |||
| aa9214477c | |||
| db24ed8b55 | |||
| 349d1849d9 | |||
| 188be2fa8c | |||
| 5c4d7df734 | |||
| 37de18c670 | |||
| 0001e3844b | |||
| 65f171f701 | |||
| c4308c239c | |||
| 2b6af3cb37 | |||
| c5a16fbbc1 | |||
| 1db83f509b | |||
| 2a06304a2c | |||
| 7bbe684135 | |||
| a996a88fc4 | |||
| 8cf9e7db51 | |||
| ed981c67b7 | |||
| 6a77a0a5b9 | |||
| bfb4b4431b | |||
| 3de44c0335 | |||
| 37afd1aec9 | |||
| b2ac561673 | |||
| c9bf7d9138 | |||
| 153a689bdb | |||
| 26b88f1a22 | |||
| c66a5d028f | |||
| 8441c95a5c | |||
| e0e32b0199 | |||
| 2bb76564e9 | |||
| 86ec25d4de | |||
| ed9929963c | |||
| 36f50118ac | |||
| 72ddf27504 | |||
| 9bd36931ff | |||
| 056ef7a3d5 | |||
| e483945d05 | |||
| 7a9c637e03 | |||
| 09ce543ea6 | |||
| 0ef24ebc3f | |||
| a4419d3af6 | |||
| 935983c02a | |||
| bd54ce5017 | |||
| af6ed78b8e | |||
| f72438996d | |||
| 9db2a55a5a | |||
| 950d90badb | |||
| 1864d180af | |||
| 0e82e98382 | |||
| 46023d35dc | |||
| 90d2e0a40b | |||
| 5f884e0796 | |||
| 16477052e2 | |||
| 66a05603f7 | |||
| fe2a883386 | |||
| ca7fdaa125 | |||
| 1283ec2008 | |||
| d1f78f9048 | |||
| 45f43bfade | |||
| 4793ab4bc9 | |||
| 88b174dea8 | |||
| 06ca382bd7 | |||
| b09175a8db | |||
| 677f68b08d | |||
| 8058b8dba4 | |||
| 4bc26f13c1 | |||
| e6cf77e724 | |||
| f8737c112e | |||
| 74b72ed9d4 | |||
| 4950391201 | |||
| 64a38d6e45 | |||
| e2d8ee53f8 | |||
| 0204ff8dd5 | |||
| 4630ee6e93 | |||
| c4f2abf143 | |||
| e7444a0194 | |||
| ffed7305ed | |||
| 334054bcd4 | |||
| 5bb98eb5b2 | |||
| e842bd882a | |||
| 74bfb46f73 | |||
| cd45bf4b70 | |||
| 51600bbcb0 | |||
| 8811a9bcaa | |||
| 4521f8a774 | |||
| 5b427783f8 | |||
| 9508983b15 | |||
| fd09912cd7 | |||
| 01cc8584b6 | |||
| c0b104e7c8 | |||
| 9a82021a82 | |||
| 1fb54a0b0f | |||
| aa5ea61638 | |||
| a59ec0258b | |||
| b403f5cf71 | |||
| 0ac6a62b86 | |||
| ed3743d3b6 | |||
| 3468cba000 | |||
| 5a4cdc5354 | |||
| af4e19a57e | |||
| 26bb743679 | |||
| 96cc726e22 | |||
| f4b00b01d0 | |||
| 127b2ca86d | |||
| 4b8b93e1b8 | |||
| 3aea412fe9 | |||
| 2aef96ad4f | |||
| ec0a77230c | |||
| b35e4bddd0 | |||
| aa32beb341 | |||
| efafe89b42 | |||
| 5ef2e49d9c | |||
| 1cfbf61a30 | |||
| 2bb5494b84 | |||
| e8a41ccb47 | |||
| 7184f7f635 | |||
| cc5727b7fa | |||
| 7f3d6ef6a5 | |||
| 44ce68097b | |||
| d976cc6c19 | |||
| d19d7d2a43 | |||
| 9bfaa02f97 | |||
| 2d9396b245 | |||
| 56e0da64ee | |||
| 997e9213f2 | |||
| 366569a23b | |||
| 36d5747b4f | |||
| ea026c726c | |||
| 1e71e32c74 | |||
| ed729bbd4f | |||
| 1a925221b7 | |||
| af7b4db062 | |||
| cfefe35e3f | |||
| 8d7845a44d | |||
| 3a15e6e5e9 | |||
| 3b8a51e855 | |||
| f2cb34cc35 | |||
| 723ce62a34 | |||
| 2c9a2ea021 | |||
| 2be008d6d1 | |||
| 7fb51bdd0a | |||
| abdbe3694f | |||
| bd87e5012f | |||
| 55e18f2364 | |||
| 655e84072d | |||
| ab5409d5b6 | |||
| 5a5da6486c | |||
| 7e99252429 | |||
| 10e0dcef8b | |||
| 80c8433aad | |||
| 5b0ae372f8 | |||
| b5a1b529ab | |||
| af821dcd9a | |||
| 10d3c81520 | |||
| e3811b453a | |||
| ca0fb05baa | |||
| 2b4b01bf6e | |||
| 3a1244bddc | |||
| b8fd922b19 | |||
| 03f856025e | |||
| 45f04d24c3 | |||
| 2435d610c7 | |||
| 06572bcd14 | |||
| 3fab79afc0 | |||
| 2b7dd3fed5 | |||
| d65efed032 | |||
| c6896ea936 | |||
| b7a41bddec | |||
| 6fd80c3737 | |||
| 0aa690b3b1 | |||
| 6d5bcfc6ed | |||
| b60de69fa5 | |||
| d991b57d08 | |||
| 1133d7fcba | |||
| 6fbf0383bb | |||
| 0296230219 | |||
| dedfbba27a | |||
| dc5441da07 | |||
| 585648b7f3 | |||
| cd0ee1c26d | |||
| d7a7be5af4 | |||
| 13e4267c11 | |||
| 15bc47e3e8 | |||
| 1a4380dcd7 | |||
| 5ace34b5cd | |||
| f010c0f1fa | |||
| 2c721ad5dd | |||
| 8a7c2ce2c2 | |||
| b2a7b813de | |||
| e85cf04b99 | |||
| ab6fa48172 | |||
| c81905882f | |||
| f0d31c1114 | |||
| 497b255216 | |||
| d96286d771 | |||
| 2441647d75 | |||
| 77b780d631 | |||
| cdd03bf3d4 | |||
| e786c35f7d | |||
| e83399acb1 | |||
| ffea9e3210 | |||
| f71a3a1ce3 | |||
| a5ccc23604 | |||
| 1ed4c8a094 | |||
| 784f3771b3 | |||
| 80316824f7 | |||
| cbaebb7b7c | |||
| 97d3764b6e | |||
| 4c937be884 | |||
| c25cce4398 | |||
| 58c422285a | |||
| f2f22f35e8 | |||
| 15867a7807 | |||
| e2c404d8bb |
@ -1,2 +0,0 @@
|
||||
[config]
|
||||
project = Oqtane.Server/Oqtane.Server.csproj
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -35,4 +35,4 @@ Oqtane.Server/wwwroot/Themes/*
|
||||
!Oqtane.Server/wwwroot/Themes/Oqtane.Themes.*
|
||||
!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>
|
||||
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018-2024 .NET Foundation
|
||||
Copyright (c) 2018-2025 .NET Foundation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
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.Extensions.Localization;
|
||||
using Oqtane.Interfaces;
|
||||
using Oqtane.Providers;
|
||||
using Oqtane.Services;
|
||||
using Oqtane.Shared;
|
||||
using Radzen;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
@ -23,7 +25,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
services.AddScoped<SiteState>();
|
||||
services.AddScoped<IInstallationService, InstallationService>();
|
||||
services.AddScoped<IModuleDefinitionService, ModuleDefinitionService>();
|
||||
services.AddScoped<IThemeService, ThemeService>();
|
||||
services.AddScoped<IThemeService, Oqtane.Services.ThemeService>();
|
||||
services.AddScoped<IAliasService, AliasService>();
|
||||
services.AddScoped<ITenantService, TenantService>();
|
||||
services.AddScoped<ISiteService, SiteService>();
|
||||
@ -39,7 +41,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
services.AddScoped<ILogService, LogService>();
|
||||
services.AddScoped<IJobService, JobService>();
|
||||
services.AddScoped<IJobLogService, JobLogService>();
|
||||
services.AddScoped<INotificationService, NotificationService>();
|
||||
services.AddScoped<INotificationService, Oqtane.Services.NotificationService>();
|
||||
services.AddScoped<IFolderService, FolderService>();
|
||||
services.AddScoped<IFileService, FileService>();
|
||||
services.AddScoped<ISiteTemplateService, SiteTemplateService>();
|
||||
@ -52,10 +54,20 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
services.AddScoped<IVisitorService, VisitorService>();
|
||||
services.AddScoped<ISyncService, SyncService>();
|
||||
services.AddScoped<ILocalizationCookieService, LocalizationCookieService>();
|
||||
services.AddScoped<ICookieConsentService, CookieConsentService>();
|
||||
services.AddScoped<ITimeZoneService, TimeZoneService>();
|
||||
services.AddScoped<IMigrationHistoryService, MigrationHistoryService>();
|
||||
services.AddScoped<IOutputCacheService, OutputCacheService>();
|
||||
|
||||
// providers
|
||||
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.QuillJSTextEditor>();
|
||||
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;
|
||||
}
|
||||
|
||||
119
Oqtane.Client/Installer/Controls/AzureSqlConfig.razor
Normal file
119
Oqtane.Client/Installer/Controls/AzureSqlConfig.razor
Normal file
@ -0,0 +1,119 @@
|
||||
@namespace Oqtane.Installer.Controls
|
||||
@implements Oqtane.Interfaces.IDatabaseConfigControl
|
||||
@inject IStringLocalizer<SqlServerConfig> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="server" HelpText="Enter the database server name. This might include a port number as well if you are using a cloud service." ResourceKey="Server">Server:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="server" type="text" class="form-control" @bind="@_server" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="database" HelpText="Enter the name of the database" ResourceKey="Database">Database:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="database" type="text" class="form-control" @bind="@_database" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="security" HelpText="Select your security method" ResourceKey="Security">Security:</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="security" class="form-select custom-select" @bind="@_security">
|
||||
<option value="integrated" selected>@Localizer["Integrated"]</option>
|
||||
<option value="custom">@Localizer["Custom"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (_security == "custom")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="uid" HelpText="Enter the username to use for the database" ResourceKey="Uid">User Id:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="uid" type="text" class="form-control" @bind="@_uid" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="pwd" HelpText="Enter the password to use for the database" ResourceKey="Pwd">Password:</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="pwd" type="@_passwordType" class="form-control" @bind="@_pwd" autocomplete="new-password" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglePassword</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="encryption" HelpText="Specify if you are using an encrypted database connection. It is highly recommended to use encryption in a production environment." ResourceKey="Encryption">Encryption:</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="encryption" class="form-select custom-select" @bind="@_encryption">
|
||||
<option value="true">@SharedLocalizer["True"]</option>
|
||||
<option value="false">@SharedLocalizer["False"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (_encryption == "true")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="trustservercertificate" HelpText="Specify the type of certificate you are using for encryption. Verifiable is equivalent to False. Self Signed is equivalent to True." ResourceKey="TrustServerCertificate">Certificate:</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="encryption" class="form-select custom-select" @bind="@_trustservercertificate">
|
||||
<option value="true">@Localizer["Self Signed"]</option>
|
||||
<option value="false">@Localizer["Verifiable"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
private string _server = "tcp:{SQL Server Name}.database.windows.net,1433";
|
||||
private string _database = "{SQL Database Name}";
|
||||
private string _security = "custom";
|
||||
private string _uid = "{SQL Administrator Login}";
|
||||
private string _pwd = String.Empty;
|
||||
private string _passwordType = "password";
|
||||
private string _togglePassword = string.Empty;
|
||||
private string _encryption = "true";
|
||||
private string _trustservercertificate = "false";
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_togglePassword = SharedLocalizer["ShowPassword"];
|
||||
}
|
||||
|
||||
public string GetConnectionString()
|
||||
{
|
||||
var connectionString = String.Empty;
|
||||
|
||||
if (!String.IsNullOrEmpty(_server) && !String.IsNullOrEmpty(_database))
|
||||
{
|
||||
connectionString = $"Data Source={_server};Initial Catalog={_database};";
|
||||
}
|
||||
|
||||
if (_security == "integrated")
|
||||
{
|
||||
connectionString += "Integrated Security=SSPI;";
|
||||
}
|
||||
else
|
||||
{
|
||||
connectionString += $"User ID={_uid};Password={_pwd};";
|
||||
}
|
||||
connectionString += $"Encrypt={_encryption};";
|
||||
connectionString += $"TrustServerCertificate={_trustservercertificate};";
|
||||
|
||||
return connectionString;
|
||||
}
|
||||
|
||||
private void TogglePassword()
|
||||
{
|
||||
if (_passwordType == "password")
|
||||
{
|
||||
_passwordType = "text";
|
||||
_togglePassword = SharedLocalizer["HidePassword"];
|
||||
}
|
||||
else
|
||||
{
|
||||
_passwordType = "password";
|
||||
_togglePassword = SharedLocalizer["ShowPassword"];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,7 @@
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="server" HelpText="Enter the database server name. This might include a port number as well if you are using a cloud service (ie. servername.database.windows.net,1433) " ResourceKey="Server">Server:</Label>
|
||||
<Label Class="col-sm-3" For="server" HelpText="Enter the database server name. This might include a port number as well if you are using a cloud service." ResourceKey="Server">Server:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="server" type="text" class="form-control" @bind="@_server" />
|
||||
</div>
|
||||
|
||||
@ -14,8 +14,8 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="mx-auto text-center">
|
||||
<img src="oqtane-black.png" />
|
||||
<div style="font-weight: bold">@SharedLocalizer["Version"] @Constants.Version (.NET 9)</div>
|
||||
<img src="installer-logo.png" />
|
||||
<div style="font-weight: bold">@SharedLocalizer["Version"] @Constants.Version (.NET @Environment.Version.Major)</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="app-rule" />
|
||||
@ -71,14 +71,14 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="username" HelpText="Provide a username for the primary user account" ResourceKey="Username">Username:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="username" type="text" class="form-control" @bind="@_hostUsername" />
|
||||
<input id="username" type="text" class="form-control" maxlength="256" @bind="@_hostUsername" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="password" HelpText="Provide a password for the primary user account" ResourceKey="Password">Password:</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="password" type="@_passwordType" class="form-control" @bind="@_hostPassword" autocomplete="new-password" />
|
||||
<input id="password" type="@_passwordType" class="form-control" maxlength="256" @bind="@_hostPassword" autocomplete="new-password" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglePassword</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -87,7 +87,7 @@
|
||||
<Label Class="col-sm-3" For="confirm" HelpText="Please confirm the password entered above by entering it again" ResourceKey="Confirm">Confirm:</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="confirm" type="@_confirmPasswordType" class="form-control" @bind="@_confirmPassword" autocomplete="new-password" />
|
||||
<input id="confirm" type="@_confirmPasswordType" class="form-control" maxlength="256" @bind="@_confirmPassword" autocomplete="new-password" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@ToggleConfirmPassword" tabindex="-1">@_toggleConfirmPassword</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -95,7 +95,13 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="email" HelpText="Provide the email address for the host user account" ResourceKey="Email">Email:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" @bind="@_hostEmail" />
|
||||
<input type="text" class="form-control" maxlength="256" @bind="@_hostEmail" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="name" HelpText="Provide the full name of the host user" ResourceKey="Name">Full Name:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" maxlength="50" @bind="@_hostName" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
@ -153,6 +159,7 @@
|
||||
private string _toggleConfirmPassword = string.Empty;
|
||||
private string _confirmPassword = string.Empty;
|
||||
private string _hostEmail = string.Empty;
|
||||
private string _hostName = string.Empty;
|
||||
private List<SiteTemplate> _templates;
|
||||
private string _template = Constants.DefaultSiteTemplate;
|
||||
private bool _register = true;
|
||||
@ -175,7 +182,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
_databaseName = "LocalDB";
|
||||
_databaseName = Constants.DefaultDBName;
|
||||
}
|
||||
LoadDatabaseConfigComponent();
|
||||
|
||||
@ -236,7 +243,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
if (connectionString != "" && !string.IsNullOrEmpty(_hostUsername) && !string.IsNullOrEmpty(_hostPassword) && _hostPassword == _confirmPassword && !string.IsNullOrEmpty(_hostEmail) && _hostEmail.Contains("@"))
|
||||
if (connectionString != "" && !string.IsNullOrEmpty(_hostUsername) && !string.IsNullOrEmpty(_hostPassword) && _hostPassword == _confirmPassword && !string.IsNullOrEmpty(_hostEmail) && _hostEmail.Contains("@") && !string.IsNullOrEmpty(_hostName))
|
||||
{
|
||||
var result = await UserService.ValidateUserAsync(_hostUsername, _hostEmail, _hostPassword);
|
||||
if (result.Succeeded)
|
||||
@ -256,14 +263,14 @@
|
||||
HostUsername = _hostUsername,
|
||||
HostPassword = _hostPassword,
|
||||
HostEmail = _hostEmail,
|
||||
HostName = _hostUsername,
|
||||
HostName = _hostName,
|
||||
TenantName = TenantNames.Master,
|
||||
IsNewTenant = true,
|
||||
SiteName = Constants.DefaultSite,
|
||||
Register = _register,
|
||||
SiteTemplate = _template,
|
||||
RenderMode = RenderModes.Static,
|
||||
Runtime = Runtimes.Server
|
||||
RenderMode = "", // provided by appsettings.json
|
||||
Runtime = "" // provided by appsettings.json
|
||||
};
|
||||
|
||||
var installation = await InstallationService.Install(config);
|
||||
|
||||
@ -15,22 +15,34 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="parent" HelpText="Select the parent folder" ResourceKey="Parent">Parent: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="parent" class="form-select" @bind="@_parentId" required>
|
||||
@if (PageState.QueryString.ContainsKey("id"))
|
||||
{
|
||||
@if (_parentId == -1)
|
||||
{
|
||||
<select id="parent" class="form-select" @bind="@_parentId" required>
|
||||
<option value="-1"><@Localizer["NoParent"]></option>
|
||||
}
|
||||
</select>
|
||||
}
|
||||
else
|
||||
{
|
||||
<select id="parent" class="form-select" @bind="@_parentId" required>
|
||||
@foreach (Folder folder in _folders)
|
||||
{
|
||||
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
|
||||
}
|
||||
</select>
|
||||
</select>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="name" HelpText="Enter the folder name" ResourceKey="Name">Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="name" class="form-control" @bind="@_name" maxlength="256" required />
|
||||
@if (_isSystem)
|
||||
{
|
||||
<input id="name" class="form-control" @bind="@_name" readonly />
|
||||
}
|
||||
else
|
||||
{
|
||||
<input id="name" class="form-control" @bind="@_name" maxlength="256" required />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
@ -49,18 +61,24 @@
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="imagesizes" HelpText="Enter a list of image sizes which can be generated dynamically from uploaded images (ie. 200x200,400x400). Use * to indicate the folder supports all image sizes." ResourceKey="ImageSizes">Image Sizes: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="imagesizes" class="form-control" @bind="@_imagesizes" maxlength="512" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="capacity" HelpText="Enter the maximum folder capacity (in megabytes). Specify zero if the capacity is unlimited." ResourceKey="Capacity">Capacity: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="capacity" class="form-control" @bind="@_capacity" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="cachecontrol" HelpText="Optionally provide a Cache-Control directive for this folder. For example 'public, max-age=60' indicates that files in this folder should be cached for 60 seconds. Please note that when caching is enabled, changes to files will not be immediately reflected in the UI." ResourceKey="CacheControl">Caching: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="cachecontrol" class="form-control" @bind="@_cachecontrol" maxlength="50" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="imagesizes" HelpText="Optionally enter a list of image sizes which can be generated dynamically from uploaded images (ie. 200x200,400x400). Use * to indicate the folder supports all image sizes (not recommended)." ResourceKey="ImageSizes">Image Sizes: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="imagesizes" class="form-control" @bind="@_imagesizes" maxlength="512" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if (PageState.QueryString.ContainsKey("id"))
|
||||
{
|
||||
@ -100,8 +118,9 @@
|
||||
private int _parentId = -1;
|
||||
private string _name;
|
||||
private string _type = FolderTypes.Private;
|
||||
private string _imagesizes = string.Empty;
|
||||
private string _capacity = "0";
|
||||
private string _cachecontrol = string.Empty;
|
||||
private string _imagesizes = string.Empty;
|
||||
private bool _isSystem;
|
||||
private List<Permission> _permissions = null;
|
||||
private string _createdBy;
|
||||
@ -132,8 +151,9 @@
|
||||
_parentId = folder.ParentId ?? -1;
|
||||
_name = folder.Name;
|
||||
_type = folder.Type;
|
||||
_imagesizes = folder.ImageSizes;
|
||||
_capacity = folder.Capacity.ToString();
|
||||
_cachecontrol = folder.CacheControl;
|
||||
_imagesizes = folder.ImageSizes;
|
||||
_isSystem = folder.IsSystem;
|
||||
_permissions = folder.PermissionList;
|
||||
_createdBy = folder.CreatedBy;
|
||||
@ -193,7 +213,7 @@
|
||||
{
|
||||
folder.ParentId = _parentId;
|
||||
}
|
||||
|
||||
|
||||
// check for duplicate folder names
|
||||
if (_folders.Any(item => item.ParentId == folder.ParentId && item.Name == _name && item.FolderId != _folderId))
|
||||
{
|
||||
@ -204,8 +224,9 @@
|
||||
folder.SiteId = PageState.Site.SiteId;
|
||||
folder.Name = _name;
|
||||
folder.Type = _type;
|
||||
folder.ImageSizes = _imagesizes;
|
||||
folder.Capacity = int.Parse(_capacity);
|
||||
folder.CacheControl = _cachecontrol;
|
||||
folder.ImageSizes = _imagesizes;
|
||||
folder.IsSystem = _isSystem;
|
||||
folder.PermissionList = _permissionGrid.GetPermissionList();
|
||||
|
||||
@ -220,7 +241,6 @@
|
||||
|
||||
if (folder != null)
|
||||
{
|
||||
await FolderService.UpdateFolderOrderAsync(folder.SiteId, folder.FolderId, folder.ParentId);
|
||||
await logger.LogInformation("Folder Saved {Folder}", folder);
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
@ -256,17 +276,9 @@
|
||||
}
|
||||
if (!isparent)
|
||||
{
|
||||
var files = await FileService.GetFilesAsync(_folderId);
|
||||
if (files.Count == 0)
|
||||
{
|
||||
await FolderService.DeleteFolderAsync(_folderId);
|
||||
await logger.LogInformation("Folder Deleted {Folder}", _folderId);
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Folder.Files.InvalidDelete"], MessageType.Warning);
|
||||
}
|
||||
await FolderService.DeleteFolderAsync(_folderId);
|
||||
await logger.LogInformation("Folder Deleted {Folder}", _folderId);
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@ -3,54 +3,92 @@
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IFolderService FolderService
|
||||
@inject IFileService FileService
|
||||
@inject ISettingService SettingService
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@if (_files != null)
|
||||
@if (_files == null)
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-md mb-1">
|
||||
<ActionLink Action="Edit" Text="Add Folder" Class="btn btn-secondary" ResourceKey="AddFolder" />
|
||||
</div>
|
||||
<div class="col-md-8 mb-1">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">@Localizer["Folder"]:</span>
|
||||
<select class="form-select" @onchange="(e => FolderChanged(e))">
|
||||
@foreach (Folder folder in _folders)
|
||||
{
|
||||
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
|
||||
}
|
||||
</select>
|
||||
<ActionLink Action="Edit" Text="Edit Folder" Class="btn btn-secondary" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="EditFolder" />
|
||||
<p>
|
||||
<em>@SharedLocalizer["Loading"]</em>
|
||||
</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<TabStrip>
|
||||
<TabPanel Name="Files" Heading="Files" ResourceKey="Files">
|
||||
<div class="row">
|
||||
<div class="col-md mb-1">
|
||||
<ActionLink Action="Edit" Text="Add Folder" Class="btn btn-secondary" ResourceKey="AddFolder" />
|
||||
</div>
|
||||
<div class="col-md-8 mb-1">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">@Localizer["Folder"]:</span>
|
||||
<select class="form-select" @onchange="(e => FolderChanged(e))">
|
||||
@foreach (Folder folder in _folders)
|
||||
{
|
||||
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
|
||||
}
|
||||
</select>
|
||||
<ActionLink Action="Edit" Text="Edit Folder" Class="btn btn-secondary" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="EditFolder" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md mb-1 text-end">
|
||||
<ActionLink Action="Add" Text="Upload Files" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="UploadFiles" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md mb-1 text-end">
|
||||
<ActionLink Action="Add" Text="Upload Files" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="UploadFiles" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Pager Items="@_files" SearchProperties="Name">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@SharedLocalizer["Name"]</th>
|
||||
<th>@Localizer["Modified"]</th>
|
||||
<th>@Localizer["Type"]</th>
|
||||
<th>@Localizer["Size"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="Details" Text="Edit" Parameters="@($"id=" + context.FileId.ToString())" ResourceKey="Details" /></td>
|
||||
<td><ActionDialog Header="Delete File" Message="@string.Format(Localizer["Confirm.File.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteFile(context))" ResourceKey="DeleteFile" /></td>
|
||||
<td><a href="@context.Url" target="_new">@context.Name</a></td>
|
||||
<td>@context.ModifiedOn</td>
|
||||
<td>@context.Extension.ToUpper() @SharedLocalizer["File"]</td>
|
||||
<td>@string.Format("{0:0.00}", ((decimal)context.Size / 1000)) KB</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
@if (_files.Count == 0)
|
||||
{
|
||||
<div class="text-center">@Localizer["NoFiles"]</div>
|
||||
}
|
||||
@if (_files.Count != 0)
|
||||
{
|
||||
<Pager Items="@_files" SearchProperties="Name">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@SharedLocalizer["Name"]</th>
|
||||
<th>@Localizer["Modified"]</th>
|
||||
<th>@Localizer["Type"]</th>
|
||||
<th>@Localizer["Size"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="Details" Text="Edit" Parameters="@($"id=" + context.FileId.ToString())" ResourceKey="Details" /></td>
|
||||
<td><ActionDialog Header="Delete File" Message="@string.Format(Localizer["Confirm.File.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteFile(context))" ResourceKey="DeleteFile" /></td>
|
||||
<td><a href="@context.Url" target="_new">@context.Name</a></td>
|
||||
<td>@UtcToLocal(context.ModifiedOn)</td>
|
||||
<td>@context.Extension.ToUpper() @SharedLocalizer["File"]</td>
|
||||
<td>@string.Format("{0:0.00}", ((decimal)context.Size / 1000)) KB</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="text-center">@Localizer["NoFiles"]</div>
|
||||
}
|
||||
</TabPanel>
|
||||
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings" Security="SecurityAccessLevel.Admin">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="imageExt" HelpText="Enter a comma separated list of image file extensions" ResourceKey="ImageExtensions">Image Extensions: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="imageExt" spellcheck="false" class="form-control" @bind="@_imageFiles" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="uploadableFileExt" HelpText="Enter a comma separated list of uploadable file extensions" ResourceKey="UploadableFileExtensions">Uploadable File Extensions: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="uploadableFileExt" spellcheck="false" class="form-control" @bind="@_uploadableFiles" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="maxChunkSize" HelpText="Files are split into chunks to streamline the upload process. Specify the maximum chunk size in MB (note that higher chunk sizes should only be used on faster networks)." ResourceKey="MaxChunkSize">Max Upload Chunk Size (MB): </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="maxChunkSize" type="number" min="1" max="10" step="1" class="form-control" @bind="@_maxChunkSize" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||
</TabPanel>
|
||||
</TabStrip>
|
||||
}
|
||||
|
||||
@code {
|
||||
@ -58,6 +96,10 @@
|
||||
private int _folderId = -1;
|
||||
private List<File> _files;
|
||||
|
||||
private string _imageFiles = string.Empty;
|
||||
private string _uploadableFiles = string.Empty;
|
||||
private int _maxChunkSize = 1;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
@ -71,6 +113,13 @@
|
||||
_folderId = _folders[0].FolderId;
|
||||
await GetFiles();
|
||||
}
|
||||
|
||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
_imageFiles = SettingService.GetSetting(settings, "ImageFiles", Constants.ImageFiles);
|
||||
_imageFiles = (string.IsNullOrEmpty(_imageFiles)) ? Constants.ImageFiles : _imageFiles;
|
||||
_uploadableFiles = SettingService.GetSetting(settings, "UploadableFiles", Constants.UploadableFiles);
|
||||
_uploadableFiles = (string.IsNullOrEmpty(_uploadableFiles)) ? Constants.UploadableFiles : _uploadableFiles;
|
||||
_maxChunkSize = int.Parse(SettingService.GetSetting(settings, "MaxChunkSize", "1"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -115,4 +164,23 @@
|
||||
AddModuleMessage(string.Format(Localizer["Error.File.Delete"], file.Name), MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveSiteSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
settings = SettingService.SetSetting(settings, "ImageFiles", (_imageFiles != Constants.ImageFiles) ? _imageFiles.Replace(" ", "") : "", false);
|
||||
settings = SettingService.SetSetting(settings, "UploadableFiles", (_uploadableFiles != Constants.UploadableFiles) ? _uploadableFiles.Replace(" ", "") : "", false);
|
||||
settings = SettingService.SetSetting(settings, "MaxChunkSize", _maxChunkSize.ToString(), false);
|
||||
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -45,7 +45,7 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="retention" HelpText="Number of log entries to retain for this job" ResourceKey="RetentionLog">Retention Log (Items): </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="retention" type="number" min="0" step ="1" class="form-control" @bind="@_retentionHistory" maxlength="4" required />
|
||||
<input id="retention" type="number" min="0" step ="1" class="form-control" @bind="@_retentionHistory" maxlength="3" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
@ -56,7 +56,7 @@
|
||||
<input id="starting" type="date" class="form-control" @bind="@_startDate" />
|
||||
</div>
|
||||
<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>
|
||||
@ -69,7 +69,7 @@
|
||||
<input id="ending" type="date" class="form-control" @bind="@_endDate" />
|
||||
</div>
|
||||
<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>
|
||||
@ -82,7 +82,7 @@
|
||||
<input id="next" type="date" class="form-control" @bind="@_nextDate" />
|
||||
</div>
|
||||
<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>
|
||||
@ -132,13 +132,13 @@
|
||||
_isEnabled = job.IsEnabled.ToString();
|
||||
_interval = job.Interval.ToString();
|
||||
_frequency = job.Frequency;
|
||||
_startDate = Utilities.UtcAsLocalDate(job.StartDate);
|
||||
_startTime = Utilities.UtcAsLocalDateTime(job.StartDate);
|
||||
_endDate = Utilities.UtcAsLocalDate(job.EndDate);
|
||||
_endTime = Utilities.UtcAsLocalDateTime(job.EndDate);
|
||||
_startDate = UtcToLocal(job.StartDate);
|
||||
_startTime = UtcToLocal(job.StartDate);
|
||||
_endDate = UtcToLocal(job.EndDate);
|
||||
_endTime = UtcToLocal(job.EndDate);
|
||||
_retentionHistory = job.RetentionHistory.ToString();
|
||||
_nextDate = Utilities.UtcAsLocalDate(job.NextExecution);
|
||||
_nextTime = Utilities.UtcAsLocalDateTime(job.NextExecution);
|
||||
_nextDate = UtcToLocal(job.NextExecution);
|
||||
_nextTime = UtcToLocal(job.NextExecution);
|
||||
createdby = job.CreatedBy;
|
||||
createdon = job.CreatedOn;
|
||||
modifiedby = job.ModifiedBy;
|
||||
@ -176,10 +176,18 @@
|
||||
{
|
||||
job.Interval = int.Parse(_interval);
|
||||
}
|
||||
job.StartDate = Utilities.LocalDateAndTimeAsUtc(_startDate, _startTime);
|
||||
job.EndDate = Utilities.LocalDateAndTimeAsUtc(_endDate, _endTime);
|
||||
job.RetentionHistory = int.Parse(_retentionHistory);
|
||||
job.NextExecution = Utilities.LocalDateAndTimeAsUtc(_nextDate, _nextTime);
|
||||
job.StartDate = _startDate.HasValue && _startTime.HasValue
|
||||
? 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);
|
||||
|
||||
try
|
||||
{
|
||||
@ -198,5 +206,4 @@
|
||||
AddModuleMessage(Localizer["Message.Required.JobInfo"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ else
|
||||
<td>@context.Name</td>
|
||||
<td>@DisplayStatus(context.IsEnabled, context.IsExecuting)</td>
|
||||
<td>@DisplayFrequency(context.Interval, context.Frequency)</td>
|
||||
<td>@context.NextExecution?.ToLocalTime()</td>
|
||||
<td>@UtcToLocal(context.NextExecution)</td>
|
||||
<td>
|
||||
@if (context.IsStarted)
|
||||
{
|
||||
@ -55,10 +55,6 @@ else
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await GetJobs();
|
||||
if (_jobs.Count == 0)
|
||||
{
|
||||
AddModuleMessage(string.Format(Localizer["Message.NoJobs"], NavigateUrl("admin/system")), MessageType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GetJobs()
|
||||
@ -120,11 +116,19 @@ else
|
||||
{
|
||||
try
|
||||
{
|
||||
await JobService.StartJobAsync(jobId);
|
||||
await logger.LogInformation("Job Started {JobId}", jobId);
|
||||
AddModuleMessage(Localizer["Message.Job.Start"], MessageType.Success);
|
||||
_jobs = await JobService.GetJobsAsync();
|
||||
StateHasChanged();
|
||||
Job _job = await JobService.GetJobAsync(jobId);
|
||||
if (!_job.IsEnabled)
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Job.Disabled"], MessageType.Warning);
|
||||
}
|
||||
else
|
||||
{
|
||||
await JobService.StartJobAsync(jobId);
|
||||
await logger.LogInformation("Job Started {JobId}", jobId);
|
||||
AddModuleMessage(Localizer["Message.Job.Start"], MessageType.Success);
|
||||
_jobs = await JobService.GetJobsAsync();
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@ -23,8 +23,8 @@ else
|
||||
<Row>
|
||||
<td>@context.Job.Name</td>
|
||||
<td>@DisplayStatus(context.Job.IsExecuting, context.Succeeded)</td>
|
||||
<td>@context.StartDate</td>
|
||||
<td>@context.FinishDate</td>
|
||||
<td>@UtcToLocal(context.StartDate)</td>
|
||||
<td>@UtcToLocal(context.FinishDate)</td>
|
||||
</Row>
|
||||
<Detail>
|
||||
<td colspan="4">@((MarkupString)context.Notes)</td>
|
||||
@ -44,14 +44,12 @@ else
|
||||
|
||||
private async Task GetJobLogs()
|
||||
{
|
||||
_jobLogs = await JobLogService.GetJobLogsAsync();
|
||||
|
||||
var jobId = -1;
|
||||
if (PageState.QueryString.ContainsKey("id"))
|
||||
{
|
||||
_jobLogs = _jobLogs.Where(item => item.JobId == Int32.Parse(PageState.QueryString["id"])).ToList();
|
||||
jobId = int.Parse(PageState.QueryString["id"]);
|
||||
}
|
||||
|
||||
_jobLogs = _jobLogs.OrderByDescending(item => item.JobLogId).ToList();
|
||||
_jobLogs = await JobLogService.GetJobLogsAsync(jobId);
|
||||
}
|
||||
|
||||
private string DisplayStatus(bool isExecuting, bool? succeeded)
|
||||
|
||||
@ -21,20 +21,18 @@ else
|
||||
@if (_allowexternallogin)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
|
||||
<br />
|
||||
|
||||
<br />
|
||||
<br /><br />
|
||||
}
|
||||
@if (_allowsitelogin)
|
||||
{
|
||||
<div class="form-group">
|
||||
<Label Class="control-label" For="username" HelpText="Please enter your Username" ResourceKey="Username">Username:</Label>
|
||||
<input id="username" type="text" @ref="username" class="form-control" placeholder="@Localizer["Username.Placeholder"]" @bind="@_username" required />
|
||||
<input id="username" type="text" @ref="username" class="form-control" placeholder="@Localizer["Username.Placeholder"]" @bind="@_username" @bind:event="oninput" required />
|
||||
</div>
|
||||
<div class="form-group mt-2">
|
||||
<Label Class="control-label" For="password" HelpText="Please enter your Password" ResourceKey="Password">Password:</Label>
|
||||
<div class="input-group">
|
||||
<input id="password" type="@_passwordtype" name="Password" class="form-control" placeholder="@Localizer["Password.Placeholder"]" @bind="@_password" required />
|
||||
<input id="password" type="@_passwordtype" name="Password" class="form-control" placeholder="@Localizer["Password.Placeholder"]" @bind="@_password" @bind:event="oninput" required />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -49,15 +47,11 @@ else
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</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>
|
||||
@if (PageState.Site.AllowRegistration)
|
||||
{
|
||||
<br />
|
||||
|
||||
<br />
|
||||
<br /><br />
|
||||
<NavLink href="@NavigateUrl("register")">@Localizer["Register"]</NavLink>
|
||||
}
|
||||
}
|
||||
@ -100,7 +94,7 @@ else
|
||||
|
||||
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()
|
||||
@ -144,7 +138,7 @@ else
|
||||
user = await UserService.VerifyEmailAsync(user, PageState.QueryString["token"]);
|
||||
if (user != null)
|
||||
{
|
||||
await logger.LogInformation(LogFunction.Security, "Email Verified For For Username {Username}", _username);
|
||||
await logger.LogInformation(LogFunction.Security, "Email Verified For Username {Username}", _username);
|
||||
AddModuleMessage(Localizer["Success.Account.Verified"], MessageType.Info);
|
||||
}
|
||||
else
|
||||
|
||||
@ -141,7 +141,7 @@
|
||||
var log = await LogService.GetLogAsync(_logId);
|
||||
if (log != null)
|
||||
{
|
||||
_logDate = log.LogDate.ToString(CultureInfo.CurrentCulture);
|
||||
_logDate = UtcToLocal(log.LogDate).Value.ToString(CultureInfo.CurrentCulture);
|
||||
_level = log.Level;
|
||||
_feature = log.Feature;
|
||||
_function = log.Function;
|
||||
|
||||
@ -64,7 +64,7 @@ else
|
||||
</Header>
|
||||
<Row>
|
||||
<td class="@GetClass(context.Function)"><ActionLink Action="Detail" Text="Details" Parameters="@($"/{context.LogId}")" ReturnUrl="@(NavigateUrl(PageState.Page.Path, AddUrlParameters(_level, _function, _rows, _page)))" ResourceKey="LogDetails" /></td>
|
||||
<td class="@GetClass(context.Function)">@context.LogDate</td>
|
||||
<td class="@GetClass(context.Function)">@UtcToLocal(context.LogDate)</td>
|
||||
<td class="@GetClass(context.Function)">@context.Level</td>
|
||||
<td class="@GetClass(context.Function)">@context.Feature</td>
|
||||
<td class="@GetClass(context.Function)">@context.Function</td>
|
||||
|
||||
@ -101,13 +101,20 @@
|
||||
<small>@SharedLocalizer["Search.By"]:</small> <strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
|
||||
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
|
||||
<br />
|
||||
@if (!string.IsNullOrEmpty(context.PackageUrl))
|
||||
@if (_moduledefinitions.Exists(item => item.PackageName == context.PackageId))
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
|
||||
<button type="button" class="btn btn-info">@SharedLocalizer["Installed"]</button>
|
||||
}
|
||||
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
|
||||
else
|
||||
{
|
||||
<a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
|
||||
@if (!string.IsNullOrEmpty(context.PackageUrl))
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
|
||||
}
|
||||
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
|
||||
{
|
||||
<a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
|
||||
}
|
||||
}
|
||||
<br />
|
||||
</div>
|
||||
@ -171,6 +178,7 @@
|
||||
|
||||
@code {
|
||||
private bool _initialized = false;
|
||||
private List<ModuleDefinition> _moduledefinitions;
|
||||
private int _page = 1;
|
||||
private List<Package> _packages;
|
||||
private string _price = "free";
|
||||
@ -187,7 +195,8 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
await LoadModuleDefinitions();
|
||||
_moduledefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
|
||||
await LoadPackages();
|
||||
_initialized = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -197,24 +206,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadModuleDefinitions()
|
||||
private async Task LoadPackages()
|
||||
{
|
||||
ShowProgressIndicator();
|
||||
|
||||
var moduledefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
|
||||
_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();
|
||||
}
|
||||
|
||||
@ -222,25 +217,25 @@
|
||||
{
|
||||
_price = price;
|
||||
_sort = "popularity";
|
||||
await LoadModuleDefinitions();
|
||||
await LoadPackages();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task Search()
|
||||
{
|
||||
await LoadModuleDefinitions();
|
||||
await LoadPackages();
|
||||
}
|
||||
|
||||
private async Task Reset()
|
||||
{
|
||||
_page = 1;
|
||||
_search = "";
|
||||
await LoadModuleDefinitions();
|
||||
await LoadPackages();
|
||||
}
|
||||
|
||||
private async Task Refresh()
|
||||
{
|
||||
await LoadModuleDefinitions();
|
||||
await LoadPackages();
|
||||
}
|
||||
|
||||
private void OnPageChange(int page)
|
||||
@ -251,7 +246,7 @@
|
||||
private async void SortChanged(ChangeEventArgs e)
|
||||
{
|
||||
_sort = (string)e.Value;
|
||||
await LoadModuleDefinitions();
|
||||
await LoadPackages();
|
||||
}
|
||||
|
||||
private void HideModal()
|
||||
@ -310,6 +305,6 @@
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,6 +111,8 @@
|
||||
private async Task CreateModule()
|
||||
{
|
||||
validated = true;
|
||||
_owner = _owner.Trim();
|
||||
_module = _module.Trim();
|
||||
var interop = new Interop(JSRuntime);
|
||||
if (await interop.FormValid(form))
|
||||
{
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
@if (_initialized)
|
||||
{
|
||||
<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>
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
@ -63,24 +63,7 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this module was installed. This value must be specified within the module's IModule interface specification." ResourceKey="PackageName">Package Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
@if (!string.IsNullOrEmpty(_packagename))
|
||||
{
|
||||
<div class="input-group">
|
||||
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
|
||||
@if (string.IsNullOrEmpty(_packageurl))
|
||||
{
|
||||
<button type="button" class="btn btn-secondary" @onclick="ValidatePackage">@Localizer["Validate"]</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a href="@_packageurl" target="_blank" class="btn btn-primary">@SharedLocalizer["Download"]</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
|
||||
}
|
||||
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
@ -90,7 +73,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="url" HelpText="The reference url of the module" ResourceKey="ReferenceUrl">Reference Url: </Label>
|
||||
<Label Class="col-sm-3" For="url" HelpText="The url of the module" ResourceKey="Url">Url: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="url" class="form-control" @bind="@_url" disabled />
|
||||
</div>
|
||||
@ -244,7 +227,6 @@
|
||||
private string _moduledefinitionname = "";
|
||||
private string _version;
|
||||
private string _packagename = "";
|
||||
private string _packageurl = "";
|
||||
private string _owner = "";
|
||||
private string _url = "";
|
||||
private string _contact = "";
|
||||
@ -254,11 +236,10 @@
|
||||
private DateTime _createdon;
|
||||
private string _modifiedby;
|
||||
private DateTime _modifiedon;
|
||||
private List<Page> _pagesWithModules;
|
||||
|
||||
#pragma warning disable 649
|
||||
private PermissionGrid _permissionGrid;
|
||||
#pragma warning restore 649
|
||||
|
||||
private List<Page> _pagesWithModules;
|
||||
|
||||
private List<Package> _packages;
|
||||
private List<Language> _languages;
|
||||
@ -445,27 +426,5 @@
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ValidatePackage()
|
||||
{
|
||||
try
|
||||
{
|
||||
var package = await PackageService.GetPackageAsync(_packagename, _version, true);
|
||||
if (package == null || string.IsNullOrEmpty(package.PackageUrl))
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Validate"], MessageType.Warning);
|
||||
}
|
||||
else
|
||||
{
|
||||
_packageurl = package.PackageUrl;
|
||||
AddModuleMessage(Localizer["Message.Download"], MessageType.Info);
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packagename, _version);
|
||||
AddModuleMessage(Localizer["Error.Validate"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
private string Browse(Page page) => string.IsNullOrEmpty(page.Url) ? NavigateUrl(page.Path) : page.Url;
|
||||
}
|
||||
|
||||
@ -13,32 +13,32 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="container">
|
||||
<div class="row mb-3 align-items-center">
|
||||
<div class="col-sm-6">
|
||||
<ActionLink Action="Add" Text="Install Module" ResourceKey="InstallModule" />
|
||||
@((MarkupString)" ")
|
||||
<ActionLink Action="Create" Text="Create Module" ResourceKey="CreateModule" Class="btn btn-secondary" />
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<select class="form-select" @onchange="(e => CategoryChanged(e))">
|
||||
@foreach (var category in _categories)
|
||||
{
|
||||
if (category == _category)
|
||||
{
|
||||
<option value="@category" selected>@category @Localizer["Modules"]</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@category">@category @Localizer["Modules"]</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Pager Items="@_moduleDefinitions">
|
||||
<div class="container">
|
||||
<div class="row mb-3 align-items-center">
|
||||
<div class="col-sm-6">
|
||||
<ActionLink Action="Add" Text="Install Module" ResourceKey="InstallModule" />
|
||||
<ActionLink Action="Create" Text="Create Module" ResourceKey="CreateModule" Class="btn btn-secondary ms-1" />
|
||||
<button type="button" class="btn btn-secondary ms-1" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<select class="form-select" @onchange="(e => CategoryChanged(e))">
|
||||
@foreach (var category in _categories)
|
||||
{
|
||||
if (category == _category)
|
||||
{
|
||||
<option value="@category" selected>@category @Localizer["Modules"]</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@category">@category @Localizer["Modules"]</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Pager Items="@_moduleDefinitions">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
@ -61,17 +61,17 @@ else
|
||||
<td>@context.Name</td>
|
||||
<td>@context.Version</td>
|
||||
<td>
|
||||
@if (context.IsEnabled)
|
||||
{
|
||||
<span>@SharedLocalizer["Yes"]</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>@SharedLocalizer["No"]</span>
|
||||
}
|
||||
@if (context.IsEnabled)
|
||||
{
|
||||
<span>@SharedLocalizer["Yes"]</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>@SharedLocalizer["No"]</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (context.AssemblyName == Constants.ClientId || _modules.Where(m => m.ModuleDefinition?.ModuleDefinitionId == context.ModuleDefinitionId).FirstOrDefault() != null)
|
||||
@if (context.AssemblyName == Constants.ClientId || _modules.Where(m => m.ModuleDefinition?.ModuleDefinitionId == context.ModuleDefinitionId).FirstOrDefault() != null)
|
||||
{
|
||||
<span>@SharedLocalizer["Yes"]</span>
|
||||
}
|
||||
@ -87,9 +87,9 @@ else
|
||||
@((MarkupString)PurchaseLink(context.PackageName))
|
||||
</td>
|
||||
<td>
|
||||
@{
|
||||
var version = UpgradeAvailable(context.PackageName, context.Version);
|
||||
}
|
||||
@{
|
||||
var version = UpgradeAvailable(context.PackageName, context.Version);
|
||||
}
|
||||
@if (version != context.Version)
|
||||
{
|
||||
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadModule(context.PackageName, version))>@SharedLocalizer["Upgrade"]</button>
|
||||
@ -153,10 +153,10 @@ else
|
||||
link = "<a class=\"btn btn-primary\" style=\"text-decoration: none !important\" href=\"" + package.PaymentUrl + "\" target=\"_new\">" + package.ExpiryDate.Value.Date.ToString("MMM dd, yyyy") + "</a>";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return link;
|
||||
}
|
||||
}
|
||||
}
|
||||
return link;
|
||||
}
|
||||
|
||||
private string SupportLink(string packagename, string version)
|
||||
{
|
||||
@ -172,52 +172,75 @@ else
|
||||
return link;
|
||||
}
|
||||
|
||||
private string UpgradeAvailable(string packagename, string version)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(packagename) && _packages != null)
|
||||
{
|
||||
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
|
||||
if (package != null && Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0)
|
||||
{
|
||||
return package.Version;
|
||||
}
|
||||
}
|
||||
return version;
|
||||
}
|
||||
private string UpgradeAvailable(string packagename, string version)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(packagename) && _packages != null)
|
||||
{
|
||||
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
|
||||
if (package != null && Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0)
|
||||
{
|
||||
return package.Version;
|
||||
}
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
private async Task DownloadModule(string packagename, string version)
|
||||
{
|
||||
try
|
||||
{
|
||||
await PackageService.DownloadPackageAsync(packagename, version);
|
||||
await logger.LogInformation("Module Downloaded {ModuleDefinitionName} {Version}", packagename, version);
|
||||
AddModuleMessage(string.Format(Localizer["Success.Module.Install"], NavigateUrl("admin/system")), MessageType.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Downloading Module {ModuleDefinitionName} {Version} {Error}", packagename, version, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
private async Task DownloadModule(string packagename, string version)
|
||||
{
|
||||
try
|
||||
{
|
||||
await PackageService.DownloadPackageAsync(packagename, version);
|
||||
await logger.LogInformation("Module Downloaded {ModuleDefinitionName} {Version}", packagename, version);
|
||||
AddModuleMessage(string.Format(Localizer["Success.Module.Install"], NavigateUrl("admin/system")), MessageType.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Downloading Module {ModuleDefinitionName} {Version} {Error}", packagename, version, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteModule(ModuleDefinition moduleDefinition)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ModuleDefinitionService.DeleteModuleDefinitionAsync(moduleDefinition.ModuleDefinitionId, moduleDefinition.SiteId);
|
||||
AddModuleMessage(Localizer["Success.Module.Delete"], MessageType.Success);
|
||||
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Deleting Module {ModuleDefinition} {Error}", moduleDefinition, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Module.Delete"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
private async Task DeleteModule(ModuleDefinition moduleDefinition)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ModuleDefinitionService.DeleteModuleDefinitionAsync(moduleDefinition.ModuleDefinitionId, moduleDefinition.SiteId);
|
||||
AddModuleMessage(Localizer["Success.Module.Delete"], MessageType.Success);
|
||||
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Deleting Module {ModuleDefinition} {Error}", moduleDefinition, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Module.Delete"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CategoryChanged(ChangeEventArgs e)
|
||||
{
|
||||
_category = (string)e.Value;
|
||||
private async Task CategoryChanged(ChangeEventArgs e)
|
||||
{
|
||||
_category = (string)e.Value;
|
||||
await LoadModuleDefinitions();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Synchronize()
|
||||
{
|
||||
try
|
||||
{
|
||||
ShowProgressIndicator();
|
||||
foreach (var moduleDefinition in _moduleDefinitions)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(moduleDefinition.PackageName) && !_packages.Any(item => item.PackageId == moduleDefinition.PackageName))
|
||||
{
|
||||
var package = await PackageService.GetPackageAsync(moduleDefinition.PackageName, moduleDefinition.Version, false);
|
||||
}
|
||||
}
|
||||
HideProgressIndicator();
|
||||
AddModuleMessage(Localizer["Success.Module.Synchronize"], MessageType.Success);
|
||||
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Synchronizing Modules {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Module.Synchronize"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,24 +5,57 @@
|
||||
@inject IStringLocalizer<Export> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="content" HelpText="The Exported Module Content" ResourceKey="Content">Content: </Label>
|
||||
<div class="col-sm-9">
|
||||
<textarea id="content" class="form-control" @bind="@_content" rows="5" readonly></textarea>
|
||||
<TabStrip>
|
||||
<TabPanel Name="Content" Heading="Content" ResourceKey="Content">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="content" HelpText="Select the Export option and you will be able to view the module content" ResourceKey="Content">Content: </Label>
|
||||
<div class="col-sm-9">
|
||||
<textarea id="content" class="form-control" @bind="@_content" rows="5" readonly></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="ExportText">@Localizer["Export"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||
</TabPanel>
|
||||
<TabPanel Name="File" Heading="File" ResourceKey="File">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="folder" HelpText="Select a folder where you wish to save the exported content" ResourceKey="Folder">Folder: </Label>
|
||||
<div class="col-sm-9">
|
||||
<FileManager ShowFiles="false" ShowUpload="false" @ref="_filemanager" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="filename" HelpText="Specify a name for the file (without an extension)" ResourceKey="Filename">Filename: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="content" type="text" class="form-control" @bind="@_filename" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="ExportFile">@Localizer["Export"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||
</TabPanel>
|
||||
</TabStrip>
|
||||
|
||||
|
||||
<button type="button" class="btn btn-success" @onclick="ExportModule">@Localizer["Export"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||
|
||||
@code {
|
||||
private string _content = string.Empty;
|
||||
private FileManager _filemanager;
|
||||
private string _filename = string.Empty;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||
public override string Title => "Export Content";
|
||||
|
||||
private async Task ExportModule()
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_filename = Utilities.GetFriendlyUrl(ModuleState.Title);
|
||||
}
|
||||
|
||||
private async Task ExportText()
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -35,4 +68,34 @@
|
||||
AddModuleMessage(Localizer["Error.Module.Export"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExportFile()
|
||||
{
|
||||
try
|
||||
{
|
||||
var folderid = _filemanager.GetFolderId();
|
||||
if (folderid != -1 && !string.IsNullOrEmpty(_filename))
|
||||
{
|
||||
var fileid = await ModuleService.ExportModuleAsync(ModuleState.ModuleId, PageState.Page.PageId, folderid, _filename);
|
||||
if (fileid != -1)
|
||||
{
|
||||
AddModuleMessage(Localizer["Success.Content.Export"], MessageType.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Error.Module.Export"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Content.Export"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Exporting Module {ModuleId} {Error}", ModuleState.ModuleId, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Module.Export"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -2,20 +2,27 @@
|
||||
@inherits ModuleBase
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IModuleService ModuleService
|
||||
@inject IFileService FileService
|
||||
@inject IStringLocalizer<Import> 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="content" HelpText="Enter The Module Content To Import" ResourceKey="Content">Content: </Label>
|
||||
<Label Class="col-sm-3" For="file" HelpText="Optionally upload or select a file to import for this module" ResourceKey="File">File: </Label>
|
||||
<div class="col-sm-9">
|
||||
<FileManager Filter="json" OnSelectFile="OnSelectFile" />
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="content" HelpText="Provide the module content to import" ResourceKey="Content">Content: </Label>
|
||||
<div class="col-sm-9">
|
||||
<textarea id="content" class="form-control" @bind="@_content" rows="5" required></textarea>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="ImportModule">@Localizer["Import"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||
</form>
|
||||
@ -28,6 +35,12 @@
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||
public override string Title => "Import Content";
|
||||
|
||||
private async Task OnSelectFile(int fileId)
|
||||
{
|
||||
var bytes = await FileService.DownloadFileAsync(fileId);
|
||||
_content = System.Text.Encoding.UTF8.GetString(bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
private async Task ImportModule()
|
||||
{
|
||||
validated = true;
|
||||
|
||||
@ -97,6 +97,23 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<Section Name="ModuleContent" Heading="Content" ResourceKey="ModuleContent">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="moduleheader" HelpText="Optionally provide content to be injected above the module instance" ResourceKey="Header">Header: </Label>
|
||||
<div class="col-sm-9">
|
||||
<textarea id="moduleheader" class="form-control" @bind="@_header" rows="3" maxlength="4000"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="modulefooter" HelpText="Optionally provide content to be injected below the module instance" ResourceKey="Footer">Footer: </Label>
|
||||
<div class="col-sm-9">
|
||||
<textarea id="modulefooter" class="form-control" @bind="@_footer" rows="3" maxlength="4000"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
}
|
||||
</TabPanel>
|
||||
<TabPanel Name="Permissions" Heading="Permissions" ResourceKey="Permissions">
|
||||
@ -144,6 +161,8 @@
|
||||
private string _pane;
|
||||
private string _containerType;
|
||||
private string _allPages = "false";
|
||||
private string _header = "";
|
||||
private string _footer = "";
|
||||
private string _permissionNames = "";
|
||||
private List<Permission> _permissions = null;
|
||||
private string _pageId;
|
||||
@ -167,37 +186,47 @@
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
SetModuleTitle(Localizer["ModuleSettings.Title"]);
|
||||
|
||||
_title = ModuleState.Title;
|
||||
_moduleSettingsTitle = Localizer["ModuleSettings.Heading"];
|
||||
_pane = ModuleState.Pane;
|
||||
|
||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, PageState.Page.ThemeType);
|
||||
_containerType = ModuleState.ContainerType;
|
||||
_allPages = ModuleState.AllPages.ToString();
|
||||
_permissions = ModuleState.PermissionList;
|
||||
_pageId = ModuleState.PageId.ToString();
|
||||
createdby = ModuleState.CreatedBy;
|
||||
createdon = ModuleState.CreatedOn;
|
||||
modifiedby = ModuleState.ModifiedBy;
|
||||
modifiedon = ModuleState.ModifiedOn;
|
||||
_effectivedate = Utilities.UtcAsLocalDate(ModuleState.EffectiveDate);
|
||||
_expirydate = Utilities.UtcAsLocalDate(ModuleState.ExpiryDate);
|
||||
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||
|
||||
if (ModuleState.ModuleDefinition != null)
|
||||
{
|
||||
_module = ModuleState.ModuleDefinition.Name;
|
||||
_permissionNames = ModuleState.ModuleDefinition?.PermissionNames;
|
||||
var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
|
||||
|
||||
if (!string.IsNullOrEmpty(ModuleState.ModuleDefinition.SettingsType))
|
||||
_pageId = pagemodule.PageId.ToString();
|
||||
_title = pagemodule.Title;
|
||||
_pane = pagemodule.Pane;
|
||||
_containerType = pagemodule.ContainerType;
|
||||
if (string.IsNullOrEmpty(_containerType))
|
||||
{
|
||||
_containerType = (!string.IsNullOrEmpty(PageState.Page.DefaultContainerType)) ? PageState.Page.DefaultContainerType : PageState.Site.DefaultContainerType;
|
||||
}
|
||||
_header = pagemodule.Header;
|
||||
_footer = pagemodule.Footer;
|
||||
_effectivedate = Utilities.UtcAsLocalDate(pagemodule.EffectiveDate);
|
||||
_expirydate = Utilities.UtcAsLocalDate(pagemodule.ExpiryDate);
|
||||
|
||||
_allPages = pagemodule.Module.AllPages.ToString();
|
||||
createdby = pagemodule.Module.CreatedBy;
|
||||
createdon = pagemodule.Module.CreatedOn;
|
||||
modifiedby = pagemodule.Module.ModifiedBy;
|
||||
modifiedon = pagemodule.Module.ModifiedOn;
|
||||
_permissions = pagemodule.Module.PermissionList;
|
||||
|
||||
if (pagemodule.Module.ModuleDefinition != null)
|
||||
{
|
||||
_module = pagemodule.Module.ModuleDefinition.Name;
|
||||
_permissionNames = pagemodule.Module.ModuleDefinition?.PermissionNames;
|
||||
|
||||
if (!string.IsNullOrEmpty(pagemodule.Module.ModuleDefinition.SettingsType))
|
||||
{
|
||||
// module settings type explicitly declared in IModule interface
|
||||
_moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.SettingsType);
|
||||
_moduleSettingsType = Type.GetType(pagemodule.Module.ModuleDefinition.SettingsType);
|
||||
}
|
||||
else
|
||||
{
|
||||
// legacy support - module settings type determined by convention ( ie. existence of a "Settings.razor" component in module )
|
||||
_moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, PageState.Action), false, true);
|
||||
_moduleSettingsType = Type.GetType(pagemodule.Module.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, PageState.Action), false, true);
|
||||
}
|
||||
if (_moduleSettingsType != null)
|
||||
{
|
||||
@ -218,7 +247,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(string.Format(Localizer["Error.Module.Load"], ModuleState.ModuleDefinitionName), MessageType.Error);
|
||||
AddModuleMessage(string.Format(Localizer["Error.Module.Load"], pagemodule.Module.ModuleDefinitionName), MessageType.Error);
|
||||
}
|
||||
|
||||
var theme = PageState.Site.Themes.FirstOrDefault(item => item.Containers.Any(themecontrol => themecontrol.TypeName.Equals(_containerType)));
|
||||
@ -270,10 +299,12 @@
|
||||
{
|
||||
pagemodule.ContainerType = string.Empty;
|
||||
}
|
||||
pagemodule.Header = _header;
|
||||
pagemodule.Footer = _footer;
|
||||
await PageModuleService.UpdatePageModuleAsync(pagemodule);
|
||||
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
|
||||
|
||||
var module = ModuleState;
|
||||
var module = await ModuleService.GetModuleAsync(ModuleState.ModuleId);
|
||||
module.AllPages = bool.Parse(_allPages);
|
||||
module.PageModuleId = ModuleState.PageModuleId;
|
||||
module.PermissionList = _permissionGrid.GetPermissionList();
|
||||
|
||||
@ -10,13 +10,13 @@
|
||||
@if (_initialized)
|
||||
{
|
||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
<TabStrip Refresh="@_refresh" ActiveTab="@_activetab">
|
||||
<TabStrip Refresh="@_refresh">
|
||||
<TabPanel Name="Settings" ResourceKey="Settings" Heading="Settings">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="name" class="form-control" @bind="@_name" required />
|
||||
<input id="name" class="form-control" @bind="@_name" maxlength="50" required />
|
||||
</div>
|
||||
</div>
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||
@ -101,13 +101,13 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="path" HelpText="Optionally enter a url path for this page (ie. home ). If you do not provide a url path, the page name will be used. If the page is intended to be the root path specify '/'." ResourceKey="UrlPath">Url Path: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="path" class="form-control" @bind="@_path" />
|
||||
<input id="path" class="form-control" @bind="@_path" maxlength="256" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="url" HelpText="Optionally enter a url which this page should redirect to when a user navigates to it" ResourceKey="Redirect">Redirect: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="url" class="form-control" @bind="@_url" />
|
||||
<input id="url" class="form-control" @bind="@_url" maxlength="500" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
@ -147,7 +147,7 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="title" class="form-control" @bind="@_title" />
|
||||
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
@ -186,13 +186,13 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="headcontent" HelpText="Optionally enter content to be included in the page head (ie. meta, link, or script tags)" ResourceKey="HeadContent">Head Content: </Label>
|
||||
<div class="col-sm-9">
|
||||
<textarea id="headcontent" class="form-control" @bind="@_headcontent" rows="3"></textarea>
|
||||
<textarea id="headcontent" class="form-control" @bind="@_headcontent" rows="3" maxlength="4000"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="bodycontent" HelpText="Optionally enter content to be included in the page body (ie. script tags)" ResourceKey="BodyContent">Body Content: </Label>
|
||||
<div class="col-sm-9">
|
||||
<textarea id="bodycontent" class="form-control" @bind="@_bodycontent" rows="3"></textarea>
|
||||
<textarea id="bodycontent" class="form-control" @bind="@_bodycontent" rows="3" maxlength="4000"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -246,7 +246,6 @@
|
||||
private string _iconresources = "";
|
||||
private DateTime? _effectivedate = null;
|
||||
private DateTime? _expirydate = null;
|
||||
private string _activetab = "";
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
@ -270,8 +269,16 @@
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) || (_parent != null && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, _parent.PermissionList)))
|
||||
{
|
||||
_themetype = PageState.Site.DefaultThemeType;
|
||||
_themes = ThemeService.GetThemeControls(PageState.Site.Themes);
|
||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
||||
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);
|
||||
_containers = ThemeService.GetContainerControls(themes, _themetype);
|
||||
_containertype = PageState.Site.DefaultContainerType;
|
||||
_children = new List<Page>();
|
||||
foreach (Page p in _pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
|
||||
@ -317,10 +324,11 @@
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading Child Pages For Parent {PageId} {Error}", _parentid, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.ChildPage.Load"], MessageType.Error);
|
||||
await ScrollToPageTop();
|
||||
}
|
||||
}
|
||||
|
||||
private void ThemeChanged(ChangeEventArgs e)
|
||||
private async Task ThemeChanged(ChangeEventArgs e)
|
||||
{
|
||||
_themetype = (string)e.Value;
|
||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
||||
@ -331,12 +339,12 @@
|
||||
if (ThemeService.GetTheme(PageState.Site.Themes, _themetype)?.ThemeName != ThemeService.GetTheme(PageState.Site.Themes, PageState.Site.DefaultThemeType)?.ThemeName)
|
||||
{
|
||||
AddModuleMessage(Localizer["ThemeChanged.Message"], MessageType.Warning);
|
||||
await ScrollToPageTop();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SavePage()
|
||||
{
|
||||
_activetab = "Settings";
|
||||
validated = true;
|
||||
var interop = new Interop(JSRuntime);
|
||||
if (await interop.FormValid(form))
|
||||
@ -347,6 +355,7 @@
|
||||
if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate))
|
||||
{
|
||||
AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning);
|
||||
await ScrollToPageTop();
|
||||
return;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(_themetype) && !string.IsNullOrEmpty(_containertype))
|
||||
@ -397,12 +406,14 @@
|
||||
if (_pages.Any(item => item.Path == page.Path))
|
||||
{
|
||||
AddModuleMessage(string.Format(Localizer["Message.Page.Exists"], _path), MessageType.Warning);
|
||||
await ScrollToPageTop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (page.ParentId == null && Constants.ReservedRoutes.Contains(page.Name.ToLower()))
|
||||
{
|
||||
AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], page.Name), MessageType.Warning);
|
||||
await ScrollToPageTop();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -470,6 +481,7 @@
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Required.PageInfo"], MessageType.Warning);
|
||||
await ScrollToPageTop();
|
||||
}
|
||||
|
||||
}
|
||||
@ -477,11 +489,13 @@
|
||||
{
|
||||
await logger.LogError(ex, "Error Saving Page {Page} {Error}", page, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Page.Save"], MessageType.Error);
|
||||
await ScrollToPageTop();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||
await ScrollToPageTop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
@if (_page.UserId == null)
|
||||
{
|
||||
<TabStrip Refresh="@_refresh" ActiveTab="@_activetab">
|
||||
<TabStrip Refresh="@_refresh">
|
||||
<TabPanel Name="Settings" ResourceKey="Settings" Heading="Settings">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
@ -116,7 +116,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="path" HelpText="Optionally enter a url path for this page (ie. home ). If you do not provide a url path, the page name will be used. If the page is intended to be the root path specify '/'." ResourceKey="UrlPath">Url Path: </Label>
|
||||
<Label Class="col-sm-3" For="path" HelpText="Optionally enter a url path for this page (ie. home ). If you do not provide a url path, the page name will be used. Please note that spaces and punctuation will be replaced by a dash. If the page is intended to be the root path specify '/'." ResourceKey="UrlPath">Url Path: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="path" class="form-control" @bind="@_path" maxlength="256" />
|
||||
</div>
|
||||
@ -205,18 +205,21 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="headcontent" HelpText="Optionally enter content to be included in the page head (ie. meta, link, or script tags)" ResourceKey="HeadContent">Head Content: </Label>
|
||||
<div class="col-sm-9">
|
||||
<textarea id="headcontent" class="form-control" @bind="@_headcontent" rows="3"></textarea>
|
||||
<textarea id="headcontent" class="form-control" @bind="@_headcontent" rows="3" maxlength="4000"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="bodycontent" HelpText="Optionally enter content to be included in the page body (ie. script tags)" ResourceKey="BodyContent">Body Content: </Label>
|
||||
<div class="col-sm-9">
|
||||
<textarea id="bodycontent" class="form-control" @bind="@_bodycontent" rows="3"></textarea>
|
||||
<textarea id="bodycontent" class="form-control" @bind="@_bodycontent" rows="3" maxlength="4000"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
<br />
|
||||
<br />
|
||||
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
|
||||
</TabPanel>
|
||||
@ -225,6 +228,19 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<PermissionGrid EntityName="@EntityNames.Page" PermissionList="@_permissions" @ref="_permissionGrid" />
|
||||
</div>
|
||||
<br /><br />
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="updatemodulepermissions" HelpText="Specify if changes made to page permissions should be propagated to the modules on this page" ResourceKey="UpdateModulePermissions">Update Module Permissions? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="updatemodulepermissions" class="form-select" @bind="@_updatemodulepermissions" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel Name="PageModules" Heading="Modules" ResourceKey="PageModules">
|
||||
@ -247,8 +263,10 @@
|
||||
{
|
||||
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
|
||||
@_themeSettingsComponent
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
</TabPanel>
|
||||
<br />
|
||||
}
|
||||
</TabStrip>
|
||||
}
|
||||
@ -263,6 +281,12 @@
|
||||
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="path" HelpText="Provide a url path for your personalized page. Please note that spaces and punctuation will be replaced by a dash." ResourceKey="PersonalizedUrlPath">Url Path: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="path" class="form-control" @bind="@_path" maxlength="256" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
|
||||
<div class="col-sm-9">
|
||||
@ -293,19 +317,21 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
</TabPanel>
|
||||
@if (_themeSettingsType != null)
|
||||
{
|
||||
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
|
||||
@_themeSettingsComponent
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
</TabPanel>
|
||||
<br />
|
||||
}
|
||||
</TabStrip>
|
||||
}
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
</form>
|
||||
}
|
||||
|
||||
@ -342,6 +368,7 @@
|
||||
private string _bodycontent;
|
||||
private List<Permission> _permissions = null;
|
||||
private PermissionGrid _permissionGrid;
|
||||
private string _updatemodulepermissions;
|
||||
private List<Module> _pageModules;
|
||||
private string _createdby;
|
||||
private DateTime _createdon;
|
||||
@ -356,7 +383,6 @@
|
||||
private string _iconresources = "";
|
||||
private DateTime? _effectivedate = null;
|
||||
private DateTime? _expirydate = null;
|
||||
private string _activetab = "";
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
@ -417,8 +443,16 @@
|
||||
{
|
||||
_themetype = PageState.Site.DefaultThemeType;
|
||||
}
|
||||
_themes = ThemeService.GetThemeControls(PageState.Site.Themes);
|
||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
||||
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);
|
||||
_containers = ThemeService.GetContainerControls(themes, _themetype);
|
||||
_containertype = _page.DefaultContainerType;
|
||||
if (string.IsNullOrEmpty(_containertype))
|
||||
{
|
||||
@ -431,6 +465,7 @@
|
||||
|
||||
// permissions
|
||||
_permissions = _page.PermissionList;
|
||||
_updatemodulepermissions = "True";
|
||||
|
||||
// page modules
|
||||
var modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
|
||||
@ -480,10 +515,11 @@
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading Child Pages For Parent {PageId} {Error}", _parentid, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.ChildPage.Load"], MessageType.Error);
|
||||
await ScrollToPageTop();
|
||||
}
|
||||
}
|
||||
|
||||
private void ThemeChanged(ChangeEventArgs e)
|
||||
private async Task ThemeChanged(ChangeEventArgs e)
|
||||
{
|
||||
_themetype = (string)e.Value;
|
||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
||||
@ -495,6 +531,7 @@
|
||||
if (ThemeService.GetTheme(PageState.Site.Themes, _themetype)?.ThemeName != ThemeService.GetTheme(PageState.Site.Themes, PageState.Site.DefaultThemeType)?.ThemeName)
|
||||
{
|
||||
AddModuleMessage(Localizer["ThemeChanged.Message"], MessageType.Warning);
|
||||
await ScrollToPageTop();
|
||||
}
|
||||
}
|
||||
|
||||
@ -513,7 +550,7 @@
|
||||
builder.OpenComponent(0, _themeSettingsType);
|
||||
builder.AddAttribute(1, "RenderModeBoundary", RenderModeBoundary);
|
||||
builder.AddComponentReferenceCapture(2, inst => { _themeSettings = Convert.ChangeType(inst, _themeSettingsType); });
|
||||
|
||||
|
||||
builder.CloseComponent();
|
||||
};
|
||||
}
|
||||
@ -523,7 +560,6 @@
|
||||
|
||||
private async Task SavePage()
|
||||
{
|
||||
_activetab = "Settings";
|
||||
validated = true;
|
||||
var interop = new Interop(JSRuntime);
|
||||
if (await interop.FormValid(form))
|
||||
@ -533,6 +569,7 @@
|
||||
if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate))
|
||||
{
|
||||
AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning);
|
||||
await ScrollToPageTop();
|
||||
return;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(_themetype) && _containertype != "-")
|
||||
@ -583,12 +620,14 @@
|
||||
if (_pages.Any(item => item.Path == _page.Path && item.PageId != _page.PageId))
|
||||
{
|
||||
AddModuleMessage(string.Format(Localizer["Mesage.Page.PathExists"], _path), MessageType.Warning);
|
||||
await ScrollToPageTop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_page.ParentId == null && Constants.ReservedRoutes.Contains(_page.Name.ToLower()))
|
||||
{
|
||||
AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], _page.Name), MessageType.Warning);
|
||||
await ScrollToPageTop();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -642,6 +681,7 @@
|
||||
if (_page.UserId == null)
|
||||
{
|
||||
_page.PermissionList = _permissionGrid.GetPermissionList();
|
||||
_page.UpdateModulePermissions = bool.Parse(_updatemodulepermissions);
|
||||
}
|
||||
|
||||
_page = await PageService.UpdatePageAsync(_page);
|
||||
@ -672,18 +712,21 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Required.PageInfo"], MessageType.Warning);
|
||||
AddModuleMessage(Localizer["Message.Required.PageInfo"], MessageType.Warning);
|
||||
await ScrollToPageTop();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Saving Page {Page} {Error}", _page, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Page.Save"], MessageType.Error);
|
||||
await ScrollToPageTop();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||
await ScrollToPageTop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
@inherits ModuleBase
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IProfileService ProfileService
|
||||
@inject ISettingService SettingService
|
||||
@inject IStringLocalizer<Edit> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@ -56,9 +57,25 @@
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<input id="options" class="form-control" @bind="@_options" maxlength="2000" />
|
||||
<div class="input-group">
|
||||
@if (_optiontype == "Settings")
|
||||
{
|
||||
<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 class="row mb-1 align-items-center">
|
||||
@ -95,7 +112,7 @@
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveProfile">@SharedLocalizer["Save"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
@if (PageState.QueryString.ContainsKey("id"))
|
||||
@if (PageState.QueryString.ContainsKey("id"))
|
||||
{
|
||||
<br />
|
||||
<br />
|
||||
@ -116,6 +133,8 @@
|
||||
private string _rows = "1";
|
||||
private string _defaultvalue = string.Empty;
|
||||
private string _options = string.Empty;
|
||||
private string _optiontype = "Settings";
|
||||
private List<string> _entitynames;
|
||||
private string _validation = string.Empty;
|
||||
private string _autocomplete = string.Empty;
|
||||
private string _isrequired = "False";
|
||||
@ -133,6 +152,8 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
_entitynames = await SettingService.GetEntityNamesAsync();
|
||||
|
||||
if (PageState.QueryString.ContainsKey("id"))
|
||||
{
|
||||
_profileid = Int32.Parse(PageState.QueryString["id"]);
|
||||
@ -148,6 +169,11 @@
|
||||
_rows = profile.Rows.ToString();
|
||||
_defaultvalue = profile.DefaultValue;
|
||||
_options = profile.Options;
|
||||
if (_options.StartsWith("EntityName:"))
|
||||
{
|
||||
_optiontype = "Options";
|
||||
_options = _options.Substring(11);
|
||||
}
|
||||
_validation = profile.Validation;
|
||||
_autocomplete = profile.Autocomplete;
|
||||
_isrequired = profile.IsRequired.ToString();
|
||||
@ -166,6 +192,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleOptionType()
|
||||
{
|
||||
if (_optiontype == "Options")
|
||||
{
|
||||
_optiontype = "Settings";
|
||||
}
|
||||
else
|
||||
{
|
||||
_optiontype = "Options";
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveProfile()
|
||||
{
|
||||
validated = true;
|
||||
@ -193,7 +231,14 @@
|
||||
profile.MaxLength = int.Parse(_maxlength);
|
||||
profile.Rows = int.Parse(_rows);
|
||||
profile.DefaultValue = _defaultvalue;
|
||||
profile.Options = _options;
|
||||
if (_optiontype == "Options" && !string.IsNullOrEmpty(_options))
|
||||
{
|
||||
profile.Options = "EntityName:" + _options;
|
||||
}
|
||||
else
|
||||
{
|
||||
profile.Options = _options;
|
||||
}
|
||||
profile.Validation = _validation;
|
||||
profile.Autocomplete = _autocomplete;
|
||||
profile.IsRequired = (_isrequired == null ? false : Boolean.Parse(_isrequired));
|
||||
|
||||
@ -13,71 +13,71 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<TabStrip>
|
||||
<TabPanel Name="Pages" ResourceKey="Pages" Heading="Pages">
|
||||
@if (!_pages.Where(item => item.IsDeleted).Any())
|
||||
{
|
||||
<br />
|
||||
<p>@Localizer["NoPage.Deleted"]</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<Pager Items="@_pages.Where(item => item.IsDeleted).OrderByDescending(item => item.DeletedOn)" CurrentPage="@_pagePage.ToString()" OnPageChange="OnPageChangePage">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@SharedLocalizer["Name"]</th>
|
||||
<th>@Localizer["DeletedBy"]</th>
|
||||
<th>@Localizer["DeletedOn"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<TabStrip>
|
||||
<TabPanel Name="Pages" ResourceKey="Pages" Heading="Pages">
|
||||
@if (!_pages.Where(item => item.IsDeleted).Any())
|
||||
{
|
||||
<br />
|
||||
<p>@Localizer["NoPage.Deleted"]</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<Pager Items="@_pages.Where(item => item.IsDeleted).OrderByDescending(item => item.DeletedOn)" CurrentPage="@_pagePage.ToString()" OnPageChange="OnPageChangePage">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@SharedLocalizer["Path"]</th>
|
||||
<th>@Localizer["DeletedBy"]</th>
|
||||
<th>@Localizer["DeletedOn"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><button type="button" @onclick="@(() => RestorePage(context))" class="btn btn-success" title="Restore">@Localizer["Restore"]</button></td>
|
||||
<td><ActionDialog Header="Delete Page" Message="@string.Format(Localizer["Confirm.Page.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
|
||||
<td>@context.Name</td>
|
||||
<td>@context.DeletedBy</td>
|
||||
<td>@context.DeletedOn</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
<br />
|
||||
<ActionDialog Header="Remove All Deleted Pages" Message="Are You Sure You Wish To Permanently Remove All Deleted Pages?" Action="Remove All Deleted Pages" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllPages())" ResourceKey="DeleteAllPages" />
|
||||
}
|
||||
</TabPanel>
|
||||
<TabPanel Name="Modules" ResourceKey="Modules" Heading="Modules">
|
||||
@if (!_modules.Where(item => item.IsDeleted).Any())
|
||||
{
|
||||
<br />
|
||||
<p>@Localizer["NoModule.Deleted"]</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<Pager Items="@_modules.Where(item => item.IsDeleted).OrderByDescending(item => item.DeletedOn)" CurrentPage="@_pageModule.ToString()" OnPageChange="OnPageChangeModule">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@Localizer["Page"]</th>
|
||||
<th>@Localizer["Module"]</th>
|
||||
<th>@Localizer["DeletedBy"]</th>
|
||||
<th>@Localizer["DeletedOn"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><button type="button" @onclick="@(() => RestoreModule(context))" class="btn btn-success" title="Restore">@Localizer["Restore"]</button></td>
|
||||
<td><ActionDialog Header="Delete Module" Message="@string.Format(Localizer["Confirm.Module.Delete"], context.Title)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" /></td>
|
||||
<td>@_pages.Find(item => item.PageId == context.PageId).Name</td>
|
||||
<td>@context.Title</td>
|
||||
<td>@context.DeletedBy</td>
|
||||
<td>@context.DeletedOn</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
<br />
|
||||
<ActionDialog Header="Remove All Deleted Modules" Message="Are You Sure You Wish To Permanently Remove All Deleted Modules?" Action="Remove All Deleted Modules" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllModules())" ResourceKey="DeleteAllModules" />
|
||||
}
|
||||
</TabPanel>
|
||||
</TabStrip>
|
||||
<td><ActionDialog Header="Delete Page" Message="@string.Format(Localizer["Confirm.Page.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
|
||||
<td>@context.Path</td>
|
||||
<td>@context.DeletedBy</td>
|
||||
<td>@context.DeletedOn</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
<br />
|
||||
<ActionDialog Header="Remove All Deleted Pages" Message="Are You Sure You Wish To Permanently Remove All Deleted Pages?" Action="Remove All Deleted Pages" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllPages())" ResourceKey="DeleteAllPages" />
|
||||
}
|
||||
</TabPanel>
|
||||
<TabPanel Name="Modules" ResourceKey="Modules" Heading="Modules">
|
||||
@if (!_modules.Where(item => item.IsDeleted).Any())
|
||||
{
|
||||
<br />
|
||||
<p>@Localizer["NoModule.Deleted"]</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<Pager Items="@_modules.Where(item => item.IsDeleted).OrderByDescending(item => item.DeletedOn)" CurrentPage="@_pageModule.ToString()" OnPageChange="OnPageChangeModule">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@Localizer["Page"]</th>
|
||||
<th>@Localizer["Module"]</th>
|
||||
<th>@Localizer["DeletedBy"]</th>
|
||||
<th>@Localizer["DeletedOn"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><button type="button" @onclick="@(() => RestoreModule(context))" class="btn btn-success" title="Restore">@Localizer["Restore"]</button></td>
|
||||
<td><ActionDialog Header="Delete Module" Message="@string.Format(Localizer["Confirm.Module.Delete"], context.Title)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" /></td>
|
||||
<td>@_pages.Find(item => item.PageId == context.PageId).Name</td>
|
||||
<td>@context.Title</td>
|
||||
<td>@context.DeletedBy</td>
|
||||
<td>@context.DeletedOn</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
<br />
|
||||
<ActionDialog Header="Remove All Deleted Modules" Message="Are You Sure You Wish To Permanently Remove All Deleted Modules?" Action="Remove All Deleted Modules" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllModules())" ResourceKey="DeleteAllModules" />
|
||||
}
|
||||
</TabPanel>
|
||||
</TabStrip>
|
||||
}
|
||||
|
||||
@code {
|
||||
private List<Page> _pages;
|
||||
private List<Module> _modules;
|
||||
private List<Page> _pages;
|
||||
private List<Module> _modules;
|
||||
private int _pagePage = 1;
|
||||
private int _pageModule = 1;
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||
@ -105,12 +105,25 @@ else
|
||||
{
|
||||
try
|
||||
{
|
||||
page.IsDeleted = false;
|
||||
await PageService.UpdatePageAsync(page);
|
||||
await logger.LogInformation("Page Restored {Page}", page);
|
||||
await Load();
|
||||
StateHasChanged();
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
var validated = true;
|
||||
if (page.ParentId != null)
|
||||
{
|
||||
var parent = _pages.Find(item => item.PageId == page.ParentId);
|
||||
validated = !parent.IsDeleted;
|
||||
}
|
||||
if (validated)
|
||||
{
|
||||
page.IsDeleted = false;
|
||||
await PageService.UpdatePageAsync(page);
|
||||
await logger.LogInformation("Page Restored {Page}", page);
|
||||
AddModuleMessage(Localizer["Success.Page.Restore"], MessageType.Success);
|
||||
await Load();
|
||||
StateHasChanged();
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Page.Restore"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -125,9 +138,9 @@ else
|
||||
{
|
||||
await PageService.DeletePageAsync(page.PageId);
|
||||
await logger.LogInformation("Page Permanently Deleted {Page}", page);
|
||||
AddModuleMessage(Localizer["Success.Page.Delete"], MessageType.Success);
|
||||
await Load();
|
||||
StateHasChanged();
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -148,10 +161,10 @@ else
|
||||
}
|
||||
|
||||
await logger.LogInformation("Pages Permanently Deleted");
|
||||
AddModuleMessage(Localizer["Success.Pages.Delete"], MessageType.Success);
|
||||
await Load();
|
||||
HideProgressIndicator();
|
||||
StateHasChanged();
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -169,6 +182,7 @@ else
|
||||
pagemodule.IsDeleted = false;
|
||||
await PageModuleService.UpdatePageModuleAsync(pagemodule);
|
||||
await logger.LogInformation("Module Restored {Module}", module);
|
||||
AddModuleMessage(Localizer["Success.Module.Restore"], MessageType.Success);
|
||||
await Load();
|
||||
StateHasChanged();
|
||||
}
|
||||
@ -185,6 +199,7 @@ else
|
||||
{
|
||||
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
|
||||
await logger.LogInformation("Module Permanently Deleted {Module}", module);
|
||||
AddModuleMessage(Localizer["Success.Module.Delete"], MessageType.Success);
|
||||
await Load();
|
||||
StateHasChanged();
|
||||
}
|
||||
@ -205,6 +220,7 @@ else
|
||||
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
|
||||
}
|
||||
await logger.LogInformation("Modules Permanently Deleted");
|
||||
AddModuleMessage(Localizer["Success.Modules.Delete"], MessageType.Success);
|
||||
await Load();
|
||||
HideProgressIndicator();
|
||||
StateHasChanged();
|
||||
|
||||
@ -6,77 +6,95 @@
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
@inject ISettingService SettingService
|
||||
@inject ITimeZoneService TimeZoneService
|
||||
|
||||
@if (PageState.Site.AllowRegistration)
|
||||
@if (_initialized)
|
||||
{
|
||||
if (!_userCreated)
|
||||
@if (PageState.Site.AllowRegistration)
|
||||
{
|
||||
if (PageState.User != null)
|
||||
if (!_userCreated)
|
||||
{
|
||||
<ModuleMessage Message="@Localizer["Info.Registration.Exists"]" Type="MessageType.Info" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="username" HelpText="Your username. Note that this field can not be modified once it is saved." ResourceKey="Username"></Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="username" class="form-control" @bind="@_username" maxlength="256" required />
|
||||
if (PageState.User != null)
|
||||
{
|
||||
<ModuleMessage Message="@Localizer["Info.Registration.Exists"]" Type="MessageType.Info" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="username" HelpText="Your username. Note that this field can not be modified once it is saved." ResourceKey="Username"></Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="username" class="form-control" @bind="@_username" maxlength="256" required />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="password" HelpText="Please choose a sufficiently secure password and enter it here" ResourceKey="Password"></Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="password" HelpText="Please choose a sufficiently secure password and enter it here" ResourceKey="Password"></Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="confirm" HelpText="Enter your password again to confirm it matches the value entered above" ResourceKey="Confirm"></Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" required />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="email" HelpText="Your email address where you wish to receive notifications" ResourceKey="Email"></Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="email" class="form-control" @bind="@_email" maxlength="256" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="displayname" HelpText="Your full name" ResourceKey="DisplayName"></Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="displayname" class="form-control" @bind="@_displayname" maxlength="50" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="timezone" HelpText="Your time zone" ResourceKey="TimeZone">Time Zone:</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="timezone" class="form-select" @bind="@_timezoneid">
|
||||
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||
@foreach (var timezone in _timezones)
|
||||
{
|
||||
<option value="@timezone.Id">@timezone.DisplayName</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="confirm" HelpText="Enter your password again to confirm it matches the value entered above" ResourceKey="Confirm"></Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" required />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="email" HelpText="Your email address where you wish to receive notifications" ResourceKey="Email"></Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="email" class="form-control" @bind="@_email" maxlength="256" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="displayname" HelpText="Your full name" ResourceKey="DisplayName"></Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="displayname" class="form-control" @bind="@_displayname" maxlength="50" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-primary" @onclick="Register">@Localizer["Register"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
@if (_allowsitelogin)
|
||||
{
|
||||
<br />
|
||||
<button type="button" class="btn btn-primary" @onclick="Register">@Localizer["Register"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
@if (_allowsitelogin)
|
||||
{
|
||||
<br />
|
||||
|
||||
<br />
|
||||
<NavLink href="@NavigateUrl("login")">@Localizer["Login"]</NavLink>
|
||||
}
|
||||
</form>
|
||||
<br />
|
||||
<NavLink href="@NavigateUrl("login")">@Localizer["Login"]</NavLink>
|
||||
}
|
||||
</form>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<ModuleMessage Message="@Localizer["Info.Registration.Disabled"]" Type="MessageType.Info" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<ModuleMessage Message="@Localizer["Info.Registration.Disabled"]" Type="MessageType.Info" />
|
||||
}
|
||||
}
|
||||
|
||||
@code {
|
||||
private bool _initialized = false;
|
||||
private List<Models.TimeZone> _timezones;
|
||||
private string _passwordrequirements;
|
||||
private string _username = string.Empty;
|
||||
private ElementReference form;
|
||||
@ -87,6 +105,7 @@ else
|
||||
private string _confirm = string.Empty;
|
||||
private string _email = string.Empty;
|
||||
private string _displayname = string.Empty;
|
||||
private string _timezoneid = string.Empty;
|
||||
private bool _userCreated = false;
|
||||
private bool _allowsitelogin = true;
|
||||
|
||||
@ -96,6 +115,9 @@ else
|
||||
{
|
||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
_timezoneid = PageState.Site.TimeZoneId;
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
protected override void OnParametersSet()
|
||||
@ -124,6 +146,7 @@ else
|
||||
Password = _password,
|
||||
Email = _email,
|
||||
DisplayName = (_displayname == string.Empty ? _username : _displayname),
|
||||
TimeZoneId = _timezoneid,
|
||||
PhotoFileId = null
|
||||
};
|
||||
user = await UserService.AddUserAsync(user);
|
||||
|
||||
@ -58,7 +58,7 @@ else
|
||||
<td>@context.EffectiveDate</td>
|
||||
<td>@context.ExpiryDate</td>
|
||||
<td>
|
||||
<ActionDialog Header="Remove User" Message="@string.Format(Localizer["Confirm.User.DeleteRole"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.IsAutoAssigned || context.User.Username == UserNames.Host || context.User.UserId == PageState.User.UserId)" ResourceKey="DeleteUserRole" />
|
||||
<ActionDialog Header="Remove User" Message="@string.Format(Localizer["Confirm.User.DeleteRole"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.User.Username == UserNames.Host || context.User.UserId == PageState.User.UserId)" ResourceKey="DeleteUserRole" />
|
||||
</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
@ -180,27 +180,28 @@ else
|
||||
|
||||
private async Task DeleteUserRole(int UserRoleId)
|
||||
{
|
||||
validated = true;
|
||||
var interop = new Interop(JSRuntime);
|
||||
if (await interop.FormValid(form))
|
||||
try
|
||||
{
|
||||
try
|
||||
var userrole = await UserRoleService.GetUserRoleAsync(UserRoleId);
|
||||
if (userrole.Role.Name == RoleNames.Registered)
|
||||
{
|
||||
userrole.ExpiryDate = DateTime.UtcNow;
|
||||
await UserRoleService.UpdateUserRoleAsync(userrole);
|
||||
await logger.LogInformation("User {Username} Expired From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
await UserRoleService.DeleteUserRoleAsync(UserRoleId);
|
||||
await logger.LogInformation("User Removed From Role {UserRoleId}", UserRoleId);
|
||||
AddModuleMessage(Localizer["Confirm.User.RoleRemoved"], MessageType.Success);
|
||||
await GetUserRoles();
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Removing User From Role {UserRoleId} {Error}", UserRoleId, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.User.RemoveRole"], MessageType.Error);
|
||||
await logger.LogInformation("User {Username} Removed From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
||||
}
|
||||
AddModuleMessage(Localizer["Confirm.User.RoleRemoved"], MessageType.Success);
|
||||
await GetUserRoles();
|
||||
StateHasChanged();
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||
await logger.LogError(ex, "Error Removing User From Role {UserRoleId} {Error}", UserRoleId, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.User.RemoveRole"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,13 +40,16 @@
|
||||
{
|
||||
<Pager Items="@_searchResults?.Results"
|
||||
Format="Grid"
|
||||
PageSize="@_pageSize"
|
||||
DisplayPages="@_displayPages"
|
||||
CurrentPage="@_currentPage"
|
||||
Columns="1"
|
||||
Toolbar="Bottom"
|
||||
Parameters="@($"q={_keywords}")">
|
||||
<Row>
|
||||
<div class="search-item mb-2">
|
||||
<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>
|
||||
</Row>
|
||||
</Pager>
|
||||
@ -66,6 +69,7 @@
|
||||
|
||||
@code {
|
||||
public override string RenderMode => RenderModes.Static;
|
||||
private const string SearchDefaultPageSize = "10";
|
||||
|
||||
private string _includeEntities;
|
||||
private string _excludeEntities;
|
||||
@ -75,6 +79,8 @@
|
||||
private string _sortField;
|
||||
private string _sortOrder;
|
||||
private string _bodyLength;
|
||||
private string _currentPage = "0";
|
||||
private string _displayPages = "7";
|
||||
|
||||
private string _keywords;
|
||||
private SearchResults _searchResults;
|
||||
@ -89,11 +95,16 @@
|
||||
_excludeEntities = SettingService.GetSetting(ModuleState.Settings, "SearchResults_ExcludeEntities", "");
|
||||
_fromDate = SettingService.GetSetting(ModuleState.Settings, "SearchResults_FromDate", DateTime.MinValue.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");
|
||||
_sortOrder = SettingService.GetSetting(ModuleState.Settings, "SearchResults_SortOrder", "Descending");
|
||||
_bodyLength = SettingService.GetSetting(ModuleState.Settings, "SearchResults_BodyLength", "255");
|
||||
|
||||
if (PageState.QueryString.ContainsKey("p"))
|
||||
{
|
||||
_currentPage = PageState.QueryString["p"];
|
||||
}
|
||||
|
||||
if (_keywords == null && PageState.QueryString.ContainsKey("q"))
|
||||
{
|
||||
_keywords = WebUtility.UrlDecode(PageState.QueryString["q"]);
|
||||
@ -122,7 +133,7 @@
|
||||
ExcludeEntities = _excludeEntities,
|
||||
FromDate = (!string.IsNullOrEmpty(_fromDate)) ? DateTime.Parse(_fromDate) : DateTime.MinValue,
|
||||
ToDate = (!string.IsNullOrEmpty(_toDate)) ? DateTime.Parse(_toDate) : DateTime.MaxValue,
|
||||
PageSize = (!string.IsNullOrEmpty(_pageSize)) ? int.Parse(_pageSize) : int.MaxValue,
|
||||
PageSize = int.MaxValue,
|
||||
PageIndex = 0,
|
||||
SortField = (!string.IsNullOrEmpty(_sortField)) ? (SearchSortField)Enum.Parse(typeof(SearchSortField), _sortField) : SearchSortField.Relevance,
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -10,10 +10,12 @@
|
||||
@inject IAliasService AliasService
|
||||
@inject IThemeService ThemeService
|
||||
@inject ISettingService SettingService
|
||||
@inject ITimeZoneService TimeZoneService
|
||||
@inject IServiceProvider ServiceProvider
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject INotificationService NotificationService
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
@inject IOutputCacheService CacheService
|
||||
|
||||
@if (_initialized)
|
||||
{
|
||||
@ -41,20 +43,36 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="isDeleted" HelpText="Is this site deleted?" ResourceKey="IsDeleted">Deleted? </Label>
|
||||
<Label Class="col-sm-3" For="timezone" HelpText="Your time zone" ResourceKey="TimeZone">Time Zone:</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="isDeleted" class="form-select" @bind="@_isdeleted" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
<select id="timezone" class="form-select" @bind="@_timezoneid">
|
||||
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||
@foreach (var timezone in _timezones)
|
||||
{
|
||||
<option value="@timezone.Id">@timezone.DisplayName</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="isDeleted" HelpText="Is this site deleted?" ResourceKey="IsDeleted">Deleted? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="isDeleted" class="form-select" @bind="@_isdeleted" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="sitemap" HelpText="The site map url for this site which can be submitted to search engines for indexing" ResourceKey="SiteMap">Site Map: </Label>
|
||||
<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="input-group">
|
||||
<input id="sitemap" class="form-control" @bind="@_sitemap" disabled />
|
||||
<a href="@_sitemap" class="btn btn-secondary" target="_new">@Localizer["Browse"]</a>
|
||||
<button type="button" class="btn btn-danger" @onclick="EvictSitemapOutputCache">@Localizer["SiteMap.EvictCache"]</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -72,20 +90,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<Section Name="Appearance" Heading="Appearance" ResourceKey="Appearance">
|
||||
<Section Name="Theme" Heading="Theme" ResourceKey="Theme">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="logo" HelpText="Specify a logo for the site" ResourceKey="Logo">Logo: </Label>
|
||||
<div class="col-sm-9">
|
||||
<FileManager FileId="@_logofileid" Filter="@_imageFiles" @ref="_logofilemanager" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="favicon" HelpText="Specify a Favicon" ResourceKey="FavoriteIcon">Favicon: </Label>
|
||||
<div class="col-sm-9">
|
||||
<FileManager FileId="@_faviconfileid" Filter="ico,png,gif" @ref="_faviconfilemanager" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="defaultTheme" HelpText="Select the sites default theme" ResourceKey="DefaultTheme">Default Theme: </Label>
|
||||
<div class="col-sm-9">
|
||||
@ -126,6 +132,32 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="cookieconsent" HelpText="Specify if cookie consent is enabled on this site. Please note this option must be used in conjunction with a Theme which supports cookie consent." ResourceKey="CookieConsent">Cookie Consent: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="cookieconsent" class="form-select" @bind="@_cookieconsent">
|
||||
<option value="">@SharedLocalizer["Disabled"]</option>
|
||||
<option value="optin">@Localizer["OptIn"]</option>
|
||||
<option value="optout">@Localizer["OptOut"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="Appearance" Heading="Appearance" ResourceKey="Appearance">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="logo" HelpText="Specify a logo for the site" ResourceKey="Logo">Logo: </Label>
|
||||
<div class="col-sm-9">
|
||||
<FileManager FileId="@_logofileid" Filter="@_imagefiles" @ref="_logofilemanager" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="favicon" HelpText="Specify a Favicon" ResourceKey="FavoriteIcon">Favicon: </Label>
|
||||
<div class="col-sm-9">
|
||||
<FileManager FileId="@_faviconfileid" Filter="ico,png,gif" @ref="_faviconfilemanager" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="Functionality" Heading="Functionality" ResourceKey="Functionality">
|
||||
@ -144,18 +176,6 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="imageExt" HelpText="Enter a comma separated list of image file extensions" ResourceKey="ImageExtensions">Image Extensions: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="imageExt" spellcheck="false" class="form-control" @bind="@_imageFiles" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="uploadableFileExt" HelpText="Enter a comma separated list of uploadable file extensions" ResourceKey="UploadableFileExtensions">Uploadable File Extensions: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="uploadableFileExt" spellcheck="false" class="form-control" @bind="@_uploadableFiles" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="PageContent" Heading="Page Content" ResourceKey="PageContent">
|
||||
@ -177,80 +197,128 @@
|
||||
<Section Name="SMTP" Heading="SMTP Settings" ResourceKey="SMTPSettings">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<div class="col-sm-3">
|
||||
</div>
|
||||
<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">
|
||||
<strong>@Localizer["Smtp.Required.EnableNotificationJob"]</strong><br />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="host" HelpText="Enter the host name of the SMTP server" ResourceKey="Host">Host: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="host" class="form-control" @bind="@_smtphost" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="port" HelpText="Enter the port number for the SMTP server. Please note this field is required if you provide a host name." ResourceKey="Port">Port: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="port" class="form-control" @bind="@_smtpport" />
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<div class="col-sm-9">
|
||||
<select id="smtpssl" class="form-select" @bind="@_smtpssl" >
|
||||
<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>
|
||||
<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>
|
||||
<div class="col-sm-9">
|
||||
<input id="username" class="form-control" @bind="@_smtpusername" autocomplete="off"/>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<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>
|
||||
@if (_smtpenabled == "True" && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<div class="col-sm-3">
|
||||
</div>
|
||||
<div class="col-sm-9">
|
||||
<strong>@Localizer["Smtp.Required.EnableNotificationJob"]</strong><br />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<div class="col-sm-9">
|
||||
<input id="sender" class="form-control" @bind="@_smtpsender" />
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="host" HelpText="Enter the host name of the SMTP server" ResourceKey="Host">Host: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="host" class="form-control" @bind="@_smtphost" />
|
||||
</div>
|
||||
</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">
|
||||
<select id="relay" class="form-select" @bind="@_smtprelay" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="port" HelpText="Enter the port number for the SMTP server. Please note this field is required if you provide a host name." ResourceKey="Port">Port: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="port" class="form-control" @bind="@_smtpport" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="smtpenabled" HelpText="Specify if SMTP is enabled for this site" ResourceKey="SMTPEnabled">Enabled? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="smtpenabled" class="form-select" @bind="@_smtpenabled">
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<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">
|
||||
<select id="smtpssl" class="form-select" @bind="@_smtpssl">
|
||||
<option value="None">@Localizer["None"]</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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="retention" HelpText="Number of days of notifications to retain" ResourceKey="Retention">Retention (Days): </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="retention" class="form-control" type="number" min="0" step="1" @bind="@_retention" />
|
||||
<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>
|
||||
</div>
|
||||
<button type="button" class="btn btn-secondary" @onclick="SendEmail">@Localizer["Smtp.TestConfig"]</button>
|
||||
<br /><br />
|
||||
@if (_smtpauthentication == "Basic")
|
||||
{
|
||||
<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>
|
||||
<div class="col-sm-9">
|
||||
<input id="username" class="form-control" @bind="@_smtpusername" autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<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>
|
||||
</div>
|
||||
</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 below." ResourceKey="SmtpRelay">Relay Configured? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="relay" class="form-select" @bind="@_smtprelay" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<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">
|
||||
<input id="smtpauthority" class="form-control" @bind="@_smtpauthority" />
|
||||
</div>
|
||||
</div>
|
||||
<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 class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="retention" HelpText="Number of days of notifications to retain" ResourceKey="Retention">Retention (Days): </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="retention" class="form-control" type="number" min="0" step="1" @bind="@_retention" />
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-secondary" @onclick="SendEmail">@Localizer["Smtp.TestConfig"]</button>
|
||||
<br /><br />
|
||||
}
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="PWA" Heading="Progressive Web Application Settings" ResourceKey="PWASettings">
|
||||
@ -412,9 +480,11 @@
|
||||
private List<ThemeControl> _themes = new List<ThemeControl>();
|
||||
private List<ThemeControl> _containers = new List<ThemeControl>();
|
||||
private List<Page> _pages;
|
||||
private List<Models.TimeZone> _timezones;
|
||||
|
||||
private string _name = string.Empty;
|
||||
private string _homepageid = "-";
|
||||
private string _timezoneid = string.Empty;
|
||||
private string _isdeleted;
|
||||
private string _sitemap = "";
|
||||
private string _siteguid = "";
|
||||
@ -427,25 +497,32 @@
|
||||
private string _themetype = "";
|
||||
private string _containertype = "";
|
||||
private string _admincontainertype = "";
|
||||
private string _cookieconsent = "";
|
||||
|
||||
private Dictionary<string, string> _textEditors = new Dictionary<string, string>();
|
||||
private string _textEditor = "";
|
||||
private string _imageFiles = string.Empty;
|
||||
private string _uploadableFiles = string.Empty;
|
||||
private string _imagefiles = string.Empty;
|
||||
|
||||
private string _headcontent = string.Empty;
|
||||
private string _bodycontent = string.Empty;
|
||||
|
||||
private string _smtpenabled = "False";
|
||||
private string _smtpauthentication = "Basic";
|
||||
private string _smtphost = string.Empty;
|
||||
private string _smtpport = string.Empty;
|
||||
private string _smtpssl = "False";
|
||||
private string _smtpssl = "Auto";
|
||||
private string _smtpusername = string.Empty;
|
||||
private string _smtppassword = string.Empty;
|
||||
private string _smtppasswordtype = "password";
|
||||
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 _smtprelay = "False";
|
||||
private string _smtpenabled = "True";
|
||||
private int _retention = 30;
|
||||
|
||||
private string _pwaisenabled;
|
||||
@ -489,11 +566,13 @@
|
||||
Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
|
||||
if (site != null)
|
||||
{
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
||||
|
||||
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||
|
||||
_name = site.Name;
|
||||
_timezoneid = site.TimeZoneId;
|
||||
if (site.HomePageId != null)
|
||||
{
|
||||
_homepageid = site.HomePageId.Value.ToString();
|
||||
@ -513,11 +592,20 @@
|
||||
{
|
||||
_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;
|
||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
||||
_containers = ThemeService.GetContainerControls(themes, _themetype);
|
||||
_containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer;
|
||||
_admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer;
|
||||
_cookieconsent = SettingService.GetSetting(settings, "CookieConsent", string.Empty);
|
||||
|
||||
// functionality
|
||||
var textEditors = ServiceProvider.GetServices<ITextEditor>();
|
||||
@ -526,26 +614,35 @@
|
||||
_textEditors.Add(textEditor.Name, Utilities.GetFullTypeName(textEditor.GetType().AssemblyQualifiedName));
|
||||
}
|
||||
_textEditor = SettingService.GetSetting(settings, "TextEditor", Constants.DefaultTextEditor);
|
||||
_imageFiles = SettingService.GetSetting(settings, "ImageFiles", Constants.ImageFiles);
|
||||
_imageFiles = (string.IsNullOrEmpty(_imageFiles)) ? Constants.ImageFiles : _imageFiles;
|
||||
_uploadableFiles = SettingService.GetSetting(settings, "UploadableFiles", Constants.UploadableFiles);
|
||||
_uploadableFiles = (string.IsNullOrEmpty(_uploadableFiles)) ? Constants.UploadableFiles : _uploadableFiles;
|
||||
_imagefiles = SettingService.GetSetting(settings, "ImageFiles", Constants.ImageFiles);
|
||||
_imagefiles = (string.IsNullOrEmpty(_imagefiles)) ? Constants.ImageFiles : _imagefiles;
|
||||
|
||||
// page content
|
||||
_headcontent = site.HeadContent;
|
||||
_bodycontent = site.BodyContent;
|
||||
|
||||
// SMTP
|
||||
_smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty);
|
||||
_smtpport = SettingService.GetSetting(settings, "SMTPPort", string.Empty);
|
||||
_smtpssl = SettingService.GetSetting(settings, "SMTPSSL", "False");
|
||||
_smtpusername = SettingService.GetSetting(settings, "SMTPUsername", string.Empty);
|
||||
_smtppassword = SettingService.GetSetting(settings, "SMTPPassword", string.Empty);
|
||||
_togglesmtppassword = SharedLocalizer["ShowPassword"];
|
||||
_smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty);
|
||||
_smtprelay = SettingService.GetSetting(settings, "SMTPRelay", "False");
|
||||
_smtpenabled = SettingService.GetSetting(settings, "SMTPEnabled", "True");
|
||||
_retention = int.Parse(SettingService.GetSetting(settings, "NotificationRetention", "30"));
|
||||
_smtpenabled = SettingService.GetSetting(settings, "SMTPEnabled", "False");
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
_smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty);
|
||||
_smtpport = SettingService.GetSetting(settings, "SMTPPort", string.Empty);
|
||||
_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);
|
||||
_smtppassword = SettingService.GetSetting(settings, "SMTPPassword", string.Empty);
|
||||
_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);
|
||||
_smtprelay = SettingService.GetSetting(settings, "SMTPRelay", "False");
|
||||
_retention = int.Parse(SettingService.GetSetting(settings, "NotificationRetention", "30"));
|
||||
}
|
||||
|
||||
// PWA
|
||||
_pwaisenabled = site.PwaIsEnabled.ToString();
|
||||
@ -576,7 +673,8 @@
|
||||
if (tenant != null)
|
||||
{
|
||||
_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;
|
||||
}
|
||||
}
|
||||
@ -647,6 +745,7 @@
|
||||
if (site != null)
|
||||
{
|
||||
site.Name = _name;
|
||||
site.TimeZoneId = _timezoneid;
|
||||
site.HomePageId = (_homepageid != "-" ? int.Parse(_homepageid) : null);
|
||||
site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
|
||||
|
||||
@ -721,22 +820,31 @@
|
||||
|
||||
// SMTP
|
||||
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
||||
settings = SettingService.SetSetting(settings, "SMTPHost", _smtphost, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPRelay", _smtprelay, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPEnabled", _smtpenabled, true);
|
||||
settings = SettingService.SetSetting(settings, "SiteGuid", _siteguid, true);
|
||||
settings = SettingService.SetSetting(settings, "NotificationRetention", _retention.ToString(), true);
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
settings = SettingService.SetSetting(settings, "SMTPHost", _smtphost, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPAuthentication", _smtpauthentication, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, 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, "SMTPRelay", _smtprelay, true);
|
||||
settings = SettingService.SetSetting(settings, "NotificationRetention", _retention.ToString(), true);
|
||||
}
|
||||
|
||||
//cookie consent
|
||||
settings = SettingService.SetSetting(settings, "CookieConsent", _cookieconsent);
|
||||
|
||||
// functionality
|
||||
settings = SettingService.SetSetting(settings, "TextEditor", _textEditor);
|
||||
settings = SettingService.SetSetting(settings, "ImageFiles", (_imageFiles != Constants.ImageFiles) ? _imageFiles.Replace(" ", "") : "", false);
|
||||
settings = SettingService.SetSetting(settings, "UploadableFiles", (_uploadableFiles != Constants.UploadableFiles) ? _uploadableFiles.Replace(" ", "") : "", false);
|
||||
|
||||
settings = SettingService.SetSetting(settings, "SiteGuid", _siteguid, true);
|
||||
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
|
||||
|
||||
await logger.LogInformation("Site Settings Saved {Site}", site);
|
||||
@ -791,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()
|
||||
{
|
||||
if (_smtphost != "" && _smtpport != "" && _smtpsender != "")
|
||||
@ -801,8 +949,13 @@
|
||||
settings = SettingService.SetSetting(settings, "SMTPHost", _smtphost, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPAuthentication", _smtpauthentication, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, 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);
|
||||
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||
await logger.LogInformation("Site SMTP Settings Saved");
|
||||
@ -823,20 +976,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleSMTPPassword()
|
||||
{
|
||||
if (_smtppasswordtype == "password")
|
||||
{
|
||||
_smtppasswordtype = "text";
|
||||
_togglesmtppassword = SharedLocalizer["HidePassword"];
|
||||
}
|
||||
else
|
||||
{
|
||||
_smtppasswordtype = "password";
|
||||
_togglesmtppassword = SharedLocalizer["ShowPassword"];
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GetAliases()
|
||||
{
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
@ -930,4 +1069,9 @@
|
||||
_aliasname = "";
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task EvictSitemapOutputCache() {
|
||||
await CacheService.EvictByTag(Constants.SitemapOutputCacheTag);
|
||||
AddModuleMessage(Localizer["Success.SiteMap.CacheEvicted"], MessageType.Success);
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,42 +118,42 @@ else
|
||||
<Label Class="col-sm-3" For="databaseType" HelpText="Select the database type" ResourceKey="DatabaseType">Type: </Label>
|
||||
<div class="col-sm-9">
|
||||
@if (_databases != null)
|
||||
{
|
||||
<div class="input-group">
|
||||
<select id="databaseType" class="form-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))" required>
|
||||
@foreach (var database in _databases)
|
||||
{
|
||||
<option value="@database.Name">@Localizer[@database.Name]</option>
|
||||
}
|
||||
</select>
|
||||
@if (!_showConnectionString)
|
||||
{
|
||||
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionString"]</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionParameters"]</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
{
|
||||
<div class="input-group">
|
||||
<select id="databaseType" class="form-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))" required>
|
||||
@foreach (var database in _databases)
|
||||
{
|
||||
<option value="@database.Name">@Localizer[@database.Name]</option>
|
||||
}
|
||||
</select>
|
||||
@if (!_showConnectionString)
|
||||
{
|
||||
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionString"]</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionParameters"]</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@if (!_showConnectionString)
|
||||
{
|
||||
if (_databaseConfigType != null)
|
||||
{
|
||||
@DatabaseConfigComponent
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="connectionstring" HelpText="Enter a complete connection string including all parameters and delimiters" ResourceKey="ConnectionString">Settings:</Label>
|
||||
<div class="col-sm-9">
|
||||
<textarea id="connectionstring" class="form-control" @bind="@_connectionString" rows="3"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (!_showConnectionString)
|
||||
{
|
||||
if (_databaseConfigType != null)
|
||||
{
|
||||
@DatabaseConfigComponent
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="connectionstring" HelpText="Enter a complete connection string including all parameters and delimiters" ResourceKey="ConnectionString">Settings:</Label>
|
||||
<div class="col-sm-9">
|
||||
<textarea id="connectionstring" class="form-control" @bind="@_connectionString" rows="3"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="hostUsername" HelpText="Enter the username of an existing host user" ResourceKey="HostUsername">Host Username:</Label>
|
||||
<div class="col-sm-9">
|
||||
@ -216,7 +216,7 @@ else
|
||||
_tenantid = _tenants.First(item => item.Name == TenantNames.Master).TenantId.ToString();
|
||||
}
|
||||
_urls = PageState.Alias.Name;
|
||||
_themeList = await ThemeService.GetThemesAsync();
|
||||
_themeList = await ThemeService.GetThemesAsync(PageState.Site.SiteId);
|
||||
_themes = ThemeService.GetThemeControls(_themeList);
|
||||
if (_themes.Any(item => item.TypeName == Constants.DefaultTheme))
|
||||
{
|
||||
@ -237,7 +237,7 @@ else
|
||||
}
|
||||
else
|
||||
{
|
||||
_databaseName = "LocalDB";
|
||||
_databaseName = Constants.DefaultDBName;
|
||||
}
|
||||
LoadDatabaseConfigComponent();
|
||||
}
|
||||
@ -390,6 +390,8 @@ else
|
||||
config.DatabaseType = tenant.DBType;
|
||||
config.ConnectionString = tenant.DBConnectionString;
|
||||
config.IsNewTenant = false;
|
||||
config.HostEmail = PageState.User.Email;
|
||||
config.HostName = PageState.User.DisplayName;
|
||||
}
|
||||
}
|
||||
|
||||
@ -403,6 +405,7 @@ else
|
||||
config.SiteTemplate = _sitetemplatetype;
|
||||
config.RenderMode = _rendermode;
|
||||
config.Runtime = _runtime;
|
||||
config.Register = false;
|
||||
|
||||
ShowProgressIndicator();
|
||||
|
||||
|
||||
@ -200,7 +200,8 @@ else
|
||||
if (tenant != null)
|
||||
{
|
||||
_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
|
||||
@ -211,7 +212,7 @@ else
|
||||
}
|
||||
else
|
||||
{
|
||||
_databasetype = "LocalDB";
|
||||
_databasetype = Constants.DefaultDBName;
|
||||
}
|
||||
_showConnectionString = false;
|
||||
LoadDatabaseConfigComponent();
|
||||
|
||||
@ -2,223 +2,280 @@
|
||||
@inherits ModuleBase
|
||||
@inject ISystemService SystemService
|
||||
@inject IInstallationService InstallationService
|
||||
@inject IMigrationHistoryService MigrationHistoryService
|
||||
@inject ITenantService TenantService
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
<TabStrip>
|
||||
<TabPanel Name="Info" Heading="Info" ResourceKey="Info">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="version" HelpText="Framework Version" ResourceKey="FrameworkVersion">Framework Version: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="version" class="form-control" @bind="@_version" readonly />
|
||||
@if (_initialized)
|
||||
{
|
||||
<TabStrip>
|
||||
<TabPanel Name="Info" Heading="Info" ResourceKey="Info">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="version" HelpText="Framework Version" ResourceKey="FrameworkVersion">Framework Version: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="version" class="form-control" @bind="@_version" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="clrversion" HelpText="Common Language Runtime Version" ResourceKey="CLRVersion">CLR Version: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="clrversion" class="form-control" @bind="@_clrversion" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="osversion" HelpText="Operating System Version" ResourceKey="OSVersion">OS Version: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="osversion" class="form-control" @bind="@_osversion" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="process" HelpText="Indicates if the current process is 32 bit or 64 bit" ResourceKey="Process">Process: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="process" class="form-control" @bind="@_process" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="machinename" HelpText="Machine Name" ResourceKey="MachineName">Machine Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="machinename" class="form-control" @bind="@_machinename" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="ipaddress" HelpText="Server IP Address" ResourceKey="IPAddress">IP Address: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="ipaddress" class="form-control" @bind="@_ipaddress" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="environment" HelpText="Environment name" ResourceKey="Environment">Environment: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="environment" class="form-control" @bind="@_environment" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="contentrootpath" HelpText="Root Path" ResourceKey="ContentRootPath">Root Path: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="contentrootpath" class="form-control" @bind="@_contentrootpath" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="webrootpath" HelpText="Web Path" ResourceKey="WebRootPath">Web Path: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="webrootpath" class="form-control" @bind="@_webrootpath" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="servertime" HelpText="Server Date/Time (in UTC)" ResourceKey="ServerTime">Server Date/Time: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="servertime" class="form-control" @bind="@_servertime" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="workingset" HelpText="Memory Allocation Of Service (in MB)" ResourceKey="WorkingSet">Memory Allocation: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="workingset" class="form-control" @bind="@_workingset" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="installationid" HelpText="The Unique Identifier For Your Installation" ResourceKey="InstallationId">Installation ID: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="installationid" class="form-control" @bind="@_installationid" readonly />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="clrversion" HelpText="Common Language Runtime Version" ResourceKey="CLRVersion">CLR Version: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="clrversion" class="form-control" @bind="@_clrversion" readonly />
|
||||
<br /><br />
|
||||
<ActionDialog Header="Restart Application" Message="Are You Sure You Wish To Restart The Application?" Action="Restart Application" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await RestartApplication())" ResourceKey="RestartApplication" />
|
||||
</TabPanel>
|
||||
<TabPanel Name="Options" Heading="Options" ResourceKey="Options">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="detailederrors" HelpText="Specify If Detailed Errors Are Enabled For Blazor. This Option Should Not Not Be Enabled In Production." ResourceKey="DetailedErrors">Detailed Errors? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="detailederrors" class="form-select" @bind="@_detailederrors">
|
||||
<option value="true">@SharedLocalizer["True"]</option>
|
||||
<option value="false">@SharedLocalizer["False"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="logginglevel" HelpText="The Minimum Logging Level For The Event Log. This Option Can Be Used To Control The Volume Of Items Stored In Your Event Log." ResourceKey="LoggingLevel">Logging Level: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="logginglevel" class="form-select" @bind="@_logginglevel">
|
||||
<option value="Trace">@Localizer["Trace"]</option>
|
||||
<option value="Debug">@Localizer["Debug"]</option>
|
||||
<option value="Information">@Localizer["Information"]</option>
|
||||
<option value="Warning">@Localizer["Warning"]</option>
|
||||
<option value="Error">@Localizer["Error"]</option>
|
||||
<option value="Critical">@Localizer["Critical"]</option>
|
||||
<option value="None">@Localizer["None"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="notificationlevel" HelpText="The Minimum Logging Level For Which Notifications Should Be Sent To Host Users." ResourceKey="NotificationLevel">Notification Level: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="notificationlevel" class="form-select" @bind="@_notificationlevel">
|
||||
<option value="Trace">@Localizer["Trace"]</option>
|
||||
<option value="Debug">@Localizer["Debug"]</option>
|
||||
<option value="Information">@Localizer["Information"]</option>
|
||||
<option value="Warning">@Localizer["Warning"]</option>
|
||||
<option value="Error">@Localizer["Error"]</option>
|
||||
<option value="Critical">@Localizer["Critical"]</option>
|
||||
<option value="None">@Localizer["None"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="swagger" HelpText="Specify If Swagger Is Enabled For Your Server API" ResourceKey="Swagger">Swagger Enabled? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="swagger" class="form-select" @bind="@_swagger">
|
||||
<option value="true">@SharedLocalizer["True"]</option>
|
||||
<option value="false">@SharedLocalizer["False"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="cachecontrol" HelpText="Provide a Cache-Control directive for static assets. For example 'public, max-age=60' indicates that static assets should be cached for 60 seconds. A blank value indicates caching is not enabled." ResourceKey="CacheControl">Static Asset Caching: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="cachecontrol" class="form-control" @bind="@_cachecontrol" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="packageregistryurl" HelpText="Specify The Url Of The Package Manager Service For Installing Modules, Themes, And Translations. If This Field Is Blank It Means The Package Manager Service Is Disabled For This Installation." ResourceKey="PackageManager">Package Manager Url: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="packageregistryurl" class="form-control" @bind="@_packageregistryurl" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="packageregistryemail" HelpText="Specify The Email Address Of The User Account Used For Interacting With The Package Manager Service. This Account Is Used For Managing Packages Across Multiple Installations." ResourceKey="PackageManagerEmail">Package Manager Email: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="packageregistryemail" class="form-control" @bind="@_packageregistryemail" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="osversion" HelpText="Operating System Version" ResourceKey="OSVersion">OS Version: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="osversion" class="form-control" @bind="@_osversion" readonly />
|
||||
<br /><br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveConfig">@SharedLocalizer["Save"]</button>
|
||||
<ActionDialog Header="Restart Application" Message="Are You Sure You Wish To Restart The Application?" Action="Restart Application" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await RestartApplication())" ResourceKey="RestartApplication" />
|
||||
<br /><br />
|
||||
<a class="btn btn-primary" href="swagger/index.html" target="_new">@Localizer["Swagger"]</a>
|
||||
<a class="btn btn-secondary" href="api/endpoint" target="_new">@Localizer["Endpoints"]</a>
|
||||
</TabPanel>
|
||||
<TabPanel Name="Log" Heading="Log" ResourceKey="Log">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="log" HelpText="System log information for current day" ResourceKey="Log">Log: </Label>
|
||||
<div class="col-sm-9">
|
||||
<textarea id="log" class="form-control" rows="10" @bind="@_log" readonly />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="process" HelpText="Indicates if the current process is 32 bit or 64 bit" ResourceKey="Process">Process: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="process" class="form-control" @bind="@_process" readonly />
|
||||
<br /><br />
|
||||
<button type="button" class="btn btn-danger" @onclick="ClearLog">@Localizer["Clear"]</button>
|
||||
</TabPanel>
|
||||
<TabPanel Name="Migrations" Heading="Migrations" ResourceKey="Migrations">
|
||||
<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>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="machinename" HelpText="Machine Name" ResourceKey="MachineName">Machine Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="machinename" class="form-control" @bind="@_machinename" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="ipaddress" HelpText="Server IP Address" ResourceKey="IPAddress">IP Address: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="ipaddress" class="form-control" @bind="@_ipaddress" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="environment" HelpText="Environment name" ResourceKey="Environment">Environment: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="environment" class="form-control" @bind="@_environment" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="contentrootpath" HelpText="Root Path" ResourceKey="ContentRootPath">Root Path: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="contentrootpath" class="form-control" @bind="@_contentrootpath" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="webrootpath" HelpText="Web Path" ResourceKey="WebRootPath">Web Path: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="webrootpath" class="form-control" @bind="@_webrootpath" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="servertime" HelpText="Server Date/Time (in UTC)" ResourceKey="ServerTime">Server Date/Time: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="servertime" class="form-control" @bind="@_servertime" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="workingset" HelpText="Memory Allocation Of Service (in MB)" ResourceKey="WorkingSet">Memory Allocation: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="workingset" class="form-control" @bind="@_workingset" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="installationid" HelpText="The Unique Identifier For Your Installation" ResourceKey="InstallationId">Installation ID: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="installationid" class="form-control" @bind="@_installationid" readonly />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br /><br />
|
||||
<ActionDialog Header="Restart Application" Message="Are You Sure You Wish To Restart The Application?" Action="Restart Application" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await RestartApplication())" ResourceKey="RestartApplication" />
|
||||
</TabPanel>
|
||||
<TabPanel Name="Options" Heading="Options" ResourceKey="Options">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="detailederrors" HelpText="Specify If Detailed Errors Are Enabled For Blazor. This Option Should Not Not Be Enabled In Production." ResourceKey="DetailedErrors">Detailed Errors? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="detailederrors" class="form-select" @bind="@_detailederrors">
|
||||
<option value="true">@SharedLocalizer["True"]</option>
|
||||
<option value="false">@SharedLocalizer["False"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="logginglevel" HelpText="The Minimum Logging Level For The Event Log. This Option Can Be Used To Control The Volume Of Items Stored In Your Event Log." ResourceKey="LoggingLevel">Logging Level: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="logginglevel" class="form-select" @bind="@_logginglevel">
|
||||
<option value="Trace">@Localizer["Trace"]</option>
|
||||
<option value="Debug">@Localizer["Debug"]</option>
|
||||
<option value="Information">@Localizer["Information"]</option>
|
||||
<option value="Warning">@Localizer["Warning"]</option>
|
||||
<option value="Error">@Localizer["Error"]</option>
|
||||
<option value="Critical">@Localizer["Critical"]</option>
|
||||
<option value="None">@Localizer["None"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="notificationlevel" HelpText="The Minimum Logging Level For Which Notifications Should Be Sent To Host Users." ResourceKey="NotificationLevel">Notification Level: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="notificationlevel" class="form-select" @bind="@_notificationlevel">
|
||||
<option value="Trace">@Localizer["Trace"]</option>
|
||||
<option value="Debug">@Localizer["Debug"]</option>
|
||||
<option value="Information">@Localizer["Information"]</option>
|
||||
<option value="Warning">@Localizer["Warning"]</option>
|
||||
<option value="Error">@Localizer["Error"]</option>
|
||||
<option value="Critical">@Localizer["Critical"]</option>
|
||||
<option value="None">@Localizer["None"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="swagger" HelpText="Specify If Swagger Is Enabled For Your Server API" ResourceKey="Swagger">Swagger Enabled? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="swagger" class="form-select" @bind="@_swagger">
|
||||
<option value="true">@SharedLocalizer["True"]</option>
|
||||
<option value="false">@SharedLocalizer["False"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="packageregistryurl" HelpText="Specify The Package Manager Service For Installing Modules, Themes, And Translations. If This Field Is Blank It Means The Package Manager Service Is Disabled For This Installation." ResourceKey="PackageManager">Package Manager: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="packageregistryurl" class="form-control" @bind="@_packageregistryurl" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br /><br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveConfig">@SharedLocalizer["Save"]</button>
|
||||
<a class="btn btn-primary" href="swagger/index.html" target="_new">@Localizer["Access.ApiFramework"]</a>
|
||||
<ActionDialog Header="Restart Application" Message="Are You Sure You Wish To Restart The Application?" Action="Restart Application" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await RestartApplication())" ResourceKey="RestartApplication" />
|
||||
</TabPanel>
|
||||
<TabPanel Name="Log" Heading="Log" ResourceKey="Log">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="log" HelpText="System log information for current day" ResourceKey="Log">Log: </Label>
|
||||
<div class="col-sm-9">
|
||||
<textarea id="log" class="form-control" rows="10" @bind="@_log" readonly />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br /><br />
|
||||
<button type="button" class="btn btn-danger" @onclick="ClearLog">@Localizer["Clear"]</button>
|
||||
</TabPanel>
|
||||
</TabStrip>
|
||||
<br /><br />
|
||||
<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 {
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
|
||||
private string _version = string.Empty;
|
||||
private string _clrversion = string.Empty;
|
||||
private string _osversion = string.Empty;
|
||||
private bool _initialized = false;
|
||||
|
||||
private string _version = string.Empty;
|
||||
private string _clrversion = string.Empty;
|
||||
private string _osversion = string.Empty;
|
||||
private string _process = string.Empty;
|
||||
private string _machinename = string.Empty;
|
||||
private string _ipaddress = string.Empty;
|
||||
private string _environment = string.Empty;
|
||||
private string _contentrootpath = string.Empty;
|
||||
private string _webrootpath = string.Empty;
|
||||
private string _servertime = string.Empty;
|
||||
private string _workingset = string.Empty;
|
||||
private string _installationid = string.Empty;
|
||||
private string _ipaddress = string.Empty;
|
||||
private string _environment = string.Empty;
|
||||
private string _contentrootpath = string.Empty;
|
||||
private string _webrootpath = string.Empty;
|
||||
private string _servertime = string.Empty;
|
||||
private string _workingset = string.Empty;
|
||||
private string _installationid = string.Empty;
|
||||
|
||||
private string _detailederrors = string.Empty;
|
||||
private string _logginglevel = string.Empty;
|
||||
private string _notificationlevel = string.Empty;
|
||||
private string _swagger = string.Empty;
|
||||
private string _detailederrors = string.Empty;
|
||||
private string _logginglevel = string.Empty;
|
||||
private string _notificationlevel = string.Empty;
|
||||
private string _swagger = string.Empty;
|
||||
private string _cachecontrol = string.Empty;
|
||||
private string _packageregistryurl = string.Empty;
|
||||
private string _packageregistryemail = string.Empty;
|
||||
|
||||
private string _log = string.Empty;
|
||||
private string _log = string.Empty;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_version = Constants.Version;
|
||||
private string _tenant = string.Empty;
|
||||
private List<MigrationHistory> _history;
|
||||
|
||||
var systeminfo = await SystemService.GetSystemInfoAsync("environment");
|
||||
if (systeminfo != null)
|
||||
{
|
||||
_clrversion = systeminfo["CLRVersion"].ToString();
|
||||
_osversion = systeminfo["OSVersion"].ToString();
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_version = Constants.Version;
|
||||
|
||||
var systeminfo = await SystemService.GetSystemInfoAsync("environment");
|
||||
if (systeminfo != null)
|
||||
{
|
||||
_clrversion = systeminfo["CLRVersion"].ToString();
|
||||
_osversion = systeminfo["OSVersion"].ToString();
|
||||
_process = systeminfo["Process"].ToString();
|
||||
_machinename = systeminfo["MachineName"].ToString();
|
||||
_ipaddress = systeminfo["IPAddress"].ToString();
|
||||
_environment = systeminfo["Environment"].ToString();
|
||||
_contentrootpath = systeminfo["ContentRootPath"].ToString();
|
||||
_webrootpath = systeminfo["WebRootPath"].ToString();
|
||||
_servertime = systeminfo["ServerTime"].ToString() + " UTC";
|
||||
_workingset = (Convert.ToInt64(systeminfo["WorkingSet"].ToString()) / 1000000).ToString() + " MB";
|
||||
}
|
||||
_ipaddress = systeminfo["IPAddress"].ToString();
|
||||
_environment = systeminfo["Environment"].ToString();
|
||||
_contentrootpath = systeminfo["ContentRootPath"].ToString();
|
||||
_webrootpath = systeminfo["WebRootPath"].ToString();
|
||||
_servertime = systeminfo["ServerTime"].ToString() + " UTC";
|
||||
_workingset = (Convert.ToInt64(systeminfo["WorkingSet"].ToString()) / 1000000).ToString() + " MB";
|
||||
}
|
||||
|
||||
systeminfo = await SystemService.GetSystemInfoAsync("configuration");
|
||||
if (systeminfo != null)
|
||||
{
|
||||
_installationid = systeminfo["InstallationId"].ToString();
|
||||
_detailederrors = systeminfo["DetailedErrors"].ToString();
|
||||
_logginglevel = systeminfo["Logging:LogLevel:Default"].ToString();
|
||||
_notificationlevel = systeminfo["Logging:LogLevel:Notify"].ToString();
|
||||
_swagger = systeminfo["UseSwagger"].ToString();
|
||||
systeminfo = await SystemService.GetSystemInfoAsync("configuration");
|
||||
if (systeminfo != null)
|
||||
{
|
||||
_installationid = systeminfo["InstallationId"].ToString();
|
||||
_detailederrors = systeminfo["DetailedErrors"].ToString();
|
||||
_logginglevel = systeminfo["Logging:LogLevel:Default"].ToString();
|
||||
_notificationlevel = systeminfo["Logging:LogLevel:Notify"].ToString();
|
||||
_swagger = systeminfo["UseSwagger"].ToString();
|
||||
_cachecontrol = systeminfo["CacheControl"].ToString();
|
||||
_packageregistryurl = systeminfo["PackageRegistryUrl"].ToString();
|
||||
}
|
||||
_packageregistryemail = systeminfo["PackageRegistryEmail"].ToString();
|
||||
}
|
||||
|
||||
systeminfo = await SystemService.GetSystemInfoAsync("log");
|
||||
if (systeminfo != null)
|
||||
{
|
||||
_log = systeminfo["Log"].ToString();
|
||||
}
|
||||
}
|
||||
systeminfo = await SystemService.GetSystemInfoAsync("log");
|
||||
if (systeminfo != null)
|
||||
{
|
||||
_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()
|
||||
{
|
||||
@ -229,8 +286,10 @@
|
||||
settings.Add("Logging:LogLevel:Default", _logginglevel);
|
||||
settings.Add("Logging:LogLevel:Notify", _notificationlevel);
|
||||
settings.Add("UseSwagger", _swagger);
|
||||
settings.Add("PackageRegistryUrl", _packageregistryurl);
|
||||
await SystemService.UpdateSystemInfoAsync(settings);
|
||||
settings.Add("CacheControl", _cachecontrol);
|
||||
settings.Add("PackageRegistryUrl", _packageregistryurl);
|
||||
settings.Add("PackageRegistryEmail", _packageregistryemail);
|
||||
await SystemService.UpdateSystemInfoAsync(settings);
|
||||
AddModuleMessage(Localizer["Success.UpdateConfig.Restart"], MessageType.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@ -101,13 +101,20 @@
|
||||
<small>@SharedLocalizer["Search.By"]:</small> <strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
|
||||
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
|
||||
<br />
|
||||
@if (!string.IsNullOrEmpty(context.PackageUrl))
|
||||
@if (_themes.Exists(item => item.PackageName == context.PackageId))
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
|
||||
<button type="button" class="btn btn-info">@SharedLocalizer["Installed"]</button>
|
||||
}
|
||||
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
|
||||
else
|
||||
{
|
||||
<a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
|
||||
@if (!string.IsNullOrEmpty(context.PackageUrl))
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
|
||||
}
|
||||
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
|
||||
{
|
||||
<a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
|
||||
}
|
||||
}
|
||||
<br />
|
||||
</div>
|
||||
@ -171,6 +178,7 @@
|
||||
|
||||
@code {
|
||||
private bool _initialized = false;
|
||||
private List<Theme> _themes;
|
||||
private int _page = 1;
|
||||
private List<Package> _packages;
|
||||
private string _price = "free";
|
||||
@ -187,7 +195,8 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
await LoadThemes();
|
||||
_themes = await ThemeService.GetThemesAsync(PageState.Site.SiteId);
|
||||
await LoadPackages();
|
||||
_initialized = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -197,24 +206,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadThemes()
|
||||
private async Task LoadPackages()
|
||||
{
|
||||
ShowProgressIndicator();
|
||||
|
||||
var themes = await ThemeService.GetThemesAsync();
|
||||
ShowProgressIndicator();
|
||||
_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();
|
||||
}
|
||||
|
||||
@ -222,25 +217,25 @@
|
||||
{
|
||||
_price = price;
|
||||
_sort = "popularity";
|
||||
await LoadThemes();
|
||||
await LoadPackages();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task Search()
|
||||
{
|
||||
await LoadThemes();
|
||||
await LoadPackages();
|
||||
}
|
||||
|
||||
private async Task Reset()
|
||||
{
|
||||
_page = 1;
|
||||
_search = "";
|
||||
await LoadThemes();
|
||||
await LoadPackages();
|
||||
}
|
||||
|
||||
private async Task Refresh()
|
||||
{
|
||||
await LoadThemes();
|
||||
await LoadPackages();
|
||||
}
|
||||
|
||||
private void OnPageChange(int page)
|
||||
@ -251,7 +246,7 @@
|
||||
private async void SortChanged(ChangeEventArgs e)
|
||||
{
|
||||
_sort = (string)e.Value;
|
||||
await LoadThemes();
|
||||
await LoadPackages();
|
||||
}
|
||||
|
||||
private void HideModal()
|
||||
@ -310,6 +305,6 @@
|
||||
|
||||
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,101 +9,98 @@
|
||||
|
||||
@if (_initialized)
|
||||
{
|
||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="name" HelpText="The name of the module" ResourceKey="Name">Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="name" class="form-control" @bind="@_name" />
|
||||
<TabStrip>
|
||||
<TabPanel Name="Theme" ResourceKey="Theme" Heading="Theme">
|
||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="name" HelpText="The name of the theme" ResourceKey="Name">Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="name" class="form-control" @bind="@_name" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="isenabled" HelpText="Is theme enabled for this site?" ResourceKey="IsEnabled">Enabled? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="isenabled" class="form-select" @bind="@_isenabled" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="isenabled" HelpText="Is theme enabled for this site?" ResourceKey="IsEnabled">Enabled? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="isenabled" class="form-select" @bind="@_isenabled" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<Section Name="Information" ResourceKey="Information" Heading="Information">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="themename" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="themename" class="form-control" @bind="@_themeName" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="version" HelpText="The version of the theme" ResourceKey="Version">Version: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="version" class="form-control" @bind="@_version" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this theme was installed. This value must be specified within the theme's ITheme interface specification." ResourceKey="PackageName">Package Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
@if (!string.IsNullOrEmpty(_packagename))
|
||||
{
|
||||
<div class="input-group">
|
||||
</form>
|
||||
<Section Name="Information" ResourceKey="Information" Heading="Information">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="themename" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="themename" class="form-control" @bind="@_themeName" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="version" HelpText="The version of the theme" ResourceKey="Version">Version: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="version" class="form-control" @bind="@_version" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this theme was installed. This value must be specified within the theme's ITheme interface specification." ResourceKey="PackageName">Package Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
|
||||
@if (string.IsNullOrEmpty(_packageurl))
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="owner" HelpText="The owner or creator of the theme" ResourceKey="Owner">Owner: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="owner" class="form-control" @bind="@_owner" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="url" HelpText="The url of the theme" ResourceKey="Url">Url: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="url" class="form-control" @bind="@_url" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="contact" HelpText="The contact for the theme" ResourceKey="Contact">Contact: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="contact" class="form-control" @bind="@_contact" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="license" HelpText="The license of the theme" ResourceKey="License">License: </Label>
|
||||
<div class="col-sm-9">
|
||||
@if (_license.StartsWith("http") || _license.StartsWith("/") || _license.StartsWith("~"))
|
||||
{
|
||||
<button type="button" class="btn btn-secondary" @onclick="ValidatePackage">@Localizer["Validate"]</button>
|
||||
<a href="@_license.Replace("~", PageState?.Alias.BaseUrl + "/Themes/" + Utilities.GetTypeName(_themeName))" class="btn btn-info" style="text-decoration: none !important" target="_new">@Localizer["View License"]</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a href="@_packageurl" target="_blank" class="btn btn-primary">@SharedLocalizer["Download"]</a>
|
||||
<textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="owner" HelpText="The owner or creator of the theme" ResourceKey="Owner">Owner: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="owner" class="form-control" @bind="@_owner" disabled />
|
||||
</Section>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveTheme">@SharedLocalizer["Save"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
<br />
|
||||
<br />
|
||||
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
|
||||
</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>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="url" HelpText="The reference url of the theme" ResourceKey="ReferenceUrl">Reference Url: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="url" class="form-control" @bind="@_url" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="contact" HelpText="The contact for the theme" ResourceKey="Contact">Contact: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="contact" class="form-control" @bind="@_contact" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="license" HelpText="The license of the theme" ResourceKey="License">License: </Label>
|
||||
<div class="col-sm-9">
|
||||
@if (_license.StartsWith("http") || _license.StartsWith("/") || _license.StartsWith("~"))
|
||||
{
|
||||
<a href="@_license.Replace("~", PageState?.Alias.BaseUrl + "/Themes/" + Utilities.GetTypeName(_themeName))" class="btn btn-info" style="text-decoration: none !important" target="_new">@Localizer["View License"]</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveTheme">@SharedLocalizer["Save"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
<br />
|
||||
<br />
|
||||
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
|
||||
<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 {
|
||||
@ -116,16 +113,18 @@
|
||||
private string _name;
|
||||
private string _version;
|
||||
private string _packagename = "";
|
||||
private string _packageurl = "";
|
||||
private string _owner = "";
|
||||
private string _url = "";
|
||||
private string _contact = "";
|
||||
private string _license = "";
|
||||
private List<Permission> _permissions = null;
|
||||
private string _createdby;
|
||||
private DateTime _createdon;
|
||||
private string _modifiedby;
|
||||
private DateTime _modifiedon;
|
||||
|
||||
private PermissionGrid _permissionGrid;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
@ -144,6 +143,7 @@
|
||||
_url = theme.Url;
|
||||
_contact = theme.Contact;
|
||||
_license = theme.License;
|
||||
_permissions = theme.PermissionList;
|
||||
_createdby = theme.CreatedBy;
|
||||
_createdon = theme.CreatedOn;
|
||||
_modifiedby = theme.ModifiedBy;
|
||||
@ -170,6 +170,7 @@
|
||||
var theme = await ThemeService.GetThemeAsync(_themeId, ModuleState.SiteId);
|
||||
theme.Name = _name;
|
||||
theme.IsEnabled = (_isenabled == null ? true : bool.Parse(_isenabled));
|
||||
theme.PermissionList = _permissionGrid.GetPermissionList();
|
||||
await ThemeService.UpdateThemeAsync(theme);
|
||||
await logger.LogInformation("Theme Saved {Theme}", theme);
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
@ -185,27 +186,4 @@
|
||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ValidatePackage()
|
||||
{
|
||||
try
|
||||
{
|
||||
var package = await PackageService.GetPackageAsync(_packagename, _version, true);
|
||||
if (package == null || string.IsNullOrEmpty(package.PackageUrl))
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Validate"], MessageType.Warning);
|
||||
}
|
||||
else
|
||||
{
|
||||
_packageurl = package.PackageUrl;
|
||||
AddModuleMessage(Localizer["Message.Download"], MessageType.Info);
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packagename, _version);
|
||||
AddModuleMessage(Localizer["Error.Validate"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IThemeService ThemeService
|
||||
@inject IPackageService PackageService
|
||||
@inject ISiteService SiteService
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@ -14,8 +15,8 @@
|
||||
else
|
||||
{
|
||||
<ActionLink Action="Add" Text="Install Theme" ResourceKey="InstallTheme" />
|
||||
@((MarkupString)" ")
|
||||
<ActionLink Action="Create" Text="Create Theme" ResourceKey="CreateTheme" Class="btn btn-secondary" />
|
||||
<ActionLink Action="Create" Text="Create Theme" ResourceKey="CreateTheme" Class="btn btn-secondary ms-1" />
|
||||
<button type="button" class="btn btn-secondary ms-1" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
|
||||
|
||||
<Pager Items="@_themes">
|
||||
<Header>
|
||||
@ -32,9 +33,9 @@ else
|
||||
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.ThemeId.ToString())" ResourceKey="EditTheme" /></td>
|
||||
<td>
|
||||
@if (context.AssemblyName != Constants.ClientId)
|
||||
{
|
||||
{
|
||||
<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>@context.Name</td>
|
||||
<td>@context.Version</td>
|
||||
@ -77,7 +78,7 @@ else
|
||||
{
|
||||
try
|
||||
{
|
||||
_themes = await ThemeService.GetThemesAsync();
|
||||
_themes = await ThemeService.GetThemesAsync(PageState.Site.SiteId);
|
||||
_packages = await PackageService.GetPackageUpdatesAsync("theme");
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -160,7 +161,7 @@ else
|
||||
{
|
||||
try
|
||||
{
|
||||
await ThemeService.DeleteThemeAsync(Theme.ThemeName);
|
||||
await ThemeService.DeleteThemeAsync(Theme.ThemeId, PageState.Site.SiteId);
|
||||
AddModuleMessage(Localizer["Success.Theme.Delete"], MessageType.Success);
|
||||
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
|
||||
}
|
||||
@ -170,4 +171,27 @@ else
|
||||
AddModuleMessage(Localizer["Error.Theme.Delete"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Synchronize()
|
||||
{
|
||||
try
|
||||
{
|
||||
ShowProgressIndicator();
|
||||
foreach (var theme in _themes)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(theme.PackageName) && !_packages.Any(item => item.PackageId == theme.PackageName))
|
||||
{
|
||||
await PackageService.GetPackageAsync(theme.PackageName, theme.Version, false);
|
||||
}
|
||||
}
|
||||
HideProgressIndicator();
|
||||
AddModuleMessage(Localizer["Success.Theme.Synchronize"], MessageType.Success);
|
||||
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Synchronizing Themes {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Theme.Synchronize"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -13,9 +13,26 @@
|
||||
<TabPanel Name="Download" ResourceKey="Download">
|
||||
@if (_package != null && _upgradeavailable)
|
||||
{
|
||||
<ModuleMessage Type="MessageType.Info" Message="Select The Download Button To Download The Framework Upgrade Package And Then Select Upgrade"></ModuleMessage>
|
||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await Download(Constants.PackageId, @_package.Version))>@SharedLocalizer["Download"] @_package.Version</button>
|
||||
<button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button>
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" HelpText="Specify if you want to backup files during the upgrade process. Disabling this option will reduce the time required for the upgrade." ResourceKey="Backup">Backup Files? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="backup" class="form-select" @bind="@_backup">
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
@if (!_downloaded)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await Download(Constants.PackageId, @_package.Version))>@SharedLocalizer["Download"] @_package.Version</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -23,7 +40,6 @@
|
||||
}
|
||||
</TabPanel>
|
||||
<TabPanel Name="Upload" ResourceKey="Upload">
|
||||
<ModuleMessage Type="MessageType.Info" Message=@Localizer["MessageUpgrade.Text"]></ModuleMessage>
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" HelpText="Upload A Framework Package And Then Select Upgrade" ResourceKey="Framework">Framework: </Label>
|
||||
@ -31,7 +47,17 @@
|
||||
<FileManager Folder="@Constants.PackagesFolder" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" HelpText="Specify if you want to backup files during the upgrade process. Disabling this option will reduce the time required for the upgrade." ResourceKey="Backup">Backup Files? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="backup" class="form-select" @bind="@_backup">
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button>
|
||||
</TabPanel>
|
||||
</TabStrip>
|
||||
@ -39,8 +65,10 @@
|
||||
|
||||
@code {
|
||||
private bool _initialized = false;
|
||||
private bool _downloaded = false;
|
||||
private Package _package;
|
||||
private bool _upgradeavailable = false;
|
||||
private string _backup = "True";
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
|
||||
@ -86,7 +114,7 @@
|
||||
ShowProgressIndicator();
|
||||
var interop = new Interop(JSRuntime);
|
||||
await interop.RedirectBrowser(NavigateUrl(), 10);
|
||||
await InstallationService.Upgrade();
|
||||
await InstallationService.Upgrade(bool.Parse(_backup));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -102,6 +130,7 @@
|
||||
ShowProgressIndicator();
|
||||
await PackageService.DownloadPackageAsync(packageid, version);
|
||||
await PackageService.DownloadPackageAsync(Constants.UpdaterPackageId, version);
|
||||
_downloaded = true;
|
||||
HideProgressIndicator();
|
||||
AddModuleMessage(Localizer["Success.Framework.Download"], MessageType.Success);
|
||||
}
|
||||
|
||||
@ -8,13 +8,16 @@
|
||||
<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="url" HelpText="The fully qualified Url for this site" ResourceKey="Url">Url:</Label>
|
||||
<Label Class="col-sm-3" For="url" HelpText="A Url identifying a path to a specific page in the site (absolute or relative)" ResourceKey="Url">Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="url" class="form-control" @bind="@_url" maxlength="500" required />
|
||||
<div class="input-group">
|
||||
<input id="url" class="form-control" @bind="@_url" maxlength="500" required />
|
||||
<button type="button" class="btn btn-primary" @onclick="GenerateUrl">@Localizer["Generate"]</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="mappedurl" HelpText="A fully qualified Url where the user will be redirected" ResourceKey="MappedUrl">Redirect To:</Label>
|
||||
<Label Class="col-sm-3" For="mappedurl" HelpText="A Url where the user will be redirected (absolute or relative). Use '/' for site root path." ResourceKey="MappedUrl">Redirect To:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="mappedurl" class="form-control" @bind="@_mappedurl" maxlength="500" required />
|
||||
</div>
|
||||
@ -26,64 +29,80 @@
|
||||
</form>
|
||||
|
||||
@code {
|
||||
private ElementReference form;
|
||||
private bool validated = false;
|
||||
private ElementReference form;
|
||||
private bool validated = false;
|
||||
|
||||
private string _url = string.Empty;
|
||||
private string _mappedurl = string.Empty;
|
||||
private string _url = string.Empty;
|
||||
private string _mappedurl = string.Empty;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||
|
||||
private async Task SaveUrlMapping()
|
||||
{
|
||||
validated = true;
|
||||
var interop = new Interop(JSRuntime);
|
||||
if (await interop.FormValid(form))
|
||||
{
|
||||
if (_url != _mappedurl)
|
||||
{
|
||||
var url = PageState.Uri.Scheme + "://" + PageState.Uri.Authority + "/";
|
||||
url = url + (!string.IsNullOrEmpty(PageState.Alias.Path) ? PageState.Alias.Path + "/" : "");
|
||||
private async Task SaveUrlMapping()
|
||||
{
|
||||
validated = true;
|
||||
var interop = new Interop(JSRuntime);
|
||||
if (await interop.FormValid(form))
|
||||
{
|
||||
if (_url != _mappedurl)
|
||||
{
|
||||
var url = PageState.Uri.Scheme + "://" + PageState.Uri.Authority + "/";
|
||||
url = url + (!string.IsNullOrEmpty(PageState.Alias.Path) ? PageState.Alias.Path + "/" : "");
|
||||
|
||||
_url = (_url.StartsWith("/")) ? _url.Substring(1) : _url;
|
||||
_url = (!_url.StartsWith("http")) ? url + _url : _url;
|
||||
_url = (_url.StartsWith("/")) ? _url.Substring(1) : _url;
|
||||
_url = (!_url.StartsWith("http")) ? url + _url : _url;
|
||||
|
||||
if (_url.StartsWith(url))
|
||||
{
|
||||
var urlmapping = new UrlMapping();
|
||||
urlmapping.SiteId = PageState.Site.SiteId;
|
||||
var route = new Route(_url, PageState.Alias.Path);
|
||||
urlmapping.Url = route.PagePath;
|
||||
urlmapping.MappedUrl = _mappedurl.Replace(url, "");
|
||||
urlmapping.Requests = 0;
|
||||
urlmapping.CreatedOn = DateTime.UtcNow;
|
||||
urlmapping.RequestedOn = DateTime.UtcNow;
|
||||
_mappedurl = _mappedurl.Replace(url, "");
|
||||
_mappedurl = (_mappedurl.StartsWith("/") && _mappedurl != "/") ? _mappedurl.Substring(1) : _mappedurl;
|
||||
|
||||
try
|
||||
{
|
||||
urlmapping = await UrlMappingService.AddUrlMappingAsync(urlmapping);
|
||||
await logger.LogInformation("UrlMapping Saved {UrlMapping}", urlmapping);
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Saving UrlMapping {UrlMapping} {Error}", urlmapping, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.SaveUrlMapping"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.SaveUrlMapping"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_url.StartsWith(url))
|
||||
{
|
||||
var urlmapping = new UrlMapping();
|
||||
urlmapping.SiteId = PageState.Site.SiteId;
|
||||
urlmapping.Url = new Route(_url, PageState.Alias.Path).PagePath;
|
||||
urlmapping.MappedUrl = _mappedurl;
|
||||
urlmapping.Requests = 0;
|
||||
urlmapping.CreatedOn = DateTime.UtcNow;
|
||||
urlmapping.RequestedOn = DateTime.UtcNow;
|
||||
|
||||
try
|
||||
{
|
||||
urlmapping = await UrlMappingService.AddUrlMappingAsync(urlmapping);
|
||||
await logger.LogInformation("UrlMapping Saved {UrlMapping}", urlmapping);
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Saving UrlMapping {UrlMapping} {Error}", urlmapping, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.SaveUrlMapping"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.SaveUrlMapping"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.DuplicateUrlMapping"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateUrl()
|
||||
{
|
||||
var url = PageState.Uri.Scheme + "://" + PageState.Uri.Authority + "/";
|
||||
url = url + (!string.IsNullOrEmpty(PageState.Alias.Path) ? PageState.Alias.Path + "/" : "");
|
||||
|
||||
var chars = "abcdefghijklmnopqrstuvwxyz";
|
||||
Random rnd = new Random();
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
url += chars.Substring(rnd.Next(0, chars.Length - 1), 1);
|
||||
}
|
||||
_url = url;
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,13 +8,13 @@
|
||||
<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="url" HelpText="A fully qualified Url for this site" ResourceKey="Url">Url:</Label>
|
||||
<Label Class="col-sm-3" For="url" HelpText="A Url identifying a path to a specific page in the site (absolute or relative)" ResourceKey="Url">Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="url" class="form-control" @bind="@_url" maxlength="500" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="mappedurl" HelpText="A fully qualified Url where the user will be redirected" ResourceKey="MappedUrl">Redirect To:</Label>
|
||||
<Label Class="col-sm-3" For="mappedurl" HelpText="A Url where the user will be redirected (absolute or relative). Use '/' for site root path." ResourceKey="MappedUrl">Redirect To:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="mappedurl" class="form-control" @bind="@_mappedurl" maxlength="500" required />
|
||||
</div>
|
||||
@ -67,8 +67,11 @@
|
||||
var url = PageState.Uri.Scheme + "://" + PageState.Uri.Authority + "/";
|
||||
url = url + (!string.IsNullOrEmpty(PageState.Alias.Path) ? PageState.Alias.Path + "/" : "");
|
||||
|
||||
_mappedurl = _mappedurl.Replace(url, "");
|
||||
_mappedurl = (_mappedurl.StartsWith("/") && _mappedurl != "/") ? _mappedurl.Substring(1) : _mappedurl;
|
||||
|
||||
var urlmapping = await UrlMappingService.GetUrlMappingAsync(_urlmappingid);
|
||||
urlmapping.MappedUrl = _mappedurl.Replace(url, "");
|
||||
urlmapping.MappedUrl = _mappedurl;
|
||||
urlmapping = await UrlMappingService.UpdateUrlMappingAsync(urlmapping);
|
||||
await logger.LogInformation("UrlMapping Saved {UrlMapping}", urlmapping);
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IUrlMappingService UrlMappingService
|
||||
@inject ISiteService SiteService
|
||||
@inject ISettingService SettingService
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@ -47,7 +48,7 @@ else
|
||||
}
|
||||
</td>
|
||||
<td>@context.Requests</td>
|
||||
<td>@context.RequestedOn</td>
|
||||
<td>@UtcToLocal(context.RequestedOn)</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
</TabPanel>
|
||||
@ -62,7 +63,13 @@ else
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="retention" HelpText="Number of days of broken urls to retain" ResourceKey="Retention">Retention (Days): </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="retention" class="form-control" type="number" min="0" step="1" @bind="@_retention" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||
</TabPanel>
|
||||
@ -73,6 +80,7 @@ else
|
||||
private bool _mapped = true;
|
||||
private List<UrlMapping> _urlMappings;
|
||||
private string _capturebrokenurls;
|
||||
private int _retention = 30;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||
|
||||
@ -80,7 +88,10 @@ else
|
||||
{
|
||||
await GetUrlMappings();
|
||||
_capturebrokenurls = PageState.Site.CaptureBrokenUrls.ToString();
|
||||
}
|
||||
|
||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
_retention = int.Parse(SettingService.GetSetting(settings, "UrlMappingRetention", "30"));
|
||||
}
|
||||
|
||||
private async void MappedChanged(ChangeEventArgs e)
|
||||
{
|
||||
@ -124,7 +135,12 @@ else
|
||||
var site = PageState.Site;
|
||||
site.CaptureBrokenUrls = bool.Parse(_capturebrokenurls);
|
||||
await SiteService.UpdateSiteAsync(site);
|
||||
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
||||
|
||||
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
||||
settings = SettingService.SetSetting(settings, "UrlMappingRetention", _retention.ToString(), true);
|
||||
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
|
||||
|
||||
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
@inject INotificationService NotificationService
|
||||
@inject IFileService FileService
|
||||
@inject IFolderService FolderService
|
||||
@inject ITimeZoneService TimeZoneService
|
||||
@inject IJSRuntime jsRuntime
|
||||
@inject IServiceProvider ServiceProvider
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@ -16,9 +17,9 @@
|
||||
|
||||
@if (_initialized)
|
||||
{
|
||||
@if (PageState.User != null && photo != null)
|
||||
@if (PageState.User != null && _photo != null)
|
||||
{
|
||||
<img src="@ImageUrl(photofileid, 400, 400)" alt="@displayname" style="max-width: 400px" class="rounded-circle mx-auto d-block">
|
||||
<img src="@ImageUrl(_photofileid, 400, 400)" alt="@_displayname" style="max-width: 400px" class="rounded-circle mx-auto d-block">
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -31,7 +32,7 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="username" HelpText="Your username. Note that this field can not be modified." ResourceKey="Username"></Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="username" class="form-control" @bind="@username" readonly />
|
||||
<input id="username" class="form-control" @bind="@_username" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
@ -47,17 +48,17 @@
|
||||
<Label Class="col-sm-3" For="confirm" HelpText="If you are changing your password you must enter it again to confirm it matches" ResourceKey="Confirm"></Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@confirm" autocomplete="new-password" />
|
||||
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if (allowtwofactor)
|
||||
@if (_allowtwofactor)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="twofactor" HelpText="Indicates if you are using two factor authentication. Two factor authentication requires you to enter a verification code sent via email after you sign in." ResourceKey="TwoFactor"></Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="twofactor" class="form-select" @bind="@twofactor" required>
|
||||
<select id="twofactor" class="form-select" @bind="@_twofactor" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
@ -67,19 +68,31 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="email" HelpText="Your email address where you wish to receive notifications" ResourceKey="Email"></Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="email" class="form-control" @bind="@email" />
|
||||
<input id="email" class="form-control" @bind="@_email" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="displayname" HelpText="Your full name" ResourceKey="DisplayName"></Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="displayname" class="form-control" @bind="@displayname" />
|
||||
<input id="displayname" class="form-control" @bind="@_displayname" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="@photofileid.ToString()" HelpText="A photo of yourself" ResourceKey="Photo"></Label>
|
||||
<Label Class="col-sm-3" For="timezone" HelpText="Your time zone" ResourceKey="TimeZone">Time Zone:</Label>
|
||||
<div class="col-sm-9">
|
||||
<FileManager FileId="@photofileid" Filter="@PageState.Site.ImageFiles" ShowFolders="false" ShowFiles="true" UploadMultiple="false" FolderId="@folderid" @ref="filemanager" />
|
||||
<select id="timezone" class="form-select" @bind="@_timezoneid">
|
||||
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||
@foreach (var timezone in _timezones)
|
||||
{
|
||||
<option value="@timezone.Id">@timezone.DisplayName</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="@_photofileid.ToString()" HelpText="A photo of yourself" ResourceKey="Photo"></Label>
|
||||
<div class="col-sm-9">
|
||||
<FileManager FileId="@_photofileid" Filter="@PageState.Site.ImageFiles" ShowFolders="false" ShowFiles="true" UploadMultiple="false" FolderId="@_folderid" @ref="_filemanager" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -91,21 +104,21 @@
|
||||
<TabPanel Name="Profile" ResourceKey="Profile">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
@foreach (Profile profile in profiles)
|
||||
@foreach (Profile profile in _profiles)
|
||||
{
|
||||
var p = profile;
|
||||
if (!p.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||
{
|
||||
if (p.Category != category)
|
||||
if (p.Category != _category)
|
||||
{
|
||||
<div class="col text-center pb-2">
|
||||
@p.Category
|
||||
</div>
|
||||
category = p.Category;
|
||||
_category = p.Category;
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="col-sm-9">
|
||||
@if (!string.IsNullOrEmpty(p.Options))
|
||||
{
|
||||
@if (!string.IsNullOrEmpty(p.Autocomplete))
|
||||
@ -113,13 +126,16 @@
|
||||
<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))
|
||||
{
|
||||
@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
|
||||
{
|
||||
<option value="@option">@option</option>
|
||||
<option value="@name">@value</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
@ -129,13 +145,16 @@
|
||||
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
|
||||
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
|
||||
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
|
||||
{
|
||||
<option value="@option">@option</option>
|
||||
<option value="@name">@value</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
@ -149,22 +168,26 @@
|
||||
{
|
||||
@if (p.IsRequired)
|
||||
{
|
||||
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete" />
|
||||
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"
|
||||
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete" />
|
||||
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"
|
||||
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (p.IsRequired)
|
||||
{
|
||||
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" />
|
||||
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))"
|
||||
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
|
||||
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"
|
||||
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -174,22 +197,26 @@
|
||||
{
|
||||
@if (p.IsRequired)
|
||||
{
|
||||
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"></textarea>
|
||||
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"
|
||||
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
|
||||
}
|
||||
else
|
||||
{
|
||||
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"></textarea>
|
||||
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"
|
||||
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (p.IsRequired)
|
||||
{
|
||||
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))"></textarea>
|
||||
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))"
|
||||
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
|
||||
}
|
||||
else
|
||||
{
|
||||
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"></textarea>
|
||||
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"
|
||||
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -212,17 +239,17 @@
|
||||
<option value="from">@Localizer["Items.Sent"]</option>
|
||||
</select>
|
||||
<br />
|
||||
@if (filter == "to")
|
||||
@if (_filter == "to")
|
||||
{
|
||||
@if (notifications.Any())
|
||||
@if (_notifications.Any())
|
||||
{
|
||||
<Pager Items="@notifications">
|
||||
<Pager Items="@_notifications">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@Localizer["From"]</th>
|
||||
<th>@Localizer["Subject"]</th>
|
||||
<th>@Localizer["Received"]</th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@Localizer["From"]</th>
|
||||
<th>@Localizer["Subject"]</th>
|
||||
<th>@Localizer["Received"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" /></td>
|
||||
@ -230,13 +257,13 @@
|
||||
|
||||
@if (context.IsRead)
|
||||
{
|
||||
<td>@context.FromDisplayName</td>
|
||||
<td>@(string.IsNullOrEmpty(context.FromDisplayName) ? SharedLocalizer["System"] : context.FromDisplayName)</td>
|
||||
<td>@context.Subject</td>
|
||||
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</td>
|
||||
}
|
||||
else
|
||||
{
|
||||
<td><b>@context.FromDisplayName</b></td>
|
||||
<td><b>@(string.IsNullOrEmpty(context.FromDisplayName) ? SharedLocalizer["System"] : context.FromDisplayName)</b></td>
|
||||
<td><b>@context.Subject</b></td>
|
||||
<td><b>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</b></td>
|
||||
}
|
||||
@ -252,15 +279,15 @@
|
||||
context.Body = context.Body.Replace("\n", "");
|
||||
context.Body = context.Body.Replace("\r", "");
|
||||
}
|
||||
notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body;
|
||||
_notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body;
|
||||
}
|
||||
@if (context.IsRead)
|
||||
{
|
||||
@notificationSummary
|
||||
@_notificationSummary
|
||||
}
|
||||
else
|
||||
{
|
||||
<b>@notificationSummary</b>
|
||||
<b>@_notificationSummary</b>
|
||||
}
|
||||
</td>
|
||||
</Detail>
|
||||
@ -277,15 +304,15 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (notifications.Any())
|
||||
@if (_notifications.Any())
|
||||
{
|
||||
<Pager Items="@notifications">
|
||||
<Pager Items="@_notifications">
|
||||
<Header>
|
||||
<th style="width: 1px;"></th>
|
||||
<th style="width: 1px;"></th>
|
||||
<th>@Localizer["To"]</th>
|
||||
<th>@Localizer["Subject"]</th>
|
||||
<th>@Localizer["Sent"]</th>
|
||||
<th style="width: 1px;"></th>
|
||||
<th style="width: 1px;"></th>
|
||||
<th>@Localizer["To"]</th>
|
||||
<th>@Localizer["Subject"]</th>
|
||||
<th>@Localizer["Sent"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" /></td>
|
||||
@ -316,15 +343,15 @@
|
||||
context.Body = context.Body.Replace("\n", "");
|
||||
context.Body = context.Body.Replace("\r", "");
|
||||
}
|
||||
notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body;
|
||||
_notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body;
|
||||
}
|
||||
@if (context.IsRead)
|
||||
{
|
||||
@notificationSummary
|
||||
@_notificationSummary
|
||||
}
|
||||
else
|
||||
{
|
||||
<b>@notificationSummary</b>
|
||||
<b>@_notificationSummary</b>
|
||||
}
|
||||
</td>
|
||||
</Detail>
|
||||
@ -348,27 +375,30 @@
|
||||
@code {
|
||||
private bool _initialized = false;
|
||||
private string _passwordrequirements;
|
||||
private string username = string.Empty;
|
||||
private string _username = string.Empty;
|
||||
private string _password = string.Empty;
|
||||
private string _passwordtype = "password";
|
||||
private string _togglepassword = string.Empty;
|
||||
private string confirm = string.Empty;
|
||||
private bool allowtwofactor = false;
|
||||
private string twofactor = "False";
|
||||
private string email = string.Empty;
|
||||
private string displayname = string.Empty;
|
||||
private FileManager filemanager;
|
||||
private int folderid = -1;
|
||||
private int photofileid = -1;
|
||||
private File photo = null;
|
||||
private string _ImageFiles = string.Empty;
|
||||
private List<Profile> profiles;
|
||||
private Dictionary<string, string> settings;
|
||||
private string category = string.Empty;
|
||||
private string _confirm = string.Empty;
|
||||
private bool _allowtwofactor = false;
|
||||
private string _twofactor = "False";
|
||||
private string _email = string.Empty;
|
||||
private string _displayname = string.Empty;
|
||||
private FileManager _filemanager;
|
||||
private int _folderid = -1;
|
||||
private List<Models.TimeZone> _timezones;
|
||||
private string _timezoneid = string.Empty;
|
||||
private int _photofileid = -1;
|
||||
private File _photo = null;
|
||||
private string _imagefiles = string.Empty;
|
||||
|
||||
private string filter = "to";
|
||||
private List<Notification> notifications;
|
||||
private string notificationSummary = string.Empty;
|
||||
private List<Profile> _profiles;
|
||||
private Dictionary<string, string> _userSettings;
|
||||
private string _category = string.Empty;
|
||||
|
||||
private string _filter = "to";
|
||||
private List<Notification> _notifications;
|
||||
private string _notificationSummary = string.Empty;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
||||
|
||||
@ -378,43 +408,49 @@
|
||||
{
|
||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||
allowtwofactor = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "true");
|
||||
profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
||||
_allowtwofactor = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "true");
|
||||
_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}"));
|
||||
}
|
||||
}
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
|
||||
if (PageState.User != null)
|
||||
{
|
||||
username = PageState.User.Username;
|
||||
twofactor = PageState.User.TwoFactorRequired.ToString();
|
||||
email = PageState.User.Email;
|
||||
displayname = PageState.User.DisplayName;
|
||||
|
||||
if (string.IsNullOrEmpty(email))
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.User.NoEmail"], MessageType.Warning);
|
||||
}
|
||||
_username = PageState.User.Username;
|
||||
_twofactor = PageState.User.TwoFactorRequired.ToString();
|
||||
_email = PageState.User.Email;
|
||||
_displayname = PageState.User.DisplayName;
|
||||
_timezoneid = PageState.User.TimeZoneId;
|
||||
|
||||
// get user folder
|
||||
var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath);
|
||||
if (folder != null)
|
||||
{
|
||||
folderid = folder.FolderId;
|
||||
_folderid = folder.FolderId;
|
||||
}
|
||||
|
||||
if (PageState.User.PhotoFileId != null)
|
||||
{
|
||||
photofileid = PageState.User.PhotoFileId.Value;
|
||||
photo = await FileService.GetFileAsync(photofileid);
|
||||
_photofileid = PageState.User.PhotoFileId.Value;
|
||||
_photo = await FileService.GetFileAsync(_photofileid);
|
||||
}
|
||||
else
|
||||
{
|
||||
photofileid = -1;
|
||||
photo = null;
|
||||
_photofileid = -1;
|
||||
_photo = null;
|
||||
}
|
||||
|
||||
settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
|
||||
_userSettings = PageState.User.Settings;
|
||||
var sitesettings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
_ImageFiles = SettingService.GetSetting(settings, "ImageFiles", Constants.ImageFiles);
|
||||
_ImageFiles = (string.IsNullOrEmpty(_ImageFiles)) ? Constants.ImageFiles : _ImageFiles;
|
||||
_imagefiles = SettingService.GetSetting(_userSettings, "ImageFiles", Constants.ImageFiles);
|
||||
_imagefiles = (string.IsNullOrEmpty(_imagefiles)) ? Constants.ImageFiles : _imagefiles;
|
||||
|
||||
await LoadNotificationsAsync();
|
||||
|
||||
@ -434,13 +470,13 @@
|
||||
|
||||
private async Task LoadNotificationsAsync()
|
||||
{
|
||||
notifications = await NotificationService.GetNotificationsAsync(PageState.Site.SiteId, filter, PageState.User.UserId);
|
||||
notifications = notifications.Where(item => item.DeletedBy != PageState.User.Username).ToList();
|
||||
_notifications = await NotificationService.GetNotificationsAsync(PageState.Site.SiteId, _filter, PageState.User.UserId);
|
||||
_notifications = _notifications.Where(item => item.DeletedBy != PageState.User.Username).ToList();
|
||||
}
|
||||
|
||||
private string GetProfileValue(string SettingName, string DefaultValue)
|
||||
{
|
||||
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
|
||||
string value = SettingService.GetSetting(_userSettings, SettingName, DefaultValue);
|
||||
if (value.Contains("]"))
|
||||
{
|
||||
value = value.Substring(value.IndexOf("]") + 1);
|
||||
@ -452,38 +488,39 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
if (username != string.Empty && email != string.Empty)
|
||||
if (_username != string.Empty && _email != string.Empty)
|
||||
{
|
||||
if (_password == confirm)
|
||||
if (_password == _confirm)
|
||||
{
|
||||
if (ValidateProfiles())
|
||||
{
|
||||
var user = PageState.User;
|
||||
user.Username = username;
|
||||
user.Username = _username;
|
||||
user.Password = _password;
|
||||
user.TwoFactorRequired = bool.Parse(twofactor);
|
||||
user.Email = email;
|
||||
user.DisplayName = (displayname == string.Empty ? username : displayname);
|
||||
user.PhotoFileId = filemanager.GetFileId();
|
||||
user.TwoFactorRequired = bool.Parse(_twofactor);
|
||||
user.Email = _email;
|
||||
user.DisplayName = (_displayname == string.Empty ? _username : _displayname);
|
||||
user.TimeZoneId = _timezoneid;
|
||||
user.PhotoFileId = _filemanager.GetFileId();
|
||||
if (user.PhotoFileId == -1)
|
||||
{
|
||||
user.PhotoFileId = null;
|
||||
}
|
||||
if (user.PhotoFileId != null)
|
||||
{
|
||||
photofileid = user.PhotoFileId.Value;
|
||||
photo = await FileService.GetFileAsync(photofileid);
|
||||
_photofileid = user.PhotoFileId.Value;
|
||||
_photo = await FileService.GetFileAsync(_photofileid);
|
||||
}
|
||||
else
|
||||
{
|
||||
photofileid = -1;
|
||||
photo = null;
|
||||
_photofileid = -1;
|
||||
_photo = null;
|
||||
}
|
||||
|
||||
user = await UserService.UpdateUserAsync(user);
|
||||
if (user != null)
|
||||
{
|
||||
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
|
||||
await SettingService.UpdateUserSettingsAsync(_userSettings, PageState.User.UserId);
|
||||
await logger.LogInformation("User Profile Saved");
|
||||
|
||||
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
|
||||
@ -549,12 +586,12 @@
|
||||
|
||||
private bool ValidateProfiles()
|
||||
{
|
||||
foreach (Profile profile in profiles)
|
||||
foreach (Profile profile in _profiles)
|
||||
{
|
||||
var value = GetProfileValue(profile.Name, string.Empty);
|
||||
if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue))
|
||||
{
|
||||
settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue);
|
||||
_userSettings = SettingService.SetSetting(_userSettings, profile.Name, profile.DefaultValue);
|
||||
}
|
||||
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||
{
|
||||
@ -586,7 +623,7 @@
|
||||
private void ProfileChanged(ChangeEventArgs e, string SettingName)
|
||||
{
|
||||
var value = (string)e.Value;
|
||||
settings = SettingService.SetSetting(settings, SettingName, value);
|
||||
_userSettings = SettingService.SetSetting(_userSettings, SettingName, value);
|
||||
}
|
||||
|
||||
private async Task Delete(Notification Notification)
|
||||
@ -616,7 +653,7 @@
|
||||
|
||||
private async void FilterChanged(ChangeEventArgs e)
|
||||
{
|
||||
filter = (string)e.Value;
|
||||
_filter = (string)e.Value;
|
||||
await LoadNotificationsAsync();
|
||||
StateHasChanged();
|
||||
}
|
||||
@ -626,7 +663,7 @@
|
||||
try
|
||||
{
|
||||
ShowProgressIndicator();
|
||||
foreach(var Notification in notifications)
|
||||
foreach(var Notification in _notifications)
|
||||
{
|
||||
if (!Notification.IsDeleted)
|
||||
{
|
||||
|
||||
@ -128,7 +128,7 @@
|
||||
createdon = notification.CreatedOn.ToString();
|
||||
body = notification.Body;
|
||||
|
||||
if (title == "From")
|
||||
if (title == "From" && !notification.IsRead)
|
||||
{
|
||||
notification.IsRead = true;
|
||||
notification = await NotificationService.UpdateNotificationAsync(notification);
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
@inject IUserService UserService
|
||||
@inject IProfileService ProfileService
|
||||
@inject ISettingService SettingService
|
||||
@inject ITimeZoneService TimeZoneService
|
||||
@inject IStringLocalizer<Add> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@ -12,7 +13,7 @@
|
||||
{
|
||||
<TabStrip>
|
||||
<TabPanel Name="Identity" ResourceKey="Identity">
|
||||
@if (profiles != null)
|
||||
@if (_profiles != null)
|
||||
{
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
@ -27,12 +28,33 @@
|
||||
<input id="email" class="form-control" @bind="@_email" />
|
||||
</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">
|
||||
<Label Class="col-sm-3" For="displayname" HelpText="The full name of the user" ResourceKey="DisplayName"></Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="displayname" class="form-control" @bind="@_displayname" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="timezone" HelpText="Your time zone" ResourceKey="TimeZone">Time Zone:</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="timezone" class="form-select" @bind="@_timezoneid">
|
||||
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||
@foreach (var timezone in _timezones)
|
||||
{
|
||||
<option value="@timezone.Id">@timezone.DisplayName</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="notify" HelpText="Indicate if new users should receive an email notification" ResourceKey="Notify">Notify? </Label>
|
||||
<div class="col-sm-9">
|
||||
@ -48,31 +70,34 @@
|
||||
<TabPanel Name="Profile" ResourceKey="Profile">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
@foreach (Profile profile in profiles)
|
||||
@foreach (Profile profile in _profiles)
|
||||
{
|
||||
var p = profile;
|
||||
if (p.Category != category)
|
||||
if (p.Category != _category)
|
||||
{
|
||||
<div class="col text-center pb-2">
|
||||
<strong>@p.Category</strong>
|
||||
</div>
|
||||
category = p.Category;
|
||||
_category = p.Category;
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
|
||||
<div class="col-sm-9">
|
||||
@if (!string.IsNullOrEmpty(p.Options))
|
||||
<div class="col-sm-9">
|
||||
@if (!string.IsNullOrEmpty(p.Options))
|
||||
{
|
||||
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
|
||||
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
|
||||
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
|
||||
{
|
||||
<option value="@option">@option</option>
|
||||
<option value="@name">@value</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
@ -81,11 +106,11 @@
|
||||
{
|
||||
@if (p.Rows == 1)
|
||||
{
|
||||
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
|
||||
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" @attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"></textarea>
|
||||
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" @attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@ -103,14 +128,17 @@
|
||||
|
||||
|
||||
@code {
|
||||
private List<Models.TimeZone> _timezones;
|
||||
private bool _initialized = false;
|
||||
private string _username = string.Empty;
|
||||
private string _email = string.Empty;
|
||||
private string _confirmed = "True";
|
||||
private string _displayname = string.Empty;
|
||||
private string _timezoneid = string.Empty;
|
||||
private string _notify = "True";
|
||||
private List<Profile> profiles;
|
||||
private Dictionary<string, string> settings;
|
||||
private string category = string.Empty;
|
||||
private List<Profile> _profiles;
|
||||
private Dictionary<string, string> _settings;
|
||||
private string _category = string.Empty;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||
|
||||
@ -118,8 +146,19 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
||||
settings = new Dictionary<string, string>();
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
_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>();
|
||||
_timezoneid = PageState.Site.TimeZoneId;
|
||||
_initialized = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -131,7 +170,7 @@
|
||||
|
||||
private string GetProfileValue(string SettingName, string DefaultValue)
|
||||
{
|
||||
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
|
||||
string value = SettingService.GetSetting(_settings, SettingName, DefaultValue);
|
||||
if (value.Contains("]"))
|
||||
{
|
||||
value = value.Substring(value.IndexOf("]") + 1);
|
||||
@ -152,7 +191,9 @@
|
||||
user.Username = _username;
|
||||
user.Password = ""; // will be auto generated
|
||||
user.Email = _email;
|
||||
user.EmailConfirmed = bool.Parse(_confirmed);
|
||||
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
||||
user.TimeZoneId = _timezoneid;
|
||||
user.PhotoFileId = null;
|
||||
user.SuppressNotification = !bool.Parse(_notify);
|
||||
|
||||
@ -160,7 +201,7 @@
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
|
||||
await SettingService.UpdateUserSettingsAsync(_settings, user.UserId);
|
||||
await logger.LogInformation("User Created {User}", user);
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
@ -185,12 +226,12 @@
|
||||
|
||||
private bool ValidateProfiles()
|
||||
{
|
||||
foreach (Profile profile in profiles)
|
||||
foreach (Profile profile in _profiles)
|
||||
{
|
||||
var value = GetProfileValue(profile.Name, string.Empty);
|
||||
if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue))
|
||||
{
|
||||
settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue);
|
||||
_settings = SettingService.SetSetting(_settings, profile.Name, profile.DefaultValue);
|
||||
}
|
||||
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||
{
|
||||
@ -217,6 +258,6 @@
|
||||
private void ProfileChanged(ChangeEventArgs e, string SettingName)
|
||||
{
|
||||
var value = (string)e.Value;
|
||||
settings = SettingService.SetSetting(settings, SettingName, value);
|
||||
_settings = SettingService.SetSetting(_settings, SettingName, value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,8 @@
|
||||
@inject IProfileService ProfileService
|
||||
@inject ISettingService SettingService
|
||||
@inject IFileService FileService
|
||||
@inject ITimeZoneService TimeZoneService
|
||||
@inject IServiceProvider ServiceProvider
|
||||
@inject IStringLocalizer<Edit> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@ -16,13 +18,13 @@
|
||||
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="username" HelpText="The unique username for a user. Note that this field can not be modified." ResourceKey="Username"></Label>
|
||||
<Label Class="col-sm-3" For="username" HelpText="The unique username for a user. Note that this field can not be modified." ResourceKey="Username">Username:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="username" class="form-control" @bind="@username" readonly />
|
||||
<input id="username" class="form-control" @bind="@_username" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password"></Label>
|
||||
<Label Class="col-sm-3" For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password">Password:</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
|
||||
@ -31,45 +33,69 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm"></Label>
|
||||
<Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm">Confirm Password:</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@confirm" autocomplete="new-password" />
|
||||
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email"></Label>
|
||||
<Label Class="col-sm-3" For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email">Email:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="email" class="form-control" @bind="@email" />
|
||||
<input id="email" class="form-control" @bind="@_email" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="displayname" HelpText="The full name of the user" ResourceKey="DisplayName"></Label>
|
||||
<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">
|
||||
<input id="displayname" class="form-control" @bind="@displayname" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="isdeleted" HelpText="Indicate if the user is active" ResourceKey="IsDeleted"></Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="isdeleted" class="form-select" @bind="@isdeleted">
|
||||
<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">
|
||||
<Label Class="col-sm-3" For="lastlogin" HelpText="The date and time when the user last signed in" ResourceKey="LastLogin"></Label>
|
||||
<Label Class="col-sm-3" For="displayname" HelpText="The full name of the user" ResourceKey="DisplayName">Full Name:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="lastlogin" class="form-control" @bind="@lastlogin" readonly />
|
||||
<input id="displayname" class="form-control" @bind="@_displayname" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="lastipaddress" HelpText="The IP Address of the user recorded during their last login" ResourceKey="LastIPAddress"></Label>
|
||||
<Label Class="col-sm-3" For="timezone" HelpText="Your time zone" ResourceKey="TimeZone">Time Zone:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="lastipaddress" class="form-control" @bind="@lastipaddress" readonly />
|
||||
<select id="timezone" class="form-select" @bind="@_timezoneid">
|
||||
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||
@foreach (var timezone in _timezones)
|
||||
{
|
||||
<option value="@timezone.Id">@timezone.DisplayName</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="isdeleted" HelpText="Indicate if the user is active" ResourceKey="IsDeleted">Deleted?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="isdeleted" class="form-select" @bind="@_isdeleted">
|
||||
<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="lastlogin" HelpText="The date and time when the user last signed in" ResourceKey="LastLogin">Last Login:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="lastlogin" class="form-control" @bind="@_lastlogin" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="lastipaddress" HelpText="The IP Address of the user recorded during their last login" ResourceKey="LastIPAddress">Last IP Address:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="lastipaddress" class="form-control" @bind="@_lastipaddress" readonly />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -77,31 +103,34 @@
|
||||
<TabPanel Name="Profile" ResourceKey="Profile">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
@foreach (Profile profile in profiles)
|
||||
@foreach (Profile profile in _profiles)
|
||||
{
|
||||
var p = profile;
|
||||
if (p.Category != category)
|
||||
if (p.Category != _category)
|
||||
{
|
||||
<div class="col text-center pb-2">
|
||||
<strong>@p.Category</strong>
|
||||
</div>
|
||||
category = p.Category;
|
||||
_category = p.Category;
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
|
||||
<div class="col-sm-9">
|
||||
@if (!string.IsNullOrEmpty(p.Options))
|
||||
<div class="col-sm-9">
|
||||
@if (!string.IsNullOrEmpty(p.Options))
|
||||
{
|
||||
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
|
||||
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
|
||||
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
|
||||
{
|
||||
<option value="@option">@option</option>
|
||||
<option value="@name">@value</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
@ -110,11 +139,11 @@
|
||||
{
|
||||
@if (p.Rows == 1)
|
||||
{
|
||||
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
|
||||
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" @attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"></textarea>
|
||||
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" @attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@ -124,39 +153,50 @@
|
||||
</div>
|
||||
</TabPanel>
|
||||
</TabStrip>
|
||||
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
<br />
|
||||
<br />
|
||||
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon" DeletedBy="@deletedby" DeletedOn="@deletedon"></AuditInfo>
|
||||
@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>
|
||||
}
|
||||
@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 ms-1" OnClick="@(async () => await DeleteUser())" ResourceKey="DeleteUser" />
|
||||
}
|
||||
<br /><br />
|
||||
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
|
||||
}
|
||||
|
||||
@code {
|
||||
@code {
|
||||
private List<Models.TimeZone> _timezones;
|
||||
private bool _initialized = false;
|
||||
private string _passwordrequirements;
|
||||
private int userid;
|
||||
private string username = string.Empty;
|
||||
private int _userid;
|
||||
private string _username = string.Empty;
|
||||
private string _password = string.Empty;
|
||||
private string _passwordtype = "password";
|
||||
private string _togglepassword = string.Empty;
|
||||
private string confirm = string.Empty;
|
||||
private string email = string.Empty;
|
||||
private string displayname = string.Empty;
|
||||
private string isdeleted;
|
||||
private string lastlogin;
|
||||
private string lastipaddress;
|
||||
private string _confirm = string.Empty;
|
||||
private string _email = string.Empty;
|
||||
private string _confirmed = string.Empty;
|
||||
private string _displayname = string.Empty;
|
||||
private string _timezoneid = string.Empty;
|
||||
private string _isdeleted;
|
||||
private string _lastlogin;
|
||||
private string _lastipaddress;
|
||||
private bool _ishost = false;
|
||||
|
||||
private List<Profile> profiles;
|
||||
private Dictionary<string, string> settings;
|
||||
private string category = string.Empty;
|
||||
private List<Profile> _profiles;
|
||||
private Dictionary<string, string> _settings;
|
||||
private string _category = string.Empty;
|
||||
|
||||
private string createdby;
|
||||
private DateTime createdon;
|
||||
private string modifiedby;
|
||||
private DateTime modifiedon;
|
||||
private string deletedby;
|
||||
private DateTime? deletedon;
|
||||
private string _createdby;
|
||||
private DateTime _createdon;
|
||||
private string _modifiedby;
|
||||
private DateTime _modifiedon;
|
||||
private string _deletedby;
|
||||
private DateTime? _deletedon;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||
|
||||
@ -166,28 +206,41 @@
|
||||
{
|
||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||
profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
|
||||
_profiles = await ProfileService.GetProfilesAsync(PageState.Site.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}"));
|
||||
}
|
||||
}
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
|
||||
if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int UserId))
|
||||
{
|
||||
userid = UserId;
|
||||
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
|
||||
_userid = UserId;
|
||||
var user = await UserService.GetUserAsync(_userid, PageState.Site.SiteId);
|
||||
if (user != null)
|
||||
{
|
||||
username = user.Username;
|
||||
email = user.Email;
|
||||
displayname = user.DisplayName;
|
||||
isdeleted = user.IsDeleted.ToString();
|
||||
lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn);
|
||||
lastipaddress = user.LastIPAddress;
|
||||
_username = user.Username;
|
||||
_email = user.Email;
|
||||
_confirmed = user.EmailConfirmed.ToString();
|
||||
_displayname = user.DisplayName;
|
||||
_timezoneid = PageState.User.TimeZoneId;
|
||||
_isdeleted = user.IsDeleted.ToString();
|
||||
_lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", UtcToLocal(user.LastLoginOn));
|
||||
_lastipaddress = user.LastIPAddress;
|
||||
_ishost = UserSecurity.ContainsRole(user.Roles, RoleNames.Host);
|
||||
|
||||
settings = await SettingService.GetUserSettingsAsync(user.UserId);
|
||||
createdby = user.CreatedBy;
|
||||
createdon = user.CreatedOn;
|
||||
modifiedby = user.ModifiedBy;
|
||||
modifiedon = user.ModifiedOn;
|
||||
deletedby = user.DeletedBy;
|
||||
deletedon = user.DeletedOn;
|
||||
_settings = user.Settings;
|
||||
_createdby = user.CreatedBy;
|
||||
_createdon = user.CreatedOn;
|
||||
_modifiedby = user.ModifiedBy;
|
||||
_modifiedon = user.ModifiedOn;
|
||||
_deletedby = user.DeletedBy;
|
||||
_deletedon = user.DeletedOn;
|
||||
}
|
||||
}
|
||||
|
||||
@ -195,14 +248,14 @@
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading User {UserId} {Error}", userid, ex.Message);
|
||||
await logger.LogError(ex, "Error Loading User {UserId} {Error}", _userid, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetProfileValue(string SettingName, string DefaultValue)
|
||||
{
|
||||
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
|
||||
string value = SettingService.GetSetting(_settings, SettingName, DefaultValue);
|
||||
if (value.Contains("]"))
|
||||
{
|
||||
value = value.Substring(value.IndexOf("]") + 1);
|
||||
@ -214,25 +267,29 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
if (username != string.Empty && email != string.Empty)
|
||||
if (_username != string.Empty && _email != string.Empty)
|
||||
{
|
||||
if (_password == confirm)
|
||||
if (_password == _confirm)
|
||||
{
|
||||
if (ValidateProfiles())
|
||||
{
|
||||
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
|
||||
var user = await UserService.GetUserAsync(_userid, PageState.Site.SiteId);
|
||||
user.SiteId = PageState.Site.SiteId;
|
||||
user.Username = username;
|
||||
user.Username = _username;
|
||||
user.Password = _password;
|
||||
user.Email = email;
|
||||
user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname;
|
||||
|
||||
user.IsDeleted = (isdeleted == null ? true : Boolean.Parse(isdeleted));
|
||||
user.Email = _email;
|
||||
user.EmailConfirmed = bool.Parse(_confirmed);
|
||||
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
||||
user.TimeZoneId = _timezoneid;
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
user.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
|
||||
}
|
||||
|
||||
user = await UserService.UpdateUserAsync(user);
|
||||
if (user != null)
|
||||
{
|
||||
await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
|
||||
await SettingService.UpdateUserSettingsAsync(_settings, user.UserId);
|
||||
await logger.LogInformation("User Saved {User}", user);
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
@ -254,19 +311,57 @@
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Saving User {Username} {Email} {Error}", username, email, ex.Message);
|
||||
await logger.LogError(ex, "Error Saving User {Username} {Email} {Error}", _username, _email, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.User.Save"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ImpersonateUser()
|
||||
{
|
||||
try
|
||||
{
|
||||
await logger.LogInformation(LogFunction.Security, "User {Username} Impersonated By Administrator {Administrator}", _username, PageState.User.Username);
|
||||
|
||||
// post back to the server so that the cookies are set correctly
|
||||
var interop = new Interop(JSRuntime);
|
||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, returnurl = PageState.Alias.Path };
|
||||
string url = Utilities.TenantUrl(PageState.Alias, "/pages/impersonate/");
|
||||
await interop.SubmitForm(url, fields);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Impersonating User {Username} {Error}", _username, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.User.Impersonate"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteUser()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && _userid != PageState.User.UserId)
|
||||
{
|
||||
var user = await UserService.GetUserAsync(_userid, PageState.Site.SiteId);
|
||||
await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId);
|
||||
await logger.LogInformation("User Permanently Deleted {User}", user);
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Permanently Deleting User {UserId} {Error}", _userid, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.DeleteUser"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ValidateProfiles()
|
||||
{
|
||||
foreach (Profile profile in profiles)
|
||||
foreach (Profile profile in _profiles)
|
||||
{
|
||||
var value = GetProfileValue(profile.Name, string.Empty);
|
||||
if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue))
|
||||
{
|
||||
settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue);
|
||||
_settings = SettingService.SetSetting(_settings, profile.Name, profile.DefaultValue);
|
||||
}
|
||||
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||
{
|
||||
@ -293,7 +388,7 @@
|
||||
private void ProfileChanged(ChangeEventArgs e, string SettingName)
|
||||
{
|
||||
var value = (string)e.Value;
|
||||
settings = SettingService.SetSetting(settings, SettingName, value);
|
||||
_settings = SettingService.SetSetting(_settings, SettingName, value);
|
||||
}
|
||||
|
||||
private void TogglePassword()
|
||||
|
||||
@ -17,171 +17,202 @@ else
|
||||
{
|
||||
<TabStrip>
|
||||
<TabPanel Name="Users" Heading="Users" ResourceKey="Users">
|
||||
<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"/>
|
||||
<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 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">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th class="app-sort-th link-primary text-decoration-underline" @onclick="@(() => SortTable("Username"))">@Localizer["Username"]<i class="@(SetSortIcon("Username"))"></i></th>
|
||||
<th class="app-sort-th link-primary text-decoration-underline" @onclick="@(() => SortTable("DisplayName"))">@Localizer["Name"]<i class="@(SetSortIcon("DisplayName"))"></i></th>
|
||||
<th class="app-sort-th link-primary text-decoration-underline" @onclick="@(() => SortTable("Email"))">@Localizer["Email"]<i class="@(SetSortIcon("Email"))"></i></th>
|
||||
<th class="app-sort-th link-primary text-decoration-underline" @onclick="@(() => SortTable("LastLoginOn"))">@Localizer["LastLoginOn"]<i class="@(SetSortIcon("LastLoginOn"))"></i></th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td>
|
||||
</Header>
|
||||
<Row>
|
||||
<td>
|
||||
<ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="EditUser" />
|
||||
</td>
|
||||
<td>
|
||||
<ActionDialog Header="Delete User" Message="@string.Format(Localizer["Confirm.User.Delete"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUser(context))" Disabled="@(context.UserId == PageState.User.UserId)" ResourceKey="DeleteUser" />
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<ActionDialog Header="Delete User" Message="@string.Format(Localizer["Confirm.User.Delete"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUser(context))" Disabled="@(context.UserId == PageState.User.UserId || context.User.IsDeleted)" ResourceKey="DeleteUser" />
|
||||
</td>
|
||||
<td>
|
||||
<ActionLink Action="Roles" Text="Roles" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="Roles" />
|
||||
</td>
|
||||
<td>@context.User.Username</td>
|
||||
<td>@context.User.DisplayName</td>
|
||||
<td>@((MarkupString)string.Format("<a href=\"mailto:{0}\">{1}</a>", @context.User.Email, @context.User.Email))</td>
|
||||
<td>@((context.User.LastLoginOn != DateTime.MinValue) ? string.Format("{0:dd-MMM-yyyy HH:mm:ss}", context.User.LastLoginOn) : "")</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
</td>
|
||||
<td>@context.User.Username</td>
|
||||
<td>@context.User.DisplayName</td>
|
||||
<td>@((MarkupString)string.Format("<a href=\"mailto:{0}\">{1}</a>", @context.User.Email, @context.User.Email))</td>
|
||||
<td>@((context.User.LastLoginOn != DateTime.MinValue) ? string.Format("{0:dd-MMM-yyyy HH:mm:ss}", UtcToLocal(context.User.LastLoginOn)) : "")</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
</TabPanel>
|
||||
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings" Security="SecurityAccessLevel.Admin">
|
||||
<div class="container">
|
||||
<Section Name="User" Heading="User Settings" ResourceKey="UserSettings">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="allowregistration" HelpText="Do you want anonymous visitors to be able to register for an account on the site" ResourceKey="AllowRegistration">Allow User Registration?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="allowregistration" class="form-select" @bind="@_allowregistration">
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (_providertype != "")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="allowsitelogin" HelpText="Do you want to allow users to sign in using a username and password that is managed locally on this site? Note that you should only disable this option if you have already sucessfully configured an external login provider, or else you may lock yourself out of the site." ResourceKey="AllowSiteLogin">Allow Login?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="allowsitelogin" class="form-select" @bind="@_allowsitelogin">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="allowsitelogin" HelpText="Do you want to allow users to sign in using a username and password that is managed locally on this site? Note that you should only disable this option if you have already sucessfully configured an external login provider, or else you may lock yourself out of the site." ResourceKey="AllowSiteLogin">Allow Login?</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="allowsitelogin" class="form-control" value="@SharedLocalizer["Yes"]" readonly />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
<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>
|
||||
<div class="col-sm-9">
|
||||
<select id="twofactor" class="form-select" @bind="@_twofactor">
|
||||
<option value="false">@Localizer["Disabled"]</option>
|
||||
<option value="true">@Localizer["Optional"]</option>
|
||||
<option value="required">@Localizer["Required"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="cookiename" HelpText="You can choose to use a custom authentication cookie name for each site. However please be aware that if you want to share an authentication cookie between sites on the same domain they need to use a consistent cookie name. Also be aware that changing the authentication cookie name will logout all current users." ResourceKey="CookieName">Cookie Name:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="cookiename" class="form-control" @bind="@_cookiename" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<div class="container">
|
||||
<Section Name="User" Heading="User Settings" ResourceKey="UserSettings">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="allowregistration" HelpText="Do you want anonymous visitors to be able to register for an account on the site" ResourceKey="AllowRegistration">Allow User Registration?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="allowregistration" class="form-select" @bind="@_allowregistration">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (_allowregistration == "true")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="registerurl" HelpText="Optionally provide a custom registration url" ResourceKey="RegisterUrl">Register Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="registerurl" class="form-control" @bind="@_registerurl" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="profileurl" HelpText="Optionally provide a custom profile url" ResourceKey="ProfileUrl">Profile Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="profileurl" class="form-control" @bind="@_profileurl" />
|
||||
</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))
|
||||
{
|
||||
<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 Authentication?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="twofactor" class="form-select" @bind="@_twofactor">
|
||||
<option value="false">@Localizer["Disabled"]</option>
|
||||
<option value="true">@Localizer["Optional"]</option>
|
||||
<option value="required">@Localizer["Required"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="cookiename" HelpText="You can choose to use a custom authentication cookie name for each site. However please be aware that if you want to share an authentication cookie between sites on the same domain they need to use a consistent cookie name. Also be aware that changing the authentication cookie name will logout all current users." ResourceKey="CookieName">Cookie Name:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="cookiename" class="form-control" @bind="@_cookiename" />
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<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">
|
||||
<input id="cookieexpiration" class="form-control" @bind="@_cookieexpiration" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<div class="col-sm-9">
|
||||
<input id="cookieexpiration" class="form-control" @bind="@_cookieexpiration" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="alwaysremember" HelpText="Enabling this option will set a permanent cookie in conjunction with the Cookie Expiration Timespan, which will automatically sign in users the next time they visit the site. By default the site will use session cookies." ResourceKey="AlwaysRemember">Always Remember User?</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="col-sm-9">
|
||||
<select id="alwaysremember" class="form-select" @bind="@_alwaysremember">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</Section>
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
<Section Name="Password" Heading="Password Settings" ResourceKey="PasswordSettings">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="minimumlength" HelpText="The Minimum Length For A Password" ResourceKey="RequiredLength">Minimum Length:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="minimumlength" class="form-control" @bind="@_minimumlength" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="uniquecharacters" HelpText="The Minimum Number Of Unique Characters Which A Password Must Contain" ResourceKey="UniqueCharacters">Unique Characters:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="uniquecharacters" class="form-control" @bind="@_uniquecharacters" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requiredigit" HelpText="Indicate If Passwords Must Contain A Digit" ResourceKey="RequireDigit">Require Digit?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requiredigit" class="form-select" @bind="@_requiredigit" 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="requireupper" HelpText="Indicate If Passwords Must Contain An Upper Case Character" ResourceKey="RequireUpper">Require Uppercase?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requireupper" class="form-select" @bind="@_requireupper" 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="requirelower" HelpText="Indicate If Passwords Must Contain A Lower Case Character" ResourceKey="RequireLower">Require Lowercase?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requirelower" class="form-select" @bind="@_requirelower" 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="requirepunctuation" HelpText="Indicate if Passwords Must Contain A Non-alphanumeric Character (ie. Punctuation)" ResourceKey="RequirePunctuation">Require Punctuation?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requirepunctuation" class="form-select" @bind="@_requirepunctuation" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="Lockout" Heading="Lockout Settings" ResourceKey="LockoutSettings">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="maximum" HelpText="The Maximum Number Of Sign In Attempts Before A User Is Locked Out" ResourceKey="MaximumFailures">Maximum Failures:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="maximum" class="form-control" @bind="@_maximumfailures" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="lockoutduration" HelpText="The Number Of Minutes A User Should Be Locked Out" ResourceKey="LockoutDuration">Lockout Duration:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="lockoutduration" class="form-control" @bind="@_lockoutduration" required />
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="ExternalLogin" Heading="External Login Settings" ResourceKey="ExternalLoginSettings">
|
||||
<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="logouteverywhere" HelpText="Do you want users to be logged out of every active session on any device, or only their current session?" ResourceKey="LogoutEverywhere">Logout Everywhere?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="logouteverywhere" class="form-select" @bind="@_logouteverywhere">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</Section>
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
<Section Name="Password" Heading="Password Settings" ResourceKey="PasswordSettings">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="minimumlength" HelpText="The Minimum Length For A Password" ResourceKey="RequiredLength">Minimum Length:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="minimumlength" class="form-control" @bind="@_minimumlength" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="uniquecharacters" HelpText="The Minimum Number Of Unique Characters Which A Password Must Contain" ResourceKey="UniqueCharacters">Unique Characters:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="uniquecharacters" class="form-control" @bind="@_uniquecharacters" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requiredigit" HelpText="Indicate If Passwords Must Contain A Digit" ResourceKey="RequireDigit">Require Digit?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requiredigit" class="form-select" @bind="@_requiredigit" 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="requireupper" HelpText="Indicate If Passwords Must Contain An Upper Case Character" ResourceKey="RequireUpper">Require Uppercase?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requireupper" class="form-select" @bind="@_requireupper" 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="requirelower" HelpText="Indicate If Passwords Must Contain A Lower Case Character" ResourceKey="RequireLower">Require Lowercase?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requirelower" class="form-select" @bind="@_requirelower" 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="requirepunctuation" HelpText="Indicate if Passwords Must Contain A Non-alphanumeric Character (ie. Punctuation)" ResourceKey="RequirePunctuation">Require Punctuation?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requirepunctuation" class="form-select" @bind="@_requirepunctuation" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="Lockout" Heading="Lockout Settings" ResourceKey="LockoutSettings">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="maximum" HelpText="The Maximum Number Of Sign In Attempts Before A User Is Locked Out" ResourceKey="MaximumFailures">Maximum Failures:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="maximum" class="form-control" @bind="@_maximumfailures" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="lockoutduration" HelpText="The Number Of Minutes A User Should Be Locked Out" ResourceKey="LockoutDuration">Lockout Duration:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="lockoutduration" class="form-control" @bind="@_lockoutduration" required />
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="ExternalLogin" Heading="External Login Settings" ResourceKey="ExternalLoginSettings">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="provider" HelpText="Select the external login provider" ResourceKey="Provider">Provider:</Label>
|
||||
<div class="col-sm-9">
|
||||
@ -201,77 +232,77 @@ else
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="providertype" HelpText="Select the external login provider type" ResourceKey="ProviderType">Provider Type:</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="providertype" class="form-select" value="@_providertype" @onchange="(e => ProviderTypeChanged(e))">
|
||||
<Label Class="col-sm-3" For="providertype" HelpText="Select the external login provider type" ResourceKey="ProviderType">Provider Type:</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="providertype" class="form-select" value="@_providertype" @onchange="(e => ProviderTypeChanged(e))">
|
||||
<option value="" selected><@Localizer["Not Specified"]></option>
|
||||
<option value="@AuthenticationProviderTypes.OpenIDConnect">@Localizer["OIDC"]</option>
|
||||
<option value="@AuthenticationProviderTypes.OAuth2">@Localizer["OAuth2"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (_providertype != "")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="providername" HelpText="Specify a friendly name for the external login provider which will be displayed on the Login page" ResourceKey="ProviderName">Provider Name:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="providername" class="form-control" @bind="@_providername" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="authority" HelpText="The Authority Url or Issuer Url associated with the OpenID Connect provider" ResourceKey="Authority">Authority:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="authority" class="form-control" @bind="@_authority" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="metadataurl" HelpText="The discovery endpoint for obtaining metadata for this provider. Only specify if the OpenID Connect provider does not use the standard approach (ie. /.well-known/openid-configuration)" ResourceKey="MetadataUrl">Metadata Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="metadataurl" class="form-control" @bind="@_metadataurl" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_providertype == AuthenticationProviderTypes.OAuth2)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="authorizationurl" HelpText="The endpoint for obtaining an Authorization Code" ResourceKey="AuthorizationUrl">Authorization Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="authorizationurl" class="form-control" @bind="@_authorizationurl" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="tokenurl" HelpText="The endpoint for obtaining an Auth Token" ResourceKey="TokenUrl">Token Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="tokenurl" class="form-control" @bind="@_tokenurl" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="userinfourl" HelpText="The endpoint for obtaining user information. This should be an API or Page Url which contains the users email address." ResourceKey="UserInfoUrl">User Info Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="userinfourl" class="form-control" @bind="@_userinfourl" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_providertype != "")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="clientid" HelpText="The Client ID from the provider" ResourceKey="ClientID">Client ID:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="clientid" class="form-control" @bind="@_clientid" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="clientsecret" HelpText="The Client Secret from the provider" ResourceKey="ClientSecret">Client Secret:</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input type="@_clientsecrettype" id="clientsecret" class="form-control" @bind="@_clientsecret" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@ToggleClientSecret">@_toggleclientsecret</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<option value="@AuthenticationProviderTypes.OpenIDConnect">@Localizer["OIDC"]</option>
|
||||
<option value="@AuthenticationProviderTypes.OAuth2">@Localizer["OAuth2"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (_providertype != "")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="providername" HelpText="Specify a friendly name for the external login provider which will be displayed on the Login page" ResourceKey="ProviderName">Provider Name:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="providername" class="form-control" @bind="@_providername" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="authority" HelpText="The Authority Url or Issuer Url associated with the OpenID Connect provider" ResourceKey="Authority">Authority:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="authority" class="form-control" @bind="@_authority" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="metadataurl" HelpText="The discovery endpoint for obtaining metadata for this provider. Only specify if the OpenID Connect provider does not use the standard approach (ie. /.well-known/openid-configuration)" ResourceKey="MetadataUrl">Metadata Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="metadataurl" class="form-control" @bind="@_metadataurl" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_providertype == AuthenticationProviderTypes.OAuth2)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="authorizationurl" HelpText="The endpoint for obtaining an Authorization Code" ResourceKey="AuthorizationUrl">Authorization Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="authorizationurl" class="form-control" @bind="@_authorizationurl" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="tokenurl" HelpText="The endpoint for obtaining an Auth Token" ResourceKey="TokenUrl">Token Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="tokenurl" class="form-control" @bind="@_tokenurl" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="userinfourl" HelpText="The endpoint for obtaining user information. This should be an API or Page Url which contains the users email address." ResourceKey="UserInfoUrl">User Info Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="userinfourl" class="form-control" @bind="@_userinfourl" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_providertype != "")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="clientid" HelpText="The Client ID from the provider" ResourceKey="ClientID">Client ID:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="clientid" class="form-control" @bind="@_clientid" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="clientsecret" HelpText="The Client Secret from the provider" ResourceKey="ClientSecret">Client Secret:</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input type="@_clientsecrettype" id="clientsecret" class="form-control" @bind="@_clientsecret" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@ToggleClientSecret">@_toggleclientsecret</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
@ -289,34 +320,52 @@ else
|
||||
</select>
|
||||
</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">
|
||||
<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>
|
||||
<div class="col-sm-9">
|
||||
<input id="scopes" class="form-control" @bind="@_scopes" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="parameters" HelpText="Optionally specify any additional parameters as name/value pairs to send to the provider (separated by commas if there are multiple)." ResourceKey="Parameters">Parameters:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="parameters" class="form-control" @bind="@_parameters" />
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<div class="col-sm-9">
|
||||
<input id="scopes" class="form-control" @bind="@_scopes" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="pkce" HelpText="Indicate if the provider supports Proof Key for Code Exchange (PKCE)" ResourceKey="PKCE">Use PKCE?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="pkce" class="form-select" @bind="@_pkce" 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="redirecturl" HelpText="The Redirect Url (or Callback Url) which usually needs to be registered with the provider" ResourceKey="RedirectUrl">Redirect Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="redirecturl" class="form-control" @bind="@_redirecturl" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<Label Class="col-sm-3" For="parameters" HelpText="Optionally specify any additional parameters as name/value pairs to send to the provider (separated by commas if there are multiple)." ResourceKey="Parameters">Parameters:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="parameters" class="form-control" @bind="@_parameters" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="pkce" HelpText="Indicate if the provider supports Proof Key for Code Exchange (PKCE)" ResourceKey="PKCE">Use PKCE?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="pkce" class="form-select" @bind="@_pkce" 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="redirecturl" HelpText="The Redirect Url (or Callback Url) which usually needs to be registered with the provider" ResourceKey="RedirectUrl">Redirect Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="redirecturl" class="form-control" @bind="@_redirecturl" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="reviewclaims" HelpText="This option will record the full list of Claims returned by the Provider in the Event Log. It should only be used for testing purposes. External Login will be restricted when this option is enabled." ResourceKey="ReviewClaims">Review Claims?</Label>
|
||||
<div class="col-sm-9">
|
||||
@ -334,10 +383,10 @@ else
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="identifierclaimtype" HelpText="Specify the type name of the unique user identifier claim provided by the provider. The default value is 'sub'." ResourceKey="IdentifierClaimType">Identifier Claim:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="identifierclaimtype" class="form-control" @bind="@_identifierclaimtype" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-9">
|
||||
<input id="identifierclaimtype" class="form-control" @bind="@_identifierclaimtype" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="nameclaimtype" HelpText="Optionally specify the type name of the user's name claim provided by the provider. The typical value is 'name'." ResourceKey="NameClaimType">Name Claim:</Label>
|
||||
<div class="col-sm-9">
|
||||
@ -346,16 +395,16 @@ else
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="emailclaimtype" HelpText="Optionally specify the type name of the email address claim provided by the provider. The typical value is 'email'," ResourceKey="EmailClaimType">Email Claim:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="emailclaimtype" class="form-control" @bind="@_emailclaimtype" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="roleclaimtype" HelpText="The name of the roles claim provided by the provider" ResourceKey="RoleClaimType">Roles Claim:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="roleclaimtype" class="form-control" @bind="@_roleclaimtype" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-9">
|
||||
<input id="emailclaimtype" class="form-control" @bind="@_emailclaimtype" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="roleclaimtype" HelpText="The name of the roles claim provided by the provider" ResourceKey="RoleClaimType">Roles Claim:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="roleclaimtype" class="form-control" @bind="@_roleclaimtype" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="roleclaimmappings" HelpText="Optionally provide a comma delimited list of role names provided by the identity provider, as well as mappings to your site roles." ResourceKey="RoleClaimMappings">Role Claim Mappings:</Label>
|
||||
<div class="col-sm-9">
|
||||
@ -374,26 +423,35 @@ else
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="profileclaimtypes" HelpText="A comma delimited list of user profile claims provided by the provider, as well as mappings to your user profile definition. For example if the provider includes a 'given_name' claim and you have a 'FirstName' user profile definition you should specify 'given_name:FirstName'." ResourceKey="ProfileClaimTypes">User Profile Claims:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="profileclaimtypes" class="form-control" @bind="@_profileclaimtypes" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="domainfilter" HelpText="Provide any email domain filter criteria (separated by commas). Domains to exclude should be prefixed with an exclamation point (!). For example 'microsoft.com,!hotmail.com' would include microsoft.com email addresses but not hotmail.com email addresses." ResourceKey="DomainFilter">Domain Filter:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="domainfilter" class="form-control" @bind="@_domainfilter" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="createusers" HelpText="Do you want new users to be created automatically? If you disable this option, users must already be registered on the site in order to sign in with their external login." ResourceKey="CreateUsers">Create New Users?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="createusers" class="form-select" @bind="@_createusers">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<Label Class="col-sm-3" For="profileclaimtypes" HelpText="A comma delimited list of user profile claims provided by the provider, as well as mappings to your user profile definition. For example if the provider includes a 'given_name' claim and you have a 'FirstName' user profile definition you should specify 'given_name:FirstName'." ResourceKey="ProfileClaimTypes">User Profile Claims:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="profileclaimtypes" class="form-control" @bind="@_profileclaimtypes" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="savetokens" HelpText="Specify whether access and refresh tokens should be saved after a successful login. The default is false to reduce the size of the authentication cookie." ResourceKey="SaveTokens">Save Tokens?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="savetokens" class="form-select" @bind="@_savetokens" 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="domainfilter" HelpText="Provide any email domain filter criteria (separated by commas). Domains to exclude should be prefixed with an exclamation point (!). For example 'microsoft.com,!hotmail.com' would include microsoft.com email addresses but not hotmail.com email addresses." ResourceKey="DomainFilter">Domain Filter:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="domainfilter" class="form-control" @bind="@_domainfilter" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="createusers" HelpText="Do you want new users to be created automatically? If you disable this option, users must already be registered on the site in order to sign in with their external login." ResourceKey="CreateUsers">Create New Users?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="createusers" class="form-select" @bind="@_createusers">
|
||||
<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="verifyusers" HelpText="Do you want existing users to perform an additional email verification step to link their external login? If you disable this option, existing users will be linked automatically." ResourceKey="VerifyUsers">Verify Existing Users?</Label>
|
||||
<div class="col-sm-9">
|
||||
@ -403,63 +461,89 @@ else
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="allowhostrole" HelpText="Indicate if host roles are supported from the identity provider. Please use caution with this option as it allows the host user to administrate every site within your installation." ResourceKey="AllowHostRole">Allow Host Role?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="allowhostrole" class="form-select" @bind="@_allowhostrole" 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="allowsitelogin" HelpText="Do you want to allow users to sign in using a username and password that is managed locally on this site? Note that you should only disable this option if you have already sucessfully configured an external login provider, or else you may lock yourself out of the site." ResourceKey="AllowSiteLogin">Allow Local Login?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="allowsitelogin" class="form-select" @bind="@_allowsitelogin">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</Section>
|
||||
<Section Name="Token" Heading="Token Settings" ResourceKey="TokenSettings">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="jwtsecret" HelpText="If you want to want to provide API access, please specify a secret which will be used to encrypt your tokens. The secret should be 16 characters or more to ensure optimal security. Please note that if you change this secret, all existing tokens will become invalid and will need to be regenerated." ResourceKey="Secret">Secret:</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input type="@_secrettype" id="jwtsecret" class="form-control" @bind="@_secret" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@ToggleSecret">@_togglesecret</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="issuer" HelpText="Optionally provide the issuer of the token" ResourceKey="Issuer">Issuer:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="issuer" class="form-control" @bind="@_issuer" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="audience" HelpText="Optionally provide the audience for the token" ResourceKey="Audience">Audience:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="audience" class="form-control" @bind="@_audience" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="lifetime" HelpText="The number of minutes for which a token should be valid" ResourceKey="Lifetime">Lifetime:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="lifetime" class="form-control" @bind="@_lifetime" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="token" HelpText="Select the Create Token button to generate a long-lived access token (valid for 1 year). Be sure to store this token in a safe location as you will not be able to access it in the future." ResourceKey="Token">Access Token:</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="token" class="form-control" @bind="@_token" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@CreateToken">@Localizer["CreateToken"]</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
}
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||
</Section>
|
||||
<Section Name="Token" Heading="Token Settings" ResourceKey="TokenSettings">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="jwtsecret" HelpText="If you want to want to provide API access, please specify a secret which will be used to encrypt your tokens. The secret should be 16 characters or more to ensure optimal security. Please note that if you change this secret, all existing tokens will become invalid and will need to be regenerated." ResourceKey="Secret">Secret:</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input type="@_secrettype" id="jwtsecret" class="form-control" @bind="@_secret" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@ToggleSecret">@_togglesecret</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="issuer" HelpText="Optionally provide the issuer of the token" ResourceKey="Issuer">Issuer:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="issuer" class="form-control" @bind="@_issuer" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="audience" HelpText="Optionally provide the audience for the token" ResourceKey="Audience">Audience:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="audience" class="form-control" @bind="@_audience" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="lifetime" HelpText="The number of minutes for which a token should be valid" ResourceKey="Lifetime">Lifetime:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="lifetime" class="form-control" @bind="@_lifetime" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="token" HelpText="Select the Create Token button to generate a long-lived access token (valid for 1 year). Be sure to store this token in a safe location as you will not be able to access it in the future." ResourceKey="Token">Access Token:</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="token" class="form-control" @bind="@_token" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@CreateToken">@Localizer["CreateToken"]</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
}
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||
</TabPanel>
|
||||
</TabStrip>
|
||||
</TabStrip>
|
||||
}
|
||||
|
||||
@code {
|
||||
private List<UserRole> users;
|
||||
private string _deleted = "false";
|
||||
|
||||
private string _allowregistration;
|
||||
private string _allowsitelogin;
|
||||
private string _registerurl;
|
||||
private string _profileurl;
|
||||
private string _requireconfirmedemail;
|
||||
private string _twofactor;
|
||||
private string _cookiename;
|
||||
private string _cookiedomain;
|
||||
private string _cookieexpiration;
|
||||
private string _alwaysremember;
|
||||
private string _logouteverywhere;
|
||||
|
||||
private string _minimumlength;
|
||||
private string _uniquecharacters;
|
||||
@ -484,6 +568,8 @@ else
|
||||
private string _clientsecrettype = "password";
|
||||
private string _toggleclientsecret = string.Empty;
|
||||
private string _authresponsetype;
|
||||
private string _requirenonce;
|
||||
private string _singlelogout;
|
||||
private string _scopes;
|
||||
private string _parameters;
|
||||
private string _pkce;
|
||||
@ -497,9 +583,12 @@ else
|
||||
private string _roleclaimmappings;
|
||||
private string _synchronizeroles;
|
||||
private string _profileclaimtypes;
|
||||
private string _savetokens;
|
||||
private string _domainfilter;
|
||||
private string _createusers;
|
||||
private string _verifyusers;
|
||||
private string _allowhostrole;
|
||||
private string _allowsitelogin;
|
||||
|
||||
private string _secret;
|
||||
private string _secrettype = "password";
|
||||
@ -516,18 +605,22 @@ else
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadUsersAsync(true);
|
||||
await LoadUsersAsync();
|
||||
|
||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
_allowregistration = PageState.Site.AllowRegistration.ToString();
|
||||
_allowsitelogin = SettingService.GetSetting(settings, "LoginOptions:AllowSiteLogin", "true");
|
||||
_allowregistration = PageState.Site.AllowRegistration.ToString().ToLower();
|
||||
_registerurl = SettingService.GetSetting(settings, "LoginOptions:RegisterUrl", "");
|
||||
_profileurl = SettingService.GetSetting(settings, "LoginOptions:ProfileUrl", "");
|
||||
_requireconfirmedemail = SettingService.GetSetting(settings, "LoginOptions:RequireConfirmedEmail", "true");
|
||||
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
_twofactor = SettingService.GetSetting(settings, "LoginOptions:TwoFactor", "false");
|
||||
_cookiename = SettingService.GetSetting(settings, "LoginOptions:CookieName", ".AspNetCore.Identity.Application");
|
||||
_cookiedomain = SettingService.GetSetting(settings, "LoginOptions:CookieDomain", "");
|
||||
_cookieexpiration = SettingService.GetSetting(settings, "LoginOptions:CookieExpiration", "");
|
||||
_alwaysremember = SettingService.GetSetting(settings, "LoginOptions:AlwaysRemember", "false");
|
||||
_logouteverywhere = SettingService.GetSetting(settings, "LoginOptions:LogoutEverywhere", "false");
|
||||
|
||||
_minimumlength = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredLength", "6");
|
||||
_uniquecharacters = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", "1");
|
||||
@ -564,6 +657,8 @@ else
|
||||
_clientsecret = SettingService.GetSetting(settings, "ExternalLogin:ClientSecret", "");
|
||||
_toggleclientsecret = SharedLocalizer["ShowPassword"];
|
||||
_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", "");
|
||||
_parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", "");
|
||||
_pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false");
|
||||
@ -577,42 +672,69 @@ else
|
||||
_roleclaimmappings = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimMappings", "");
|
||||
_synchronizeroles = SettingService.GetSetting(settings, "ExternalLogin:SynchronizeRoles", "false");
|
||||
_profileclaimtypes = SettingService.GetSetting(settings, "ExternalLogin:ProfileClaimTypes", "");
|
||||
_savetokens = SettingService.GetSetting(settings, "ExternalLogin:SaveTokens", "false");
|
||||
_domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", "");
|
||||
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
|
||||
_verifyusers = SettingService.GetSetting(settings, "ExternalLogin:VerifyUsers", "true");
|
||||
_allowhostrole = SettingService.GetSetting(settings, "ExternalLogin:AllowHostRole", "false");
|
||||
_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);
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
var hosts = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Host);
|
||||
users.AddRange(hosts);
|
||||
users = users.OrderBy(u => u.User.DisplayName).ToList();
|
||||
}
|
||||
var hosts = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Host);
|
||||
users.AddRange(hosts);
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteUser(UserRole UserRole)
|
||||
{
|
||||
try
|
||||
{
|
||||
var user = await UserService.GetUserAsync(UserRole.UserId, PageState.Site.SiteId);
|
||||
if (user != null)
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId);
|
||||
await logger.LogInformation("User Deleted {User}", UserRole.User);
|
||||
await LoadUsersAsync(true);
|
||||
StateHasChanged();
|
||||
var user = await UserService.GetUserAsync(UserRole.UserId, PageState.Site.SiteId);
|
||||
if (user != null)
|
||||
{
|
||||
user.IsDeleted = true;
|
||||
await UserService.UpdateUserAsync(user);
|
||||
await logger.LogInformation("User Soft Deleted {User}", user);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var userrole = await UserRoleService.GetUserRoleAsync(UserRole.UserRoleId);
|
||||
userrole.ExpiryDate = DateTime.UtcNow;
|
||||
await UserRoleService.UpdateUserRoleAsync(userrole);
|
||||
await logger.LogInformation("User {Username} Expired From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
||||
}
|
||||
AddModuleMessage(Localizer["Success.DeleteUser"], MessageType.Success);
|
||||
await LoadUsersAsync();
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Deleting User {User} {Error}", UserRole.User, ex.Message);
|
||||
AddModuleMessage(ex.Message, MessageType.Error);
|
||||
AddModuleMessage(Localizer["Error.DeleteUser"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
@ -625,14 +747,18 @@ else
|
||||
await SiteService.UpdateSiteAsync(site);
|
||||
|
||||
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:AllowSiteLogin", _allowsitelogin, false);
|
||||
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:RegisterUrl", _registerurl, 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:CookieName", _cookiename, true);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:CookieDomain", _cookiedomain, true);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:AlwaysRemember", _alwaysremember, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:LogoutEverywhere", _logouteverywhere, false);
|
||||
|
||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredLength", _minimumlength, true);
|
||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", _uniquecharacters, true);
|
||||
@ -655,6 +781,8 @@ else
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientId", _clientid, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientSecret", _clientsecret, 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:Parameters", _parameters, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true);
|
||||
@ -666,9 +794,12 @@ else
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimMappings", _roleclaimmappings, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:SynchronizeRoles", _synchronizeroles, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ProfileClaimTypes", _profileclaimtypes, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:SaveTokens", _savetokens, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:VerifyUsers", _verifyusers, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:AllowHostRole", _allowhostrole, true);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:AllowSiteLogin", _allowsitelogin, false);
|
||||
|
||||
settings = SettingService.SetSetting(settings, "JwtOptions:Secret", _secret, true);
|
||||
settings = SettingService.SetSetting(settings, "JwtOptions:Issuer", _issuer, true);
|
||||
|
||||
@ -53,17 +53,17 @@ else
|
||||
<p align="center">
|
||||
<Pager Items="@userroles">
|
||||
<Header>
|
||||
<th>@Localizer["Roles"]</th>
|
||||
<th>@Localizer["Effective"]</th>
|
||||
<th>@Localizer["Expiry"]</th>
|
||||
<th> </th>
|
||||
<th>@Localizer["Roles"]</th>
|
||||
<th>@Localizer["Effective"]</th>
|
||||
<th>@Localizer["Expiry"]</th>
|
||||
<th> </th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td>@context.Role.Name</td>
|
||||
<td>@Utilities.UtcAsLocalDate(context.EffectiveDate)</td>
|
||||
<td>@Utilities.UtcAsLocalDate(context.ExpiryDate)</td>
|
||||
<td>
|
||||
<ActionDialog Header="Remove Role" Message="@string.Format(Localizer["Confirm.User.RemoveRole"], context.Role.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.IsAutoAssigned || (context.Role.Name == RoleNames.Host && userid == PageState.User.UserId))" ResourceKey="DeleteUserRole" />
|
||||
<ActionDialog Header="Remove Role" Message="@string.Format(Localizer["Confirm.User.RemoveRole"], context.Role.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.Name == RoleNames.Host && userid == PageState.User.UserId)" ResourceKey="DeleteUserRole" />
|
||||
</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
@ -171,8 +171,18 @@ else
|
||||
{
|
||||
try
|
||||
{
|
||||
await UserRoleService.DeleteUserRoleAsync(UserRoleId);
|
||||
await logger.LogInformation("User Removed From Role {UserRoleId}", UserRoleId);
|
||||
var userrole = await UserRoleService.GetUserRoleAsync(UserRoleId);
|
||||
if (userrole.Role.Name == RoleNames.Registered)
|
||||
{
|
||||
userrole.ExpiryDate = DateTime.UtcNow;
|
||||
await UserRoleService.UpdateUserRoleAsync(userrole);
|
||||
await logger.LogInformation("User {Username} Expired From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
await UserRoleService.DeleteUserRoleAsync(UserRoleId);
|
||||
await logger.LogInformation("User {Username} Removed From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
||||
}
|
||||
AddModuleMessage(Localizer["Success.User.Remove"], MessageType.Success);
|
||||
await GetUserRoles();
|
||||
StateHasChanged();
|
||||
|
||||
@ -101,8 +101,8 @@
|
||||
_url = visitor.Url;
|
||||
_referrer = visitor.Referrer;
|
||||
_visits = visitor.Visits.ToString();
|
||||
_visited = visitor.VisitedOn.ToString(CultureInfo.CurrentCulture);
|
||||
_created = visitor.CreatedOn.ToString(CultureInfo.CurrentCulture);
|
||||
_visited = UtcToLocal(visitor.VisitedOn).Value.ToString(CultureInfo.CurrentCulture);
|
||||
_created = UtcToLocal(visitor.CreatedOn).Value.ToString(CultureInfo.CurrentCulture);
|
||||
|
||||
if (visitor.UserId != null)
|
||||
{
|
||||
|
||||
@ -53,8 +53,8 @@ else
|
||||
</td>
|
||||
<td>@context.Language</td>
|
||||
<td>@context.Visits</td>
|
||||
<td>@context.VisitedOn</td>
|
||||
<td>@context.CreatedOn</td>
|
||||
<td>@UtcToLocal(context.VisitedOn)</td>
|
||||
<td>@UtcToLocal(context.CreatedOn)</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
</TabPanel>
|
||||
@ -100,6 +100,18 @@ else
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||
</TabPanel>
|
||||
<TabPanel Name="Robots" Heading="Robots.txt" ResourceKey="Robots">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="robots" HelpText="Specify your robots.txt instructions to provide bots with guidance on which parts of your site should be indexed" ResourceKey="Robots">Instructions: </Label>
|
||||
<div class="col-sm-9">
|
||||
<textarea id="robots" class="form-control" @bind="@_robots" rows="3"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||
</TabPanel>
|
||||
</TabStrip>
|
||||
}
|
||||
|
||||
@ -113,6 +125,7 @@ else
|
||||
private string _filter = "";
|
||||
private int _retention = 30;
|
||||
private string _correlation = "true";
|
||||
private string _robots = "";
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||
|
||||
@ -139,7 +152,8 @@ else
|
||||
_filter = SettingService.GetSetting(settings, "VisitorFilter", Constants.DefaultVisitorFilter);
|
||||
_retention = int.Parse(SettingService.GetSetting(settings, "VisitorRetention", "30"));
|
||||
_correlation = SettingService.GetSetting(settings, "VisitorCorrelation", "true");
|
||||
}
|
||||
_robots = SettingService.GetSetting(settings, "Robots", "");
|
||||
}
|
||||
|
||||
private async void TypeChanged(ChangeEventArgs e)
|
||||
{
|
||||
@ -191,6 +205,7 @@ else
|
||||
settings = SettingService.SetSetting(settings, "VisitorFilter", _filter, true);
|
||||
settings = SettingService.SetSetting(settings, "VisitorRetention", _retention.ToString(), true);
|
||||
settings = SettingService.SetSetting(settings, "VisitorCorrelation", _correlation, true);
|
||||
settings = SettingService.SetSetting(settings, "Robots", _robots, true);
|
||||
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||
|
||||
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
||||
|
||||
@ -22,9 +22,9 @@
|
||||
<div class="modal-footer">
|
||||
@if (!string.IsNullOrEmpty(Action))
|
||||
{
|
||||
<button type="button" class="@Class" @onclick="Confirm">@((MarkupString)_iconSpan) @Text</button>
|
||||
<button type="button" class="@ConfirmClass" @onclick="Confirm">@((MarkupString)_iconSpan) @Text</button>
|
||||
}
|
||||
<button type="button" class="btn btn-secondary" @onclick="DisplayModal">@SharedLocalizer["Cancel"]</button>
|
||||
<button type="button" class="@CancelClass" @onclick="DisplayModal">@SharedLocalizer["Cancel"]</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -66,12 +66,12 @@ else
|
||||
{
|
||||
<form method="post" @formname="@($"ActionDialogConfirmForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="Confirm" data-enhance>
|
||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<button type="submit" class="@Class">@((MarkupString)_iconSpan) @Text</button>
|
||||
<button type="submit" class="@ConfirmClass">@((MarkupString)_iconSpan) @Text</button>
|
||||
</form>
|
||||
}
|
||||
<form method="post" @formname="@($"ActionDialogCancelForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="DisplayModal" data-enhance>
|
||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<button type="submit" class="btn btn-secondary">@SharedLocalizer["Cancel"]</button>
|
||||
<button type="submit" class="@CancelClass">@SharedLocalizer["Cancel"]</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@ -128,6 +128,12 @@ else
|
||||
[Parameter]
|
||||
public string Class { get; set; } // optional
|
||||
|
||||
[Parameter]
|
||||
public string ConfirmClass { get; set; } // optional - for Confirm modal button
|
||||
|
||||
[Parameter]
|
||||
public string CancelClass { get; set; } // optional - for Cancel modal button
|
||||
|
||||
[Parameter]
|
||||
public bool Disabled { get; set; } // optional
|
||||
|
||||
@ -168,6 +174,16 @@ else
|
||||
Class = "btn btn-success";
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(ConfirmClass))
|
||||
{
|
||||
ConfirmClass = Class;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(CancelClass))
|
||||
{
|
||||
CancelClass = "btn btn-secondary";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(EditMode))
|
||||
{
|
||||
_editmode = bool.Parse(EditMode);
|
||||
@ -196,7 +212,7 @@ else
|
||||
_openIconSpan = $"<span class=\"{IconName}\"></span>{(IconOnly ? "" : " ")}";
|
||||
_iconSpan = $"<span class=\"{IconName}\"></span> ";
|
||||
}
|
||||
|
||||
|
||||
_permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList;
|
||||
_authorized = IsAuthorized();
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user