Compare commits
1079 Commits
moduledbg
...
9833d37ebe
| Author | SHA1 | Date | |
|---|---|---|---|
| 9833d37ebe | |||
|
|
6299412fa5 | ||
|
|
7c4c6f9c02 | ||
|
|
2dfa7b6b2f | ||
|
|
0ea27e7147 | ||
|
|
6852b28b71 | ||
|
|
1741029057 | ||
|
|
a47b8942eb | ||
|
|
a110497967 | ||
|
|
59b2a34444 | ||
|
|
6814a7bcdb | ||
|
|
4f7f24a725 | ||
|
|
99949bdeb9 | ||
|
|
26eefa336f | ||
|
|
4c39aadff4 | ||
|
|
3364f18341 | ||
|
|
7fd4c617db | ||
|
|
a801048ad5 | ||
|
|
d114ae488c | ||
|
|
2d6b05650b | ||
|
|
8ed48c6702 | ||
|
|
6f7789ab3b | ||
|
|
72a684a35a | ||
|
|
f8ca688b2d | ||
|
|
edb3f3750c | ||
|
|
96291c4a0e | ||
|
|
e9b756092e | ||
|
|
8349f73e1e | ||
|
|
7d810ead6c | ||
|
|
d01622409a | ||
|
|
c01fa810f2 | ||
|
|
64e51038a6 | ||
|
|
4e07b41772 | ||
|
|
50b2b80778 | ||
|
|
f6d9300cd0 | ||
|
|
ebcc3866ab | ||
|
|
36ab6f40c7 | ||
|
|
12809df732 | ||
|
|
82761530ac | ||
|
|
3eb1542b96 | ||
|
|
b27b38eccf | ||
|
|
e3b0bdd500 | ||
|
|
957309b5f0 | ||
|
|
c7d32dc5f0 | ||
|
|
aaffb7b84d | ||
|
|
0648b23c75 | ||
|
|
f09b21295a | ||
|
|
f57d296fcb | ||
|
|
d2e5ab61da | ||
|
|
d7d3273018 | ||
|
|
c2bb6be2da | ||
|
|
976d8a3369 | ||
|
|
ae9beedca2 | ||
|
|
0d668f1469 | ||
|
|
8de67fdaec | ||
|
|
a4c94c0dda | ||
|
|
91b15b5e2a | ||
|
|
489629e642 | ||
|
|
ef92a88908 | ||
|
|
f249801541 | ||
|
|
147ee8b1e7 | ||
|
|
263091c27e | ||
|
|
aac1bb582b | ||
|
|
b097871d97 | ||
|
|
72542f0146 | ||
|
|
6a4f3bdb8e | ||
|
|
e65f61bd65 | ||
|
|
2c4dfe0ee0 | ||
|
|
6160941aee | ||
|
|
3db3d9561b | ||
|
|
cf61a58b4c | ||
|
|
e988a9f9c9 | ||
|
|
d1b40f0603 | ||
|
|
772f6ad490 | ||
|
|
f1934028a1 | ||
|
|
5cb0598025 | ||
|
|
18730c5e53 | ||
|
|
f79c3ae994 | ||
|
|
779a3dd379 | ||
|
|
c3a0a96623 | ||
|
|
f9d6ad2c0f | ||
|
|
1cb0a45715 | ||
|
|
6464604f5c | ||
|
|
573a914699 | ||
|
|
ebbe618e98 | ||
|
|
0cc1b5a3e9 | ||
|
|
50ccd29872 | ||
|
|
00d14552b1 | ||
|
|
cad5694145 | ||
|
|
06e555ddd1 | ||
|
|
46aa5225c6 | ||
|
|
09f6a1d531 | ||
|
|
f8633fd390 | ||
|
|
9aad400038 | ||
|
|
91ce840592 | ||
|
|
458c8534c7 | ||
|
|
f7df3a4720 | ||
|
|
36789495df | ||
|
|
ed0b341f76 | ||
|
|
0d4d51448e | ||
|
|
3df2c04795 | ||
|
|
12f06a7662 | ||
|
|
f0cab1ca79 | ||
|
|
ae0c4c1099 | ||
|
|
dc4ded82b1 | ||
|
|
8752b24723 | ||
|
|
289252d39c | ||
|
|
2736fa451c | ||
|
|
31954a971c | ||
|
|
a6006ce1fe | ||
|
|
3db09a2fa6 | ||
|
|
5c2bd8093a | ||
|
|
8b8048724a | ||
|
|
060eaa7aff | ||
|
|
67dbc4b7ca | ||
|
|
0fd97d34d9 | ||
|
|
ba51342fd6 | ||
|
|
13a58ed099 | ||
|
|
48a70c8be3 | ||
|
|
4db58c2866 | ||
|
|
539bad1463 | ||
|
|
df7f3f7bba | ||
|
|
064448deaf | ||
|
|
b25279cdcf | ||
|
|
26c8d00cca | ||
|
|
912ed66547 | ||
|
|
6f6870b16d | ||
|
|
1ddf6fe74e | ||
|
|
3e0b5bfa09 | ||
|
|
f60df5c8dc | ||
|
|
3af03d308e | ||
|
|
7605fd7ec7 | ||
|
|
e85b1001c6 | ||
|
|
87620f2251 | ||
|
|
772da734dc | ||
|
|
646b6fea84 | ||
|
|
dd816f7c44 | ||
|
|
c601c0cdc4 | ||
|
|
a4f7d1f745 | ||
|
|
a4980eac33 | ||
|
|
d3c4e78baa | ||
|
|
5bf5d2d515 | ||
|
|
657c6620d5 | ||
|
|
de5725b92c | ||
|
|
d9db41ac99 | ||
|
|
dcd862900f | ||
|
|
0f8d22e6f0 | ||
|
|
0d75e0f555 | ||
|
|
84e8edb159 | ||
|
|
edb6109db9 | ||
|
|
4cc9348769 | ||
|
|
1a6420ee45 | ||
|
|
486132f918 | ||
|
|
a18f317e85 | ||
|
|
171735fac4 | ||
|
|
72d4361fb2 | ||
|
|
69fc1c9895 | ||
|
|
831bc31e06 | ||
|
|
68445c40f7 | ||
|
|
2b2d960c3e | ||
|
|
d687d81d0c | ||
|
|
2d19f21b2e | ||
|
|
e85a2a6cf5 | ||
|
|
6d35d40829 | ||
|
|
4b3377cc78 | ||
|
|
971ba796ef | ||
|
|
3195e4b46f | ||
|
|
10c1779f84 | ||
|
|
b5c9717f71 | ||
|
|
d13e6fcdad | ||
|
|
357337572e | ||
|
|
e95a6b774e | ||
|
|
59bdc4c04e | ||
|
|
ad300a58c1 | ||
|
|
8f25d9f06a | ||
|
|
3dcb391a14 | ||
|
|
9073b8a9ff | ||
|
|
6f2e676c00 | ||
|
|
dbd9388d4c | ||
|
|
ddd6dfc475 | ||
|
|
16e2efdd66 | ||
|
|
c0e191537f | ||
|
|
1652afc10f | ||
|
|
aab0dd96dd | ||
|
|
783d01bf9f | ||
|
|
60b2e50511 | ||
|
|
6520f8ade4 | ||
|
|
57deeb6acf | ||
|
|
49102491a6 | ||
|
|
dff2261994 | ||
|
|
daca055f90 | ||
|
|
b2ef3cc574 | ||
|
|
8bd035b8f4 | ||
|
|
8817af42bd | ||
|
|
7b1a12f1e7 | ||
|
|
d313c2f3a5 | ||
|
|
831f7c5738 | ||
|
|
7bb84fbe33 | ||
|
|
0dc9382215 | ||
|
|
563696c7a5 | ||
|
|
66963a6508 | ||
|
|
8ec4922cd3 | ||
|
|
ed7d1eef14 | ||
|
|
7ab1184a04 | ||
|
|
d638f9492d | ||
|
|
6d4870f41a | ||
|
|
81f4f87493 | ||
|
|
4d1ba921dc | ||
|
|
d6458eeaf6 | ||
|
|
f4f935ab43 | ||
|
|
738ad9bbfa | ||
|
|
a470db594e | ||
|
|
ce905499b0 | ||
|
|
7e6b60405b | ||
|
|
8a4275c240 | ||
|
|
5735f65628 | ||
|
|
8749ab7588 | ||
|
|
912c01cdf8 | ||
|
|
d6bbc6b82f | ||
|
|
1588d4626b | ||
|
|
c95725d444 | ||
|
|
d3e4c57ede | ||
|
|
e34013ace5 | ||
|
|
3be2b9c720 | ||
|
|
256af74e0c | ||
|
|
0295b66c22 | ||
|
|
3393ece21d | ||
|
|
1ac722bd06 | ||
|
|
d0fb8dc5fc | ||
|
|
6006e6f63c | ||
|
|
0a3f60c007 | ||
|
|
472e8eadec | ||
|
|
d2c515d837 | ||
|
|
0446bdb970 | ||
|
|
adf2468373 | ||
|
|
54d3f1c659 | ||
|
|
e3b45a2329 | ||
|
|
1a777b29e0 | ||
|
|
0c3754ca86 | ||
|
|
b0211b2e6e | ||
|
|
20548710ce | ||
|
|
92f4a8b683 | ||
|
|
c1709db676 | ||
|
|
7d7ecf4757 | ||
|
|
f1c85d23d6 | ||
|
|
780e2a8484 | ||
|
|
0624d539bf | ||
|
|
e98d84784a | ||
|
|
1678817bf0 | ||
|
|
bf0fb0cbae | ||
|
|
9dc91aec58 | ||
|
|
8ba480f168 | ||
|
|
9d42dac7d9 | ||
|
|
236aa84f87 | ||
|
|
a6f7ec9bd5 | ||
|
|
2bf8c947b6 | ||
|
|
c8872a5e99 | ||
|
|
36f04708ba | ||
|
|
3b990aa633 | ||
|
|
3192ab8452 | ||
|
|
51638fdcb0 | ||
|
|
95f98b89fe | ||
|
|
6c484ea9cd | ||
|
|
ea0271ba79 | ||
|
|
526b797d03 | ||
|
|
0d0efaf4ca | ||
| f3175b6b06 | |||
| 90e254fae6 | |||
| 4a7d088612 | |||
|
|
5c70f4f6a7 | ||
|
|
b0d624034a | ||
|
|
43ee682d1f | ||
|
|
d4399955ac | ||
|
|
86f88c4f7c | ||
|
|
d6ea610764 | ||
|
|
ff8417ed31 | ||
|
|
bf49e52cbb | ||
|
|
2844b793fa | ||
|
|
9218863dae | ||
|
|
ef20d870ee | ||
|
|
76c79206b6 | ||
|
|
b4c6b6b794 | ||
|
|
6db5c924c7 | ||
|
|
51aecacee6 | ||
|
|
b3b8febd12 | ||
|
|
7c770d9a9d | ||
|
|
30b89fe56f | ||
|
|
06870f2577 | ||
|
|
e84170b8ea | ||
|
|
e8d26b2cb2 | ||
|
|
a8f87ea572 | ||
|
|
1fb78a457f | ||
|
|
82cb7a8c02 | ||
|
|
cae61ab701 | ||
|
|
975c1955f2 | ||
|
|
fc2a8cb9dd | ||
|
|
e2d9aaaa0f | ||
|
|
aca70dd6c7 | ||
|
|
5a1b3a7017 | ||
|
|
6733299290 | ||
|
|
e11e021750 | ||
|
|
0ad5bd2335 | ||
|
|
255764a4ac | ||
|
|
7330d5b2a7 | ||
|
|
5d3e507672 | ||
|
|
15ee2c9bcb | ||
|
|
e1163b7ab1 | ||
|
|
96bea78424 | ||
|
|
8ba67a63ba | ||
|
|
8120db84f4 | ||
|
|
4e6a6afaab | ||
|
|
417a6bf226 | ||
|
|
5e5d91bd93 | ||
|
|
5c536aafc2 | ||
|
|
15efe75aa8 | ||
|
|
a10575bfc3 | ||
|
|
57e26d6156 | ||
|
|
1682a123b4 | ||
|
|
13064cdb26 | ||
|
|
f74eda274a | ||
|
|
7dee55ce30 | ||
|
|
8113c82da8 | ||
|
|
2d684d23d7 | ||
|
|
33da5809e3 | ||
|
|
68a6e6862e | ||
|
|
07fb15d5be | ||
|
|
0f791253ba | ||
|
|
828d070194 | ||
|
|
0d5bb3f3b3 | ||
|
|
32de1ca511 | ||
|
|
f5f00c51c1 | ||
|
|
1305125390 | ||
|
|
c539f41ebf | ||
|
|
075e754830 | ||
|
|
87fd9dd000 | ||
|
|
e34321f727 | ||
|
|
a48dff4a85 | ||
|
|
576d3d0b56 | ||
|
|
640c2cee00 | ||
|
|
1958787185 | ||
|
|
073e1ac13a | ||
|
|
c0eacd0d6b | ||
|
|
7938eaf123 | ||
|
|
b4f8896713 | ||
|
|
c418ddf240 | ||
|
|
6c6b36f3da | ||
|
|
c0c71251ab | ||
|
|
2685c18798 | ||
|
|
7f914271ed | ||
|
|
7b37cc3c82 | ||
|
|
ec2afd5f03 | ||
|
|
e62268af2e | ||
|
|
01ad99b925 | ||
|
|
a33e9d25cc | ||
|
|
a0e45cbea0 | ||
|
|
171314947c | ||
|
|
6b883b3f94 | ||
|
|
c99348650f | ||
|
|
011375a081 | ||
|
|
38f43c9988 | ||
|
|
f459d0503a | ||
|
|
c2912a291e | ||
|
|
53a88e0c9f | ||
|
|
9cf670bcad | ||
|
|
156e7bd3d4 | ||
|
|
009829c8f9 | ||
|
|
d7c0b0aaaf | ||
|
|
06071fb7f9 | ||
|
|
a51f87d743 | ||
|
|
12fa6ff4f0 | ||
|
|
23d14c62a5 | ||
|
|
ad993c6180 | ||
|
|
e99ce3ac7b | ||
|
|
270b447fbd | ||
|
|
1c55a74ff1 | ||
|
|
47f42747cb | ||
|
|
86a3f67871 | ||
|
|
29b87f809f | ||
|
|
8bd63fdc61 | ||
|
|
cf88347c3d | ||
|
|
171f9c84a0 | ||
|
|
a6069e572d | ||
|
|
16a13a6c01 | ||
|
|
ac31cd3f41 | ||
|
|
321fe2954e | ||
|
|
e2f174e0b5 | ||
|
|
50c085fe65 | ||
|
|
4e63c9ce9d | ||
|
|
fb6e8bb233 | ||
|
|
44103c1311 | ||
|
|
6ef6e6aac8 | ||
|
|
9499012825 | ||
|
|
6af73873d7 | ||
|
|
0a04035b2f | ||
|
|
1e3c176ddf | ||
|
|
f5bb9a934c | ||
|
|
476cf7c080 | ||
|
|
1279c30fbb | ||
|
|
012b7ba6ed | ||
|
|
e08c033e76 | ||
|
|
dafbae7237 | ||
|
|
31b8080a3d | ||
|
|
28c7617227 | ||
|
|
7f7c53dabe | ||
|
|
950a9bf2fa | ||
|
|
f0d4a416be | ||
|
|
708d79ffaf | ||
|
|
583bd3b511 | ||
|
|
5f8798c224 | ||
|
|
74c259cd79 | ||
|
|
c96787f1b9 | ||
|
|
aaac471956 | ||
|
|
db41bfe638 | ||
|
|
bfc9acb170 | ||
|
|
50540b50aa | ||
|
|
9f9d3460b1 | ||
|
|
e2f02bcd4b | ||
|
|
538bc093e0 | ||
|
|
3d34ab83c6 | ||
|
|
5fb413505c | ||
|
|
efcbdee869 | ||
|
|
2ea6f9e447 | ||
|
|
2acd5799d9 | ||
|
|
78bfc91469 | ||
|
|
c9590247eb | ||
|
|
df4209ed77 | ||
|
|
4daaaa8dbf | ||
|
|
eb10f2a6a0 | ||
|
|
05993ab462 | ||
|
|
f276a892fd | ||
|
|
7a7508c4ca | ||
|
|
d2cf817aac | ||
|
|
b9497cbb56 | ||
|
|
b2aa17410e | ||
|
|
545096a753 | ||
|
|
d652757614 | ||
|
|
bff00832fc | ||
|
|
7a09a48b64 | ||
|
|
c5fbb5b61b | ||
|
|
f6be499e47 | ||
|
|
49ef0d7464 | ||
|
|
528cbde7e5 | ||
|
|
cf7e082dbc | ||
|
|
b786faa6a1 | ||
|
|
ebbf39c360 | ||
|
|
92dc46a81e | ||
|
|
63494bc7ee | ||
|
|
e6ee13784f | ||
|
|
11284f0285 | ||
|
|
0b2ade4b01 | ||
|
|
f687c09adc | ||
|
|
20fdd211be | ||
|
|
21fcd653b8 | ||
|
|
62db107d90 | ||
|
|
f6c1d65c89 | ||
|
|
8f3c5f5768 | ||
|
|
eb8cfa28ed | ||
|
|
b4789dff3e | ||
|
|
ee25c46ee1 | ||
|
|
dc9d4a1938 | ||
|
|
e58ee4e5b1 | ||
|
|
3c5d839e9d | ||
|
|
590901bf6e | ||
|
|
76938503b6 | ||
|
|
9ffd4f39e9 | ||
|
|
a707ae7da9 | ||
|
|
0e717c8f57 | ||
|
|
78853173db | ||
|
|
20005797e5 | ||
|
|
206806d01a | ||
|
|
2def6ad854 | ||
|
|
4339833aa3 | ||
|
|
afbe6c7054 | ||
|
|
2914749253 | ||
|
|
54902051ce | ||
|
|
01ee9650ff | ||
|
|
2f6b9a2fc7 | ||
|
|
20e270c040 | ||
|
|
afb2613f67 | ||
|
|
b917a7bbf6 | ||
|
|
7d01aa449e | ||
|
|
fe16594885 | ||
|
|
29f74131d1 | ||
|
|
b0e861e985 | ||
|
|
ed8df61143 | ||
|
|
6374314d3c | ||
|
|
aedfa91ce6 | ||
|
|
087d5ef394 | ||
|
|
637e285441 | ||
|
|
ab4bc7e678 | ||
|
|
e68fe3a9c3 | ||
|
|
adfd870319 | ||
|
|
edd89bf133 | ||
|
|
d774557522 | ||
|
|
f9c1906fe2 | ||
|
|
d5ad29be34 | ||
|
|
d62f8e966c | ||
|
|
670f3854fa | ||
|
|
ed4ab703c0 | ||
|
|
1d3c1c158f | ||
|
|
431fa05763 | ||
|
|
cd24573599 | ||
|
|
c43af46d38 | ||
|
|
7e69b5193f | ||
|
|
a06b1becc5 | ||
|
|
b2dfde58d5 | ||
|
|
e548c21c94 | ||
|
|
f4a1bf659f | ||
|
|
cf3a86dc4a | ||
|
|
bbd441b0b7 | ||
|
|
ac9b7a60fd | ||
|
|
9abe4a1c42 | ||
|
|
273097d96d | ||
|
|
57aeac2277 | ||
|
|
39ad5a0638 | ||
|
|
b4ef5faa28 | ||
|
|
a4c2989062 | ||
|
|
2ef9963587 | ||
|
|
19b003ae49 | ||
|
|
5d66c904cd | ||
|
|
ca521f3a5e | ||
|
|
9a622dd88f | ||
|
|
ff6c71e587 | ||
|
|
7c3d59915e | ||
|
|
970f6b400f | ||
|
|
abc4905a14 | ||
|
|
be3ecdf7f6 | ||
|
|
5875c1caa7 | ||
|
|
ebfcd59c44 | ||
|
|
73dc4c6e5f | ||
|
|
0c514743b7 | ||
|
|
1e11b0f3a3 | ||
|
|
e7ae1b26d1 | ||
|
|
46fa9abf43 | ||
|
|
d837cd8af5 | ||
|
|
cca6aff735 | ||
|
|
2be11b52c3 | ||
|
|
7b026c5b14 | ||
|
|
9135894053 | ||
|
|
8ebe34b038 | ||
|
|
7ef4376363 | ||
|
|
9a4bfd7009 | ||
|
|
356e350588 | ||
|
|
b976983a36 | ||
|
|
7ce7020b51 | ||
|
|
d22949522f | ||
|
|
8bac8677c5 | ||
|
|
4a4edfa857 | ||
|
|
2648887f7d | ||
|
|
eac7ad90e3 | ||
|
|
75c7b55b20 | ||
|
|
23ae819b70 | ||
|
|
f908637313 | ||
|
|
68aeee7c45 | ||
|
|
e4248ed569 | ||
|
|
7cf325f4f6 | ||
|
|
a80e449e9c | ||
|
|
83b56966f4 | ||
| 29ac9334ba | |||
|
|
cc2e34fc0b | ||
|
|
bc617db649 | ||
|
|
4a57abc99d | ||
|
|
db85d1fbc3 | ||
|
|
20f2eeefbf | ||
|
|
02c4da5539 | ||
|
|
8e10a8e042 | ||
|
|
0c3aed5fa9 | ||
|
|
95ec163f2c | ||
|
|
bf2d0e35d3 | ||
|
|
002cf28e12 | ||
|
|
c0a75ba665 | ||
|
|
4b05406d40 | ||
|
|
832f94070f | ||
|
|
cf761f56d4 | ||
|
|
27ba8dfeab | ||
|
|
044bd54201 | ||
|
|
e48ba633be | ||
|
|
08757b42df | ||
|
|
a59f2e7ca6 | ||
|
|
e9a672ebd0 | ||
|
|
72e475bca5 | ||
|
|
c49637444a | ||
|
|
b1cc0ffc13 | ||
|
|
4e49092434 | ||
|
|
7713f18ddf | ||
|
|
c1880c54ce | ||
|
|
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 | ||
|
|
026d716ece | ||
| 3f90653894 | |||
|
|
92496f4369 | ||
|
|
ec00b1162f | ||
|
|
e638aee1ac | ||
|
|
5420f625b4 | ||
|
|
77fa7f4a79 | ||
|
|
2db1fe0890 | ||
|
|
63bb70785a | ||
|
|
8d23d9aba3 | ||
| ea056165ca | |||
|
|
bebe70f46b | ||
|
|
34f2db5985 | ||
|
|
8e75c09e3f | ||
|
|
b5d4eaa36e | ||
|
|
116d163b9d | ||
|
|
2cb568773c | ||
|
|
916019f015 | ||
|
|
e83d7e9d57 | ||
|
|
151af30259 | ||
|
|
7fed6bb93a | ||
|
|
382a8eb8f3 | ||
|
|
9508ff68db | ||
|
|
9684e6e1a8 | ||
|
|
52745b1946 | ||
|
|
3db2d03a37 | ||
|
|
a85ae69ed1 | ||
|
|
9052d6abb6 | ||
|
|
3c528f0b93 | ||
|
|
3322297eaa | ||
|
|
3c1167d359 | ||
|
|
9e35a520cc | ||
|
|
beb4919d97 | ||
|
|
6895d16a20 | ||
|
|
05b37080c1 | ||
|
|
51894de708 | ||
|
|
442ec291a1 | ||
|
|
6ef106be31 | ||
|
|
70551f9d27 | ||
|
|
fe422ed5aa | ||
|
|
1995a96a98 | ||
|
|
4abcc6e58f | ||
|
|
a6f4921055 | ||
|
|
33f525dbda | ||
|
|
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 | ||
|
|
4ba7e034b7 | ||
|
|
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 | ||
|
|
74a5fb656e | ||
|
|
2bb6226e78 | ||
|
|
6a0c47f7b1 | ||
|
|
31b688cbf6 | ||
|
|
7f1fed2fb1 | ||
|
|
aa6c876b12 | ||
|
|
4e33aeef89 | ||
|
|
e2601dcf05 | ||
|
|
247baa375d | ||
|
|
a4adba846e | ||
|
|
52799c7cb0 | ||
|
|
a8635dc555 | ||
|
|
cca0f2219e | ||
|
|
d2f8c3c2bb | ||
|
|
0f38df053f | ||
|
|
5c926a10a7 | ||
|
|
036bbb418e | ||
|
|
bc5ce74925 | ||
|
|
93d224fa37 | ||
|
|
5b45e3e417 | ||
|
|
338c652635 | ||
|
|
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 | ||
|
|
4834761f64 | ||
|
|
bbbd6e9e3e | ||
|
|
06712faee9 | ||
|
|
48a90072ee | ||
|
|
0344f4d60b | ||
|
|
6a4affd5a6 | ||
|
|
d73e2288bb | ||
|
|
7d7500ba05 | ||
|
|
247fc5248b | ||
|
|
85fcd1ed33 | ||
|
|
4ab8f8cc25 | ||
|
|
ccdfe9bc26 | ||
|
|
dc47961cc2 | ||
|
|
87394cd330 | ||
|
|
b5a9c32c3e | ||
|
|
d16521f037 | ||
|
|
b553b16049 | ||
|
|
784548be57 | ||
|
|
cf96a80ead | ||
|
|
ede6babeaf | ||
|
|
9a57cae4bd | ||
|
|
1a296bf58c | ||
|
|
e900d2f35a | ||
|
|
69d2d3d942 | ||
|
|
b7ff49bdb2 | ||
|
|
d0ef5d0fe3 | ||
|
|
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 | ||
|
|
7d9b102ec4 | ||
|
|
31684bf7ca | ||
|
|
7b36f8d122 | ||
|
|
f2a0be4f57 | ||
|
|
2cefab1c64 | ||
|
|
5b4b96f065 | ||
|
|
77949331e2 | ||
|
|
4f8c4f47e2 | ||
|
|
334137454e | ||
|
|
af7ea3efa8 | ||
|
|
6119417331 | ||
|
|
580397a82d | ||
|
|
23c3c47db4 | ||
|
|
df3073fb12 | ||
|
|
aa9664e187 | ||
|
|
44f4aee55d | ||
|
|
02861b8e01 | ||
|
|
9607110381 | ||
|
|
9ae12ff678 | ||
|
|
2bcb8636ca | ||
|
|
4c2960eeae | ||
|
|
7e2c76e872 | ||
|
|
30fcde7157 | ||
|
|
4971d3317d | ||
|
|
85ae7b01b8 | ||
|
|
5b2dff254f | ||
|
|
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 | ||
|
|
986c9d9f72 | ||
|
|
1b78c9ad81 | ||
|
|
7a4b98aec9 | ||
|
|
9ef6c15014 | ||
|
|
f4cea3fe03 | ||
|
|
5dd9b1ec91 | ||
|
|
658059806b | ||
|
|
4f8a18451c | ||
|
|
b1770ebb76 | ||
|
|
6923065d86 | ||
|
|
9f097521f6 | ||
|
|
235e5c1d3a | ||
|
|
e179976fe8 | ||
|
|
082726b405 | ||
|
|
91c5309855 | ||
|
|
92be1e7a5c | ||
|
|
cceda1db1e | ||
|
|
a59191cea7 | ||
|
|
b0dee4a60c | ||
|
|
3f33f2b9df | ||
|
|
97116b4e0c | ||
|
|
a5f51ff9a1 | ||
|
|
962488fd34 | ||
|
|
4db37059cd | ||
|
|
190d973b77 | ||
|
|
397e0b3f71 | ||
|
|
83ba9ca73e | ||
|
|
3d08138686 | ||
|
|
262fa6b99b | ||
|
|
372db9dcfa | ||
|
|
378c68be4b | ||
|
|
e9dc52919c | ||
|
|
a981dd0e97 | ||
|
|
7c2775119b | ||
|
|
0be7f1bdb5 | ||
|
|
8446b9e8d5 | ||
|
|
ce404668d3 | ||
|
|
948fab50ee | ||
|
|
9690f1df48 | ||
|
|
5a24f87293 | ||
|
|
d2ff49fe73 | ||
|
|
e9035df9d2 | ||
|
|
1ddf21f4fc | ||
|
|
63d2ded038 | ||
|
|
7b8e0e48c0 | ||
|
|
bb52402a17 | ||
|
|
13d9cb461b | ||
|
|
0a994afd67 | ||
|
|
ebca580a0b | ||
|
|
57a1257750 | ||
|
|
b0c1d36bab | ||
|
|
0621751968 | ||
|
|
461330773a | ||
|
|
818a97cc2c | ||
|
|
17045073c8 | ||
|
|
7a818ee698 | ||
|
|
668e0cb4eb | ||
|
|
741b16ca4e | ||
|
|
85a376b17d | ||
|
|
df86cd909c | ||
|
|
ac236607f5 | ||
|
|
19813b7eb6 | ||
|
|
cb5e4e076f | ||
|
|
48fca77f59 | ||
|
|
76372451aa | ||
|
|
34cd197122 | ||
|
|
6b567364f9 | ||
|
|
711de49571 | ||
|
|
9a0f7ad83f | ||
|
|
0d3d693799 | ||
|
|
b63590d6c7 | ||
|
|
5f3a3d4d54 | ||
|
|
413df647d3 | ||
|
|
b1a8c28283 | ||
|
|
1412737036 | ||
|
|
ffb3f4fa50 | ||
|
|
ff450ca43a | ||
|
|
d4f0805108 | ||
|
|
a1011ed709 | ||
|
|
64ce69d1c7 | ||
|
|
ca3cb48091 | ||
|
|
85085bf4c7 | ||
|
|
71be6d4ded | ||
|
|
873af6b598 | ||
|
|
c423895f31 | ||
|
|
4418e27c29 | ||
|
|
f776977af8 | ||
|
|
f586401a14 | ||
|
|
c13ce3d0f1 | ||
| 7a9941fe66 | |||
|
|
2c4c669ea2 | ||
|
|
fa384cb6f3 | ||
|
|
05db1bcbfb | ||
|
|
bdd6d9781c | ||
|
|
d23a5ad91b | ||
|
|
018737c42a | ||
|
|
a50e179744 | ||
|
|
3811b8f0c0 | ||
|
|
d81514e9be | ||
|
|
874d9f32a9 | ||
|
|
14b0d7abf0 |
@@ -1,25 +0,0 @@
|
|||||||
name: build-docker-imge
|
|
||||||
on:
|
|
||||||
- push
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Build the docker container
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: "Git clone"
|
|
||||||
run: git clone ${{ gitea.server_url }}/${{ gitea.repository }}.git .
|
|
||||||
- name: "Git checkout"
|
|
||||||
run: git checkout "${{ gitea.sha }}"
|
|
||||||
- uses: aevea/action-kaniko@master
|
|
||||||
name: Run Kaniko to build our api docker container.
|
|
||||||
with:
|
|
||||||
image: kocoded/oqtane.framework
|
|
||||||
tag: ${{ git.workflow_sha }}
|
|
||||||
tag_with_latest: github.ref == 'refs/heads/master'
|
|
||||||
registry: git.kocoder.xyz
|
|
||||||
username: ${{ secrets.CI_RUNNER_USER }}
|
|
||||||
password: ${{ secrets.CI_RUNNER_TOKEN }}
|
|
||||||
build_file: Dockerfile
|
|
||||||
target: deploy
|
|
||||||
|
|
||||||
23
.gitea/workflows/build-oqtane.yml
Normal file
23
.gitea/workflows/build-oqtane.yml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
name: build-oqtane
|
||||||
|
on:
|
||||||
|
- push
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build the oqtane
|
||||||
|
runs-on: mcr.microsoft.com/dotnet/sdk:10.0-noble-amd64
|
||||||
|
steps:
|
||||||
|
- name: "Git clone"
|
||||||
|
run: git clone ${{ gitea.server_url }}/${{ gitea.repository }}.git .
|
||||||
|
- name: "Git checkout"
|
||||||
|
run: git checkout "${{ gitea.sha }}"
|
||||||
|
- name: "Oqtane Framework bauen"
|
||||||
|
run: dotnet build -c Release ./oqtane.framework/Oqtane.slnx
|
||||||
|
- name: "Oqtane Framework publish"
|
||||||
|
run: dotnet publish -c Release ./oqtane.framework/Oqtane.slnx -o ./out
|
||||||
|
- name: Upload Package
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
include-hidden-files: true
|
||||||
|
name: oqtane.framework-amd64
|
||||||
|
path: ./out
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -35,4 +35,4 @@ Oqtane.Server/wwwroot/Themes/*
|
|||||||
!Oqtane.Server/wwwroot/Themes/Oqtane.Themes.*
|
!Oqtane.Server/wwwroot/Themes/Oqtane.Themes.*
|
||||||
!Oqtane.Server/wwwroot/Themes/Templates
|
!Oqtane.Server/wwwroot/Themes/Templates
|
||||||
Oqtane.Server/wwwroot/Themes/Templates/*
|
Oqtane.Server/wwwroot/Themes/Templates/*
|
||||||
Oqtane.Server/wwwroot/Themes/Templates/External
|
!Oqtane.Server/wwwroot/Themes/Templates/External
|
||||||
|
|||||||
17
Directory.Build.props
Normal file
17
Directory.Build.props
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<Configurations>Debug;Release</Configurations>
|
||||||
|
<Version>10.1.2</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/v10.1.2</PackageReleaseNotes>
|
||||||
|
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||||
|
<RepositoryType>Git</RepositoryType>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
@@ -17,8 +17,11 @@ RUN dotnet publish "Oqtane.Server/Oqtane.Server.csproj" -c Release -o /source/pu
|
|||||||
# Deploy
|
# Deploy
|
||||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS deploy
|
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS deploy
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /codefiles
|
||||||
|
|
||||||
|
COPY --from=publish /source/publish/ /codefiles/
|
||||||
|
|
||||||
COPY --from=publish /source/publish/ /app/
|
COPY entrypoint.sh .
|
||||||
ENTRYPOINT ["dotnet", "Oqtane.Server.dll"]
|
RUN chmod +x entrypoint.sh
|
||||||
|
|
||||||
|
ENTRYPOINT ["./entrypoint.sh"]
|
||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2018-2025 .NET Foundation
|
Copyright (c) 2018-2026 .NET Foundation
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
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
|
||||||
81
Oqtane.Application/.template.config/template.json
Normal file
81
Oqtane.Application/.template.config/template.json
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
{
|
||||||
|
"$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,
|
||||||
|
"symbols": {
|
||||||
|
"Framework": {
|
||||||
|
"type": "parameter",
|
||||||
|
"description": "The target framework for the project",
|
||||||
|
"datatype": "choice",
|
||||||
|
"choices": [
|
||||||
|
{
|
||||||
|
"choice": "net10.0",
|
||||||
|
"description": "Target net10.0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"replaces": "net10.0",
|
||||||
|
"defaultValue": "net10.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.slnx"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
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")]
|
||||||
29
Oqtane.Application/Client/Oqtane.Application.Client.csproj
Normal file
29
Oqtane.Application/Client/Oqtane.Application.Client.csproj
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<Version>1.0.0</Version>
|
||||||
|
<AssemblyName>Oqtane.Application.Client.Oqtane</AssemblyName>
|
||||||
|
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
|
||||||
|
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
|
||||||
|
<PublishTrimmed>false</PublishTrimmed>
|
||||||
|
<BlazorEnableCompression>false</BlazorEnableCompression>
|
||||||
|
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.5" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="10.0.5" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Localization" Version="10.0.5" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.5" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Shared\Oqtane.Application.Shared.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Oqtane.Client" Version="10.1.2" />
|
||||||
|
</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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
@using System
|
@using System
|
||||||
@using System.Linq
|
@using System.Linq
|
||||||
@using System.Collections.Generic
|
@using System.Collections.Generic
|
||||||
@using System.Net.Http
|
@using System.Net.Http
|
||||||
@@ -9,7 +9,9 @@
|
|||||||
@using Microsoft.AspNetCore.Components.Web
|
@using Microsoft.AspNetCore.Components.Web
|
||||||
@using Microsoft.Extensions.Localization
|
@using Microsoft.Extensions.Localization
|
||||||
@using Microsoft.JSInterop
|
@using Microsoft.JSInterop
|
||||||
|
@using Microsoft.AspNetCore.Authorization
|
||||||
|
|
||||||
|
@using Oqtane
|
||||||
@using Oqtane.Models
|
@using Oqtane.Models
|
||||||
@using Oqtane.Modules
|
@using Oqtane.Modules
|
||||||
@using Oqtane.Modules.Controls
|
@using Oqtane.Modules.Controls
|
||||||
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>10.1.2</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>
|
||||||
5
Oqtane.Application/Oqtane.Application.slnx
Normal file
5
Oqtane.Application/Oqtane.Application.slnx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<Solution>
|
||||||
|
<Project Path="Server\Oqtane.Application.Server.csproj" DefaultStartup="true" />
|
||||||
|
<Project Path="Client\Oqtane.Application.Client.csproj" />
|
||||||
|
<Project Path="Shared\Oqtane.Application.Shared.csproj" />
|
||||||
|
</Solution>
|
||||||
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")]
|
||||||
39
Oqtane.Application/Server/Oqtane.Application.Server.csproj
Normal file
39
Oqtane.Application/Server/Oqtane.Application.Server.csproj
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.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>
|
||||||
|
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
|
||||||
|
<RequiresAspNetWebAssets>true</RequiresAspNetWebAssets>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove="wwwroot\Modules\Templates\**" />
|
||||||
|
<Compile Remove="wwwroot\Themes\Templates\**" />
|
||||||
|
<Content Remove="wwwroot\Modules\Templates\**" />
|
||||||
|
<Content Remove="wwwroot\Themes\Templates\**" />
|
||||||
|
<EmbeddedResource Remove="wwwroot\Modules\Templates\**" />
|
||||||
|
<EmbeddedResource Remove="wwwroot\Themes\Templates\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.5" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.5" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.5" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Client\Oqtane.Application.Client.csproj" />
|
||||||
|
<ProjectReference Include="..\Shared\Oqtane.Application.Shared.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Oqtane.Server" Version="10.1.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
55
Oqtane.Application/Server/Program.cs
Normal file
55
Oqtane.Application/Server/Program.cs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Cors.Infrastructure;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Oqtane.Extensions;
|
||||||
|
using Oqtane.Infrastructure;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
|
||||||
|
namespace Oqtane.Application.Server
|
||||||
|
{
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
AppDomain.CurrentDomain.SetData(Constants.DataDirectory, Path.Combine(builder.Environment.ContentRootPath, "Data"));
|
||||||
|
|
||||||
|
var configurationBuilder = new ConfigurationBuilder()
|
||||||
|
.SetBasePath(builder.Environment.ContentRootPath)
|
||||||
|
.AddJsonFile("appsettings.json", false, true)
|
||||||
|
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", true, true)
|
||||||
|
.AddEnvironmentVariables();
|
||||||
|
var configuration = configurationBuilder.Build();
|
||||||
|
|
||||||
|
builder.Services.AddOqtane(configuration, builder.Environment);
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
var corsService = app.Services.GetRequiredService<ICorsService>();
|
||||||
|
var corsPolicyProvider = app.Services.GetRequiredService<ICorsPolicyProvider>();
|
||||||
|
var syncManager = app.Services.GetRequiredService<ISyncManager>();
|
||||||
|
|
||||||
|
app.UseOqtane(configuration, builder.Environment, corsService, corsPolicyProvider, syncManager);
|
||||||
|
|
||||||
|
var databaseManager = app.Services.GetService<IDatabaseManager>();
|
||||||
|
var install = databaseManager.Install();
|
||||||
|
if (!string.IsNullOrEmpty(install.Message))
|
||||||
|
{
|
||||||
|
var filelogger = app.Services.GetRequiredService<ILogger<Program>>();
|
||||||
|
if (filelogger != null)
|
||||||
|
{
|
||||||
|
filelogger.LogError($"[Oqtane.Server.Program.Main] {install.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
app.Run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"RenderMode": "Interactive",
|
"RenderMode": "Static",
|
||||||
"Runtime": "Server",
|
"Runtime": "Server",
|
||||||
"Database": {
|
"Database": {
|
||||||
"DefaultDBType": ""
|
"DefaultDBType": ""
|
||||||
@@ -22,27 +22,42 @@
|
|||||||
{
|
{
|
||||||
"Name": "LocalDB",
|
"Name": "LocalDB",
|
||||||
"ControlType": "Oqtane.Installer.Controls.LocalDBConfig, Oqtane.Client",
|
"ControlType": "Oqtane.Installer.Controls.LocalDBConfig, Oqtane.Client",
|
||||||
"DBType": "Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Database.SqlServer"
|
"DBType": "Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Server"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Name": "SQL Server",
|
"Name": "SQL Server",
|
||||||
"ControlType": "Oqtane.Installer.Controls.SqlServerConfig, Oqtane.Client",
|
"ControlType": "Oqtane.Installer.Controls.SqlServerConfig, Oqtane.Client",
|
||||||
"DBType": "Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Database.SqlServer"
|
"DBType": "Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Server"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Name": "SQLite",
|
"Name": "SQLite",
|
||||||
"ControlType": "Oqtane.Installer.Controls.SqliteConfig, Oqtane.Client",
|
"ControlType": "Oqtane.Installer.Controls.SqliteConfig, Oqtane.Client",
|
||||||
"DBType": "Oqtane.Database.Sqlite.SqliteDatabase, Oqtane.Database.Sqlite"
|
"DBType": "Oqtane.Database.Sqlite.SqliteDatabase, Oqtane.Server"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Name": "MySQL",
|
"Name": "MySQL",
|
||||||
"ControlType": "Oqtane.Installer.Controls.MySQLConfig, Oqtane.Client",
|
"ControlType": "Oqtane.Installer.Controls.MySQLConfig, Oqtane.Client",
|
||||||
"DBType": "Oqtane.Database.MySQL.MySQLDatabase, Oqtane.Database.MySQL"
|
"DBType": "Oqtane.Database.MySQL.MySQLDatabase, Oqtane.Server"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Name": "PostgreSQL",
|
"Name": "PostgreSQL",
|
||||||
"ControlType": "Oqtane.Installer.Controls.PostgreSQLConfig, Oqtane.Client",
|
"ControlType": "Oqtane.Installer.Controls.PostgreSQLConfig, Oqtane.Client",
|
||||||
"DBType": "Oqtane.Database.PostgreSQL.PostgreSQLDatabase, Oqtane.Database.PostgreSQL"
|
"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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
@namespace [Owner].Module.[Module]
|
@namespace [Owner].Module.[Module]
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject I[Module]Service [Module]Service
|
@inject I[Module]Service Client[Module]Service
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IStringLocalizer<Edit> Localizer
|
@inject IStringLocalizer<Edit> Localizer
|
||||||
|
|
||||||
@@ -33,11 +33,6 @@
|
|||||||
|
|
||||||
public override string Title => "Manage [Module]";
|
public override string Title => "Manage [Module]";
|
||||||
|
|
||||||
public override List<Resource> Resources => new List<Resource>()
|
|
||||||
{
|
|
||||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }
|
|
||||||
};
|
|
||||||
|
|
||||||
private ElementReference form;
|
private ElementReference form;
|
||||||
private bool validated = false;
|
private bool validated = false;
|
||||||
|
|
||||||
@@ -55,7 +50,7 @@
|
|||||||
if (PageState.Action == "Edit")
|
if (PageState.Action == "Edit")
|
||||||
{
|
{
|
||||||
_id = Int32.Parse(PageState.QueryString["id"]);
|
_id = Int32.Parse(PageState.QueryString["id"]);
|
||||||
[Module] [Module] = await [Module]Service.Get[Module]Async(_id, ModuleState.ModuleId);
|
[Module] [Module] = await Client[Module]Service.Get[Module]Async(_id, ModuleState.ModuleId);
|
||||||
if ([Module] != null)
|
if ([Module] != null)
|
||||||
{
|
{
|
||||||
_name = [Module].Name;
|
_name = [Module].Name;
|
||||||
@@ -86,14 +81,14 @@
|
|||||||
[Module] [Module] = new [Module]();
|
[Module] [Module] = new [Module]();
|
||||||
[Module].ModuleId = ModuleState.ModuleId;
|
[Module].ModuleId = ModuleState.ModuleId;
|
||||||
[Module].Name = _name;
|
[Module].Name = _name;
|
||||||
[Module] = await [Module]Service.Add[Module]Async([Module]);
|
[Module] = await Client[Module]Service.Add[Module]Async([Module]);
|
||||||
await logger.LogInformation("[Module] Added {[Module]}", [Module]);
|
await logger.LogInformation("[Module] Added {[Module]}", [Module]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
[Module] [Module] = await [Module]Service.Get[Module]Async(_id, ModuleState.ModuleId);
|
[Module] [Module] = await Client[Module]Service.Get[Module]Async(_id, ModuleState.ModuleId);
|
||||||
[Module].Name = _name;
|
[Module].Name = _name;
|
||||||
await [Module]Service.Update[Module]Async([Module]);
|
await Client[Module]Service.Update[Module]Async([Module]);
|
||||||
await logger.LogInformation("[Module] Updated {[Module]}", [Module]);
|
await logger.LogInformation("[Module] Updated {[Module]}", [Module]);
|
||||||
}
|
}
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
@namespace [Owner].Module.[Module]
|
@namespace [Owner].Module.[Module]
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject I[Module]Service [Module]Service
|
@inject I[Module]Service Client[Module]Service
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
|
|
||||||
@@ -42,8 +42,8 @@ else
|
|||||||
|
|
||||||
public override List<Resource> Resources => new List<Resource>()
|
public override List<Resource> Resources => new List<Resource>()
|
||||||
{
|
{
|
||||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" },
|
new Stylesheet(ModulePath() + "Module.css"),
|
||||||
new Resource { ResourceType = ResourceType.Script, Url = ModulePath() + "Module.js" }
|
new Script(ModulePath() + "Module.js")
|
||||||
};
|
};
|
||||||
|
|
||||||
List<[Module]> _[Module]s;
|
List<[Module]> _[Module]s;
|
||||||
@@ -52,7 +52,7 @@ else
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_[Module]s = await [Module]Service.Get[Module]sAsync(ModuleState.ModuleId);
|
_[Module]s = await Client[Module]Service.Get[Module]sAsync(ModuleState.ModuleId);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -65,9 +65,9 @@ else
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await [Module]Service.Delete[Module]Async([Module].[Module]Id, ModuleState.ModuleId);
|
await Client[Module]Service.Delete[Module]Async([Module].[Module]Id, ModuleState.ModuleId);
|
||||||
await logger.LogInformation("[Module] Deleted {[Module]}", [Module]);
|
await logger.LogInformation("[Module] Deleted {[Module]}", [Module]);
|
||||||
_[Module]s = await [Module]Service.Get[Module]sAsync(ModuleState.ModuleId);
|
_[Module]s = await Client[Module]Service.Get[Module]sAsync(ModuleState.ModuleId);
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -10,10 +10,7 @@ namespace [Owner].Module.[Module]
|
|||||||
Name = "[Module]",
|
Name = "[Module]",
|
||||||
Description = "[Description]",
|
Description = "[Description]",
|
||||||
Version = "1.0.0",
|
Version = "1.0.0",
|
||||||
ServerManagerType = "[ServerManagerType]",
|
ServerManagerType = "[ServerManagerType]"
|
||||||
ReleaseVersions = "1.0.0",
|
|
||||||
Dependencies = "[Owner].Module.[Module].Shared.Oqtane",
|
|
||||||
PackageName = "[Owner].Module.[Module]"
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
@namespace [Owner].Module.[Module]
|
@namespace [Owner].Module.[Module]
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
|
@implements Oqtane.Interfaces.ISettingsControl
|
||||||
@inject ISettingService SettingService
|
@inject ISettingService SettingService
|
||||||
@inject IStringLocalizer<Settings> Localizer
|
@inject IStringLocalizer<Settings> Localizer
|
||||||
|
|
||||||
@@ -35,8 +36,8 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Dictionary<string, string> settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
|
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
|
||||||
SettingService.SetSetting(settings, "SettingName", _value);
|
settings = SettingService.SetSetting(settings, "SettingName", _value);
|
||||||
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
|
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -7,9 +7,10 @@ using Oqtane.Shared;
|
|||||||
|
|
||||||
namespace [Owner].Module.[Module].Services
|
namespace [Owner].Module.[Module].Services
|
||||||
{
|
{
|
||||||
public class [Module]Service : ServiceBase, I[Module]Service
|
|
||||||
|
public class Client[Module]Service : ServiceBase, I[Module]Service
|
||||||
{
|
{
|
||||||
public [Module]Service(HttpClient http, SiteState siteState) : base(http, siteState) { }
|
public Client[Module]Service(HttpClient http, SiteState siteState) : base(http, siteState) { }
|
||||||
|
|
||||||
private string Apiurl => CreateApiUrl("[Module]");
|
private string Apiurl => CreateApiUrl("[Module]");
|
||||||
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using System.Linq;
|
||||||
using Oqtane.Services;
|
using Oqtane.Services;
|
||||||
using [Owner].Module.[Module].Services;
|
using [Owner].Module.[Module].Services;
|
||||||
|
|
||||||
@@ -8,7 +9,10 @@ namespace [Owner].Module.[Module].Startup
|
|||||||
{
|
{
|
||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddScoped<I[Module]Service, [Module]Service>();
|
if (!services.Any(s => s.ServiceType == typeof(I[Module]Service)))
|
||||||
|
{
|
||||||
|
services.AddScoped<I[Module]Service, Client[Module]Service>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,9 +9,9 @@ namespace [Owner].Module.[Module].Migrations
|
|||||||
{
|
{
|
||||||
[DbContext(typeof([Module]Context))]
|
[DbContext(typeof([Module]Context))]
|
||||||
[Migration("[Owner].Module.[Module].01.00.00.00")]
|
[Migration("[Owner].Module.[Module].01.00.00.00")]
|
||||||
public class InitializeModule : MultiDatabaseMigration
|
public class [Module]Initialize : MultiDatabaseMigration
|
||||||
{
|
{
|
||||||
public InitializeModule(IDatabase database) : base(database)
|
public [Module]Initialize(IDatabase database) : base(database)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5,6 +5,16 @@ using Oqtane.Modules;
|
|||||||
|
|
||||||
namespace [Owner].Module.[Module].Repository
|
namespace [Owner].Module.[Module].Repository
|
||||||
{
|
{
|
||||||
|
public interface I[Module]Repository
|
||||||
|
{
|
||||||
|
IEnumerable<Models.[Module]> Get[Module]s(int ModuleId);
|
||||||
|
Models.[Module] Get[Module](int [Module]Id);
|
||||||
|
Models.[Module] Get[Module](int [Module]Id, bool tracking);
|
||||||
|
Models.[Module] Add[Module](Models.[Module] [Module]);
|
||||||
|
Models.[Module] Update[Module](Models.[Module] [Module]);
|
||||||
|
void Delete[Module](int [Module]Id);
|
||||||
|
}
|
||||||
|
|
||||||
public class [Module]Repository : I[Module]Repository, ITransientService
|
public class [Module]Repository : I[Module]Repository, ITransientService
|
||||||
{
|
{
|
||||||
private readonly IDbContextFactory<[Module]Context> _factory;
|
private readonly IDbContextFactory<[Module]Context> _factory;
|
||||||
@@ -7,7 +7,7 @@ using [Owner].Module.[Module].Services;
|
|||||||
|
|
||||||
namespace [Owner].Module.[Module].Startup
|
namespace [Owner].Module.[Module].Startup
|
||||||
{
|
{
|
||||||
public class ServerStartup : IServerStartup
|
public class [Module]ServerStartup : IServerStartup
|
||||||
{
|
{
|
||||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||||
{
|
{
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using Oqtane.Models;
|
using Oqtane.Models;
|
||||||
@@ -6,16 +5,11 @@ using Oqtane.Models;
|
|||||||
namespace [Owner].Module.[Module].Models
|
namespace [Owner].Module.[Module].Models
|
||||||
{
|
{
|
||||||
[Table("[Owner][Module]")]
|
[Table("[Owner][Module]")]
|
||||||
public class [Module] : IAuditable
|
public class [Module] : ModelBase
|
||||||
{
|
{
|
||||||
[Key]
|
[Key]
|
||||||
public int [Module]Id { get; set; }
|
public int [Module]Id { get; set; }
|
||||||
public int ModuleId { get; set; }
|
public int ModuleId { get; set; }
|
||||||
public string Name { 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; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"Title": "Default Module Template",
|
"Title": "Default Module Template",
|
||||||
"Type": "External",
|
"Type": "Internal",
|
||||||
"Version": "5.2.0",
|
"Version": "10.0.0",
|
||||||
"Namespace": "[Owner].Module.[Module]"
|
"Namespace": "[Owner].Module.[Module]"
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Oqtane.Models;
|
||||||
|
using Oqtane.Themes;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
|
||||||
|
namespace [Owner].Theme.[Theme]
|
||||||
|
{
|
||||||
|
public class ThemeInfo : ITheme
|
||||||
|
{
|
||||||
|
public Oqtane.Models.Theme Theme => new Oqtane.Models.Theme
|
||||||
|
{
|
||||||
|
Name = "[Owner] [Theme]",
|
||||||
|
Version = "1.0.0",
|
||||||
|
PackageName = "[Owner].Theme.[Theme]",
|
||||||
|
ThemeSettingsType = "[Owner].Theme.[Theme].ThemeSettings, [Owner].Theme.[Theme].Client.Oqtane",
|
||||||
|
ContainerSettingsType = "[Owner].Theme.[Theme].ContainerSettings, [Owner].Theme.[Theme].Client.Oqtane",
|
||||||
|
Resources = new List<Resource>()
|
||||||
|
{
|
||||||
|
// obtained from https://cdnjs.com/libraries
|
||||||
|
new Stylesheet(Constants.BootstrapStylesheetUrl, Constants.BootstrapStylesheetIntegrity, "anonymous"),
|
||||||
|
new Stylesheet("~/Theme.css"),
|
||||||
|
new Script(Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"Title": "Default Theme Template",
|
"Title": "Default Theme Template",
|
||||||
"Type": "External",
|
"Type": "Internal",
|
||||||
"Version": "5.2.0",
|
"Version": "10.0.0",
|
||||||
"Namespace": "[Owner].Theme.[Theme]"
|
"Namespace": "[Owner].Theme.[Theme]"
|
||||||
}
|
}
|
||||||
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>net10.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="10.1.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
@@ -1,8 +1,10 @@
|
|||||||
using Microsoft.AspNetCore.Components.Authorization;
|
using Microsoft.AspNetCore.Components.Authorization;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
using Oqtane.Interfaces;
|
using Oqtane.Interfaces;
|
||||||
using Oqtane.Providers;
|
using Oqtane.Providers;
|
||||||
using Oqtane.Services;
|
using Oqtane.Services;
|
||||||
using Oqtane.Shared;
|
using Oqtane.Shared;
|
||||||
|
using Radzen;
|
||||||
|
|
||||||
namespace Microsoft.Extensions.DependencyInjection
|
namespace Microsoft.Extensions.DependencyInjection
|
||||||
{
|
{
|
||||||
@@ -23,7 +25,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||||||
services.AddScoped<SiteState>();
|
services.AddScoped<SiteState>();
|
||||||
services.AddScoped<IInstallationService, InstallationService>();
|
services.AddScoped<IInstallationService, InstallationService>();
|
||||||
services.AddScoped<IModuleDefinitionService, ModuleDefinitionService>();
|
services.AddScoped<IModuleDefinitionService, ModuleDefinitionService>();
|
||||||
services.AddScoped<IThemeService, ThemeService>();
|
services.AddScoped<IThemeService, Oqtane.Services.ThemeService>();
|
||||||
services.AddScoped<IAliasService, AliasService>();
|
services.AddScoped<IAliasService, AliasService>();
|
||||||
services.AddScoped<ITenantService, TenantService>();
|
services.AddScoped<ITenantService, TenantService>();
|
||||||
services.AddScoped<ISiteService, SiteService>();
|
services.AddScoped<ISiteService, SiteService>();
|
||||||
@@ -39,7 +41,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||||||
services.AddScoped<ILogService, LogService>();
|
services.AddScoped<ILogService, LogService>();
|
||||||
services.AddScoped<IJobService, JobService>();
|
services.AddScoped<IJobService, JobService>();
|
||||||
services.AddScoped<IJobLogService, JobLogService>();
|
services.AddScoped<IJobLogService, JobLogService>();
|
||||||
services.AddScoped<INotificationService, NotificationService>();
|
services.AddScoped<INotificationService, Oqtane.Services.NotificationService>();
|
||||||
services.AddScoped<IFolderService, FolderService>();
|
services.AddScoped<IFolderService, FolderService>();
|
||||||
services.AddScoped<IFileService, FileService>();
|
services.AddScoped<IFileService, FileService>();
|
||||||
services.AddScoped<ISiteTemplateService, SiteTemplateService>();
|
services.AddScoped<ISiteTemplateService, SiteTemplateService>();
|
||||||
@@ -53,12 +55,22 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||||||
services.AddScoped<ISyncService, SyncService>();
|
services.AddScoped<ISyncService, SyncService>();
|
||||||
services.AddScoped<ILocalizationCookieService, LocalizationCookieService>();
|
services.AddScoped<ILocalizationCookieService, LocalizationCookieService>();
|
||||||
services.AddScoped<ICookieConsentService, CookieConsentService>();
|
services.AddScoped<ICookieConsentService, CookieConsentService>();
|
||||||
services.AddScoped<IOutputCacheService, OutputCacheService>();
|
|
||||||
services.AddScoped<ITimeZoneService, TimeZoneService>();
|
services.AddScoped<ITimeZoneService, TimeZoneService>();
|
||||||
|
services.AddScoped<IMigrationHistoryService, MigrationHistoryService>();
|
||||||
|
services.AddScoped<ISiteGroupService, SiteGroupService>();
|
||||||
|
services.AddScoped<ISiteGroupMemberService, SiteGroupMemberService>();
|
||||||
|
services.AddScoped<ISiteTaskService, SiteTaskService>();
|
||||||
|
services.AddScoped<IOutputCacheService, OutputCacheService>();
|
||||||
|
|
||||||
// providers
|
// providers
|
||||||
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.QuillJSTextEditor>();
|
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.QuillJSTextEditor>();
|
||||||
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.TextAreaTextEditor>();
|
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.TextAreaTextEditor>();
|
||||||
|
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.RadzenTextEditor>();
|
||||||
|
|
||||||
|
services.AddRadzenComponents();
|
||||||
|
|
||||||
|
var localizer = services.BuildServiceProvider().GetService<IStringLocalizer<Oqtane.Modules.Controls.RadzenTextEditor>>();
|
||||||
|
Oqtane.Modules.Controls.RadzenEditorDefinitions.Localizer = localizer;
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="mx-auto text-center">
|
<div class="mx-auto text-center">
|
||||||
<img src="oqtane-black.png" />
|
<img src="installer-logo.png" />
|
||||||
<div style="font-weight: bold">@SharedLocalizer["Version"] @Constants.Version (.NET @Environment.Version.Major)</div>
|
<div style="font-weight: bold">@SharedLocalizer["Version"] @Constants.Version (.NET @Environment.Version.Major)</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -182,7 +182,7 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_databaseName = "LocalDB";
|
_databaseName = Constants.DefaultDBName;
|
||||||
}
|
}
|
||||||
LoadDatabaseConfigComponent();
|
LoadDatabaseConfigComponent();
|
||||||
|
|
||||||
@@ -269,8 +269,8 @@
|
|||||||
SiteName = Constants.DefaultSite,
|
SiteName = Constants.DefaultSite,
|
||||||
Register = _register,
|
Register = _register,
|
||||||
SiteTemplate = _template,
|
SiteTemplate = _template,
|
||||||
RenderMode = RenderModes.Static,
|
RenderMode = "", // provided by appsettings.json
|
||||||
Runtime = Runtimes.Server
|
Runtime = "" // provided by appsettings.json
|
||||||
};
|
};
|
||||||
|
|
||||||
var installation = await InstallationService.Install(config);
|
var installation = await InstallationService.Install(config);
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
{
|
{
|
||||||
string url = NavigateUrl(p.Path);
|
string url = NavigateUrl(p.Path);
|
||||||
<div class="col-md-2 mx-auto text-center my-3">
|
<div class="col-md-2 mx-auto text-center my-3">
|
||||||
<NavLink class="nav-link text-body" href="@url" Match="NavLinkMatch.All">
|
<NavLink class="nav-link text-body" href="@url" Match="NavLinkMatch.All" @attributes="_attributes">
|
||||||
<h2><span class="@p.Icon" aria-hidden="true"></span></h2>
|
<h2><span class="@p.Icon" aria-hidden="true"></span></h2>
|
||||||
<div class="lead">@((MarkupString)SharedLocalizer[p.Name].ToString().Replace(" ", "<br />"))</div>
|
<div class="lead">@((MarkupString)SharedLocalizer[p.Name].ToString().Replace(" ", "<br />"))</div>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
Dictionary<string, object> _attributes { get; set; } = new();
|
||||||
private List<Page> _pages;
|
private List<Page> _pages;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
||||||
@@ -31,6 +32,11 @@
|
|||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
|
if (PageState.RenderMode == RenderModes.Static && !PageState.Site.EnhancedNavigation)
|
||||||
|
{
|
||||||
|
_attributes.Add("data-enhance-nav", "true"); // Admin Dashboard utilizes enhanced navigation
|
||||||
|
}
|
||||||
|
|
||||||
var admin = PageState.Pages.FirstOrDefault(item => item.Path == "admin");
|
var admin = PageState.Pages.FirstOrDefault(item => item.Path == "admin");
|
||||||
if (admin != null)
|
if (admin != null)
|
||||||
{
|
{
|
||||||
|
|||||||
117
Oqtane.Client/Modules/Admin/GlobalReplace/Index.razor
Normal file
117
Oqtane.Client/Modules/Admin/GlobalReplace/Index.razor
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
@namespace Oqtane.Modules.Admin.GlobalReplace
|
||||||
|
@using System.Text.Json
|
||||||
|
@inherits ModuleBase
|
||||||
|
@inject ISiteTaskService SiteTaskService
|
||||||
|
@inject IStringLocalizer<Index> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="find" HelpText="Specify the content which needs to be replaced" ResourceKey="Find">Find What: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="find" class="form-control" @bind="@_find" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="replace" HelpText="Specify the replacement content" ResourceKey="Replace">Replace With: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="replace" class="form-control" @bind="@_replace" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="casesensitive" HelpText="Specify if the replacement operation should be case sensitive" ResourceKey="CaseSensitive">Match Case? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="casesensitive" class="form-select" @bind="@_caseSensitive">
|
||||||
|
<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="site" HelpText="Specify if site information should be updated (ie. name, head content, body content, settings)" ResourceKey="Site">Site Info? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="site" class="form-select" @bind="@_site">
|
||||||
|
<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="pages" HelpText="Specify if page information should be updated (ie. name, title, head content, body content, settings)" ResourceKey="Pages">Page Info? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="pages" class="form-select" @bind="@_pages">
|
||||||
|
<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="modules" HelpText="Specify if module information should be updated (ie. title, header, footer, settings)" ResourceKey="Modules">Module Info? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="modules" class="form-select" @bind="@_modules">
|
||||||
|
<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="content" HelpText="Specify if module content should be updated" ResourceKey="Content">Module Content? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="content" class="form-select" @bind="@_content">
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br /><br />
|
||||||
|
<ActionDialog Header="Global Replace" Message="This Operation is Permanent. Are You Sure You Wish To Proceed?" Action="Replace" Class="btn btn-primary" OnClick="@(async () => await Save())" ResourceKey="GlobalReplace" />
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
@code {
|
||||||
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
public override string Title => "Global Replace";
|
||||||
|
|
||||||
|
private string _find;
|
||||||
|
private string _replace;
|
||||||
|
private string _caseSensitive = "True";
|
||||||
|
private string _site = "True";
|
||||||
|
private string _pages = "True";
|
||||||
|
private string _modules = "True";
|
||||||
|
private string _content = "True";
|
||||||
|
|
||||||
|
private async Task Save()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(_find) && !string.IsNullOrEmpty(_replace))
|
||||||
|
{
|
||||||
|
var replace = new GlobalReplace
|
||||||
|
{
|
||||||
|
Find = _find,
|
||||||
|
Replace = _replace,
|
||||||
|
CaseSensitive = bool.Parse(_caseSensitive),
|
||||||
|
Site = bool.Parse(_site),
|
||||||
|
Pages = bool.Parse(_pages),
|
||||||
|
Modules = bool.Parse(_modules),
|
||||||
|
Content = bool.Parse(_content)
|
||||||
|
};
|
||||||
|
|
||||||
|
var siteTask = new SiteTask(PageState.Site.SiteId, "Global Replace", "Oqtane.Infrastructure.GlobalReplaceTask, Oqtane.Server", JsonSerializer.Serialize(replace));
|
||||||
|
await SiteTaskService.AddSiteTaskAsync(siteTask);
|
||||||
|
|
||||||
|
AddModuleMessage(Localizer["Success.Save"], MessageType.Success);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Saving Global Replace Settings {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.Save"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,24 +5,30 @@
|
|||||||
@inject IStringLocalizer<Edit> Localizer
|
@inject IStringLocalizer<Edit> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
@if (_initialized)
|
||||||
|
{
|
||||||
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
@if (!_editable)
|
||||||
<Label Class="col-sm-3" For="name" HelpText="Enter the job name" ResourceKey="Name">Name: </Label>
|
{
|
||||||
<div class="col-sm-9">
|
<ModuleMessage Message="@Localizer["JobNotEditable"]" Type="MessageType.Warning" />
|
||||||
<input id="name" class="form-control" @bind="@_name" maxlength="200" required />
|
}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="type" HelpText="The fully qualified job type name" ResourceKey="Type">Type: </Label>
|
<Label Class="col-sm-3" For="type" HelpText="The fully qualified job type name" ResourceKey="Type">Type: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="type" class="form-control" @bind="@_jobType" readonly />
|
<input id="type" class="form-control" @bind="@_jobType" required disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="name" HelpText="Enter the job name" ResourceKey="Name">Name: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="name" class="form-control" @bind="@_name" maxlength="200" required disabled="@(!_editable)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="enabled" HelpText="Select whether you want the job enabled or not" ResourceKey="Enabled">Enabled? </Label>
|
<Label Class="col-sm-3" For="enabled" HelpText="Select whether you want the job enabled or not" ResourceKey="Enabled">Enabled? </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="enabled" class="form-select" @bind="@_isEnabled" required>
|
<select id="enabled" class="form-select" @bind="@_isEnabled" required disabled="@(!_editable)">
|
||||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
<option value="False">@SharedLocalizer["No"]</option>
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
</select>
|
</select>
|
||||||
@@ -31,8 +37,8 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="runs-every" HelpText="Select how often you want the job to run" ResourceKey="RunsEvery">Runs Every: </Label>
|
<Label Class="col-sm-3" For="runs-every" HelpText="Select how often you want the job to run" ResourceKey="RunsEvery">Runs Every: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="runs-every" class="form-control mb-1" @bind="@_interval" maxlength="4" required />
|
<input id="runs-every" class="form-control mb-1" @bind="@_interval" maxlength="4" required disabled="@(!_editable)" />
|
||||||
<select id="runs-every" class="form-select" @bind="@_frequency" required>
|
<select id="runs-every" class="form-select" @bind="@_frequency" required disabled="@(!_editable)">
|
||||||
<option value="m">@Localizer["Minute(s)"]</option>
|
<option value="m">@Localizer["Minute(s)"]</option>
|
||||||
<option value="H">@Localizer["Hour(s)"]</option>
|
<option value="H">@Localizer["Hour(s)"]</option>
|
||||||
<option value="d">@Localizer["Day(s)"]</option>
|
<option value="d">@Localizer["Day(s)"]</option>
|
||||||
@@ -45,7 +51,7 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<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>
|
<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">
|
<div class="col-sm-9">
|
||||||
<input id="retention" type="number" min="0" step ="1" class="form-control" @bind="@_retentionHistory" maxlength="3" required />
|
<input id="retention" type="number" min="0" step="1" class="form-control" @bind="@_retentionHistory" maxlength="3" required disabled="@(!_editable)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -53,10 +59,10 @@
|
|||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<input id="starting" type="date" class="form-control" @bind="@_startDate" />
|
<input id="starting" type="date" class="form-control" @bind="@_startDate" disabled="@(!_editable)" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<input id="starting" type="time" class="form-control" placeholder="hh:mm" @bind="@_startTime" />
|
<input id="starting" type="time" class="form-control" @bind="@_startTime" placeholder="hh:mm" required="@(_startDate.HasValue)" disabled="@(!_editable)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -66,39 +72,42 @@
|
|||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<input id="ending" type="date" class="form-control" @bind="@_endDate" />
|
<input id="ending" type="date" class="form-control" @bind="@_endDate" disabled="@(!_editable)" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<input id="ending" type="time" class="form-control" placeholder="hh:mm" @bind="@_endTime" />
|
<input id="ending" type="time" class="form-control" placeholder="hh:mm" @bind="@_endTime" required="@(_endDate.HasValue)" disabled="@(!_editable)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="next" HelpText="Optionally modify the date and time when this job should execute next" ResourceKey="NextExecution">Next Execution: </Label>
|
<Label Class="col-sm-3" For="next" HelpText="The date and time when this job will execute next. This value cannot be modified. Use the settings above to control the execution of the job." ResourceKey="NextExecution">Next Execution: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="row">
|
<input id="next" class="form-control" @bind="@_nextDate" disabled />
|
||||||
<div class="col">
|
|
||||||
<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" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
|
@if (_editable)
|
||||||
|
{
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveJob">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveJob">@SharedLocalizer["Save"]</button>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-danger" @onclick="DisableJob">@Localizer["Disable"]</button>
|
||||||
|
}
|
||||||
|
<NavLink class="btn btn-secondary ms-1" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
|
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
|
||||||
</form>
|
</form>
|
||||||
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private ElementReference form;
|
private ElementReference form;
|
||||||
private bool validated = false;
|
private bool validated = false;
|
||||||
|
private bool _initialized = false;
|
||||||
|
private bool _editable = true;
|
||||||
private int _jobId;
|
private int _jobId;
|
||||||
private string _name = string.Empty;
|
private string _name = string.Empty;
|
||||||
private string _jobType = string.Empty;
|
private string _jobType = string.Empty;
|
||||||
@@ -120,6 +129,11 @@
|
|||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await LoadJob();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task LoadJob()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -127,6 +141,7 @@
|
|||||||
Job job = await JobService.GetJobAsync(_jobId);
|
Job job = await JobService.GetJobAsync(_jobId);
|
||||||
if (job != null)
|
if (job != null)
|
||||||
{
|
{
|
||||||
|
_editable = !job.IsEnabled && !job.IsExecuting;
|
||||||
_name = job.Name;
|
_name = job.Name;
|
||||||
_jobType = job.JobType;
|
_jobType = job.JobType;
|
||||||
_isEnabled = job.IsEnabled.ToString();
|
_isEnabled = job.IsEnabled.ToString();
|
||||||
@@ -144,6 +159,8 @@
|
|||||||
modifiedby = job.ModifiedBy;
|
modifiedby = job.ModifiedBy;
|
||||||
modifiedon = job.ModifiedOn;
|
modifiedon = job.ModifiedOn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_initialized = true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -154,19 +171,15 @@
|
|||||||
|
|
||||||
private async Task SaveJob()
|
private async Task SaveJob()
|
||||||
{
|
{
|
||||||
if (!Utilities.ValidateEffectiveExpiryDates(_startDate, _endDate))
|
try
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Message.StartEndDateError"], MessageType.Warning);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
validated = true;
|
|
||||||
var interop = new Interop(JSRuntime);
|
var interop = new Interop(JSRuntime);
|
||||||
if (await interop.FormValid(form))
|
if (await interop.FormValid(form))
|
||||||
{
|
{
|
||||||
var job = await JobService.GetJobAsync(_jobId);
|
var job = await JobService.GetJobAsync(_jobId);
|
||||||
job.Name = _name;
|
job.Name = _name;
|
||||||
job.JobType = _jobType;
|
job.IsEnabled = bool.Parse(_isEnabled);
|
||||||
job.IsEnabled = Boolean.Parse(_isEnabled);
|
|
||||||
job.Frequency = _frequency;
|
job.Frequency = _frequency;
|
||||||
if (job.Frequency == "O") // once
|
if (job.Frequency == "O") // once
|
||||||
{
|
{
|
||||||
@@ -176,21 +189,34 @@
|
|||||||
{
|
{
|
||||||
job.Interval = int.Parse(_interval);
|
job.Interval = int.Parse(_interval);
|
||||||
}
|
}
|
||||||
job.StartDate = LocalToUtc(_startDate.Value.Date.Add(_startTime.Value.TimeOfDay));
|
|
||||||
job.EndDate = LocalToUtc(_endDate.Value.Date.Add(_endTime.Value.TimeOfDay));
|
|
||||||
job.RetentionHistory = int.Parse(_retentionHistory);
|
|
||||||
job.NextExecution = LocalToUtc(_nextDate.Value.Date.Add(_nextTime.Value.TimeOfDay));
|
|
||||||
|
|
||||||
try
|
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.RetentionHistory = int.Parse(_retentionHistory);
|
||||||
|
|
||||||
|
if (!job.IsEnabled || Utilities.ValidateEffectiveExpiryDates(job.StartDate, job.EndDate))
|
||||||
{
|
{
|
||||||
|
if (!job.IsEnabled || (job.StartDate >= DateTime.UtcNow || job.StartDate == null))
|
||||||
|
{
|
||||||
|
job.NextExecution = null;
|
||||||
job = await JobService.UpdateJobAsync(job);
|
job = await JobService.UpdateJobAsync(job);
|
||||||
await logger.LogInformation("Job Updated {Job}", job);
|
await logger.LogInformation("Job Updated {Job}", job);
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
else
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Udate Job {Job} {Error}", job, ex.Message);
|
AddModuleMessage(Localizer["Message.StartDateError"], MessageType.Warning);
|
||||||
AddModuleMessage(Localizer["Error.Job.Update"], MessageType.Error);
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.StartEndDateError"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -198,5 +224,38 @@
|
|||||||
AddModuleMessage(Localizer["Message.Required.JobInfo"], MessageType.Warning);
|
AddModuleMessage(Localizer["Message.Required.JobInfo"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Updating Job {JobId} {Error}", _jobId, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.Job.Update"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DisableJob()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var job = await JobService.GetJobAsync(_jobId);
|
||||||
|
if (job != null)
|
||||||
|
{
|
||||||
|
if (job.IsExecuting)
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.ExecutingError"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
job.IsEnabled = false;
|
||||||
|
job.NextExecution = null;
|
||||||
|
job = await JobService.UpdateJobAsync(job);
|
||||||
|
await logger.LogInformation("Job Updated {Job}", job);
|
||||||
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Updating Job {JobId} {Error}", _jobId, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.Job.Update"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,6 +115,13 @@ else
|
|||||||
private async Task StartJob(int jobId)
|
private async Task StartJob(int jobId)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
Job _job = await JobService.GetJobAsync(jobId);
|
||||||
|
if (!_job.IsEnabled)
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.Job.Disabled"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
await JobService.StartJobAsync(jobId);
|
await JobService.StartJobAsync(jobId);
|
||||||
await logger.LogInformation("Job Started {JobId}", jobId);
|
await logger.LogInformation("Job Started {JobId}", jobId);
|
||||||
@@ -122,6 +129,7 @@ else
|
|||||||
_jobs = await JobService.GetJobsAsync();
|
_jobs = await JobService.GetJobsAsync();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Starting Job {JobId} {Error}", jobId, ex.Message);
|
await logger.LogError(ex, "Error Starting Job {JobId} {Error}", jobId, ex.Message);
|
||||||
|
|||||||
@@ -14,93 +14,140 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@if (!twofactor)
|
|
||||||
{
|
|
||||||
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<div class="Oqtane-Modules-Admin-Login" @onkeypress="@(e => KeyPressed(e))">
|
<div class="Oqtane-Modules-Admin-Login" @onkeypress="@(e => KeyPressed(e))">
|
||||||
|
@switch (_action)
|
||||||
|
{
|
||||||
|
case "Login":
|
||||||
@if (_allowexternallogin)
|
@if (_allowexternallogin)
|
||||||
{
|
{
|
||||||
<button type="button" class="btn btn-primary" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
|
<button type="button" class="btn btn-primary col-12" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
|
||||||
<br />
|
<hr class="app-rule mt-3 mb-2" />
|
||||||
|
|
||||||
<br />
|
|
||||||
}
|
}
|
||||||
@if (_allowsitelogin)
|
@if (_allowsitelogin)
|
||||||
{
|
{
|
||||||
<div class="form-group">
|
<div class="form-group text-center">
|
||||||
<Label Class="control-label" For="username" HelpText="Please enter your Username" ResourceKey="Username">Username:</Label>
|
<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" @bind:event="oninput" required />
|
<input id="username" type="text" @ref="username" class="form-control" placeholder="@Localizer["Username.Placeholder"]" @bind="@_username" @bind:event="oninput" required />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group mt-2">
|
<div class="form-group text-center mt-2">
|
||||||
<Label Class="control-label" For="password" HelpText="Please enter your Password" ResourceKey="Password">Password:</Label>
|
<Label Class="control-label" For="password" HelpText="Please enter your Password" ResourceKey="Password">Password:</Label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="password" type="@_passwordtype" name="Password" class="form-control" placeholder="@Localizer["Password.Placeholder"]" @bind="@_password" @bind:event="oninput" required />
|
<input id="password" type="@_passwordtype" @ref="password" 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>
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group mt-2">
|
|
||||||
@if (!_alwaysremember)
|
@if (!_alwaysremember)
|
||||||
{
|
{
|
||||||
<div class="form-check">
|
<div class="form-group text-center mt-2">
|
||||||
|
<div>
|
||||||
<input id="remember" type="checkbox" class="form-check-input" @bind="@_remember" />
|
<input id="remember" type="checkbox" class="form-check-input" @bind="@_remember" />
|
||||||
<Label Class="control-label" For="remember" HelpText="Specify if you would like to be signed back in automatically the next time you visit this site" ResourceKey="Remember">Remember Me?</Label>
|
<Label Class="control-label" For="remember" HelpText="Specify if you would like to be signed back in automatically the next time you visit this site" ResourceKey="Remember">Stay Signed In?</Label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</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 />
|
<div class="btn-group mt-2 col-12" role="group">
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["ForgotPassword"]</button>
|
<button type="button" class="btn btn-primary col-6" @onclick="Login">@SharedLocalizer["Login"]</button>
|
||||||
|
<button type="button" class="btn btn-secondary col-6" @onclick="CancelLogin">@SharedLocalizer["Cancel"]</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" class="btn btn-secondary col-12 mt-4" @onclick="@(() => SetAction("ForgotPassword"))">@Localizer["ForgotPassword"]</button>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (_allowloginlink)
|
||||||
|
{
|
||||||
|
<hr class="app-rule mt-3" />
|
||||||
|
<button type="button" class="btn btn-primary col-12 mt-2" @onclick="@(() => SetAction("LoginLink"))">@Localizer["UseLoginLink"]</button>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (_allowpasskeys)
|
||||||
|
{
|
||||||
|
<hr class="app-rule mt-3" />
|
||||||
|
<button type="button" class="btn btn-primary col-12 mt-2" @onclick="PasskeyLogin">@Localizer["Passkey"]</button>
|
||||||
|
}
|
||||||
|
|
||||||
@if (PageState.Site.AllowRegistration)
|
@if (PageState.Site.AllowRegistration)
|
||||||
{
|
{
|
||||||
<br />
|
<hr class="app-rule mt-3" />
|
||||||
|
<div class="text-center mt-2">
|
||||||
<br />
|
<NavLink href="@_registerurl">@Localizer["Register"]</NavLink>
|
||||||
<NavLink href="@NavigateUrl("register")">@Localizer["Register"]</NavLink>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
|
||||||
}
|
}
|
||||||
else
|
break;
|
||||||
{
|
case "ForgotPassword":
|
||||||
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
<div class="form-group text-center">
|
||||||
<div class="container Oqtane-Modules-Admin-Login">
|
<Label Class="control-label" For="username" HelpText="Please enter your Username" ResourceKey="Username">Username:</Label>
|
||||||
|
<input id="username" type="text" class="form-control" placeholder="@Localizer["Username.Placeholder"]" @bind="@_username" @bind:event="oninput" required />
|
||||||
|
</div>
|
||||||
|
<div class="btn-group mt-4 col-12" role="group">
|
||||||
|
<button type="button" class="btn btn-primary col-6" @onclick="ForgotPassword">@SharedLocalizer["Send"]</button>
|
||||||
|
<button type="button" class="btn btn-secondary col-6" @onclick="@(() => SetAction("Login"))">@SharedLocalizer["Cancel"]</button>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-secondary col-12 mt-4" @onclick="@(() => SetAction("ForgotUsername"))">@Localizer["ForgotUsername"]</button>
|
||||||
|
break;
|
||||||
|
case "ForgotUsername":
|
||||||
|
<div class="form-group text-center">
|
||||||
|
<Label Class="control-label" For="email" HelpText="Please enter your Email" ResourceKey="Email">Email:</Label>
|
||||||
|
<input id="email" type="text" class="form-control" placeholder="@Localizer["Email.Placeholder"]" @bind="@_email" required />
|
||||||
|
</div>
|
||||||
|
<div class="btn-group mt-4 col-12" role="group">
|
||||||
|
<button type="button" class="btn btn-primary col-6" @onclick="ForgotUsername">@SharedLocalizer["Send"]</button>
|
||||||
|
<button type="button" class="btn btn-secondary col-6" @onclick="@(() => SetAction("Login"))">@SharedLocalizer["Cancel"]</button>
|
||||||
|
</div>
|
||||||
|
break;
|
||||||
|
case "LoginLink":
|
||||||
|
<div class="form-group text-center">
|
||||||
|
<Label Class="control-label" For="email" HelpText="Please enter your Email" ResourceKey="Email">Email:</Label>
|
||||||
|
<input id="email" type="text" class="form-control" placeholder="@Localizer["Email.Placeholder"]" @bind="@_email" required />
|
||||||
|
</div>
|
||||||
|
<div class="btn-group mt-4 col-12" role="group">
|
||||||
|
<button type="button" class="btn btn-primary col-6" @onclick="LoginLink">@SharedLocalizer["Send"]</button>
|
||||||
|
<button type="button" class="btn btn-secondary col-6" @onclick="@(() => SetAction("Login"))">@SharedLocalizer["Cancel"]</button>
|
||||||
|
</div>
|
||||||
|
break;
|
||||||
|
case "TwoFactor":
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<Label Class="control-label" For="code" HelpText="Please enter the secure verification code which was sent to you by email" ResourceKey="Code">Verification Code:</Label>
|
<Label Class="control-label" For="code" HelpText="Please enter the secure verification code which was sent to you by email" ResourceKey="Code">Verification Code:</Label>
|
||||||
<input id="code" class="form-control" @bind="@_code" placeholder="@Localizer["Code.Placeholder"]" maxlength="6" required />
|
<input id="code" class="form-control" @bind="@_code" placeholder="@Localizer["Code.Placeholder"]" maxlength="6" required />
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<div class="btn-group mt-4 col-12" role="group">
|
||||||
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
|
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Cancel"]</button>
|
<button type="button" class="btn btn-secondary" @onclick="@(() => SetAction("Login"))">@SharedLocalizer["Cancel"]</button>
|
||||||
|
</div>
|
||||||
|
break;
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private bool _allowsitelogin = true;
|
private string _action = "Login";
|
||||||
private bool _allowexternallogin = false;
|
private bool _allowexternallogin = false;
|
||||||
|
private bool _allowsitelogin = true;
|
||||||
|
private bool _allowloginlink = false;
|
||||||
|
private bool _allowpasskeys = false;
|
||||||
|
private string _returnurl = string.Empty;
|
||||||
|
|
||||||
private ElementReference login;
|
private ElementReference login;
|
||||||
private bool validated = false;
|
private bool validated = false;
|
||||||
private bool twofactor = false;
|
|
||||||
private string _username = string.Empty;
|
private string _username = string.Empty;
|
||||||
private ElementReference username;
|
private ElementReference username;
|
||||||
private string _password = string.Empty;
|
private string _password = string.Empty;
|
||||||
private string _passwordtype = "password";
|
private string _passwordtype = "password";
|
||||||
private string _togglepassword = string.Empty;
|
private string _togglepassword = string.Empty;
|
||||||
|
private ElementReference password;
|
||||||
private bool _remember = false;
|
private bool _remember = false;
|
||||||
private bool _alwaysremember = false;
|
private bool _alwaysremember = false;
|
||||||
|
private string _registerurl = string.Empty;
|
||||||
|
private string _email = string.Empty;
|
||||||
private string _code = string.Empty;
|
private string _code = string.Empty;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
|
||||||
public override bool? Prerender => true;
|
|
||||||
|
|
||||||
public override List<Resource> Resources => new List<Resource>()
|
public override List<Resource> Resources => new List<Resource>()
|
||||||
{
|
{
|
||||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }
|
new Stylesheet(ModulePath() + "Module.css")
|
||||||
};
|
};
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
@@ -109,8 +156,22 @@ else
|
|||||||
{
|
{
|
||||||
_allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false;
|
_allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false;
|
||||||
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
|
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
|
||||||
|
_allowloginlink = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:LoginLink", "false"));
|
||||||
|
_allowpasskeys = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:Passkeys", "false"));
|
||||||
_alwaysremember = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AlwaysRemember", "false"));
|
_alwaysremember = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AlwaysRemember", "false"));
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:RegisterUrl", "")))
|
||||||
|
{
|
||||||
|
_registerurl = SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:RegisterUrl", "");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_registerurl = NavigateUrl("register");
|
||||||
|
}
|
||||||
|
|
||||||
|
// PageState.ReturnUrl is not specified if user navigated directly to login page
|
||||||
|
_returnurl = (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : PageState.Alias.Path;
|
||||||
|
|
||||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
|
|
||||||
if (PageState.QueryString.ContainsKey("name"))
|
if (PageState.QueryString.ContainsKey("name"))
|
||||||
@@ -126,7 +187,7 @@ else
|
|||||||
|
|
||||||
if (PageState.QueryString.ContainsKey("key"))
|
if (PageState.QueryString.ContainsKey("key"))
|
||||||
{
|
{
|
||||||
user = await UserService.LinkUserAsync(user, PageState.QueryString["token"], PageState.Site.Settings["ExternalLogin:ProviderType"], PageState.QueryString["key"], PageState.Site.Settings["ExternalLogin:ProviderName"]);
|
user = await UserService.AddLoginAsync(user, PageState.QueryString["token"], PageState.Site.Settings["ExternalLogin:ProviderType"], PageState.QueryString["key"], PageState.Site.Settings["ExternalLogin:ProviderName"]);
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
await logger.LogInformation(LogFunction.Security, "External Login Linkage Successful For Username {Username}", _username);
|
await logger.LogInformation(LogFunction.Security, "External Login Linkage Successful For Username {Username}", _username);
|
||||||
@@ -158,7 +219,15 @@ else
|
|||||||
{
|
{
|
||||||
if (PageState.QueryString.ContainsKey("status"))
|
if (PageState.QueryString.ContainsKey("status"))
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["ExternalLoginStatus." + PageState.QueryString["status"]], MessageType.Info);
|
AddModuleMessage(Localizer["ExternalLoginStatus." + PageState.QueryString["status"]], MessageType.Warning);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_allowexternallogin && !_allowsitelogin)
|
||||||
|
{
|
||||||
|
// external login
|
||||||
|
ExternalLogin();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,20 +238,45 @@ else
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
private async Task KeyPressed(KeyboardEventArgs e)
|
||||||
{
|
{
|
||||||
if (firstRender && PageState.User == null && _allowsitelogin)
|
if (e.Code == "Enter" || e.Code == "NumpadEnter")
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(username.Id)) // ensure username is visible in UI
|
switch (_action)
|
||||||
{
|
{
|
||||||
await username.FocusAsync();
|
case "Login":
|
||||||
|
await Login();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// redirect logged in user to specified page
|
private void SetAction(string action)
|
||||||
if (PageState.User != null && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
_action = action;
|
||||||
|
_username = "";
|
||||||
|
_password = "";
|
||||||
|
_email = "";
|
||||||
|
ClearModuleMessage();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExternalLogin()
|
||||||
|
{
|
||||||
|
NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + WebUtility.UrlEncode(_returnurl)), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TogglePassword()
|
||||||
|
{
|
||||||
|
if (_passwordtype == "password")
|
||||||
|
{
|
||||||
|
_passwordtype = "text";
|
||||||
|
_togglepassword = SharedLocalizer["HidePassword"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_passwordtype = "password";
|
||||||
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +291,7 @@ else
|
|||||||
var hybrid = (PageState.Runtime == Shared.Runtime.Hybrid);
|
var hybrid = (PageState.Runtime == Shared.Runtime.Hybrid);
|
||||||
var user = new User { SiteId = PageState.Site.SiteId, Username = _username, Password = _password, LastIPAddress = SiteState.RemoteIPAddress};
|
var user = new User { SiteId = PageState.Site.SiteId, Username = _username, Password = _password, LastIPAddress = SiteState.RemoteIPAddress};
|
||||||
|
|
||||||
if (!twofactor)
|
if (_action == "Login")
|
||||||
{
|
{
|
||||||
_remember = _alwaysremember || _remember;
|
_remember = _alwaysremember || _remember;
|
||||||
user = await UserService.LoginUserAsync(user, hybrid, _remember);
|
user = await UserService.LoginUserAsync(user, hybrid, _remember);
|
||||||
@@ -211,20 +305,17 @@ else
|
|||||||
{
|
{
|
||||||
await logger.LogInformation(LogFunction.Security, "Login Successful For {Username} From IP Address {IPAddress}", _username, SiteState.RemoteIPAddress);
|
await logger.LogInformation(LogFunction.Security, "Login Successful For {Username} From IP Address {IPAddress}", _username, SiteState.RemoteIPAddress);
|
||||||
|
|
||||||
// return url is not specified if user navigated directly to login page
|
|
||||||
var returnurl = (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : PageState.Alias.Path;
|
|
||||||
|
|
||||||
if (hybrid)
|
if (hybrid)
|
||||||
{
|
{
|
||||||
// hybrid apps utilize an interactive login
|
// hybrid apps utilize an interactive login
|
||||||
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
|
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
|
||||||
authstateprovider.NotifyAuthenticationChanged();
|
authstateprovider.NotifyAuthenticationChanged();
|
||||||
NavigationManager.NavigateTo(NavigateUrl(returnurl, true));
|
NavigationManager.NavigateTo(NavigateUrl(_returnurl, true));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// post back to the Login page so that the cookies are set correctly
|
// post back to the Login page so that the cookies are set correctly
|
||||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = WebUtility.UrlEncode(returnurl) };
|
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = WebUtility.UrlEncode(_returnurl) };
|
||||||
string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/");
|
string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/");
|
||||||
await interop.SubmitForm(url, fields);
|
await interop.SubmitForm(url, fields);
|
||||||
}
|
}
|
||||||
@@ -233,13 +324,13 @@ else
|
|||||||
{
|
{
|
||||||
if (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "required" || (user != null && user.TwoFactorRequired))
|
if (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "required" || (user != null && user.TwoFactorRequired))
|
||||||
{
|
{
|
||||||
twofactor = true;
|
_action = "TwoFactor";
|
||||||
validated = false;
|
validated = false;
|
||||||
AddModuleMessage(Localizer["Message.TwoFactor"], MessageType.Info);
|
AddModuleMessage(Localizer["Message.TwoFactor"], MessageType.Info);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!twofactor)
|
if (_action != "TwoFactor")
|
||||||
{
|
{
|
||||||
await logger.LogInformation(LogFunction.Security, "Login Failed For Username {Username}", _username);
|
await logger.LogInformation(LogFunction.Security, "Login Failed For Username {Username}", _username);
|
||||||
AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error);
|
||||||
@@ -264,23 +355,30 @@ else
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Cancel()
|
private void CancelLogin()
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
NavigationManager.NavigateTo(_returnurl);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Forgot()
|
private async Task PasskeyLogin()
|
||||||
|
{
|
||||||
|
// post back to the Passkey page so that the cookies are set correctly
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, operation = "request", returnurl = _returnurl };
|
||||||
|
string url = Utilities.TenantUrl(PageState.Alias, "/pages/passkey/");
|
||||||
|
await interop.SubmitForm(url, fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ForgotPassword()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_username != string.Empty)
|
if (!string.IsNullOrEmpty(_username))
|
||||||
{
|
{
|
||||||
var user = await UserService.GetUserAsync(_username, PageState.Site.SiteId);
|
if (await UserService.ForgotPasswordAsync(_username))
|
||||||
if (user != null)
|
|
||||||
{
|
{
|
||||||
await UserService.ForgotPasswordAsync(user);
|
|
||||||
await logger.LogInformation(LogFunction.Security, "Password Reset Notification Sent For Username {Username}", _username);
|
await logger.LogInformation(LogFunction.Security, "Password Reset Notification Sent For Username {Username}", _username);
|
||||||
AddModuleMessage(Localizer["Message.ForgotUser"], MessageType.Info);
|
AddModuleMessage(Localizer["Message.ForgotPassword"], MessageType.Info);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -289,10 +387,8 @@ else
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Message.ForgotPassword"], MessageType.Info);
|
AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -301,40 +397,114 @@ else
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Reset()
|
private async Task ForgotUsername()
|
||||||
{
|
{
|
||||||
twofactor = false;
|
try
|
||||||
_username = "";
|
|
||||||
_password = "";
|
|
||||||
ClearModuleMessage();
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task KeyPressed(KeyboardEventArgs e)
|
|
||||||
{
|
{
|
||||||
if (e.Code == "Enter" || e.Code == "NumpadEnter")
|
if (!string.IsNullOrEmpty(_email))
|
||||||
{
|
{
|
||||||
await Login();
|
if (await UserService.ForgotUsernameAsync(_email))
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TogglePassword()
|
|
||||||
{
|
{
|
||||||
if (_passwordtype == "password")
|
AddModuleMessage(Localizer["Message.ForgotUsername"], MessageType.Info);
|
||||||
{
|
await logger.LogInformation(LogFunction.Security, "Username Reminder Notification Sent For Email {Email}", _email);
|
||||||
_passwordtype = "text";
|
|
||||||
_togglepassword = SharedLocalizer["HidePassword"];
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_passwordtype = "password";
|
AddModuleMessage(Localizer["Message.UserDoesNotExist"], MessageType.Warning);
|
||||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
private void ExternalLogin()
|
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + WebUtility.UrlEncode(PageState.ReturnUrl)), true);
|
AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Sending Username Reminder {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.ForgotUsername"], MessageType.Error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task LoginLink()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(_email))
|
||||||
|
{
|
||||||
|
if (await UserService.SendLoginLinkAsync(_email, _returnurl))
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.SendLoginLink"], MessageType.Info);
|
||||||
|
await logger.LogInformation(LogFunction.Security, "Login Link Sent To Email {Email}", _email);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.UserDoesNotExist"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Sending Login Link {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.SendLoginLink"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender && PageState.QueryString.ContainsKey("options"))
|
||||||
|
{
|
||||||
|
// user has initiated a passkey login
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
var credential = await interop.RequestCredential(WebUtility.UrlDecode(PageState.QueryString["options"]));
|
||||||
|
if (!string.IsNullOrEmpty(credential))
|
||||||
|
{
|
||||||
|
// post back to the Passkey page so that the cookies are set correctly
|
||||||
|
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, operation = "login", credential = credential, returnurl = _returnurl };
|
||||||
|
string url = Utilities.TenantUrl(PageState.Alias, "/pages/passkey/");
|
||||||
|
await interop.SubmitForm(url, fields);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await logger.LogError("Passkey Login Was Not Successful");
|
||||||
|
AddModuleMessage(Localizer["Error.Passkey.Fail"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Passkey Login Was Not Successful");
|
||||||
|
AddModuleMessage(Localizer["Error.Passkey.Fail"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstRender && PageState.User == null && _allowsitelogin && _action == "Login")
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(_username))
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(username.Id)) // ensure username is visible in UI
|
||||||
|
{
|
||||||
|
await username.FocusAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(password.Id)) // ensure password is visible in UI
|
||||||
|
{
|
||||||
|
await password.FocusAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// redirect logged in user to specified page
|
||||||
|
if (PageState.User != null && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
|
{
|
||||||
|
NavigationManager.NavigateTo(_returnurl);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,12 @@
|
|||||||
<small>@SharedLocalizer["Search.By"]:</small> <strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
|
<small>@SharedLocalizer["Search.By"]:</small> <strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
|
||||||
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
|
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
|
||||||
<br />
|
<br />
|
||||||
|
@if (_moduledefinitions.Exists(item => item.PackageName == context.PackageId))
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-info">@SharedLocalizer["Installed"]</button>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
@if (!string.IsNullOrEmpty(context.PackageUrl))
|
@if (!string.IsNullOrEmpty(context.PackageUrl))
|
||||||
{
|
{
|
||||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
|
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
|
||||||
@@ -109,6 +115,7 @@
|
|||||||
{
|
{
|
||||||
<a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
|
<a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
<br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -171,6 +178,7 @@
|
|||||||
|
|
||||||
@code {
|
@code {
|
||||||
private bool _initialized = false;
|
private bool _initialized = false;
|
||||||
|
private List<ModuleDefinition> _moduledefinitions;
|
||||||
private int _page = 1;
|
private int _page = 1;
|
||||||
private List<Package> _packages;
|
private List<Package> _packages;
|
||||||
private string _price = "free";
|
private string _price = "free";
|
||||||
@@ -187,7 +195,8 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await LoadModuleDefinitions();
|
_moduledefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
|
||||||
|
await LoadPackages();
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -197,24 +206,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadModuleDefinitions()
|
private async Task LoadPackages()
|
||||||
{
|
{
|
||||||
ShowProgressIndicator();
|
ShowProgressIndicator();
|
||||||
|
|
||||||
var moduledefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
|
|
||||||
_packages = await PackageService.GetPackagesAsync("module", _search, _price, "", _sort);
|
_packages = await PackageService.GetPackagesAsync("module", _search, _price, "", _sort);
|
||||||
|
|
||||||
if (_packages != null)
|
|
||||||
{
|
|
||||||
foreach (Package package in _packages.ToArray())
|
|
||||||
{
|
|
||||||
if (moduledefinitions.Exists(item => item.PackageName == package.PackageId))
|
|
||||||
{
|
|
||||||
_packages.Remove(package);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HideProgressIndicator();
|
HideProgressIndicator();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,25 +217,25 @@
|
|||||||
{
|
{
|
||||||
_price = price;
|
_price = price;
|
||||||
_sort = "popularity";
|
_sort = "popularity";
|
||||||
await LoadModuleDefinitions();
|
await LoadPackages();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Search()
|
private async Task Search()
|
||||||
{
|
{
|
||||||
await LoadModuleDefinitions();
|
await LoadPackages();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Reset()
|
private async Task Reset()
|
||||||
{
|
{
|
||||||
_page = 1;
|
_page = 1;
|
||||||
_search = "";
|
_search = "";
|
||||||
await LoadModuleDefinitions();
|
await LoadPackages();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Refresh()
|
private async Task Refresh()
|
||||||
{
|
{
|
||||||
await LoadModuleDefinitions();
|
await LoadPackages();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPageChange(int page)
|
private void OnPageChange(int page)
|
||||||
@@ -251,7 +246,7 @@
|
|||||||
private async void SortChanged(ChangeEventArgs e)
|
private async void SortChanged(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
_sort = (string)e.Value;
|
_sort = (string)e.Value;
|
||||||
await LoadModuleDefinitions();
|
await LoadPackages();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HideModal()
|
private void HideModal()
|
||||||
@@ -310,6 +305,6 @@
|
|||||||
|
|
||||||
private void OnUpload()
|
private void OnUpload()
|
||||||
{
|
{
|
||||||
AddModuleMessage(string.Format(Localizer["Success.Module.Download"], NavigateUrl("admin/system")), MessageType.Success);
|
AddModuleMessage(string.Format(Localizer["Success.Module.Upload"], NavigateUrl("admin/system")), MessageType.Success);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
@namespace Oqtane.Modules.Admin.ModuleDefinitions
|
@namespace Oqtane.Modules.Admin.ModuleDefinitions
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@using System.Text.RegularExpressions
|
@using System.Text.RegularExpressions
|
||||||
|
@using System.Reflection
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IModuleDefinitionService ModuleDefinitionService
|
@inject IModuleDefinitionService ModuleDefinitionService
|
||||||
@inject IModuleService ModuleService
|
@inject IModuleService ModuleService
|
||||||
@@ -42,6 +43,8 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if (_type == "External")
|
||||||
|
{
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
|
<Label Class="col-sm-3" For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -66,6 +69,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-success" @onclick="CreateModule">@Localizer["CreateModule"]</button>
|
<button type="button" class="btn btn-success" @onclick="CreateModule">@Localizer["CreateModule"]</button>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
@@ -80,9 +84,10 @@
|
|||||||
private string _description = string.Empty;
|
private string _description = string.Empty;
|
||||||
private List<Template> _templates;
|
private List<Template> _templates;
|
||||||
private string _template = "-";
|
private string _template = "-";
|
||||||
|
private string _minversion = "2.0.0";
|
||||||
|
private string _type = "";
|
||||||
private string[] _versions;
|
private string[] _versions;
|
||||||
private string _reference = "local";
|
private string _reference = "local";
|
||||||
private string _minversion = "2.0.0";
|
|
||||||
private string _location = string.Empty;
|
private string _location = string.Empty;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
@@ -93,6 +98,16 @@
|
|||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Info.Module.Development"], MessageType.Info);
|
AddModuleMessage(Localizer["Info.Module.Development"], MessageType.Info);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var entryAssemblyName = Assembly.GetEntryAssembly().GetName().Name;
|
||||||
|
if (entryAssemblyName.EndsWith(".Oqtane"))
|
||||||
|
{
|
||||||
|
// Oqtane Application assemblies end with .Server.Oqtane or .Client.Oqtane
|
||||||
|
string[] segments = entryAssemblyName.Split('.');
|
||||||
|
_owner = string.Join(".", segments, 0, segments.Length - 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
@@ -123,11 +138,18 @@
|
|||||||
if (string.IsNullOrEmpty(_description)) _description = _module;
|
if (string.IsNullOrEmpty(_description)) _description = _module;
|
||||||
if (IsValidXML(_description))
|
if (IsValidXML(_description))
|
||||||
{
|
{
|
||||||
|
if (_type == "Internal")
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Success.Module.Create.Internal"], MessageType.Success);
|
||||||
|
}
|
||||||
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
||||||
var moduleDefinition = new ModuleDefinition { Owner = _owner, Name = _module, Description = _description, Template = _template, Version = _reference, ModuleDefinitionName = template.Namespace };
|
var moduleDefinition = new ModuleDefinition { Owner = _owner, Name = _module, Description = _description, Template = _template, Version = _reference, ModuleDefinitionName = template.Namespace };
|
||||||
moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition);
|
moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition);
|
||||||
|
if (_type == "External")
|
||||||
|
{
|
||||||
GetLocation();
|
GetLocation();
|
||||||
AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success);
|
AddModuleMessage(string.Format(Localizer["Success.Module.Create.External"], NavigateUrl("admin/system")), MessageType.Success);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -153,7 +175,7 @@
|
|||||||
private bool IsValid(string name)
|
private bool IsValid(string name)
|
||||||
{
|
{
|
||||||
// must contain letters, underscores and digits and first character must be letter or underscore
|
// must contain letters, underscores and digits and first character must be letter or underscore
|
||||||
return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
|
return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_.]*$");
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsValidXML(string description)
|
private bool IsValidXML(string description)
|
||||||
@@ -165,11 +187,16 @@
|
|||||||
private void TemplateChanged(ChangeEventArgs e)
|
private void TemplateChanged(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
_template = (string)e.Value;
|
_template = (string)e.Value;
|
||||||
_minversion = "2.0.0";
|
|
||||||
if (_template != "-")
|
if (_template != "-")
|
||||||
{
|
{
|
||||||
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
||||||
_minversion = template.Version;
|
_minversion = template.Version;
|
||||||
|
_type = template.Type;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_minversion = "2.0.0";
|
||||||
|
_type = "";
|
||||||
}
|
}
|
||||||
GetLocation();
|
GetLocation();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
@if (_initialized)
|
@if (_initialized)
|
||||||
{
|
{
|
||||||
<TabStrip>
|
<TabStrip>
|
||||||
<TabPanel Name="Definition" ResourceKey="Definition" Heading="Definition">
|
<TabPanel Name="Module" ResourceKey="Module" Heading="Module">
|
||||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -46,6 +46,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<br />
|
||||||
<Section Name="Information" ResourceKey="Information">
|
<Section Name="Information" ResourceKey="Information">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -97,6 +98,12 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="fingerprint" HelpText="A unique identifier for the module's static resources. This value can be changed by clicking the Save option below (ie. cache busting)." ResourceKey="Fingerprint">Fingerprint: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="fingerprint" class="form-control" @bind="@_fingerprint" disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
<br />
|
<br />
|
||||||
@@ -231,16 +238,16 @@
|
|||||||
private string _url = "";
|
private string _url = "";
|
||||||
private string _contact = "";
|
private string _contact = "";
|
||||||
private string _license = "";
|
private string _license = "";
|
||||||
|
private string _fingerprint = "";
|
||||||
private List<Permission> _permissions = null;
|
private List<Permission> _permissions = null;
|
||||||
private string _createdby;
|
private string _createdby;
|
||||||
private DateTime _createdon;
|
private DateTime _createdon;
|
||||||
private string _modifiedby;
|
private string _modifiedby;
|
||||||
private DateTime _modifiedon;
|
private DateTime _modifiedon;
|
||||||
private List<Page> _pagesWithModules;
|
|
||||||
|
|
||||||
#pragma warning disable 649
|
|
||||||
private PermissionGrid _permissionGrid;
|
private PermissionGrid _permissionGrid;
|
||||||
#pragma warning restore 649
|
|
||||||
|
private List<Page> _pagesWithModules;
|
||||||
|
|
||||||
private List<Package> _packages;
|
private List<Package> _packages;
|
||||||
private List<Language> _languages;
|
private List<Language> _languages;
|
||||||
@@ -267,6 +274,7 @@
|
|||||||
_url = moduleDefinition.Url;
|
_url = moduleDefinition.Url;
|
||||||
_contact = moduleDefinition.Contact;
|
_contact = moduleDefinition.Contact;
|
||||||
_license = moduleDefinition.License;
|
_license = moduleDefinition.License;
|
||||||
|
_fingerprint = moduleDefinition.Fingerprint;
|
||||||
_permissions = moduleDefinition.PermissionList;
|
_permissions = moduleDefinition.PermissionList;
|
||||||
_createdby = moduleDefinition.CreatedBy;
|
_createdby = moduleDefinition.CreatedBy;
|
||||||
_createdon = moduleDefinition.CreatedOn;
|
_createdon = moduleDefinition.CreatedOn;
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ else
|
|||||||
<div class="row mb-3 align-items-center">
|
<div class="row mb-3 align-items-center">
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<ActionLink Action="Add" Text="Install Module" ResourceKey="InstallModule" />
|
<ActionLink Action="Add" Text="Install Module" ResourceKey="InstallModule" />
|
||||||
<ActionLink Action="Create" Text="Create Module" ResourceKey="CreateModule" Class="btn btn-secondary ps-2" />
|
<ActionLink Action="Create" Text="Create Module" ResourceKey="CreateModule" Class="btn btn-secondary ms-1" />
|
||||||
<button type="button" class="btn btn-secondary pw-2" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
|
<button type="button" class="btn btn-secondary ms-1" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<select class="form-select" @onchange="(e => CategoryChanged(e))">
|
<select class="form-select" @onchange="(e => CategoryChanged(e))">
|
||||||
@@ -130,7 +130,7 @@ else
|
|||||||
|
|
||||||
private async Task LoadModuleDefinitions()
|
private async Task LoadModuleDefinitions()
|
||||||
{
|
{
|
||||||
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(_category)).ToList();
|
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Split(',', StringSplitOptions.RemoveEmptyEntries).Contains(_category)).ToList();
|
||||||
_packages = await PackageService.GetPackageUpdatesAsync("module");
|
_packages = await PackageService.GetPackageUpdatesAsync("module");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,16 +73,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="page" HelpText="The page that the module is located on" ResourceKey="Page">Page: </Label>
|
<Label Class="col-sm-3" For="page" HelpText="The page that the module is located on. Please note that shared modules cannot be moved to other pages." ResourceKey="Page">Page: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="page" class="form-select" @bind="@_pageId" required>
|
@if (PageState.Page.UserId != null || _isShared)
|
||||||
@if (PageState.Page.UserId != null)
|
|
||||||
{
|
{
|
||||||
|
<select id="page" class="form-select" @bind="@_pageId" required disabled>
|
||||||
<option value="@PageState.Page.PageId">@(PageState.Page.Name)</option>
|
<option value="@PageState.Page.PageId">@(PageState.Page.Name)</option>
|
||||||
|
</select>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (_pages != null)
|
<select id="page" class="form-select" @bind="@_pageId" required>
|
||||||
|
@if (_pages != null)
|
||||||
{
|
{
|
||||||
foreach (Page p in _pages)
|
foreach (Page p in _pages)
|
||||||
{
|
{
|
||||||
@@ -92,8 +94,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
</select>
|
</select>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -161,6 +163,7 @@
|
|||||||
private string _pane;
|
private string _pane;
|
||||||
private string _containerType;
|
private string _containerType;
|
||||||
private string _allPages = "false";
|
private string _allPages = "false";
|
||||||
|
private bool _isShared = false;
|
||||||
private string _header = "";
|
private string _header = "";
|
||||||
private string _footer = "";
|
private string _footer = "";
|
||||||
private string _permissionNames = "";
|
private string _permissionNames = "";
|
||||||
@@ -207,6 +210,7 @@
|
|||||||
_expirydate = Utilities.UtcAsLocalDate(pagemodule.ExpiryDate);
|
_expirydate = Utilities.UtcAsLocalDate(pagemodule.ExpiryDate);
|
||||||
|
|
||||||
_allPages = pagemodule.Module.AllPages.ToString();
|
_allPages = pagemodule.Module.AllPages.ToString();
|
||||||
|
_isShared = pagemodule.Module.IsShared;
|
||||||
createdby = pagemodule.Module.CreatedBy;
|
createdby = pagemodule.Module.CreatedBy;
|
||||||
createdon = pagemodule.Module.CreatedOn;
|
createdon = pagemodule.Module.CreatedOn;
|
||||||
modifiedby = pagemodule.Module.ModifiedBy;
|
modifiedby = pagemodule.Module.ModifiedBy;
|
||||||
@@ -276,7 +280,6 @@
|
|||||||
var interop = new Interop(JSRuntime);
|
var interop = new Interop(JSRuntime);
|
||||||
if (await interop.FormValid(form))
|
if (await interop.FormValid(form))
|
||||||
{
|
{
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(_title))
|
if (!string.IsNullOrEmpty(_title))
|
||||||
{
|
{
|
||||||
if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate))
|
if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate))
|
||||||
@@ -284,7 +287,33 @@
|
|||||||
AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning);
|
AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update module settings first
|
||||||
|
if (_moduleSettingsType != null)
|
||||||
|
{
|
||||||
|
if (_moduleSettings is ISettingsControl moduleSettingsControl)
|
||||||
|
{
|
||||||
|
// module settings updated using explicit interface
|
||||||
|
await moduleSettingsControl.UpdateSettings();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// legacy approach - module settings updated by convention (ie. by calling a public method named "UpdateSettings" in settings component)
|
||||||
|
// this method should be removed however the ISettingsControl declaration was not added to the default module template until version 10.1.2
|
||||||
|
_moduleSettings?.GetType().GetMethod("UpdateSettings")?.Invoke(_moduleSettings, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update container settings
|
||||||
|
if (_containerSettingsType != null && _containerSettings is ISettingsControl containerSettingsControl)
|
||||||
|
{
|
||||||
|
await containerSettingsControl.UpdateSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
// update page module
|
||||||
var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
|
var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
|
||||||
|
var pageId = pagemodule.PageId; // preserve
|
||||||
|
var pane = pagemodule.Pane; // preserve
|
||||||
pagemodule.PageId = int.Parse(_pageId);
|
pagemodule.PageId = int.Parse(_pageId);
|
||||||
pagemodule.Title = _title;
|
pagemodule.Title = _title;
|
||||||
pagemodule.Pane = _pane;
|
pagemodule.Pane = _pane;
|
||||||
@@ -302,33 +331,21 @@
|
|||||||
pagemodule.Header = _header;
|
pagemodule.Header = _header;
|
||||||
pagemodule.Footer = _footer;
|
pagemodule.Footer = _footer;
|
||||||
await PageModuleService.UpdatePageModuleAsync(pagemodule);
|
await PageModuleService.UpdatePageModuleAsync(pagemodule);
|
||||||
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
|
|
||||||
|
|
||||||
|
// update page module order if page or pane changed
|
||||||
|
if (pageId != pagemodule.PageId || pane != pagemodule.Pane)
|
||||||
|
{
|
||||||
|
await PageModuleService.UpdatePageModuleOrderAsync(pageId, pane); // old page/pane
|
||||||
|
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); // new page/pane
|
||||||
|
}
|
||||||
|
|
||||||
|
// update module
|
||||||
var module = await ModuleService.GetModuleAsync(ModuleState.ModuleId);
|
var module = await ModuleService.GetModuleAsync(ModuleState.ModuleId);
|
||||||
module.AllPages = bool.Parse(_allPages);
|
module.AllPages = bool.Parse(_allPages);
|
||||||
module.PageModuleId = ModuleState.PageModuleId;
|
module.PageModuleId = ModuleState.PageModuleId;
|
||||||
module.PermissionList = _permissionGrid.GetPermissionList();
|
module.PermissionList = _permissionGrid.GetPermissionList();
|
||||||
await ModuleService.UpdateModuleAsync(module);
|
await ModuleService.UpdateModuleAsync(module);
|
||||||
|
|
||||||
if (_moduleSettingsType != null)
|
|
||||||
{
|
|
||||||
if (_moduleSettings is ISettingsControl moduleSettingsControl)
|
|
||||||
{
|
|
||||||
// module settings updated using explicit interface
|
|
||||||
await moduleSettingsControl.UpdateSettings();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// legacy support - module settings updated by convention ( ie. by calling a public method named "UpdateSettings" in settings component )
|
|
||||||
_moduleSettings?.GetType().GetMethod("UpdateSettings")?.Invoke(_moduleSettings, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_containerSettingsType != null && _containerSettings is ISettingsControl containerSettingsControl)
|
|
||||||
{
|
|
||||||
await containerSettingsControl.UpdateSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
|
<Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="name" class="form-control" @bind="@_name" maxlength="50" required />
|
<input id="name" class="form-control" @bind="@_name" maxlength="100" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
@@ -81,21 +81,9 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
|
<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">
|
<div class="col-sm-9">
|
||||||
<select id="navigation" class="form-select" @bind="@_isnavigation" required>
|
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
||||||
<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="clickable" HelpText="Select whether the link in the site navigation is enabled or disabled" ResourceKey="Clickable">Clickable? </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<select id="clickable" class="form-select" @bind="@_isclickable" 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">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -110,27 +98,6 @@
|
|||||||
<input id="url" class="form-control" @bind="@_url" maxlength="500" />
|
<input id="url" class="form-control" @bind="@_url" maxlength="500" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<InputList Value="@_icon" ValueChanged="IconChanged" DataList="@_icons" ResourceKey="Icon" ResourceType="@_iconresources" />
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-1">
|
|
||||||
<i class="@_icon"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this page is active" ResourceKey="EffectiveDate">Effective Date: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this page expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label>
|
<Label Class="col-sm-3" For="personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -141,15 +108,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Section Name="Theme" Heading="Theme" ResourceKey="Theme">
|
||||||
<Section Name="Appearance" ResourceKey="Appearance" Heading=@Localizer["Appearance.Name"]>
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
|
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -181,6 +141,49 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</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="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<InputList Value="@_icon" ValueChanged="IconChanged" DataList="@_icons" ResourceKey="Icon" ResourceType="@_iconresources" />
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-1">
|
||||||
|
<i class="@_icon"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="navigation" class="form-select" @bind="@_isnavigation" 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="clickable" HelpText="Select whether the link in the site navigation is enabled or disabled" ResourceKey="Clickable">Clickable? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="clickable" class="form-select" @bind="@_isclickable" 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="effectiveDate" HelpText="The date that this page is active" ResourceKey="EffectiveDate">Effective Date: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this page expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
<Section Name="PageContent" ResourceKey="PageContent" Heading=@Localizer["PageContent.Heading"]>
|
<Section Name="PageContent" ResourceKey="PageContent" Heading=@Localizer["PageContent.Heading"]>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -269,8 +272,16 @@
|
|||||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) || (_parent != null && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, _parent.PermissionList)))
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) || (_parent != null && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, _parent.PermissionList)))
|
||||||
{
|
{
|
||||||
_themetype = PageState.Site.DefaultThemeType;
|
_themetype = PageState.Site.DefaultThemeType;
|
||||||
_themes = ThemeService.GetThemeControls(PageState.Site.Themes);
|
var themes = new List<Theme>();
|
||||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
foreach (var theme in PageState.Site.Themes)
|
||||||
|
{
|
||||||
|
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Utilize, theme.PermissionList))
|
||||||
|
{
|
||||||
|
themes.Add(theme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_themes = ThemeService.GetThemeControls(themes);
|
||||||
|
_containers = ThemeService.GetContainerControls(themes, _themetype);
|
||||||
_containertype = PageState.Site.DefaultContainerType;
|
_containertype = PageState.Site.DefaultContainerType;
|
||||||
_children = new List<Page>();
|
_children = new List<Page>();
|
||||||
foreach (Page p in _pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
|
foreach (Page p in _pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
|
<Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="name" class="form-control" @bind="@_name" maxlength="50" required />
|
<input id="name" class="form-control" @bind="@_name" maxlength="100" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
<Label Class="col-sm-3" For="move" HelpText="Select the location where you would like the page to be moved in relation to other pages" ResourceKey="Move">Move: </Label>
|
<Label Class="col-sm-3" For="move" HelpText="Select the location where you would like the page to be moved in relation to other pages" ResourceKey="Move">Move: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="move" class="form-select" @bind="@_insert" required>
|
<select id="move" class="form-select" @bind="@_insert" required>
|
||||||
@if (_parentid == _currentparentid)
|
@if (_parentid == _currentparentid && !_copy)
|
||||||
{
|
{
|
||||||
<option value="="><@Localizer["ThisLocation.Keep"]></option>
|
<option value="="><@Localizer["ThisLocation.Keep"]></option>
|
||||||
}
|
}
|
||||||
@@ -98,21 +98,9 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
|
<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">
|
<div class="col-sm-9">
|
||||||
<select id="navigation" class="form-select" @bind="@_isnavigation" required>
|
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
||||||
<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="clickable" HelpText="Select whether the link in the site navigation is enabled or disabled" ResourceKey="Clickable">Clickable? </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<select id="clickable" class="form-select" @bind="@_isclickable" 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">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -127,27 +115,6 @@
|
|||||||
<input id="url" class="form-control" @bind="@_url" maxlength="500" />
|
<input id="url" class="form-control" @bind="@_url" maxlength="500" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<InputList Value="@_icon" ValueChanged="IconChanged" DataList="@_icons" ResourceKey="Icon" ResourceType="@_iconresources" />
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-1">
|
|
||||||
<i class="@_icon"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this page is active" ResourceKey="EffectiveDate">Effective Date: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this page expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label>
|
<Label Class="col-sm-3" For="personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -158,14 +125,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Section Name="Appearance" ResourceKey="Appearance" Heading="Appearance">
|
<Section Name="Theme" ResourceKey="Theme" Heading="Theme">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
|
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -200,6 +161,49 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
|
<Section Name="Appearance" ResourceKey="Appearance" Heading="Appearance">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<InputList Value="@_icon" ValueChanged="IconChanged" DataList="@_icons" ResourceKey="Icon" ResourceType="@_iconresources" />
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-1">
|
||||||
|
<i class="@_icon"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="navigation" class="form-select" @bind="@_isnavigation" 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="clickable" HelpText="Select whether the link in the site navigation is enabled or disabled" ResourceKey="Clickable">Clickable? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="clickable" class="form-select" @bind="@_isclickable" 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="effectiveDate" HelpText="The date that this page is active" ResourceKey="EffectiveDate">Effective Date: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this page expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
<Section Name="PageContent" Heading="Page Content" ResourceKey="PageContent">
|
<Section Name="PageContent" Heading="Page Content" ResourceKey="PageContent">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -221,7 +225,10 @@
|
|||||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
|
@if (!_copy)
|
||||||
|
{
|
||||||
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
|
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
|
||||||
|
}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Permissions" ResourceKey="Permissions" Heading="Permissions">
|
<TabPanel Name="Permissions" ResourceKey="Permissions" Heading="Permissions">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@@ -243,6 +250,8 @@
|
|||||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
</div>
|
</div>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
@if (!_copy)
|
||||||
|
{
|
||||||
<TabPanel Name="PageModules" Heading="Modules" ResourceKey="PageModules">
|
<TabPanel Name="PageModules" Heading="Modules" ResourceKey="PageModules">
|
||||||
<Pager Items="_pageModules">
|
<Pager Items="_pageModules">
|
||||||
<Header>
|
<Header>
|
||||||
@@ -268,6 +277,8 @@
|
|||||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
</TabStrip>
|
</TabStrip>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -345,6 +356,7 @@
|
|||||||
private List<ThemeControl> _containers = new List<ThemeControl>();
|
private List<ThemeControl> _containers = new List<ThemeControl>();
|
||||||
private List<Page> _pages;
|
private List<Page> _pages;
|
||||||
private int _pageId;
|
private int _pageId;
|
||||||
|
private bool _copy = false;
|
||||||
private string _name;
|
private string _name;
|
||||||
private string _currentparentid;
|
private string _currentparentid;
|
||||||
private string _parentid = "-1";
|
private string _parentid = "-1";
|
||||||
@@ -390,6 +402,10 @@
|
|||||||
{
|
{
|
||||||
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||||
_pageId = Int32.Parse(PageState.QueryString["id"]);
|
_pageId = Int32.Parse(PageState.QueryString["id"]);
|
||||||
|
if (PageState.QueryString.ContainsKey("copy"))
|
||||||
|
{
|
||||||
|
_copy = bool.Parse(PageState.QueryString["copy"]);
|
||||||
|
}
|
||||||
_page = await PageService.GetPageAsync(_pageId);
|
_page = await PageService.GetPageAsync(_pageId);
|
||||||
_icons = await SystemService.GetIconsAsync();
|
_icons = await SystemService.GetIconsAsync();
|
||||||
_iconresources = Utilities.GetFullTypeName(typeof(IconResources).AssemblyQualifiedName);
|
_iconresources = Utilities.GetFullTypeName(typeof(IconResources).AssemblyQualifiedName);
|
||||||
@@ -409,7 +425,7 @@
|
|||||||
_children = new List<Page>();
|
_children = new List<Page>();
|
||||||
foreach (Page p in _pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid, CultureInfo.InvariantCulture))))
|
foreach (Page p in _pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid, CultureInfo.InvariantCulture))))
|
||||||
{
|
{
|
||||||
if (p.PageId != _pageId && UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
if ((p.PageId != _pageId || _copy) && UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
||||||
{
|
{
|
||||||
_children.Add(p);
|
_children.Add(p);
|
||||||
}
|
}
|
||||||
@@ -436,6 +452,12 @@
|
|||||||
_expirydate = Utilities.UtcAsLocalDate(_page.ExpiryDate);
|
_expirydate = Utilities.UtcAsLocalDate(_page.ExpiryDate);
|
||||||
_ispersonalizable = _page.IsPersonalizable.ToString();
|
_ispersonalizable = _page.IsPersonalizable.ToString();
|
||||||
|
|
||||||
|
if (_copy)
|
||||||
|
{
|
||||||
|
_insert = ">";
|
||||||
|
_childid = _page.PageId;
|
||||||
|
}
|
||||||
|
|
||||||
// appearance
|
// appearance
|
||||||
_title = _page.Title;
|
_title = _page.Title;
|
||||||
_themetype = _page.ThemeType;
|
_themetype = _page.ThemeType;
|
||||||
@@ -443,8 +465,16 @@
|
|||||||
{
|
{
|
||||||
_themetype = PageState.Site.DefaultThemeType;
|
_themetype = PageState.Site.DefaultThemeType;
|
||||||
}
|
}
|
||||||
_themes = ThemeService.GetThemeControls(PageState.Site.Themes);
|
var themes = new List<Theme>();
|
||||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
foreach (var theme in PageState.Site.Themes)
|
||||||
|
{
|
||||||
|
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Utilize, theme.PermissionList))
|
||||||
|
{
|
||||||
|
themes.Add(theme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_themes = ThemeService.GetThemeControls(themes);
|
||||||
|
_containers = ThemeService.GetContainerControls(themes, _themetype);
|
||||||
_containertype = _page.DefaultContainerType;
|
_containertype = _page.DefaultContainerType;
|
||||||
if (string.IsNullOrEmpty(_containertype))
|
if (string.IsNullOrEmpty(_containertype))
|
||||||
{
|
{
|
||||||
@@ -458,6 +488,19 @@
|
|||||||
// permissions
|
// permissions
|
||||||
_permissions = _page.PermissionList;
|
_permissions = _page.PermissionList;
|
||||||
_updatemodulepermissions = "True";
|
_updatemodulepermissions = "True";
|
||||||
|
if (_copy)
|
||||||
|
{
|
||||||
|
_permissions = _page.PermissionList.Select(item => new Permission
|
||||||
|
{
|
||||||
|
SiteId = item.SiteId,
|
||||||
|
EntityName = item.EntityName,
|
||||||
|
EntityId = -1,
|
||||||
|
PermissionName = item.PermissionName,
|
||||||
|
RoleName = item.RoleName,
|
||||||
|
UserId = item.UserId,
|
||||||
|
IsAuthorized = item.IsAuthorized,
|
||||||
|
}).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
// page modules
|
// page modules
|
||||||
var modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
|
var modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
|
||||||
@@ -472,6 +515,13 @@
|
|||||||
_deletedon = _page.DeletedOn;
|
_deletedon = _page.DeletedOn;
|
||||||
|
|
||||||
ThemeSettings();
|
ThemeSettings();
|
||||||
|
|
||||||
|
if (_copy)
|
||||||
|
{
|
||||||
|
_name = "";
|
||||||
|
_path = "";
|
||||||
|
}
|
||||||
|
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -564,10 +614,18 @@
|
|||||||
await ScrollToPageTop();
|
await ScrollToPageTop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(_themetype) && _containertype != "-")
|
if (!string.IsNullOrEmpty(_themetype) && _containertype != "-")
|
||||||
{
|
{
|
||||||
string currentPath = _page.Path;
|
string currentPath = _page.Path;
|
||||||
|
|
||||||
|
if (_copy)
|
||||||
|
{
|
||||||
|
_page = new Page();
|
||||||
|
_page.SiteId = PageState.Site.SiteId;
|
||||||
|
currentPath = "";
|
||||||
|
}
|
||||||
|
|
||||||
_page.Name = _name;
|
_page.Name = _name;
|
||||||
|
|
||||||
if (_parentid == "-1")
|
if (_parentid == "-1")
|
||||||
@@ -623,6 +681,13 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update theme settings
|
||||||
|
if (_themeSettingsType != null && _themeSettings is ISettingsControl themeSettingsControl)
|
||||||
|
{
|
||||||
|
await themeSettingsControl.UpdateSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
// default page properties
|
||||||
if (_insert != "=")
|
if (_insert != "=")
|
||||||
{
|
{
|
||||||
Page child;
|
Page child;
|
||||||
@@ -676,7 +741,21 @@
|
|||||||
_page.UpdateModulePermissions = bool.Parse(_updatemodulepermissions);
|
_page.UpdateModulePermissions = bool.Parse(_updatemodulepermissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_copy)
|
||||||
|
{
|
||||||
|
// create page
|
||||||
|
_page = await PageService.AddPageAsync(_page);
|
||||||
|
await PageService.CopyPageAsync(_pageId, _page.PageId, bool.Parse(_updatemodulepermissions));
|
||||||
|
await logger.LogInformation("Page Added {Page}", _page);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// update page
|
||||||
_page = await PageService.UpdatePageAsync(_page);
|
_page = await PageService.UpdatePageAsync(_page);
|
||||||
|
await logger.LogInformation("Page Saved {Page}", _page);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update page order
|
||||||
await PageService.UpdatePageOrderAsync(_page.SiteId, _page.PageId, _page.ParentId);
|
await PageService.UpdatePageOrderAsync(_page.SiteId, _page.PageId, _page.ParentId);
|
||||||
if (_currentparentid == string.Empty)
|
if (_currentparentid == string.Empty)
|
||||||
{
|
{
|
||||||
@@ -687,12 +766,6 @@
|
|||||||
await PageService.UpdatePageOrderAsync(_page.SiteId, _page.PageId, int.Parse(_currentparentid));
|
await PageService.UpdatePageOrderAsync(_page.SiteId, _page.PageId, int.Parse(_currentparentid));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_themeSettingsType != null && _themeSettings is ISettingsControl themeSettingsControl)
|
|
||||||
{
|
|
||||||
await themeSettingsControl.UpdateSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
await logger.LogInformation("Page Saved {Page}", _page);
|
|
||||||
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
|
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(PageState.ReturnUrl, true); // redirect to page being edited and reload
|
NavigationManager.NavigateTo(PageState.ReturnUrl, true); // redirect to page being edited and reload
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IProfileService ProfileService
|
@inject IProfileService ProfileService
|
||||||
|
@inject ISettingService SettingService
|
||||||
@inject IStringLocalizer<Edit> Localizer
|
@inject IStringLocalizer<Edit> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@@ -56,9 +57,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="options" HelpText="A comma delimited list of options the user can select from" ResourceKey="Options">Options: </Label>
|
<Label Class="col-sm-3" For="options" HelpText="A comma delimited list of options. Options can contain a key and value if they are seperated by a colon (ie. key:value). You can also dynamically load your options from Settings." ResourceKey="Options">Options: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
@if (_optiontype == "Settings")
|
||||||
|
{
|
||||||
<input id="options" class="form-control" @bind="@_options" maxlength="2000" />
|
<input id="options" class="form-control" @bind="@_options" maxlength="2000" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<select id="entityName" class="form-select" @bind="@_options">
|
||||||
|
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||||
|
@foreach (var entityname in _entitynames)
|
||||||
|
{
|
||||||
|
<option value="@entityname">@entityname</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
}
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="ToggleOptionType">@Localizer[_optiontype]</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -116,6 +133,8 @@
|
|||||||
private string _rows = "1";
|
private string _rows = "1";
|
||||||
private string _defaultvalue = string.Empty;
|
private string _defaultvalue = string.Empty;
|
||||||
private string _options = string.Empty;
|
private string _options = string.Empty;
|
||||||
|
private string _optiontype = "Settings";
|
||||||
|
private List<string> _entitynames;
|
||||||
private string _validation = string.Empty;
|
private string _validation = string.Empty;
|
||||||
private string _autocomplete = string.Empty;
|
private string _autocomplete = string.Empty;
|
||||||
private string _isrequired = "False";
|
private string _isrequired = "False";
|
||||||
@@ -133,6 +152,8 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
_entitynames = await SettingService.GetEntityNamesAsync();
|
||||||
|
|
||||||
if (PageState.QueryString.ContainsKey("id"))
|
if (PageState.QueryString.ContainsKey("id"))
|
||||||
{
|
{
|
||||||
_profileid = Int32.Parse(PageState.QueryString["id"]);
|
_profileid = Int32.Parse(PageState.QueryString["id"]);
|
||||||
@@ -148,6 +169,11 @@
|
|||||||
_rows = profile.Rows.ToString();
|
_rows = profile.Rows.ToString();
|
||||||
_defaultvalue = profile.DefaultValue;
|
_defaultvalue = profile.DefaultValue;
|
||||||
_options = profile.Options;
|
_options = profile.Options;
|
||||||
|
if (_options.StartsWith("EntityName:"))
|
||||||
|
{
|
||||||
|
_optiontype = "Options";
|
||||||
|
_options = _options.Substring(11);
|
||||||
|
}
|
||||||
_validation = profile.Validation;
|
_validation = profile.Validation;
|
||||||
_autocomplete = profile.Autocomplete;
|
_autocomplete = profile.Autocomplete;
|
||||||
_isrequired = profile.IsRequired.ToString();
|
_isrequired = profile.IsRequired.ToString();
|
||||||
@@ -166,6 +192,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ToggleOptionType()
|
||||||
|
{
|
||||||
|
if (_optiontype == "Options")
|
||||||
|
{
|
||||||
|
_optiontype = "Settings";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_optiontype = "Options";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task SaveProfile()
|
private async Task SaveProfile()
|
||||||
{
|
{
|
||||||
validated = true;
|
validated = true;
|
||||||
@@ -193,7 +231,14 @@
|
|||||||
profile.MaxLength = int.Parse(_maxlength);
|
profile.MaxLength = int.Parse(_maxlength);
|
||||||
profile.Rows = int.Parse(_rows);
|
profile.Rows = int.Parse(_rows);
|
||||||
profile.DefaultValue = _defaultvalue;
|
profile.DefaultValue = _defaultvalue;
|
||||||
|
if (_optiontype == "Options" && !string.IsNullOrEmpty(_options))
|
||||||
|
{
|
||||||
|
profile.Options = "EntityName:" + _options;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
profile.Options = _options;
|
profile.Options = _options;
|
||||||
|
}
|
||||||
profile.Validation = _validation;
|
profile.Validation = _validation;
|
||||||
profile.Autocomplete = _autocomplete;
|
profile.Autocomplete = _autocomplete;
|
||||||
profile.IsRequired = (_isrequired == null ? false : Boolean.Parse(_isrequired));
|
profile.IsRequired = (_isrequired == null ? false : Boolean.Parse(_isrequired));
|
||||||
|
|||||||
@@ -3,10 +3,11 @@
|
|||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IUserService UserService
|
@inject IUserService UserService
|
||||||
|
@inject ISettingService SettingService
|
||||||
@inject ITimeZoneService TimeZoneService
|
@inject ITimeZoneService TimeZoneService
|
||||||
|
@inject ILanguageService LanguageService
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
@inject ISettingService SettingService
|
|
||||||
|
|
||||||
@if (_initialized)
|
@if (_initialized)
|
||||||
{
|
{
|
||||||
@@ -71,6 +72,18 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="culture" HelpText="Your preferred language. Note that you will only be able to choose from languages supported on this site." ResourceKey="CultureCode">Language:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="culture" class="form-select" @bind="@_culturecode">
|
||||||
|
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||||
|
@foreach (var language in _languages)
|
||||||
|
{
|
||||||
|
<option value="@language.Code">@language.Name</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<button type="button" class="btn btn-primary" @onclick="Register">@Localizer["Register"]</button>
|
<button type="button" class="btn btn-primary" @onclick="Register">@Localizer["Register"]</button>
|
||||||
@@ -95,6 +108,8 @@
|
|||||||
@code {
|
@code {
|
||||||
private bool _initialized = false;
|
private bool _initialized = false;
|
||||||
private List<Models.TimeZone> _timezones;
|
private List<Models.TimeZone> _timezones;
|
||||||
|
private IEnumerable<Models.Language> _languages;
|
||||||
|
|
||||||
private string _passwordrequirements;
|
private string _passwordrequirements;
|
||||||
private string _username = string.Empty;
|
private string _username = string.Empty;
|
||||||
private ElementReference form;
|
private ElementReference form;
|
||||||
@@ -106,6 +121,7 @@
|
|||||||
private string _email = string.Empty;
|
private string _email = string.Empty;
|
||||||
private string _displayname = string.Empty;
|
private string _displayname = string.Empty;
|
||||||
private string _timezoneid = string.Empty;
|
private string _timezoneid = string.Empty;
|
||||||
|
private string _culturecode = string.Empty;
|
||||||
private bool _userCreated = false;
|
private bool _userCreated = false;
|
||||||
private bool _allowsitelogin = true;
|
private bool _allowsitelogin = true;
|
||||||
|
|
||||||
@@ -113,10 +129,13 @@
|
|||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
|
_timezones = TimeZoneService.GetTimeZones();
|
||||||
|
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
|
||||||
|
|
||||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||||
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
|
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
|
||||||
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
|
||||||
_timezoneid = PageState.Site.TimeZoneId;
|
_timezoneid = PageState.Site.TimeZoneId;
|
||||||
|
_culturecode = PageState.Site.CultureCode;
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,6 +166,7 @@
|
|||||||
Email = _email,
|
Email = _email,
|
||||||
DisplayName = (_displayname == string.Empty ? _username : _displayname),
|
DisplayName = (_displayname == string.Empty ? _username : _displayname),
|
||||||
TimeZoneId = _timezoneid,
|
TimeZoneId = _timezoneid,
|
||||||
|
CultureCode = _culturecode,
|
||||||
PhotoFileId = null
|
PhotoFileId = null
|
||||||
};
|
};
|
||||||
user = await UserService.AddUserAsync(user);
|
user = await UserService.AddUserAsync(user);
|
||||||
|
|||||||
@@ -40,13 +40,16 @@
|
|||||||
{
|
{
|
||||||
<Pager Items="@_searchResults?.Results"
|
<Pager Items="@_searchResults?.Results"
|
||||||
Format="Grid"
|
Format="Grid"
|
||||||
|
PageSize="@_pageSize"
|
||||||
|
DisplayPages="@_displayPages"
|
||||||
|
CurrentPage="@_currentPage"
|
||||||
Columns="1"
|
Columns="1"
|
||||||
Toolbar="Bottom"
|
Toolbar="Bottom"
|
||||||
Parameters="@($"q={_keywords}")">
|
Parameters="@($"q={_keywords}")">
|
||||||
<Row>
|
<Row>
|
||||||
<div class="search-item mb-2">
|
<div class="search-item mb-2">
|
||||||
<h4 class="mb-1"><a href="@context.Url">@context.Title</a></h4>
|
<h4 class="mb-1"><a href="@context.Url">@context.Title</a></h4>
|
||||||
<p class="mb-0 text-muted">@((MarkupString)context.Snippet)</p>
|
<p class="mb-0 text-body-secondary">@((MarkupString)context.Snippet)</p>
|
||||||
</div>
|
</div>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
@@ -66,6 +69,7 @@
|
|||||||
|
|
||||||
@code {
|
@code {
|
||||||
public override string RenderMode => RenderModes.Static;
|
public override string RenderMode => RenderModes.Static;
|
||||||
|
private const string SearchDefaultPageSize = "10";
|
||||||
|
|
||||||
private string _includeEntities;
|
private string _includeEntities;
|
||||||
private string _excludeEntities;
|
private string _excludeEntities;
|
||||||
@@ -75,6 +79,8 @@
|
|||||||
private string _sortField;
|
private string _sortField;
|
||||||
private string _sortOrder;
|
private string _sortOrder;
|
||||||
private string _bodyLength;
|
private string _bodyLength;
|
||||||
|
private string _currentPage = "0";
|
||||||
|
private string _displayPages = "7";
|
||||||
|
|
||||||
private string _keywords;
|
private string _keywords;
|
||||||
private SearchResults _searchResults;
|
private SearchResults _searchResults;
|
||||||
@@ -89,11 +95,16 @@
|
|||||||
_excludeEntities = SettingService.GetSetting(ModuleState.Settings, "SearchResults_ExcludeEntities", "");
|
_excludeEntities = SettingService.GetSetting(ModuleState.Settings, "SearchResults_ExcludeEntities", "");
|
||||||
_fromDate = SettingService.GetSetting(ModuleState.Settings, "SearchResults_FromDate", DateTime.MinValue.ToString());
|
_fromDate = SettingService.GetSetting(ModuleState.Settings, "SearchResults_FromDate", DateTime.MinValue.ToString());
|
||||||
_toDate = SettingService.GetSetting(ModuleState.Settings, "SearchResults_ToDate", DateTime.MaxValue.ToString());
|
_toDate = SettingService.GetSetting(ModuleState.Settings, "SearchResults_ToDate", DateTime.MaxValue.ToString());
|
||||||
_pageSize = SettingService.GetSetting(ModuleState.Settings, "SearchResults_PageSize", int.MaxValue.ToString());
|
_pageSize = SettingService.GetSetting(ModuleState.Settings, "SearchResults_PageSize", SearchDefaultPageSize);
|
||||||
_sortField = SettingService.GetSetting(ModuleState.Settings, "SearchResults_SortField", "Relevance");
|
_sortField = SettingService.GetSetting(ModuleState.Settings, "SearchResults_SortField", "Relevance");
|
||||||
_sortOrder = SettingService.GetSetting(ModuleState.Settings, "SearchResults_SortOrder", "Descending");
|
_sortOrder = SettingService.GetSetting(ModuleState.Settings, "SearchResults_SortOrder", "Descending");
|
||||||
_bodyLength = SettingService.GetSetting(ModuleState.Settings, "SearchResults_BodyLength", "255");
|
_bodyLength = SettingService.GetSetting(ModuleState.Settings, "SearchResults_BodyLength", "255");
|
||||||
|
|
||||||
|
if (PageState.QueryString.ContainsKey("p"))
|
||||||
|
{
|
||||||
|
_currentPage = PageState.QueryString["p"];
|
||||||
|
}
|
||||||
|
|
||||||
if (_keywords == null && PageState.QueryString.ContainsKey("q"))
|
if (_keywords == null && PageState.QueryString.ContainsKey("q"))
|
||||||
{
|
{
|
||||||
_keywords = WebUtility.UrlDecode(PageState.QueryString["q"]);
|
_keywords = WebUtility.UrlDecode(PageState.QueryString["q"]);
|
||||||
@@ -122,7 +133,7 @@
|
|||||||
ExcludeEntities = _excludeEntities,
|
ExcludeEntities = _excludeEntities,
|
||||||
FromDate = (!string.IsNullOrEmpty(_fromDate)) ? DateTime.Parse(_fromDate) : DateTime.MinValue,
|
FromDate = (!string.IsNullOrEmpty(_fromDate)) ? DateTime.Parse(_fromDate) : DateTime.MinValue,
|
||||||
ToDate = (!string.IsNullOrEmpty(_toDate)) ? DateTime.Parse(_toDate) : DateTime.MaxValue,
|
ToDate = (!string.IsNullOrEmpty(_toDate)) ? DateTime.Parse(_toDate) : DateTime.MaxValue,
|
||||||
PageSize = (!string.IsNullOrEmpty(_pageSize)) ? int.Parse(_pageSize) : int.MaxValue,
|
PageSize = int.MaxValue,
|
||||||
PageIndex = 0,
|
PageIndex = 0,
|
||||||
SortField = (!string.IsNullOrEmpty(_sortField)) ? (SearchSortField)Enum.Parse(typeof(SearchSortField), _sortField) : SearchSortField.Relevance,
|
SortField = (!string.IsNullOrEmpty(_sortField)) ? (SearchSortField)Enum.Parse(typeof(SearchSortField), _sortField) : SearchSortField.Relevance,
|
||||||
SortOrder = (!string.IsNullOrEmpty(_sortOrder)) ? (SearchSortOrder)Enum.Parse(typeof(SearchSortOrder), _sortOrder) : SearchSortOrder.Descending,
|
SortOrder = (!string.IsNullOrEmpty(_sortOrder)) ? (SearchSortOrder)Enum.Parse(typeof(SearchSortOrder), _sortOrder) : SearchSortOrder.Descending,
|
||||||
|
|||||||
226
Oqtane.Client/Modules/Admin/Settings/Add.razor
Normal file
226
Oqtane.Client/Modules/Admin/Settings/Add.razor
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
@namespace Oqtane.Modules.Admin.Settings
|
||||||
|
@inherits ModuleBase
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject ISettingService SettingService
|
||||||
|
@inject IStringLocalizer<Edit> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="entityName" HelpText="Entity Name" ResourceKey="EntityName">Entity:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
@if (_entityNameElement == "input")
|
||||||
|
{
|
||||||
|
<input id="entityName" class="form-control" @bind="@_entityName" maxlength="256" required />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<select class="form-select custom-select" value="@_entityName" @onchange="(e => EntityNameChanged(e))">
|
||||||
|
<option value="-"><@Localizer["Select Entity"]></option>
|
||||||
|
@foreach (var entityName in _entityNames)
|
||||||
|
{
|
||||||
|
<option value="@entityName">@entityName</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
}
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@EntityNameClicked" tabindex="-1">@_entityNameTitle</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="entityId" HelpText="Entity Id" ResourceKey="EntityId">Id:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
@if (_entityIdElement == "input")
|
||||||
|
{
|
||||||
|
<input id="entityId" class="form-control" @bind="@_entityId" maxlength="256" required />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<select class="form-select custom-select" @bind="@_entityId">
|
||||||
|
<option value="-"><@Localizer["Select Id"]></option>
|
||||||
|
@foreach (var entityId in _entityIds)
|
||||||
|
{
|
||||||
|
<option value="@entityId">@entityId</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
}
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@EntityIdClicked" tabindex="-1">@_entityIdTitle</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="settingName" HelpText="Setting Name" ResourceKey="SettingName">Name:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="settingName" class="form-control" @bind="@_settingName" maxlength="256" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="settingValue" HelpText="Setting Value" ResourceKey="SettingValue">Value:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="SettingValue" class="form-control" @bind="@_settingValue" maxlength="256" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="isPrivate" HelpText="Private" ResourceKey="IsPrivate">Private?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="isPrivate" class="form-select" @bind="@_isPrivate">
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br /><br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SaveSetting">@SharedLocalizer["Save"]</button>
|
||||||
|
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private ElementReference form;
|
||||||
|
private bool validated = false;
|
||||||
|
|
||||||
|
private string _entityName = "-";
|
||||||
|
private List<string> _entityNames = new List<string>();
|
||||||
|
private string _entityNameElement = "select";
|
||||||
|
private string _entityNameTitle = "";
|
||||||
|
private string _entityId = "-";
|
||||||
|
private List<int> _entityIds = new List<int>();
|
||||||
|
private string _entityIdElement = "select";
|
||||||
|
private string _entityIdTitle = "";
|
||||||
|
private string _settingName = "";
|
||||||
|
private string _settingValue = "";
|
||||||
|
private string _isPrivate = "True";
|
||||||
|
|
||||||
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_entityNameTitle = Localizer["Input"];
|
||||||
|
_entityIdTitle = Localizer["Input"];
|
||||||
|
|
||||||
|
// default entity names
|
||||||
|
_entityNames.Add(EntityNames.Host);
|
||||||
|
_entityNames.Add(EntityNames.Job);
|
||||||
|
_entityNames.Add(EntityNames.ModuleDefinition);
|
||||||
|
_entityNames.Add(EntityNames.Theme);
|
||||||
|
_entityNames.Add(EntityNames.Tenant);
|
||||||
|
_entityNames.Add(EntityNames.Site);
|
||||||
|
_entityNames.Add(EntityNames.Role);
|
||||||
|
_entityNames.Add(EntityNames.Page);
|
||||||
|
_entityNames.Add(EntityNames.Module);
|
||||||
|
_entityNames.Add(EntityNames.Folder);
|
||||||
|
_entityNames.Add(EntityNames.User);
|
||||||
|
_entityNames.Add(EntityNames.Visitor);
|
||||||
|
|
||||||
|
// custom entity names
|
||||||
|
var entityNames = await SettingService.GetEntityNamesAsync();
|
||||||
|
foreach (var entityName in entityNames)
|
||||||
|
{
|
||||||
|
if (!_entityNames.Contains(entityName))
|
||||||
|
{
|
||||||
|
_entityNames.Add(entityName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Loading Setting {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.LoadSetting"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EntityNameClicked()
|
||||||
|
{
|
||||||
|
if (_entityNameElement == "select")
|
||||||
|
{
|
||||||
|
_entityName = "";
|
||||||
|
_entityNameElement = "input";
|
||||||
|
_entityNameTitle = Localizer["Select"];
|
||||||
|
_entityId = "";
|
||||||
|
_entityIdElement = "input";
|
||||||
|
_entityIdTitle = Localizer["Select"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_entityName = "-";
|
||||||
|
_entityNameElement = "select";
|
||||||
|
_entityNameTitle = Localizer["Input"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EntityIdClicked()
|
||||||
|
{
|
||||||
|
if (_entityIdElement == "select")
|
||||||
|
{
|
||||||
|
_entityId = "";
|
||||||
|
_entityIdElement = "input";
|
||||||
|
_entityIdTitle = Localizer["Select"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_entityId = "-";
|
||||||
|
_entityIdElement = "select";
|
||||||
|
_entityIdTitle = Localizer["Input"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void EntityNameChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_entityName = e.Value.ToString();
|
||||||
|
_entityId = "-";
|
||||||
|
_entityIdElement = "select";
|
||||||
|
_entityIdTitle = Localizer["Input"];
|
||||||
|
if (_entityName != "-")
|
||||||
|
{
|
||||||
|
_entityIds = await SettingService.GetEntityIdsAsync(_entityName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_entityIds = new List<int>();
|
||||||
|
}
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error On EntityNameChanged");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveSetting()
|
||||||
|
{
|
||||||
|
validated = true;
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
if (await interop.FormValid(form) && _entityName != "-" && int.TryParse(_entityId, out int entityId))
|
||||||
|
{
|
||||||
|
var setting = new Setting();
|
||||||
|
setting.EntityName = _entityName;
|
||||||
|
setting.EntityId = entityId;
|
||||||
|
setting.SettingName = _settingName;
|
||||||
|
setting.SettingValue = _settingValue;
|
||||||
|
setting.IsPrivate = (bool.Parse(_isPrivate));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
setting = await SettingService.AddSettingAsync(setting);
|
||||||
|
await logger.LogInformation("Setting Saved {Setting}", setting);
|
||||||
|
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Saving Setting {Setting} {Error}", setting, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.SaveSetting"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
122
Oqtane.Client/Modules/Admin/Settings/Edit.razor
Normal file
122
Oqtane.Client/Modules/Admin/Settings/Edit.razor
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
@namespace Oqtane.Modules.Admin.Settings
|
||||||
|
@inherits ModuleBase
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject ISettingService SettingService
|
||||||
|
@inject IStringLocalizer<Edit> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="entityName" HelpText="Entity Name" ResourceKey="EntityName">Entity:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="entityName" class="form-control" @bind="@_entityName" maxlength="256" disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="entityId" HelpText="Entity Id" ResourceKey="EntityId">Id:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="entityId" class="form-control" @bind="@_entityId" maxlength="256" disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="settingName" HelpText="Setting Name" ResourceKey="SettingName">Name:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="settingName" class="form-control" @bind="@_settingName" maxlength="256" disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="settingValue" HelpText="Setting Value" ResourceKey="SettingValue">Value:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="SettingValue" class="form-control" @bind="@_settingValue" maxlength="256" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="isPrivate" HelpText="Private" ResourceKey="IsPrivate">Private?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="isPrivate" class="form-select" @bind="@_isPrivate">
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br /><br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SaveSetting">@SharedLocalizer["Save"]</button>
|
||||||
|
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
<br /><br />
|
||||||
|
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private ElementReference form;
|
||||||
|
private bool validated = false;
|
||||||
|
|
||||||
|
private int _settingId;
|
||||||
|
private string _entityName;
|
||||||
|
private string _entityId;
|
||||||
|
private string _settingName;
|
||||||
|
private string _settingValue;
|
||||||
|
private string _isPrivate;
|
||||||
|
private string _createdby;
|
||||||
|
private DateTime _createdon;
|
||||||
|
private string _modifiedby;
|
||||||
|
private DateTime _modifiedon;
|
||||||
|
|
||||||
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
_settingId = int.Parse(PageState.QueryString["id"]);
|
||||||
|
_entityName = PageState.QueryString["entity"];
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var setting = await SettingService.GetSettingAsync(_entityName, _settingId);
|
||||||
|
if (setting != null)
|
||||||
|
{
|
||||||
|
_entityId = setting.EntityId.ToString();
|
||||||
|
_settingName = setting.SettingName;
|
||||||
|
_settingValue = setting.SettingValue;
|
||||||
|
_isPrivate = setting.IsPrivate.ToString();
|
||||||
|
_createdby = setting.CreatedBy;
|
||||||
|
_createdon = setting.CreatedOn;
|
||||||
|
_modifiedby = setting.ModifiedBy;
|
||||||
|
_modifiedon = setting.ModifiedOn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Loading Setting {SettingId} {Error}", _settingId, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.LoadSetting"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveSetting()
|
||||||
|
{
|
||||||
|
validated = true;
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
if (await interop.FormValid(form))
|
||||||
|
{
|
||||||
|
var setting = await SettingService.GetSettingAsync(_entityName, _settingId);
|
||||||
|
setting.SettingValue = _settingValue;
|
||||||
|
setting.IsPrivate = (_isPrivate != null && Boolean.Parse(_isPrivate));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
setting = await SettingService.UpdateSettingAsync(setting);
|
||||||
|
await logger.LogInformation("Setting Saved {Setting}", setting);
|
||||||
|
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Saving Setting {Setting} {Error}", setting, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.SaveSetting"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
56
Oqtane.Client/Modules/Admin/Settings/ImportSettings.razor
Normal file
56
Oqtane.Client/Modules/Admin/Settings/ImportSettings.razor
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
@namespace Oqtane.Modules.Admin.Settings
|
||||||
|
@inherits ModuleBase
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject ISettingService SettingService
|
||||||
|
@inject IStringLocalizer<ImportSettings> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="settings" HelpText="Provide settings in comma delimited format using the column template specified" ResourceKey="Settings">Settings:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea id="settings" class="form-control" @bind="@_settings" rows="5" required></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="Import">@Localizer["Import"]</button>
|
||||||
|
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private string _settings = "Entity,Id,Name,Value,Private\n";
|
||||||
|
|
||||||
|
public override string Title => "Import Settings";
|
||||||
|
|
||||||
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
|
private async Task Import()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(_settings))
|
||||||
|
{
|
||||||
|
ShowProgressIndicator();
|
||||||
|
var result = await SettingService.ImportSettingsAsync(new Result { Message = _settings });
|
||||||
|
if (result.Success)
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.Import.Success"], MessageType.Success);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.Import.Failure"], MessageType.Error);
|
||||||
|
}
|
||||||
|
HideProgressIndicator();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.Import.Validation"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Importing Settings {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.Import"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
146
Oqtane.Client/Modules/Admin/Settings/Index.razor
Normal file
146
Oqtane.Client/Modules/Admin/Settings/Index.razor
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
@namespace Oqtane.Modules.Admin.Settings
|
||||||
|
@inherits ModuleBase
|
||||||
|
@inject ISettingService SettingService
|
||||||
|
@inject IStringLocalizer<Index> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<ActionLink Action="Add" Text="Add Setting" Security="SecurityAccessLevel.Host" ResourceKey="AddSetting" ReturnUrl="@(NavigateUrl(PageState.Page.Path, AddUrlParameters(_entityName, _entityId)))" />
|
||||||
|
<ActionLink Action="ImportSettings" Text="Import" Class="btn btn-secondary ms-1" Security="SecurityAccessLevel.Host" ResourceKey="ImportSettings" ReturnUrl="@(NavigateUrl(PageState.Page.Path, AddUrlParameters(_entityName, _entityId)))" />
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<select class="form-select custom-select" value="@_entityName" @onchange="(e => EntityNameChanged(e))">
|
||||||
|
<option value="-"><@Localizer["Select Entity"]></option>
|
||||||
|
@foreach (var entityName in _entityNames)
|
||||||
|
{
|
||||||
|
<option value="@entityName">@entityName</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<select class="form-select custom-select" value="@_entityId" @onchange="(e => EntityIdChanged(e))">
|
||||||
|
<option value="-"><@Localizer["Select Id"]></option>
|
||||||
|
@foreach (var entityId in _entityIds)
|
||||||
|
{
|
||||||
|
<option value="@entityId">@entityId</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<Pager Items="@_settings" SearchProperties="SettingName,SettingValue">
|
||||||
|
<Header>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
|
<th>@Localizer["Name"]</th>
|
||||||
|
<th>@Localizer["Value"]</th>
|
||||||
|
</Header>
|
||||||
|
<Row>
|
||||||
|
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"entity={context.EntityName}&id={context.SettingId}")" Security="SecurityAccessLevel.Host" ResourceKey="EditSetting" ReturnUrl="@(NavigateUrl(PageState.Page.Path, AddUrlParameters(_entityName, _entityId)))" /></td>
|
||||||
|
<td><ActionDialog Header="Delete Setting" Message="@string.Format(Localizer["Confirm.DeleteSetting"], context.SettingName)" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteSetting(context))" ResourceKey="DeleteSetting" /></td>
|
||||||
|
<td>@context.SettingName</td>
|
||||||
|
<td>@context.SettingValue</td>
|
||||||
|
</Row>
|
||||||
|
</Pager>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private string _entityName = "-";
|
||||||
|
private List<string> _entityNames = new List<string>();
|
||||||
|
private string _entityId = "-";
|
||||||
|
private List<int> _entityIds = new List<int>();
|
||||||
|
private List<Setting> _settings = new List<Setting>();
|
||||||
|
|
||||||
|
public override string UrlParametersTemplate => "/{entityname}/{entityid}";
|
||||||
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
|
protected override async Task OnParametersSetAsync()
|
||||||
|
{
|
||||||
|
_entityNames = await SettingService.GetEntityNamesAsync();
|
||||||
|
|
||||||
|
if (UrlParameters.ContainsKey("entityname"))
|
||||||
|
{
|
||||||
|
_entityName = UrlParameters["entityname"];
|
||||||
|
await GetEntityIds();
|
||||||
|
}
|
||||||
|
if (UrlParameters.ContainsKey("entityid"))
|
||||||
|
{
|
||||||
|
_entityId = UrlParameters["entityid"];
|
||||||
|
await GetSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GetEntityIds()
|
||||||
|
{
|
||||||
|
if (_entityName != "-")
|
||||||
|
{
|
||||||
|
_entityIds = await SettingService.GetEntityIdsAsync(_entityName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_entityIds = new List<int>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GetSettings()
|
||||||
|
{
|
||||||
|
if (_entityName != "-" && _entityId != "-")
|
||||||
|
{
|
||||||
|
_settings = await SettingService.GetSettingsAsync(_entityName, int.Parse(_entityId), "");
|
||||||
|
_settings = _settings.OrderBy(item => item.SettingName).ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_settings = new List<Setting>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void EntityNameChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_entityName = e.Value.ToString();
|
||||||
|
_entityId = "-";
|
||||||
|
await GetEntityIds();
|
||||||
|
await GetSettings();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error On EntityNameChanged");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void EntityIdChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_entityId = e.Value.ToString();
|
||||||
|
await GetSettings();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error On EntityIdChanged");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DeleteSetting(Setting setting)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await SettingService.DeleteSettingAsync(setting.EntityName, setting.EntityId, setting.SettingName);
|
||||||
|
await logger.LogInformation("Setting Deleted {Setting}", setting);
|
||||||
|
await GetSettings();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Deleting Setting {Setting} {Error}", setting, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.DeleteSetting"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,6 @@
|
|||||||
@inject ITenantService TenantService
|
@inject ITenantService TenantService
|
||||||
@inject IAliasService AliasService
|
@inject IAliasService AliasService
|
||||||
@inject ISiteService SiteService
|
@inject ISiteService SiteService
|
||||||
@inject IThemeService ThemeService
|
|
||||||
@inject ISiteTemplateService SiteTemplateService
|
@inject ISiteTemplateService SiteTemplateService
|
||||||
@inject IUserService UserService
|
@inject IUserService UserService
|
||||||
@inject IInstallationService InstallationService
|
@inject IInstallationService InstallationService
|
||||||
@@ -29,33 +28,9 @@ else
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="alias" HelpText="The urls for the site (comman delimited). This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or virtual folders (ie. domain.com/folder)." ResourceKey="Aliases">Urls: </Label>
|
<Label Class="col-sm-3" For="alias" HelpText="The primary url for the site. This can be a domain name (ie. domain.com), subdomain (ie. sub.domain.com) or a virtual folder (ie. domain.com/folder)." ResourceKey="Aliases">Url: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<textarea id="alias" class="form-control" @bind="@_urls" rows="3" required></textarea>
|
<input id="alias" class="form-control" @bind="@_urls" required></input>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="defaultTheme" HelpText="Select the default theme for the website" ResourceKey="DefaultTheme">Default Theme: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<select id="defaultTheme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
|
|
||||||
<option value="-"><@Localizer["Theme.Select"]></option>
|
|
||||||
@foreach (var theme in _themes)
|
|
||||||
{
|
|
||||||
<option value="@theme.TypeName">@theme.Name</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<select id="defaultContainer" class="form-select" @bind="@_containertype" required>
|
|
||||||
<option value="-"><@Localizer["Container.Select"]></option>
|
|
||||||
@foreach (var container in _containers)
|
|
||||||
{
|
|
||||||
<option value="@container.TypeName">@container.Name</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -70,26 +45,6 @@ else
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="rendermode" HelpText="The default render mode for the site" ResourceKey="Rendermode">Render Mode: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<select id="rendermode" class="form-select" @bind="@_rendermode" required>
|
|
||||||
<option value="@RenderModes.Interactive">@(SharedLocalizer["RenderMode" + @RenderModes.Interactive])</option>
|
|
||||||
<option value="@RenderModes.Static">@(SharedLocalizer["RenderMode" + @RenderModes.Static])</option>
|
|
||||||
<option value="@RenderModes.Headless">@(SharedLocalizer["RenderMode" + @RenderModes.Headless])</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="runtime" HelpText="The render mode for UI components which require interactivity" ResourceKey="Runtime">Interactivity: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<select id="runtime" class="form-select" @bind="@_runtime" required>
|
|
||||||
<option value="@Runtimes.Server">@(SharedLocalizer["Runtime" + @Runtimes.Server])</option>
|
|
||||||
<option value="@Runtimes.WebAssembly">@(SharedLocalizer["Runtime" + @Runtimes.WebAssembly])</option>
|
|
||||||
<option value="@Runtimes.Auto">@(SharedLocalizer["Runtime" + @Runtimes.Auto])</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="tenant" HelpText="Select the database for the site" ResourceKey="Tenant">Database: </Label>
|
<Label Class="col-sm-3" For="tenant" HelpText="Select the database for the site" ResourceKey="Tenant">Database: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -186,9 +141,6 @@ else
|
|||||||
private bool _showConnectionString = false;
|
private bool _showConnectionString = false;
|
||||||
private string _connectionString = string.Empty;
|
private string _connectionString = string.Empty;
|
||||||
|
|
||||||
private List<Theme> _themeList;
|
|
||||||
private List<ThemeControl> _themes = new List<ThemeControl>();
|
|
||||||
private List<ThemeControl> _containers = new List<ThemeControl>();
|
|
||||||
private List<SiteTemplate> _siteTemplates;
|
private List<SiteTemplate> _siteTemplates;
|
||||||
private List<Tenant> _tenants;
|
private List<Tenant> _tenants;
|
||||||
private string _tenantid = "-";
|
private string _tenantid = "-";
|
||||||
@@ -200,11 +152,7 @@ else
|
|||||||
|
|
||||||
private string _name = string.Empty;
|
private string _name = string.Empty;
|
||||||
private string _urls = string.Empty;
|
private string _urls = string.Empty;
|
||||||
private string _themetype = "-";
|
|
||||||
private string _containertype = "-";
|
|
||||||
private string _sitetemplatetype = "-";
|
private string _sitetemplatetype = "-";
|
||||||
private string _rendermode = RenderModes.Static;
|
|
||||||
private string _runtime = Runtimes.Server;
|
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
@@ -215,19 +163,11 @@ else
|
|||||||
{
|
{
|
||||||
_tenantid = _tenants.First(item => item.Name == TenantNames.Master).TenantId.ToString();
|
_tenantid = _tenants.First(item => item.Name == TenantNames.Master).TenantId.ToString();
|
||||||
}
|
}
|
||||||
_urls = PageState.Alias.Name;
|
_urls = PageState.Alias.Name + "/sitename";
|
||||||
_themeList = await ThemeService.GetThemesAsync();
|
|
||||||
_themes = ThemeService.GetThemeControls(_themeList);
|
|
||||||
if (_themes.Any(item => item.TypeName == Constants.DefaultTheme))
|
|
||||||
{
|
|
||||||
_themetype = Constants.DefaultTheme;
|
|
||||||
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
|
|
||||||
_containertype = _containers.First().TypeName;
|
|
||||||
}
|
|
||||||
_siteTemplates = await SiteTemplateService.GetSiteTemplatesAsync();
|
_siteTemplates = await SiteTemplateService.GetSiteTemplatesAsync();
|
||||||
if (_siteTemplates.Any(item => item.TypeName == Constants.DefaultSiteTemplate))
|
if (_siteTemplates.Any(item => item.TypeName == Constants.EmptySiteTemplate))
|
||||||
{
|
{
|
||||||
_sitetemplatetype = Constants.DefaultSiteTemplate;
|
_sitetemplatetype = Constants.EmptySiteTemplate;
|
||||||
}
|
}
|
||||||
|
|
||||||
_databases = await DatabaseService.GetDatabasesAsync();
|
_databases = await DatabaseService.GetDatabasesAsync();
|
||||||
@@ -237,7 +177,7 @@ else
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_databaseName = "LocalDB";
|
_databaseName = Constants.DefaultDBName;
|
||||||
}
|
}
|
||||||
LoadDatabaseConfigComponent();
|
LoadDatabaseConfigComponent();
|
||||||
}
|
}
|
||||||
@@ -281,37 +221,13 @@ else
|
|||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void ThemeChanged(ChangeEventArgs e)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_themetype = (string)e.Value;
|
|
||||||
if (_themetype != "-")
|
|
||||||
{
|
|
||||||
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
|
|
||||||
_containertype = _containers.First().TypeName;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_containers = new List<ThemeControl>();
|
|
||||||
_containertype = "-";
|
|
||||||
}
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error Loading Containers For Theme {ThemeType} {Error}", _themetype, ex.Message);
|
|
||||||
AddModuleMessage(Localizer["Error.Theme.LoadContainers"], MessageType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SaveSite()
|
private async Task SaveSite()
|
||||||
{
|
{
|
||||||
validated = true;
|
validated = true;
|
||||||
var interop = new Interop(JSRuntime);
|
var interop = new Interop(JSRuntime);
|
||||||
if (await interop.FormValid(form))
|
if (await interop.FormValid(form))
|
||||||
{
|
{
|
||||||
if (_tenantid != "-" && _name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-" && _sitetemplatetype != "-")
|
if (_tenantid != "-" && _name != string.Empty && _urls != string.Empty && _sitetemplatetype != "-")
|
||||||
{
|
{
|
||||||
_urls = Regex.Replace(_urls, @"\r\n?|\n", ",");
|
_urls = Regex.Replace(_urls, @"\r\n?|\n", ",");
|
||||||
var duplicates = new List<string>();
|
var duplicates = new List<string>();
|
||||||
@@ -390,6 +306,8 @@ else
|
|||||||
config.DatabaseType = tenant.DBType;
|
config.DatabaseType = tenant.DBType;
|
||||||
config.ConnectionString = tenant.DBConnectionString;
|
config.ConnectionString = tenant.DBConnectionString;
|
||||||
config.IsNewTenant = false;
|
config.IsNewTenant = false;
|
||||||
|
config.HostEmail = PageState.User.Email;
|
||||||
|
config.HostName = PageState.User.DisplayName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,12 +315,13 @@ else
|
|||||||
{
|
{
|
||||||
config.SiteName = _name;
|
config.SiteName = _name;
|
||||||
config.Aliases = _urls;
|
config.Aliases = _urls;
|
||||||
config.DefaultTheme = _themetype;
|
config.DefaultTheme = Constants.DefaultTheme;
|
||||||
config.DefaultContainer = _containertype;
|
config.DefaultContainer = Constants.DefaultContainer;
|
||||||
config.DefaultAdminContainer = "";
|
config.DefaultAdminContainer = "";
|
||||||
config.SiteTemplate = _sitetemplatetype;
|
config.SiteTemplate = _sitetemplatetype;
|
||||||
config.RenderMode = _rendermode;
|
config.RenderMode = RenderModes.Static;
|
||||||
config.Runtime = _runtime;
|
config.Runtime = Runtimes.Server;
|
||||||
|
config.Register = false;
|
||||||
|
|
||||||
ShowProgressIndicator();
|
ShowProgressIndicator();
|
||||||
|
|
||||||
|
|||||||
@@ -83,14 +83,14 @@ else
|
|||||||
{
|
{
|
||||||
@if (_connection != "-")
|
@if (_connection != "-")
|
||||||
{
|
{
|
||||||
@if (!string.IsNullOrEmpty(_tenant))
|
|
||||||
{
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="databasetype" HelpText="The database type" ResourceKey="DatabaseType">Type: </Label>
|
<Label Class="col-sm-3" For="databasetype" HelpText="The database type" ResourceKey="DatabaseType">Type: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="databasetype" class="form-control" @bind="@_databasetype" readonly />
|
<input id="databasetype" class="form-control" @bind="@_databasetype" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if (!string.IsNullOrEmpty(_tenant))
|
||||||
|
{
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="tenant" HelpText="The database using this connection" ResourceKey="Tenant">Database: </Label>
|
<Label Class="col-sm-3" For="tenant" HelpText="The database using this connection" ResourceKey="Tenant">Database: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -200,7 +200,26 @@ else
|
|||||||
if (tenant != null)
|
if (tenant != null)
|
||||||
{
|
{
|
||||||
_tenant = tenant.Name;
|
_tenant = tenant.Name;
|
||||||
_databasetype = _databases.FirstOrDefault(item => item.DBType == tenant.DBType && item.Name != "LocalDB").Name;
|
// hack - there are 3 providers with SqlServerDatabase DBTypes - so we are choosing the last one in alphabetical order
|
||||||
|
_databasetype = _databases.Where(item => item.DBType == tenant.DBType).OrderBy(item => item.Name).Last()?.Name;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_connection.Contains(" ("))
|
||||||
|
{
|
||||||
|
_databasetype = _connection.Substring(_connection.LastIndexOf(" (") + 2).Replace(")", "");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_databases.Exists(item => item.IsDefault))
|
||||||
|
{
|
||||||
|
_databasetype = _databases.Find(item => item.IsDefault).Name;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_databasetype = Constants.DefaultDBName;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -211,7 +230,7 @@ else
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_databasetype = "LocalDB";
|
_databasetype = Constants.DefaultDBName;
|
||||||
}
|
}
|
||||||
_showConnectionString = false;
|
_showConnectionString = false;
|
||||||
LoadDatabaseConfigComponent();
|
LoadDatabaseConfigComponent();
|
||||||
@@ -272,6 +291,7 @@ else
|
|||||||
}
|
}
|
||||||
if (!string.IsNullOrEmpty(_name) && !string.IsNullOrEmpty(connectionstring))
|
if (!string.IsNullOrEmpty(_name) && !string.IsNullOrEmpty(connectionstring))
|
||||||
{
|
{
|
||||||
|
_name = _name + " (" + _databasetype +")";
|
||||||
var settings = new Dictionary<string, object>();
|
var settings = new Dictionary<string, object>();
|
||||||
settings.Add($"{SettingKeys.ConnectionStringsSection}:{_name}", connectionstring);
|
settings.Add($"{SettingKeys.ConnectionStringsSection}:{_name}", connectionstring);
|
||||||
await SystemService.UpdateSystemInfoAsync(settings);
|
await SystemService.UpdateSystemInfoAsync(settings);
|
||||||
|
|||||||
@@ -2,10 +2,14 @@
|
|||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject ISystemService SystemService
|
@inject ISystemService SystemService
|
||||||
@inject IInstallationService InstallationService
|
@inject IInstallationService InstallationService
|
||||||
|
@inject IMigrationHistoryService MigrationHistoryService
|
||||||
|
@inject ITenantService TenantService
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<TabStrip>
|
@if (_initialized)
|
||||||
|
{
|
||||||
|
<TabStrip>
|
||||||
<TabPanel Name="Info" Heading="Info" ResourceKey="Info">
|
<TabPanel Name="Info" Heading="Info" ResourceKey="Info">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -170,12 +174,38 @@
|
|||||||
<br /><br />
|
<br /><br />
|
||||||
<button type="button" class="btn btn-danger" @onclick="ClearLog">@Localizer["Clear"]</button>
|
<button type="button" class="btn btn-danger" @onclick="ClearLog">@Localizer["Clear"]</button>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabStrip>
|
<TabPanel Name="Migrations" Heading="Migrations" ResourceKey="Migrations">
|
||||||
<br /><br />
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="tenant" HelpText="The name of the current database. Note that this is not the physical database name but rather the tenant name which is used within the framework to identify a database." ResourceKey="Tenant">Database: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="tenant" class="form-control" @bind="@_tenant" readonly />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<Pager Items="@_history" SearchProperties="MigrationId">
|
||||||
|
<Header>
|
||||||
|
<th>@Localizer["Migration"]</th>
|
||||||
|
<th>@Localizer["Date"]</th>
|
||||||
|
<th>@Localizer["Version"]</th>
|
||||||
|
</Header>
|
||||||
|
<Row>
|
||||||
|
<td>@context.MigrationId</td>
|
||||||
|
<td>@UtcToLocal(context.AppliedDate)</td>
|
||||||
|
<td>@context.AppliedVersion</td>
|
||||||
|
</Row>
|
||||||
|
</Pager>
|
||||||
|
</TabPanel>
|
||||||
|
</TabStrip>
|
||||||
|
<br /><br />
|
||||||
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
|
private bool _initialized = false;
|
||||||
|
|
||||||
private string _version = string.Empty;
|
private string _version = string.Empty;
|
||||||
private string _clrversion = string.Empty;
|
private string _clrversion = string.Empty;
|
||||||
private string _osversion = string.Empty;
|
private string _osversion = string.Empty;
|
||||||
@@ -199,6 +229,9 @@
|
|||||||
|
|
||||||
private string _log = string.Empty;
|
private string _log = string.Empty;
|
||||||
|
|
||||||
|
private string _tenant = string.Empty;
|
||||||
|
private List<MigrationHistory> _history;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
_version = Constants.Version;
|
_version = Constants.Version;
|
||||||
@@ -236,6 +269,12 @@
|
|||||||
{
|
{
|
||||||
_log = systeminfo["Log"].ToString();
|
_log = systeminfo["Log"].ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tenants = await TenantService.GetTenantsAsync();
|
||||||
|
_tenant = tenants.Find(item => item.TenantId == PageState.Alias.TenantId).Name;
|
||||||
|
_history = await MigrationHistoryService.GetMigrationHistoryAsync();
|
||||||
|
|
||||||
|
_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SaveConfig()
|
private async Task SaveConfig()
|
||||||
|
|||||||
@@ -101,6 +101,12 @@
|
|||||||
<small>@SharedLocalizer["Search.By"]:</small> <strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
|
<small>@SharedLocalizer["Search.By"]:</small> <strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
|
||||||
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
|
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
|
||||||
<br />
|
<br />
|
||||||
|
@if (_themes.Exists(item => item.PackageName == context.PackageId))
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-info">@SharedLocalizer["Installed"]</button>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
@if (!string.IsNullOrEmpty(context.PackageUrl))
|
@if (!string.IsNullOrEmpty(context.PackageUrl))
|
||||||
{
|
{
|
||||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
|
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
|
||||||
@@ -109,6 +115,7 @@
|
|||||||
{
|
{
|
||||||
<a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
|
<a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
<br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -171,6 +178,7 @@
|
|||||||
|
|
||||||
@code {
|
@code {
|
||||||
private bool _initialized = false;
|
private bool _initialized = false;
|
||||||
|
private List<Theme> _themes;
|
||||||
private int _page = 1;
|
private int _page = 1;
|
||||||
private List<Package> _packages;
|
private List<Package> _packages;
|
||||||
private string _price = "free";
|
private string _price = "free";
|
||||||
@@ -187,7 +195,8 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await LoadThemes();
|
_themes = await ThemeService.GetThemesAsync(PageState.Site.SiteId);
|
||||||
|
await LoadPackages();
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -197,24 +206,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadThemes()
|
private async Task LoadPackages()
|
||||||
{
|
{
|
||||||
ShowProgressIndicator();
|
ShowProgressIndicator();
|
||||||
|
|
||||||
var themes = await ThemeService.GetThemesAsync();
|
|
||||||
_packages = await PackageService.GetPackagesAsync("theme", _search, _price, "", _sort);
|
_packages = await PackageService.GetPackagesAsync("theme", _search, _price, "", _sort);
|
||||||
|
|
||||||
if (_packages != null)
|
|
||||||
{
|
|
||||||
foreach (Package package in _packages.ToArray())
|
|
||||||
{
|
|
||||||
if (themes.Exists(item => item.PackageName == package.PackageId))
|
|
||||||
{
|
|
||||||
_packages.Remove(package);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HideProgressIndicator();
|
HideProgressIndicator();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,25 +217,25 @@
|
|||||||
{
|
{
|
||||||
_price = price;
|
_price = price;
|
||||||
_sort = "popularity";
|
_sort = "popularity";
|
||||||
await LoadThemes();
|
await LoadPackages();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Search()
|
private async Task Search()
|
||||||
{
|
{
|
||||||
await LoadThemes();
|
await LoadPackages();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Reset()
|
private async Task Reset()
|
||||||
{
|
{
|
||||||
_page = 1;
|
_page = 1;
|
||||||
_search = "";
|
_search = "";
|
||||||
await LoadThemes();
|
await LoadPackages();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Refresh()
|
private async Task Refresh()
|
||||||
{
|
{
|
||||||
await LoadThemes();
|
await LoadPackages();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPageChange(int page)
|
private void OnPageChange(int page)
|
||||||
@@ -251,7 +246,7 @@
|
|||||||
private async void SortChanged(ChangeEventArgs e)
|
private async void SortChanged(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
_sort = (string)e.Value;
|
_sort = (string)e.Value;
|
||||||
await LoadThemes();
|
await LoadPackages();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HideModal()
|
private void HideModal()
|
||||||
@@ -310,6 +305,6 @@
|
|||||||
|
|
||||||
private void OnUpload()
|
private void OnUpload()
|
||||||
{
|
{
|
||||||
AddModuleMessage(string.Format(Localizer["Success.Theme.Download"], NavigateUrl("admin/system")), MessageType.Success);
|
AddModuleMessage(string.Format(Localizer["Success.Theme.Upload"], NavigateUrl("admin/system")), MessageType.Success);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
@namespace Oqtane.Modules.Admin.Themes
|
@namespace Oqtane.Modules.Admin.Themes
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@using System.Text.RegularExpressions
|
@using System.Text.RegularExpressions
|
||||||
|
@using System.Reflection
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IThemeService ThemeService
|
@inject IThemeService ThemeService
|
||||||
@inject IModuleService ModuleService
|
@inject IModuleService ModuleService
|
||||||
@@ -36,6 +37,8 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if (_type == "External")
|
||||||
|
{
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
|
<Label Class="col-sm-3" For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -60,6 +63,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="CreateTheme">@Localizer["Theme.Create"]</button>
|
<button type="button" class="btn btn-success" @onclick="CreateTheme">@Localizer["Theme.Create"]</button>
|
||||||
@@ -71,9 +75,10 @@
|
|||||||
private string _theme = string.Empty;
|
private string _theme = string.Empty;
|
||||||
private List<Template> _templates;
|
private List<Template> _templates;
|
||||||
private string _template = "-";
|
private string _template = "-";
|
||||||
|
private string _minversion = "2.0.0";
|
||||||
|
private string _type = "";
|
||||||
private string[] _versions;
|
private string[] _versions;
|
||||||
private string _reference = "local";
|
private string _reference = "local";
|
||||||
private string _minversion = "2.0.0";
|
|
||||||
private string _location = string.Empty;
|
private string _location = string.Empty;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
@@ -84,6 +89,16 @@
|
|||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Info.Theme.CreatorIntent"], MessageType.Info);
|
AddModuleMessage(Localizer["Info.Theme.CreatorIntent"], MessageType.Info);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var entryAssemblyName = Assembly.GetEntryAssembly().GetName().Name;
|
||||||
|
if (entryAssemblyName.EndsWith(".Oqtane"))
|
||||||
|
{
|
||||||
|
// Oqtane Application assemblies end with .Server.Oqtane or .Client.Oqtane
|
||||||
|
string[] segments = entryAssemblyName.Split('.');
|
||||||
|
_owner = string.Join(".", segments, 0, segments.Length - 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
@@ -105,11 +120,18 @@
|
|||||||
{
|
{
|
||||||
if (IsValid(_owner) && IsValid(_theme) && _owner != _theme && _template != "-")
|
if (IsValid(_owner) && IsValid(_theme) && _owner != _theme && _template != "-")
|
||||||
{
|
{
|
||||||
|
if (_type == "Internal")
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Success.Theme.Create.Internal"], MessageType.Success);
|
||||||
|
}
|
||||||
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
||||||
var theme = new Theme { Owner = _owner, Name = _theme, Template = _template, Version = _reference, ThemeName = template.Namespace };
|
var theme = new Theme { Owner = _owner, Name = _theme, Template = _template, Version = _reference, ThemeName = template.Namespace };
|
||||||
theme = await ThemeService.CreateThemeAsync(theme);
|
theme = await ThemeService.CreateThemeAsync(theme);
|
||||||
|
if (_type == "External")
|
||||||
|
{
|
||||||
GetLocation();
|
GetLocation();
|
||||||
AddModuleMessage(string.Format(Localizer["Success.Theme.Create"], NavigateUrl("admin/system")), MessageType.Success);
|
AddModuleMessage(string.Format(Localizer["Success.Theme.Create.External"], NavigateUrl("admin/system")), MessageType.Success);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -125,17 +147,22 @@
|
|||||||
private bool IsValid(string name)
|
private bool IsValid(string name)
|
||||||
{
|
{
|
||||||
// must contain letters, underscores and digits and first character must be letter or underscore
|
// must contain letters, underscores and digits and first character must be letter or underscore
|
||||||
return !string.IsNullOrEmpty(name) && name.ToLower() != "theme" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
|
return !string.IsNullOrEmpty(name) && name.ToLower() != "theme" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_.]*$");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TemplateChanged(ChangeEventArgs e)
|
private void TemplateChanged(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
_template = (string)e.Value;
|
_template = (string)e.Value;
|
||||||
_minversion = "2.0.0";
|
|
||||||
if (_template != "-")
|
if (_template != "-")
|
||||||
{
|
{
|
||||||
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
||||||
_minversion = template.Version;
|
_minversion = template.Version;
|
||||||
|
_type = template.Type;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_minversion = "2.0.0";
|
||||||
|
_type = "";
|
||||||
}
|
}
|
||||||
GetLocation();
|
GetLocation();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,12 @@
|
|||||||
|
|
||||||
@if (_initialized)
|
@if (_initialized)
|
||||||
{
|
{
|
||||||
|
<TabStrip>
|
||||||
|
<TabPanel Name="Theme" ResourceKey="Theme" Heading="Theme">
|
||||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="name" HelpText="The name of the module" ResourceKey="Name">Name: </Label>
|
<Label Class="col-sm-3" For="name" HelpText="The name of the theme" ResourceKey="Name">Name: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="name" class="form-control" @bind="@_name" />
|
<input id="name" class="form-control" @bind="@_name" />
|
||||||
</div>
|
</div>
|
||||||
@@ -28,6 +30,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<br />
|
||||||
<Section Name="Information" ResourceKey="Information" Heading="Information">
|
<Section Name="Information" ResourceKey="Information" Heading="Information">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -79,6 +82,12 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="fingerprint" HelpText="A unique identifier for the theme's static resources. This value can be changed by clicking the Save option below (ie. cache busting)." ResourceKey="Fingerprint">Fingerprint: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="fingerprint" class="form-control" @bind="@_fingerprint" disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
<br />
|
<br />
|
||||||
@@ -87,6 +96,18 @@
|
|||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
|
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel Name="Permissions" ResourceKey="Permissions" Heading="Permissions">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<PermissionGrid EntityName="@EntityNames.Theme" PermissionNames="@PermissionNames.Utilize" PermissionList="@_permissions" @ref="_permissionGrid" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SaveTheme">@SharedLocalizer["Save"]</button>
|
||||||
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
</TabPanel>
|
||||||
|
</TabStrip>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@@ -103,11 +124,15 @@
|
|||||||
private string _url = "";
|
private string _url = "";
|
||||||
private string _contact = "";
|
private string _contact = "";
|
||||||
private string _license = "";
|
private string _license = "";
|
||||||
|
private string _fingerprint = "";
|
||||||
|
private List<Permission> _permissions = null;
|
||||||
private string _createdby;
|
private string _createdby;
|
||||||
private DateTime _createdon;
|
private DateTime _createdon;
|
||||||
private string _modifiedby;
|
private string _modifiedby;
|
||||||
private DateTime _modifiedon;
|
private DateTime _modifiedon;
|
||||||
|
|
||||||
|
private PermissionGrid _permissionGrid;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
@@ -126,6 +151,8 @@
|
|||||||
_url = theme.Url;
|
_url = theme.Url;
|
||||||
_contact = theme.Contact;
|
_contact = theme.Contact;
|
||||||
_license = theme.License;
|
_license = theme.License;
|
||||||
|
_fingerprint = theme.Fingerprint;
|
||||||
|
_permissions = theme.PermissionList;
|
||||||
_createdby = theme.CreatedBy;
|
_createdby = theme.CreatedBy;
|
||||||
_createdon = theme.CreatedOn;
|
_createdon = theme.CreatedOn;
|
||||||
_modifiedby = theme.ModifiedBy;
|
_modifiedby = theme.ModifiedBy;
|
||||||
@@ -152,6 +179,7 @@
|
|||||||
var theme = await ThemeService.GetThemeAsync(_themeId, ModuleState.SiteId);
|
var theme = await ThemeService.GetThemeAsync(_themeId, ModuleState.SiteId);
|
||||||
theme.Name = _name;
|
theme.Name = _name;
|
||||||
theme.IsEnabled = (_isenabled == null ? true : bool.Parse(_isenabled));
|
theme.IsEnabled = (_isenabled == null ? true : bool.Parse(_isenabled));
|
||||||
|
theme.PermissionList = _permissionGrid.GetPermissionList();
|
||||||
await ThemeService.UpdateThemeAsync(theme);
|
await ThemeService.UpdateThemeAsync(theme);
|
||||||
await logger.LogInformation("Theme Saved {Theme}", theme);
|
await logger.LogInformation("Theme Saved {Theme}", theme);
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
|
|||||||
@@ -15,12 +15,11 @@
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
<ActionLink Action="Add" Text="Install Theme" ResourceKey="InstallTheme" />
|
<ActionLink Action="Add" Text="Install Theme" ResourceKey="InstallTheme" />
|
||||||
<ActionLink Action="Create" Text="Create Theme" ResourceKey="CreateTheme" Class="btn btn-secondary ps-2" />
|
<ActionLink Action="Create" Text="Create Theme" ResourceKey="CreateTheme" Class="btn btn-secondary ms-1" />
|
||||||
<button type="button" class="btn btn-secondary pw-2" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
|
<button type="button" class="btn btn-secondary ms-1" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
|
||||||
|
|
||||||
<Pager Items="@_themes">
|
<Pager Items="@_themes">
|
||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th>@SharedLocalizer["Name"]</th>
|
<th>@SharedLocalizer["Name"]</th>
|
||||||
@@ -38,7 +37,6 @@ else
|
|||||||
<ActionDialog Header="Delete Theme" Message="@string.Format(Localizer["Confirm.Theme.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteTheme(context))" ResourceKey="DeleteTheme" />
|
<ActionDialog Header="Delete Theme" Message="@string.Format(Localizer["Confirm.Theme.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteTheme(context))" ResourceKey="DeleteTheme" />
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td><NavLink class="btn btn-secondary" href="@NavigateUrl("admin/site")">@Localizer["Assign"]</NavLink></td>
|
|
||||||
<td>@context.Name</td>
|
<td>@context.Name</td>
|
||||||
<td>@context.Version</td>
|
<td>@context.Version</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -80,7 +78,7 @@ else
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_themes = await ThemeService.GetThemesAsync();
|
_themes = await ThemeService.GetThemesAsync(PageState.Site.SiteId);
|
||||||
_packages = await PackageService.GetPackageUpdatesAsync("theme");
|
_packages = await PackageService.GetPackageUpdatesAsync("theme");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -163,7 +161,7 @@ else
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ThemeService.DeleteThemeAsync(Theme.ThemeName);
|
await ThemeService.DeleteThemeAsync(Theme.ThemeId, PageState.Site.SiteId);
|
||||||
AddModuleMessage(Localizer["Success.Theme.Delete"], MessageType.Success);
|
AddModuleMessage(Localizer["Success.Theme.Delete"], MessageType.Success);
|
||||||
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
|
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ else
|
|||||||
<th>@Localizer["Url"]</th>
|
<th>@Localizer["Url"]</th>
|
||||||
<th>@Localizer["Requests"]</th>
|
<th>@Localizer["Requests"]</th>
|
||||||
<th>@Localizer["Requested"]</th>
|
<th>@Localizer["Requested"]</th>
|
||||||
|
<th>@Localizer["Referrer"]</th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.UrlMappingId.ToString())" ResourceKey="Edit" /></td>
|
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.UrlMappingId.ToString())" ResourceKey="Edit" /></td>
|
||||||
@@ -49,6 +50,7 @@ else
|
|||||||
</td>
|
</td>
|
||||||
<td>@context.Requests</td>
|
<td>@context.Requests</td>
|
||||||
<td>@UtcToLocal(context.RequestedOn)</td>
|
<td>@UtcToLocal(context.RequestedOn)</td>
|
||||||
|
<td>@context.Referrer</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
@inject IFileService FileService
|
@inject IFileService FileService
|
||||||
@inject IFolderService FolderService
|
@inject IFolderService FolderService
|
||||||
@inject ITimeZoneService TimeZoneService
|
@inject ITimeZoneService TimeZoneService
|
||||||
|
@inject ILanguageService LanguageService
|
||||||
@inject IJSRuntime jsRuntime
|
@inject IJSRuntime jsRuntime
|
||||||
@inject IServiceProvider ServiceProvider
|
@inject IServiceProvider ServiceProvider
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@@ -26,8 +27,7 @@
|
|||||||
<br />
|
<br />
|
||||||
}
|
}
|
||||||
<TabStrip>
|
<TabStrip>
|
||||||
<TabPanel Name="Identity" ResourceKey="Identity">
|
<TabPanel Name="Identity" Heading="Identity" ResourceKey="Identity">
|
||||||
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="username" HelpText="Your username. Note that this field can not be modified." ResourceKey="Username"></Label>
|
<Label Class="col-sm-3" For="username" HelpText="Your username. Note that this field can not be modified." ResourceKey="Username"></Label>
|
||||||
@@ -35,36 +35,6 @@
|
|||||||
<input id="username" class="form-control" @bind="@_username" readonly />
|
<input id="username" class="form-control" @bind="@_username" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="password" HelpText="If you wish to change your password you can enter it here. Please choose a sufficiently secure password." ResourceKey="Password"></Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<div class="input-group">
|
|
||||||
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="confirm" HelpText="If you are changing your password you must enter it again to confirm it matches" ResourceKey="Confirm"></Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<div class="input-group">
|
|
||||||
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" />
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@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>
|
|
||||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
|
||||||
<option value="False">@SharedLocalizer["No"]</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="email" HelpText="Your email address where you wish to receive notifications" ResourceKey="Email"></Label>
|
<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">
|
<div class="col-sm-9">
|
||||||
@@ -89,6 +59,18 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="culture" HelpText="Your preferred language. Note that you will only be able to choose from languages supported on this site." ResourceKey="CultureCode">Language:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="culture" class="form-select" @bind="@_culturecode">
|
||||||
|
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||||
|
@foreach (var language in _languages)
|
||||||
|
{
|
||||||
|
<option value="@language.Code">@language.Name</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="@_photofileid.ToString()" HelpText="A photo of yourself" ResourceKey="Photo"></Label>
|
<Label Class="col-sm-3" For="@_photofileid.ToString()" HelpText="A photo of yourself" ResourceKey="Photo"></Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -99,9 +81,113 @@
|
|||||||
<br />
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
<button type="button" class="btn btn-danger" @onclick="Logout">@Localizer["Logout Everywhere"]</button>
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Profile" ResourceKey="Profile">
|
<TabPanel Name="Security" Heading="Security" ResourceKey="Security">
|
||||||
|
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="password" HelpText="If you wish to change your password you can enter it here. Please choose a sufficiently secure password." ResourceKey="Password"></Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="confirm" HelpText="If you are changing your password you must enter it again to confirm it matches" ResourceKey="Confirm"></Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" />
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
|
<br /><br />
|
||||||
|
@if (_allowtwofactor)
|
||||||
|
{
|
||||||
|
<Section Name="MFA" Heading="Multi-Factor Authentication" ResourceKey="MFA">
|
||||||
|
<div class="container">
|
||||||
|
<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>
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
<br />
|
||||||
|
}
|
||||||
|
@if (_allowpasskeys)
|
||||||
|
{
|
||||||
|
<Section Name="Passkeys" Heading="Passkeys" ResourceKey="Passkeys" Expanded="@((_passkeys.Count > 0).ToString())">
|
||||||
|
<button type="button" class="btn btn-primary" @onclick="AddPasskey">@SharedLocalizer["Add"]</button>
|
||||||
|
@if (_passkeys.Count > 0)
|
||||||
|
{
|
||||||
|
<Pager Items="@_passkeys">
|
||||||
|
<Header>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
|
<th>@Localizer["Passkey"]</th>
|
||||||
|
</Header>
|
||||||
|
<Row>
|
||||||
|
@if (context.CredentialId != _passkeyId)
|
||||||
|
{
|
||||||
|
<td><button type="button" class="btn btn-primary" @onclick="@(() => EditPasskey(context))">@SharedLocalizer["Edit"]</button></td>
|
||||||
|
<td><ActionDialog Action="Delete" OnClick="@(async () => await DeletePasskey(context))" ResourceKey="DeletePasskey" Class="btn btn-danger" Header="Delete Passkey" Message="@string.Format(Localizer["Confirm.Passkey.Delete", context.Name])" /></td>
|
||||||
|
<td>@context.Name</td>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<td><button type="button" class="btn btn-success" @onclick="@(async () => await SavePasskey())">@SharedLocalizer["Save"]</button></td>
|
||||||
|
<td><button type="button" class="btn btn-secondary" @onclick="@(async () => await CancelPasskey())">@SharedLocalizer["Cancel"]</button></td>
|
||||||
|
<td><input id="passkeyname" class="form-control" @bind="@_passkeyName" /></td>
|
||||||
|
}
|
||||||
|
</Row>
|
||||||
|
</Pager>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="mt-2">@Localizer["Message.Passkeys.None"]</div>
|
||||||
|
}
|
||||||
|
</Section>
|
||||||
|
<br />
|
||||||
|
}
|
||||||
|
@if (_allowexternallogin)
|
||||||
|
{
|
||||||
|
<Section Name="Logins" Heading="Logins" ResourceKey="Logins" Expanded="@((_logins.Count > 0).ToString())">
|
||||||
|
@if (_logins.Count > 0)
|
||||||
|
{
|
||||||
|
<Pager Items="@_logins">
|
||||||
|
<Header>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
|
<th>@Localizer["Login"]</th>
|
||||||
|
</Header>
|
||||||
|
<Row>
|
||||||
|
<td><ActionDialog Action="Delete" OnClick="@(async () => await DeleteLogin(context))" ResourceKey="DeleteLogin" Class="btn btn-danger" Header="Delete Login" Message="@string.Format(Localizer["Confirm.Login.Delete", context.Name])" /></td>
|
||||||
|
<td>@context.Name</td>
|
||||||
|
</Row>
|
||||||
|
</Pager>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="mt-2">@Localizer["Message.Logins.None"]</div>
|
||||||
|
}
|
||||||
|
</Section>
|
||||||
|
<br />
|
||||||
|
}
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-danger" @onclick="Logout">@Localizer["Logout Everywhere"]</button>
|
||||||
|
<br />
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel Name="Profile" Heading="Profile" ResourceKey="Profile">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@foreach (Profile profile in _profiles)
|
@foreach (Profile profile in _profiles)
|
||||||
@@ -126,13 +212,16 @@
|
|||||||
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete">
|
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete">
|
||||||
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||||
{
|
{
|
||||||
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
|
var values = option.Split(':');
|
||||||
|
var name = values[0];
|
||||||
|
var value = values.Length > 1 ? values[1] : values[0];
|
||||||
|
@if (GetProfileValue(p.Name, "") == name || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == name))
|
||||||
{
|
{
|
||||||
<option value="@option" selected>@option</option>
|
<option value="@name" selected>@value</option>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<option value="@option">@option</option>
|
<option value="@name">@value</option>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
@@ -142,13 +231,16 @@
|
|||||||
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
|
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
|
||||||
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||||
{
|
{
|
||||||
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
|
var values = option.Split(':');
|
||||||
|
var name = values[0];
|
||||||
|
var value = values.Length > 1 ? values[1] : values[0];
|
||||||
|
@if (GetProfileValue(p.Name, "") == name || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == name))
|
||||||
{
|
{
|
||||||
<option value="@option" selected>@option</option>
|
<option value="@name" selected>@value</option>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<option value="@option">@option</option>
|
<option value="@name">@value</option>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
@@ -224,11 +316,11 @@
|
|||||||
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Notifications" ResourceKey="Notifications">
|
<TabPanel Name="Notifications" Heading="Notifications" ResourceKey="Notifications">
|
||||||
<ActionLink Action="Add" Text="Send Notification" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="SendNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" />
|
<ActionLink Action="Add" Text="Send Notification" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="SendNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" />
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<select class="form-select" @onchange="(e => FilterChanged(e))">
|
<select class="form-select" @onchange="(e => FilterNotifications(e))">
|
||||||
<option value="to">@Localizer["Inbox"]</option>
|
<option value="to">@Localizer["Inbox"]</option>
|
||||||
<option value="from">@Localizer["Items.Sent"]</option>
|
<option value="from">@Localizer["Items.Sent"]</option>
|
||||||
</select>
|
</select>
|
||||||
@@ -247,7 +339,7 @@
|
|||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<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>
|
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" /></td>
|
||||||
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
|
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await DeleteNotification(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
|
||||||
|
|
||||||
@if (context.IsRead)
|
@if (context.IsRead)
|
||||||
{
|
{
|
||||||
@@ -292,7 +384,7 @@
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="no-notifications-text">
|
<div class="no-notifications-text">
|
||||||
@Localizer["NoNotificationsReceived.Text"]
|
@Localizer["NoNotificationsReceived"]
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -310,7 +402,7 @@
|
|||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<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>
|
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" /></td>
|
||||||
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
|
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await DeleteNotification(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
|
||||||
|
|
||||||
@if (context.IsRead)
|
@if (context.IsRead)
|
||||||
{
|
{
|
||||||
@@ -356,7 +448,7 @@
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="no-notifications-text">
|
<div class="no-notifications-text">
|
||||||
@Localizer["NoNotificationsSent.Text"]
|
@Localizer["NoNotificationsSent"]
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -367,25 +459,38 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
||||||
|
|
||||||
private List<Models.TimeZone> _timezones;
|
private List<Models.TimeZone> _timezones;
|
||||||
|
private IEnumerable<Language> _languages;
|
||||||
|
|
||||||
private bool _initialized = false;
|
private bool _initialized = false;
|
||||||
private string _passwordrequirements;
|
|
||||||
private string _username = string.Empty;
|
|
||||||
private string _password = string.Empty;
|
|
||||||
private string _passwordtype = "password";
|
|
||||||
private string _togglepassword = string.Empty;
|
|
||||||
private string _confirm = string.Empty;
|
|
||||||
private bool _allowtwofactor = false;
|
private bool _allowtwofactor = false;
|
||||||
private string _twofactor = "False";
|
private bool _allowpasskeys = false;
|
||||||
|
private bool _allowexternallogin = false;
|
||||||
|
|
||||||
|
private string _username = string.Empty;
|
||||||
private string _email = string.Empty;
|
private string _email = string.Empty;
|
||||||
private string _displayname = string.Empty;
|
private string _displayname = string.Empty;
|
||||||
private FileManager _filemanager;
|
private FileManager _filemanager;
|
||||||
private int _folderid = -1;
|
private int _folderid = -1;
|
||||||
private string _timezoneid = string.Empty;
|
private string _timezoneid = string.Empty;
|
||||||
|
private string _culturecode = string.Empty;
|
||||||
private int _photofileid = -1;
|
private int _photofileid = -1;
|
||||||
private File _photo = null;
|
private File _photo = null;
|
||||||
private string _imagefiles = string.Empty;
|
private string _imagefiles = string.Empty;
|
||||||
|
|
||||||
|
private string _passwordrequirements;
|
||||||
|
private string _password = string.Empty;
|
||||||
|
private string _passwordtype = "password";
|
||||||
|
private string _togglepassword = string.Empty;
|
||||||
|
private string _confirm = string.Empty;
|
||||||
|
private string _twofactor = "False";
|
||||||
|
private List<UserPasskey> _passkeys;
|
||||||
|
private byte[] _passkeyId;
|
||||||
|
private string _passkeyName = string.Empty;
|
||||||
|
private List<UserLogin> _logins;
|
||||||
|
|
||||||
private List<Profile> _profiles;
|
private List<Profile> _profiles;
|
||||||
private Dictionary<string, string> _userSettings;
|
private Dictionary<string, string> _userSettings;
|
||||||
private string _category = string.Empty;
|
private string _category = string.Empty;
|
||||||
@@ -394,38 +499,32 @@
|
|||||||
private List<Notification> _notifications;
|
private List<Notification> _notifications;
|
||||||
private string _notificationSummary = string.Empty;
|
private string _notificationSummary = string.Empty;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
|
||||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
|
||||||
_allowtwofactor = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "true");
|
_allowtwofactor = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "true");
|
||||||
_profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
_allowpasskeys = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:Passkeys", "false") == "true");
|
||||||
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
_allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false;
|
||||||
|
|
||||||
if (PageState.User != null)
|
if (PageState.User != null)
|
||||||
{
|
{
|
||||||
|
_timezones = TimeZoneService.GetTimeZones();
|
||||||
|
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
|
||||||
|
|
||||||
|
// identity section
|
||||||
_username = PageState.User.Username;
|
_username = PageState.User.Username;
|
||||||
_twofactor = PageState.User.TwoFactorRequired.ToString();
|
|
||||||
_email = PageState.User.Email;
|
_email = PageState.User.Email;
|
||||||
_displayname = PageState.User.DisplayName;
|
_displayname = PageState.User.DisplayName;
|
||||||
_timezoneid = PageState.User.TimeZoneId;
|
_timezoneid = PageState.User.TimeZoneId;
|
||||||
|
_culturecode = PageState.User.CultureCode;
|
||||||
if (string.IsNullOrEmpty(_email))
|
|
||||||
{
|
|
||||||
AddModuleMessage(Localizer["Message.User.NoEmail"], MessageType.Warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get user folder
|
|
||||||
var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath);
|
var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath);
|
||||||
if (folder != null)
|
if (folder != null)
|
||||||
{
|
{
|
||||||
_folderid = folder.FolderId;
|
_folderid = folder.FolderId;
|
||||||
}
|
}
|
||||||
|
_imagefiles = SettingService.GetSetting(PageState.Site.Settings, "ImageFiles", Constants.ImageFiles);
|
||||||
|
_imagefiles = (string.IsNullOrEmpty(_imagefiles)) ? Constants.ImageFiles : _imagefiles;
|
||||||
if (PageState.User.PhotoFileId != null)
|
if (PageState.User.PhotoFileId != null)
|
||||||
{
|
{
|
||||||
_photofileid = PageState.User.PhotoFileId.Value;
|
_photofileid = PageState.User.PhotoFileId.Value;
|
||||||
@@ -437,11 +536,27 @@
|
|||||||
_photo = null;
|
_photo = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_userSettings = PageState.User.Settings;
|
// security section
|
||||||
var sitesettings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||||
_imagefiles = SettingService.GetSetting(_userSettings, "ImageFiles", Constants.ImageFiles);
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
_imagefiles = (string.IsNullOrEmpty(_imagefiles)) ? Constants.ImageFiles : _imagefiles;
|
_twofactor = PageState.User.TwoFactorRequired.ToString();
|
||||||
|
await GetPasskeys();
|
||||||
|
await GetLogins();
|
||||||
|
|
||||||
|
// profile section
|
||||||
|
_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}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_userSettings = PageState.User.Settings;
|
||||||
|
|
||||||
|
// notification section
|
||||||
await LoadNotificationsAsync();
|
await LoadNotificationsAsync();
|
||||||
|
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
@@ -458,22 +573,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadNotificationsAsync()
|
// identity methods
|
||||||
{
|
|
||||||
_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(_userSettings, SettingName, DefaultValue);
|
|
||||||
if (value.Contains("]"))
|
|
||||||
{
|
|
||||||
value = value.Substring(value.IndexOf("]") + 1);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task Save()
|
private async Task Save()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -491,6 +591,7 @@
|
|||||||
user.Email = _email;
|
user.Email = _email;
|
||||||
user.DisplayName = (_displayname == string.Empty ? _username : _displayname);
|
user.DisplayName = (_displayname == string.Empty ? _username : _displayname);
|
||||||
user.TimeZoneId = _timezoneid;
|
user.TimeZoneId = _timezoneid;
|
||||||
|
user.CultureCode = _culturecode;
|
||||||
user.PhotoFileId = _filemanager.GetFileId();
|
user.PhotoFileId = _filemanager.GetFileId();
|
||||||
if (user.PhotoFileId == -1)
|
if (user.PhotoFileId == -1)
|
||||||
{
|
{
|
||||||
@@ -548,6 +649,124 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Cancel()
|
||||||
|
{
|
||||||
|
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// security methods
|
||||||
|
|
||||||
|
private void TogglePassword()
|
||||||
|
{
|
||||||
|
if (_passwordtype == "password")
|
||||||
|
{
|
||||||
|
_passwordtype = "text";
|
||||||
|
_togglepassword = SharedLocalizer["HidePassword"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_passwordtype = "password";
|
||||||
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GetPasskeys()
|
||||||
|
{
|
||||||
|
if (_allowpasskeys)
|
||||||
|
{
|
||||||
|
_passkeys = await UserService.GetPasskeysAsync(PageState.User.UserId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AddPasskey()
|
||||||
|
{
|
||||||
|
// post back to the Passkey page so that the cookies are set correctly
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, operation = "create", returnurl = NavigateUrl(PageState.Page.Path, "tab=Security") };
|
||||||
|
string url = Utilities.TenantUrl(PageState.Alias, "/pages/passkey/");
|
||||||
|
await interop.SubmitForm(url, fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
// user has initiated a passkey addition
|
||||||
|
if (PageState.QueryString.ContainsKey("options"))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
var credential = await interop.CreateCredential(WebUtility.UrlDecode(PageState.QueryString["options"]));
|
||||||
|
if (!string.IsNullOrEmpty(credential))
|
||||||
|
{
|
||||||
|
// post back to the Passkey page so that the cookies are set correctly
|
||||||
|
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, operation = "validate", credential = credential, returnurl = NavigateUrl(PageState.Page.Path, "tab=Security") };
|
||||||
|
string url = Utilities.TenantUrl(PageState.Alias, "/pages/passkey/");
|
||||||
|
await interop.SubmitForm(url, fields);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await logger.LogError("Passkey Could Not Be Created");
|
||||||
|
AddModuleMessage(Localizer["Error.Passkey.Fail"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Passkey Could Not Be Created");
|
||||||
|
AddModuleMessage(Localizer["Error.Passkey.Fail"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EditPasskey(UserPasskey passkey)
|
||||||
|
{
|
||||||
|
_passkeyId = passkey.CredentialId;
|
||||||
|
_passkeyName = passkey.Name;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DeletePasskey(UserPasskey passkey)
|
||||||
|
{
|
||||||
|
await UserService.DeletePasskeyAsync(PageState.User.UserId, passkey.CredentialId);
|
||||||
|
await GetPasskeys();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SavePasskey()
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(_passkeyName))
|
||||||
|
{
|
||||||
|
await UserService.UpdatePasskeyAsync(new UserPasskey { CredentialId = _passkeyId, Name = _passkeyName, UserId = PageState.User.UserId });
|
||||||
|
await GetPasskeys();
|
||||||
|
_passkeyName = "";
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CancelPasskey()
|
||||||
|
{
|
||||||
|
await GetPasskeys();
|
||||||
|
_passkeyName = "";
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GetLogins()
|
||||||
|
{
|
||||||
|
if (_allowexternallogin)
|
||||||
|
{
|
||||||
|
_logins = await UserService.GetLoginsAsync(PageState.User.UserId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DeleteLogin(UserLogin login)
|
||||||
|
{
|
||||||
|
await UserService.DeleteLoginAsync(PageState.User.UserId, login.Provider, login.Key);
|
||||||
|
await GetLogins();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task Logout()
|
private async Task Logout()
|
||||||
{
|
{
|
||||||
await logger.LogInformation("User Logout Everywhere For Username {Username}", PageState.User?.Username);
|
await logger.LogInformation("User Logout Everywhere For Username {Username}", PageState.User?.Username);
|
||||||
@@ -574,6 +793,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// profile methods
|
||||||
|
|
||||||
|
private string GetProfileValue(string SettingName, string DefaultValue)
|
||||||
|
{
|
||||||
|
string value = SettingService.GetSetting(_userSettings, SettingName, DefaultValue);
|
||||||
|
if (value.Contains("]"))
|
||||||
|
{
|
||||||
|
value = value.Substring(value.IndexOf("]") + 1);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProfileChanged(ChangeEventArgs e, string SettingName)
|
||||||
|
{
|
||||||
|
var value = (string)e.Value;
|
||||||
|
_userSettings = SettingService.SetSetting(_userSettings, SettingName, value);
|
||||||
|
}
|
||||||
|
|
||||||
private bool ValidateProfiles()
|
private bool ValidateProfiles()
|
||||||
{
|
{
|
||||||
foreach (Profile profile in _profiles)
|
foreach (Profile profile in _profiles)
|
||||||
@@ -605,18 +842,22 @@
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Cancel()
|
// notification methods
|
||||||
|
|
||||||
|
private async Task LoadNotificationsAsync()
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
_notifications = await NotificationService.GetNotificationsAsync(PageState.Site.SiteId, _filter, PageState.User.UserId);
|
||||||
|
_notifications = _notifications.Where(item => item.DeletedBy != PageState.User.Username).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProfileChanged(ChangeEventArgs e, string SettingName)
|
private async void FilterNotifications(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
var value = (string)e.Value;
|
_filter = (string)e.Value;
|
||||||
_userSettings = SettingService.SetSetting(_userSettings, SettingName, value);
|
await LoadNotificationsAsync();
|
||||||
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Delete(Notification Notification)
|
private async Task DeleteNotification(Notification Notification)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -641,13 +882,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void FilterChanged(ChangeEventArgs e)
|
|
||||||
{
|
|
||||||
_filter = (string)e.Value;
|
|
||||||
await LoadNotificationsAsync();
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task DeleteAllNotifications()
|
private async Task DeleteAllNotifications()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -679,18 +913,4 @@
|
|||||||
HideProgressIndicator();
|
HideProgressIndicator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TogglePassword()
|
|
||||||
{
|
|
||||||
if (_passwordtype == "password")
|
|
||||||
{
|
|
||||||
_passwordtype = "text";
|
|
||||||
_togglepassword = SharedLocalizer["HidePassword"];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_passwordtype = "password";
|
|
||||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
@inject IProfileService ProfileService
|
@inject IProfileService ProfileService
|
||||||
@inject ISettingService SettingService
|
@inject ISettingService SettingService
|
||||||
@inject ITimeZoneService TimeZoneService
|
@inject ITimeZoneService TimeZoneService
|
||||||
|
@inject ILanguageService LanguageService
|
||||||
@inject IStringLocalizer<Add> Localizer
|
@inject IStringLocalizer<Add> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@@ -28,6 +29,15 @@
|
|||||||
<input id="email" class="form-control" @bind="@_email" />
|
<input id="email" class="form-control" @bind="@_email" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="confirmed" HelpText="Indicates if the user's email is verified" ResourceKey="Confirmed">Verified?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="confirmed" class="form-select" @bind="@_confirmed">
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="displayname" HelpText="The full name of the user" ResourceKey="DisplayName"></Label>
|
<Label Class="col-sm-3" For="displayname" HelpText="The full name of the user" ResourceKey="DisplayName"></Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -46,6 +56,18 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="culture" HelpText="The user's preferred language. Note that you will only be able to choose from languages supported on this site." ResourceKey="CultureCode">Language:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="culture" class="form-select" @bind="@_culturecode">
|
||||||
|
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||||
|
@foreach (var language in _languages)
|
||||||
|
{
|
||||||
|
<option value="@language.Code">@language.Name</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="notify" HelpText="Indicate if new users should receive an email notification" ResourceKey="Notify">Notify? </Label>
|
<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">
|
<div class="col-sm-9">
|
||||||
@@ -79,13 +101,16 @@
|
|||||||
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
|
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
|
||||||
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||||
{
|
{
|
||||||
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
|
var values = option.Split(':');
|
||||||
|
var name = values[0];
|
||||||
|
var value = values.Length > 1 ? values[1] : values[0];
|
||||||
|
@if (GetProfileValue(p.Name, "") == name || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == name))
|
||||||
{
|
{
|
||||||
<option value="@option" selected>@option</option>
|
<option value="@name" selected>@value</option>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<option value="@option">@option</option>
|
<option value="@name">@value</option>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
@@ -117,11 +142,15 @@
|
|||||||
|
|
||||||
@code {
|
@code {
|
||||||
private List<Models.TimeZone> _timezones;
|
private List<Models.TimeZone> _timezones;
|
||||||
|
private IEnumerable<Models.Language> _languages;
|
||||||
|
|
||||||
private bool _initialized = false;
|
private bool _initialized = false;
|
||||||
private string _username = string.Empty;
|
private string _username = string.Empty;
|
||||||
private string _email = string.Empty;
|
private string _email = string.Empty;
|
||||||
|
private string _confirmed = "True";
|
||||||
private string _displayname = string.Empty;
|
private string _displayname = string.Empty;
|
||||||
private string _timezoneid = string.Empty;
|
private string _timezoneid = string.Empty;
|
||||||
|
private string _culturecode = string.Empty;
|
||||||
private string _notify = "True";
|
private string _notify = "True";
|
||||||
private List<Profile> _profiles;
|
private List<Profile> _profiles;
|
||||||
private Dictionary<string, string> _settings;
|
private Dictionary<string, string> _settings;
|
||||||
@@ -133,10 +162,25 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
_timezones = TimeZoneService.GetTimeZones();
|
||||||
_profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
|
||||||
_settings = new Dictionary<string, string>();
|
|
||||||
_timezoneid = PageState.Site.TimeZoneId;
|
_timezoneid = PageState.Site.TimeZoneId;
|
||||||
|
_culturecode = PageState.Site.CultureCode;
|
||||||
|
|
||||||
|
_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>();
|
||||||
|
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -169,8 +213,10 @@
|
|||||||
user.Username = _username;
|
user.Username = _username;
|
||||||
user.Password = ""; // will be auto generated
|
user.Password = ""; // will be auto generated
|
||||||
user.Email = _email;
|
user.Email = _email;
|
||||||
|
user.EmailConfirmed = bool.Parse(_confirmed);
|
||||||
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
||||||
user.TimeZoneId = _timezoneid;
|
user.TimeZoneId = _timezoneid;
|
||||||
|
user.CultureCode = _culturecode;
|
||||||
user.PhotoFileId = null;
|
user.PhotoFileId = null;
|
||||||
user.SuppressNotification = !bool.Parse(_notify);
|
user.SuppressNotification = !bool.Parse(_notify);
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
@inject ISettingService SettingService
|
@inject ISettingService SettingService
|
||||||
@inject IFileService FileService
|
@inject IFileService FileService
|
||||||
@inject ITimeZoneService TimeZoneService
|
@inject ITimeZoneService TimeZoneService
|
||||||
|
@inject ILanguageService LanguageService
|
||||||
@inject IServiceProvider ServiceProvider
|
@inject IServiceProvider ServiceProvider
|
||||||
@inject IStringLocalizer<Edit> Localizer
|
@inject IStringLocalizer<Edit> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
@@ -14,8 +15,7 @@
|
|||||||
@if (_initialized)
|
@if (_initialized)
|
||||||
{
|
{
|
||||||
<TabStrip>
|
<TabStrip>
|
||||||
<TabPanel Name="Identity" ResourceKey="Identity">
|
<TabPanel Name="Identity" Heading="Identity" ResourceKey="Identity">
|
||||||
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="username" HelpText="The unique username for a user. Note that this field can not be modified." ResourceKey="Username">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>
|
||||||
@@ -23,24 +23,6 @@
|
|||||||
<input id="username" class="form-control" @bind="@_username" readonly />
|
<input id="username" class="form-control" @bind="@_username" readonly />
|
||||||
</div>
|
</div>
|
||||||
</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">Password:</Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<div class="input-group">
|
|
||||||
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm">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" />
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email">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">
|
<div class="col-sm-9">
|
||||||
@@ -48,7 +30,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="confirmed" HelpText="Indicates if the user's email is verified" ResourceKey="Confirmed">Confirmed?</Label>
|
<Label Class="col-sm-3" For="confirmed" HelpText="Indicates if the user's email is verified" ResourceKey="Confirmed">Verified?</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="confirmed" class="form-select" @bind="@_confirmed">
|
<select id="confirmed" class="form-select" @bind="@_confirmed">
|
||||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
@@ -74,6 +56,18 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="culture" HelpText="The user's preferred language. Note that you will only be able to choose from languages supported on this site." ResourceKey="CultureCode">Language:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="culture" class="form-select" @bind="@_culturecode">
|
||||||
|
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||||
|
@foreach (var language in _languages)
|
||||||
|
{
|
||||||
|
<option value="@language.Code">@language.Name</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -100,7 +94,77 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Profile" ResourceKey="Profile">
|
<TabPanel Name="Security" Heading="Security" ResourceKey="Security">
|
||||||
|
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||||
|
<div class="container">
|
||||||
|
<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">Password:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm">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" />
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br /><br />
|
||||||
|
@if (_allowpasskeys)
|
||||||
|
{
|
||||||
|
<Section Name="Passkeys" Heading="Passkeys" ResourceKey="Passkeys" Expanded="@((_passkeys.Count > 0).ToString())">
|
||||||
|
@if (_passkeys.Count > 0)
|
||||||
|
{
|
||||||
|
<Pager Items="@_passkeys">
|
||||||
|
<Header>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
|
<th>@Localizer["Passkey"]</th>
|
||||||
|
</Header>
|
||||||
|
<Row>
|
||||||
|
<td><ActionDialog Action="Delete" OnClick="@(async () => await DeletePasskey(context))" ResourceKey="DeletePasskey" Class="btn btn-danger" Header="Delete Passkey" Message="@string.Format(Localizer["Confirm.Passkey.Delete", context.Name])" /></td>
|
||||||
|
<td>@context.Name</td>
|
||||||
|
</Row>
|
||||||
|
</Pager>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="mt-2">@Localizer["Message.Passkeys.None"]</div>
|
||||||
|
}
|
||||||
|
</Section>
|
||||||
|
<br />
|
||||||
|
}
|
||||||
|
@if (_allowexternallogin)
|
||||||
|
{
|
||||||
|
<Section Name="Logins" Heading="Logins" ResourceKey="Logins" Expanded="@((_logins.Count > 0).ToString())">
|
||||||
|
@if (_logins.Count > 0)
|
||||||
|
{
|
||||||
|
<Pager Items="@_logins">
|
||||||
|
<Header>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
|
<th>@Localizer["Login"]</th>
|
||||||
|
</Header>
|
||||||
|
<Row>
|
||||||
|
<td><ActionDialog Action="Delete" OnClick="@(async () => await DeleteLogin(context))" ResourceKey="DeleteLogin" Class="btn btn-danger" Header="Delete Login" Message="@string.Format(Localizer["Confirm.Login.Delete", context.Name])" /></td>
|
||||||
|
<td>@context.Name</td>
|
||||||
|
</Row>
|
||||||
|
</Pager>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="mt-2">@Localizer["Message.Logins.None"]</div>
|
||||||
|
}
|
||||||
|
</Section>
|
||||||
|
<br />
|
||||||
|
}
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel Name="Profile" Heading="Profile" ResourceKey="Profile">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@foreach (Profile profile in _profiles)
|
@foreach (Profile profile in _profiles)
|
||||||
@@ -121,13 +185,16 @@
|
|||||||
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
|
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
|
||||||
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||||
{
|
{
|
||||||
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
|
var values = option.Split(':');
|
||||||
|
var name = values[0];
|
||||||
|
var value = values.Length > 1 ? values[1] : values[0];
|
||||||
|
@if (GetProfileValue(p.Name, "") == name || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == name))
|
||||||
{
|
{
|
||||||
<option value="@option" selected>@option</option>
|
<option value="@name" selected>@value</option>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<option value="@option">@option</option>
|
<option value="@name">@value</option>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
@@ -153,36 +220,46 @@
|
|||||||
<br />
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) && PageState.Runtime != Shared.Runtime.Hybrid && !_ishost)
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) && PageState.Runtime != Shared.Runtime.Hybrid && !_ishost && _isdeleted != "True")
|
||||||
{
|
{
|
||||||
<button type="button" class="btn btn-primary ms-1" @onclick="ImpersonateUser">@Localizer["Impersonate"]</button>
|
<button type="button" class="btn btn-primary ms-1" @onclick="ImpersonateUser">@Localizer["Impersonate"]</button>
|
||||||
}
|
}
|
||||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && _isdeleted == "True")
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && _candelete)
|
||||||
{
|
{
|
||||||
<ActionDialog Header="Delete User" Message="Are You Sure You Wish To Permanently Delete This User?" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteUser())" ResourceKey="DeleteUser" />
|
<ActionDialog Header="Delete User" Message="Are You Sure You Wish To Permanently Delete This User?" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger ms-1" OnClick="@(async () => await DeleteUser())" ResourceKey="DeleteUser" />
|
||||||
}
|
}
|
||||||
<br /><br />
|
<br /><br />
|
||||||
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
|
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private List<Models.TimeZone> _timezones;
|
|
||||||
private bool _initialized = false;
|
private bool _initialized = false;
|
||||||
private string _passwordrequirements;
|
private bool _allowpasskeys = false;
|
||||||
|
private bool _allowexternallogin = false;
|
||||||
|
|
||||||
|
private List<Models.TimeZone> _timezones;
|
||||||
|
private IEnumerable<Language> _languages;
|
||||||
|
|
||||||
private int _userid;
|
private int _userid;
|
||||||
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 string _email = string.Empty;
|
private string _email = string.Empty;
|
||||||
private string _confirmed = string.Empty;
|
private string _confirmed = string.Empty;
|
||||||
private string _displayname = string.Empty;
|
private string _displayname = string.Empty;
|
||||||
private string _timezoneid = string.Empty;
|
private string _timezoneid = string.Empty;
|
||||||
|
private string _culturecode = string.Empty;
|
||||||
private string _isdeleted;
|
private string _isdeleted;
|
||||||
private string _lastlogin;
|
private string _lastlogin;
|
||||||
private string _lastipaddress;
|
private string _lastipaddress;
|
||||||
private bool _ishost = false;
|
private bool _ishost = false;
|
||||||
|
private bool _candelete = false;
|
||||||
|
|
||||||
|
private string _passwordrequirements;
|
||||||
|
private string _password = string.Empty;
|
||||||
|
private string _passwordtype = "password";
|
||||||
|
private string _togglepassword = string.Empty;
|
||||||
|
private string _confirm = string.Empty;
|
||||||
|
private List<UserPasskey> _passkeys;
|
||||||
|
private List<UserLogin> _logins;
|
||||||
|
|
||||||
private List<Profile> _profiles;
|
private List<Profile> _profiles;
|
||||||
private Dictionary<string, string> _settings;
|
private Dictionary<string, string> _settings;
|
||||||
@@ -201,10 +278,8 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
_allowpasskeys = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:Passkeys", "false") == "true");
|
||||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
_allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false;
|
||||||
_profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
|
|
||||||
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
|
||||||
|
|
||||||
if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int UserId))
|
if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int UserId))
|
||||||
{
|
{
|
||||||
@@ -212,17 +287,38 @@
|
|||||||
var user = await UserService.GetUserAsync(_userid, PageState.Site.SiteId);
|
var user = await UserService.GetUserAsync(_userid, PageState.Site.SiteId);
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
|
_timezones = TimeZoneService.GetTimeZones();
|
||||||
|
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
|
||||||
|
|
||||||
_username = user.Username;
|
_username = user.Username;
|
||||||
_email = user.Email;
|
_email = user.Email;
|
||||||
_confirmed = user.EmailConfirmed.ToString();
|
_confirmed = user.EmailConfirmed.ToString();
|
||||||
_displayname = user.DisplayName;
|
_displayname = user.DisplayName;
|
||||||
_timezoneid = PageState.User.TimeZoneId;
|
_timezoneid = PageState.User.TimeZoneId;
|
||||||
|
_culturecode = PageState.User.CultureCode;
|
||||||
_isdeleted = user.IsDeleted.ToString();
|
_isdeleted = user.IsDeleted.ToString();
|
||||||
|
_candelete = user.IsDeleted;
|
||||||
_lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", UtcToLocal(user.LastLoginOn));
|
_lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", UtcToLocal(user.LastLoginOn));
|
||||||
_lastipaddress = user.LastIPAddress;
|
_lastipaddress = user.LastIPAddress;
|
||||||
_ishost = UserSecurity.ContainsRole(user.Roles, RoleNames.Host);
|
_ishost = UserSecurity.ContainsRole(user.Roles, RoleNames.Host);
|
||||||
|
|
||||||
|
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||||
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
|
await GetPasskeys();
|
||||||
|
await GetLogins();
|
||||||
|
|
||||||
|
_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}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
_settings = user.Settings;
|
_settings = user.Settings;
|
||||||
|
|
||||||
_createdby = user.CreatedBy;
|
_createdby = user.CreatedBy;
|
||||||
_createdon = user.CreatedOn;
|
_createdon = user.CreatedOn;
|
||||||
_modifiedby = user.ModifiedBy;
|
_modifiedby = user.ModifiedBy;
|
||||||
@@ -269,6 +365,7 @@
|
|||||||
user.EmailConfirmed = bool.Parse(_confirmed);
|
user.EmailConfirmed = bool.Parse(_confirmed);
|
||||||
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
||||||
user.TimeZoneId = _timezoneid;
|
user.TimeZoneId = _timezoneid;
|
||||||
|
user.CultureCode = _culturecode;
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
user.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
|
user.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
|
||||||
@@ -342,6 +439,35 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task GetPasskeys()
|
||||||
|
{
|
||||||
|
if (_allowpasskeys)
|
||||||
|
{
|
||||||
|
_passkeys = await UserService.GetPasskeysAsync(_userid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private async Task DeletePasskey(UserPasskey passkey)
|
||||||
|
{
|
||||||
|
await UserService.DeletePasskeyAsync(_userid, passkey.CredentialId);
|
||||||
|
await GetPasskeys();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GetLogins()
|
||||||
|
{
|
||||||
|
if (_allowexternallogin)
|
||||||
|
{
|
||||||
|
_logins = await UserService.GetLoginsAsync(_userid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DeleteLogin(UserLogin login)
|
||||||
|
{
|
||||||
|
await UserService.DeleteLoginAsync(_userid, login.Provider, login.Key);
|
||||||
|
await GetLogins();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
private bool ValidateProfiles()
|
private bool ValidateProfiles()
|
||||||
{
|
{
|
||||||
foreach (Profile profile in _profiles)
|
foreach (Profile profile in _profiles)
|
||||||
|
|||||||
@@ -17,10 +17,23 @@ else
|
|||||||
{
|
{
|
||||||
<TabStrip>
|
<TabStrip>
|
||||||
<TabPanel Name="Users" Heading="Users" ResourceKey="Users">
|
<TabPanel Name="Users" Heading="Users" ResourceKey="Users">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<div class="col-sm-6">
|
||||||
<ActionLink Action="Add" Text="Add User" Security="SecurityAccessLevel.Edit" ResourceKey="AddUser" />
|
<ActionLink Action="Add" Text="Add User" Security="SecurityAccessLevel.Edit" ResourceKey="AddUser" />
|
||||||
<ActionLink Text="Import Users" Class="btn btn-secondary ms-2" Action="Users" Security="SecurityAccessLevel.Admin" ResourceKey="ImportUsers"/>
|
<ActionLink Text="Import Users" Class="btn btn-secondary ms-2" Action="Users" Security="SecurityAccessLevel.Admin" ResourceKey="ImportUsers" />
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<select id="deleted" class="form-select custom-select" value="@_deleted" @onchange="(e => DeletedChanged(e))">
|
||||||
|
<option value="false">@Localizer["Active Users"]</option>
|
||||||
|
<option value="true">@Localizer["Deleted Users"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
|
||||||
<Pager Items="@users" RowClass="align-middle" SearchProperties="User.Username,User.Email,User.DisplayName">
|
<Pager Items="@users" CurrentPage="@_page.ToString()" OnPageChange="@((page) => _page = page)" RowClass="align-middle" SearchProperties="User.Username,User.Email,User.DisplayName">
|
||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
@@ -47,9 +60,46 @@ else
|
|||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings" Security="SecurityAccessLevel.Admin">
|
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings" Security="SecurityAccessLevel.Host">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<Section Name="User" Heading="User Settings" ResourceKey="UserSettings">
|
<Section Name="User" Heading="User Settings" ResourceKey="UserSettings">
|
||||||
|
<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 successfully configured an alternative login method, 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>
|
||||||
|
<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">Use 2FA?</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="loginlink" HelpText="Do you want to allow users to login using a time sensitive link sent by email" ResourceKey="LoginLink">Allow Login Link?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="loginlink" class="form-select" @bind="@_loginlink">
|
||||||
|
<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="passkeys" HelpText="Do you want to allow users to login using passkeys (ie. passwordless authentication using WebAuthn/FIDO2)" ResourceKey="Passkeys">Allow Passkeys?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="passkeys" class="form-select" @bind="@_passkeys">
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="allowregistration" HelpText="Do you want anonymous visitors to be able to register for an account on the site" ResourceKey="AllowRegistration">Allow User Registration?</Label>
|
<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">
|
<div class="col-sm-9">
|
||||||
@@ -68,30 +118,33 @@ else
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="requireconfirmedemail" HelpText="Do you want to require registered users to verify their email address before they are allowed to log in?" ResourceKey="RequireConfirmedEmail">Require Verified Email?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="requireconfirmedemail" class="form-select" @bind="@_requireconfirmedemail">
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<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>
|
<Label Class="col-sm-3" For="profileurl" HelpText="Optionally provide a custom profile url" ResourceKey="ProfileUrl">Profile Url:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="profileurl" class="form-control" @bind="@_profileurl" />
|
<input id="profileurl" class="form-control" @bind="@_profileurl" />
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<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>
|
<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">
|
<div class="col-sm-9">
|
||||||
<input id="cookiename" class="form-control" @bind="@_cookiename" />
|
<input id="cookiename" class="form-control" @bind="@_cookiename" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="cookiedomain" HelpText="If you would like to share cookies across subdomains you will need to specify a root domain with a leading dot (ie. '.example.com')" ResourceKey="CookieDomain">Cookie Domain:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="cookiedomain" class="form-control" @bind="@_cookiedomain" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="cookieexpiration" HelpText="You can choose to use a custom authentication cookie expiration timespan for each site (e.g. '08:00:00' for 8 hours). The default is 14 days if not specified." ResourceKey="CookieExpiration">Cookie Expiration Timespan:</Label>
|
<Label Class="col-sm-3" For="cookieexpiration" HelpText="You can choose to use a custom authentication cookie expiration timespan for each site (e.g. '08:00:00' for 8 hours). The default is 14 days if not specified." ResourceKey="CookieExpiration">Cookie Expiration Timespan:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -116,10 +169,7 @@ else
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
</Section>
|
</Section>
|
||||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
|
||||||
{
|
|
||||||
<Section Name="Password" Heading="Password Settings" ResourceKey="PasswordSettings">
|
<Section Name="Password" Heading="Password Settings" ResourceKey="PasswordSettings">
|
||||||
<div class="row mb-1 align-items-center">
|
<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>
|
<Label Class="col-sm-3" For="minimumlength" HelpText="The Minimum Length For A Password" ResourceKey="RequiredLength">Minimum Length:</Label>
|
||||||
@@ -292,6 +342,24 @@ else
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="requirenonce" HelpText="Specify if Nonce validation is required for the ID token (the default is true)" ResourceKey="RequireNonce">Require Nonce?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="requirenonce" class="form-select" @bind="@_requirenonce" required>
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="singlelogout" HelpText="Specify if users should be logged out of both the application and provider (the default is false indicating they will only be logged out of the application)" ResourceKey="SingleLogout">Use Single Logout?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="singlelogout" class="form-select" @bind="@_singlelogout" required>
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="scopes" HelpText="A list of Scopes to request from the provider (separated by commas). If none are specified, standard Scopes will be used by default." ResourceKey="Scopes">Scopes:</Label>
|
<Label Class="col-sm-3" For="scopes" HelpText="A list of Scopes to request from the provider (separated by commas). If none are specified, standard Scopes will be used by default." ResourceKey="Scopes">Scopes:</Label>
|
||||||
@@ -426,15 +494,6 @@ else
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
@@ -476,7 +535,6 @@ else
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||||
@@ -486,12 +544,19 @@ else
|
|||||||
|
|
||||||
@code {
|
@code {
|
||||||
private List<UserRole> users;
|
private List<UserRole> users;
|
||||||
|
private string _deleted = "false";
|
||||||
|
private int _page = 1;
|
||||||
|
|
||||||
|
private string _allowsitelogin;
|
||||||
|
private string _twofactor;
|
||||||
|
private string _loginlink;
|
||||||
|
private string _passkeys;
|
||||||
private string _allowregistration;
|
private string _allowregistration;
|
||||||
private string _registerurl;
|
private string _registerurl;
|
||||||
|
private string _requireconfirmedemail;
|
||||||
private string _profileurl;
|
private string _profileurl;
|
||||||
private string _twofactor;
|
|
||||||
private string _cookiename;
|
private string _cookiename;
|
||||||
|
private string _cookiedomain;
|
||||||
private string _cookieexpiration;
|
private string _cookieexpiration;
|
||||||
private string _alwaysremember;
|
private string _alwaysremember;
|
||||||
private string _logouteverywhere;
|
private string _logouteverywhere;
|
||||||
@@ -519,6 +584,8 @@ else
|
|||||||
private string _clientsecrettype = "password";
|
private string _clientsecrettype = "password";
|
||||||
private string _toggleclientsecret = string.Empty;
|
private string _toggleclientsecret = string.Empty;
|
||||||
private string _authresponsetype;
|
private string _authresponsetype;
|
||||||
|
private string _requirenonce;
|
||||||
|
private string _singlelogout;
|
||||||
private string _scopes;
|
private string _scopes;
|
||||||
private string _parameters;
|
private string _parameters;
|
||||||
private string _pkce;
|
private string _pkce;
|
||||||
@@ -537,7 +604,6 @@ else
|
|||||||
private string _createusers;
|
private string _createusers;
|
||||||
private string _verifyusers;
|
private string _verifyusers;
|
||||||
private string _allowhostrole;
|
private string _allowhostrole;
|
||||||
private string _allowsitelogin;
|
|
||||||
|
|
||||||
private string _secret;
|
private string _secret;
|
||||||
private string _secrettype = "password";
|
private string _secrettype = "password";
|
||||||
@@ -554,17 +620,22 @@ else
|
|||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
await LoadUsersAsync(true);
|
await LoadUsersAsync();
|
||||||
|
|
||||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
_allowregistration = PageState.Site.AllowRegistration.ToString().ToLower();
|
_allowregistration = PageState.Site.AllowRegistration.ToString().ToLower();
|
||||||
_registerurl = SettingService.GetSetting(settings, "LoginOptions:RegisterUrl", "");
|
|
||||||
_profileurl = SettingService.GetSetting(settings, "LoginOptions:ProfileUrl", "");
|
|
||||||
|
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
|
_allowsitelogin = SettingService.GetSetting(settings, "LoginOptions:AllowSiteLogin", "true");
|
||||||
_twofactor = SettingService.GetSetting(settings, "LoginOptions:TwoFactor", "false");
|
_twofactor = SettingService.GetSetting(settings, "LoginOptions:TwoFactor", "false");
|
||||||
|
_loginlink = SettingService.GetSetting(settings, "LoginOptions:LoginLink", "false");
|
||||||
|
_passkeys = SettingService.GetSetting(settings, "LoginOptions:Passkeys", "false");
|
||||||
|
_registerurl = SettingService.GetSetting(settings, "LoginOptions:RegisterUrl", "");
|
||||||
|
_requireconfirmedemail = SettingService.GetSetting(settings, "LoginOptions:RequireConfirmedEmail", "true");
|
||||||
|
_profileurl = SettingService.GetSetting(settings, "LoginOptions:ProfileUrl", "");
|
||||||
_cookiename = SettingService.GetSetting(settings, "LoginOptions:CookieName", ".AspNetCore.Identity.Application");
|
_cookiename = SettingService.GetSetting(settings, "LoginOptions:CookieName", ".AspNetCore.Identity.Application");
|
||||||
|
_cookiedomain = SettingService.GetSetting(settings, "LoginOptions:CookieDomain", "");
|
||||||
_cookieexpiration = SettingService.GetSetting(settings, "LoginOptions:CookieExpiration", "");
|
_cookieexpiration = SettingService.GetSetting(settings, "LoginOptions:CookieExpiration", "");
|
||||||
_alwaysremember = SettingService.GetSetting(settings, "LoginOptions:AlwaysRemember", "false");
|
_alwaysremember = SettingService.GetSetting(settings, "LoginOptions:AlwaysRemember", "false");
|
||||||
_logouteverywhere = SettingService.GetSetting(settings, "LoginOptions:LogoutEverywhere", "false");
|
_logouteverywhere = SettingService.GetSetting(settings, "LoginOptions:LogoutEverywhere", "false");
|
||||||
@@ -604,6 +675,8 @@ else
|
|||||||
_clientsecret = SettingService.GetSetting(settings, "ExternalLogin:ClientSecret", "");
|
_clientsecret = SettingService.GetSetting(settings, "ExternalLogin:ClientSecret", "");
|
||||||
_toggleclientsecret = SharedLocalizer["ShowPassword"];
|
_toggleclientsecret = SharedLocalizer["ShowPassword"];
|
||||||
_authresponsetype = SettingService.GetSetting(settings, "ExternalLogin:AuthResponseType", "code");
|
_authresponsetype = SettingService.GetSetting(settings, "ExternalLogin:AuthResponseType", "code");
|
||||||
|
_requirenonce = SettingService.GetSetting(settings, "ExternalLogin:RequireNonce", "true");
|
||||||
|
_singlelogout = SettingService.GetSetting(settings, "ExternalLogin:SingleLogout", "false");
|
||||||
_scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", "");
|
_scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", "");
|
||||||
_parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", "");
|
_parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", "");
|
||||||
_pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false");
|
_pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false");
|
||||||
@@ -622,12 +695,9 @@ else
|
|||||||
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
|
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
|
||||||
_verifyusers = SettingService.GetSetting(settings, "ExternalLogin:VerifyUsers", "true");
|
_verifyusers = SettingService.GetSetting(settings, "ExternalLogin:VerifyUsers", "true");
|
||||||
_allowhostrole = SettingService.GetSetting(settings, "ExternalLogin:AllowHostRole", "false");
|
_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);
|
users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
@@ -636,6 +706,20 @@ else
|
|||||||
users.AddRange(hosts);
|
users.AddRange(hosts);
|
||||||
users = users.OrderBy(u => u.User.DisplayName).ToList();
|
users = users.OrderBy(u => u.User.DisplayName).ToList();
|
||||||
}
|
}
|
||||||
|
users = users.Where(item => item.User.IsDeleted == bool.Parse(_deleted)).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void DeletedChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_deleted = e.Value.ToString();
|
||||||
|
await LoadUsersAsync();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error On DeletedChanged");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -661,7 +745,7 @@ else
|
|||||||
await logger.LogInformation("User {Username} Expired From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
await logger.LogInformation("User {Username} Expired From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
||||||
}
|
}
|
||||||
AddModuleMessage(Localizer["Success.DeleteUser"], MessageType.Success);
|
AddModuleMessage(Localizer["Success.DeleteUser"], MessageType.Success);
|
||||||
await LoadUsersAsync(true);
|
await LoadUsersAsync();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -674,6 +758,8 @@ else
|
|||||||
private async Task SaveSiteSettings()
|
private async Task SaveSiteSettings()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
var site = PageState.Site;
|
var site = PageState.Site;
|
||||||
site.AllowRegistration = bool.Parse(_allowregistration);
|
site.AllowRegistration = bool.Parse(_allowregistration);
|
||||||
@@ -681,12 +767,15 @@ else
|
|||||||
|
|
||||||
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
||||||
|
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
settings = SettingService.SetSetting(settings, "LoginOptions:AllowSiteLogin", _allowsitelogin, false);
|
||||||
{
|
|
||||||
settings = SettingService.SetSetting(settings, "LoginOptions:RegisterUrl", _registerurl, false);
|
|
||||||
settings = SettingService.SetSetting(settings, "LoginOptions:ProfileUrl", _profileurl, false);
|
|
||||||
settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false);
|
settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false);
|
||||||
|
settings = SettingService.SetSetting(settings, "LoginOptions:LoginLink", _loginlink, false);
|
||||||
|
settings = SettingService.SetSetting(settings, "LoginOptions:Passkeys", _passkeys, false);
|
||||||
|
settings = SettingService.SetSetting(settings, "LoginOptions:RegisterUrl", _registerurl, false);
|
||||||
|
settings = SettingService.SetSetting(settings, "LoginOptions:RequireConfirmedEmail", _requireconfirmedemail, false);
|
||||||
|
settings = SettingService.SetSetting(settings, "LoginOptions:ProfileUrl", _profileurl, false);
|
||||||
settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true);
|
settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "LoginOptions:CookieDomain", _cookiedomain, true);
|
||||||
settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true);
|
settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true);
|
||||||
settings = SettingService.SetSetting(settings, "LoginOptions:AlwaysRemember", _alwaysremember, false);
|
settings = SettingService.SetSetting(settings, "LoginOptions:AlwaysRemember", _alwaysremember, false);
|
||||||
settings = SettingService.SetSetting(settings, "LoginOptions:LogoutEverywhere", _logouteverywhere, false);
|
settings = SettingService.SetSetting(settings, "LoginOptions:LogoutEverywhere", _logouteverywhere, false);
|
||||||
@@ -712,6 +801,8 @@ else
|
|||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientId", _clientid, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientId", _clientid, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientSecret", _clientsecret, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientSecret", _clientsecret, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:AuthResponseType", _authresponsetype, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:AuthResponseType", _authresponsetype, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:RequireNonce", _requirenonce, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:SingleLogout", _singlelogout, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:Scopes", _scopes, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:Scopes", _scopes, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:Parameters", _parameters, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:Parameters", _parameters, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true);
|
||||||
@@ -728,16 +819,15 @@ else
|
|||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:VerifyUsers", _verifyusers, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:VerifyUsers", _verifyusers, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:AllowHostRole", _allowhostrole, 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:Secret", _secret, true);
|
||||||
settings = SettingService.SetSetting(settings, "JwtOptions:Issuer", _issuer, true);
|
settings = SettingService.SetSetting(settings, "JwtOptions:Issuer", _issuer, true);
|
||||||
settings = SettingService.SetSetting(settings, "JwtOptions:Audience", _audience, true);
|
settings = SettingService.SetSetting(settings, "JwtOptions:Audience", _audience, true);
|
||||||
settings = SettingService.SetSetting(settings, "JwtOptions:Lifetime", _lifetime, true);
|
settings = SettingService.SetSetting(settings, "JwtOptions:Lifetime", _lifetime, true);
|
||||||
}
|
|
||||||
|
|
||||||
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
|
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
|
||||||
await SettingService.ClearSiteSettingsCacheAsync();
|
await SettingService.ClearSiteSettingsCacheAsync();
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(_secret))
|
if (!string.IsNullOrEmpty(_secret))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
@namespace Oqtane.Modules.Admin.Users
|
@namespace Oqtane.Modules.Admin.Users
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IUserService UserService
|
@inject ISiteTaskService SiteTaskService
|
||||||
@inject IStringLocalizer<Users> Localizer
|
@inject IStringLocalizer<Users> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@@ -43,17 +43,9 @@
|
|||||||
var fileid = _filemanager.GetFileId();
|
var fileid = _filemanager.GetFileId();
|
||||||
if (fileid != -1)
|
if (fileid != -1)
|
||||||
{
|
{
|
||||||
ShowProgressIndicator();
|
var siteTask = new SiteTask(PageState.Site.SiteId, "Import Users", "Oqtane.Infrastructure.ImportUsersTask, Oqtane.Server", $"{fileid}:{_notify}");
|
||||||
var results = await UserService.ImportUsersAsync(PageState.Site.SiteId, fileid, bool.Parse(_notify));
|
await SiteTaskService.AddSiteTaskAsync(siteTask);
|
||||||
if (bool.Parse(results["Success"]))
|
AddModuleMessage(Localizer["Message.Import.Success"], MessageType.Success);
|
||||||
{
|
|
||||||
AddModuleMessage(string.Format(Localizer["Message.Import.Success"], results["Users"]), MessageType.Success);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AddModuleMessage(Localizer["Message.Import.Failure"], MessageType.Error);
|
|
||||||
}
|
|
||||||
HideProgressIndicator();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -35,11 +35,11 @@
|
|||||||
{
|
{
|
||||||
if (Disabled)
|
if (Disabled)
|
||||||
{
|
{
|
||||||
<button type="button" class="@Class" disabled>@((MarkupString)_openIconSpan) @_openText</button>
|
<button type="button" class="@Class" title="@AltText" disabled>@((MarkupString)_openIconSpan) @_openText</button>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<button type="button" class="@Class" @onclick="DisplayModal">@((MarkupString)_openIconSpan) @_openText</button>
|
<button type="button" class="@Class" title="@AltText" @onclick="DisplayModal">@((MarkupString)_openIconSpan) @_openText</button>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,13 +83,13 @@ else
|
|||||||
{
|
{
|
||||||
if (Disabled)
|
if (Disabled)
|
||||||
{
|
{
|
||||||
<button type="button" class="@Class" disabled>@((MarkupString)_openIconSpan) @_openText</button>
|
<button type="button" title="@AltText" class="@Class" disabled>@((MarkupString)_openIconSpan) @_openText</button>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<form method="post" class="app-form-inline" @formname="@($"ActionDialogActionForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="DisplayModal" data-enhance>
|
<form method="post" class="app-form-inline" @formname="@($"ActionDialogActionForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="DisplayModal" data-enhance>
|
||||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||||
<button type="submit" class="@Class">@((MarkupString)_openIconSpan) @_openText</button>
|
<button type="submit" title="@AltText" class="@Class">@((MarkupString)_openIconSpan) @_openText</button>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,6 +113,9 @@ else
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public string Text { get; set; } // optional - defaults to Action if not specified
|
public string Text { get; set; } // optional - defaults to Action if not specified
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string AltText { get; set; } // optional
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Action { get; set; } // optional
|
public string Action { get; set; } // optional
|
||||||
|
|
||||||
|
|||||||
@@ -8,17 +8,17 @@
|
|||||||
{
|
{
|
||||||
if (Disabled)
|
if (Disabled)
|
||||||
{
|
{
|
||||||
<NavLink class="@($"{_classname} disabled")" href="@_url" style="@_style">@((MarkupString)_iconSpan) @_text</NavLink>
|
<NavLink class="@($"{_classname} disabled")" title="@AltText" href="@_url" style="@_style">@((MarkupString)_iconSpan) @_text</NavLink>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (OnClick == null)
|
if (OnClick == null)
|
||||||
{
|
{
|
||||||
<NavLink class="@_classname" href="@_url" style="@_style">@((MarkupString)_iconSpan) @_text</NavLink>
|
<NavLink class="@_classname" title="@AltText" href="@_url" style="@_style">@((MarkupString)_iconSpan) @_text</NavLink>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<button type="button" class="@_classname" style="@_style" onclick="@OnClick">@((MarkupString)_iconSpan) @_text</button>
|
<button type="button" class="@_classname" title="@AltText" style="@_style" onclick="@OnClick">@((MarkupString)_iconSpan) @_text</button>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,6 +42,9 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public string Text { get; set; } // optional - defaults to Action if not specified
|
public string Text { get; set; } // optional - defaults to Action if not specified
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string AltText { get; set; } // optional
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public int ModuleId { get; set; } = -1; // optional - allows the link to target a specific moduleid
|
public int ModuleId { get; set; } = -1; // optional - allows the link to target a specific moduleid
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
|
|
||||||
if (CreatedOn != null)
|
if (CreatedOn != null)
|
||||||
{
|
{
|
||||||
_text += $" {Localizer["On"]} <b>{UtcToLocal(CreatedOn).Value.ToString(DateTimeFormat)}</ b >";
|
_text += $" {Localizer["On"]} <b>{UtcToLocal(CreatedOn).Value.ToString(DateTimeFormat)}</b>";
|
||||||
}
|
}
|
||||||
|
|
||||||
_text += "</p>";
|
_text += "</p>";
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
|
|
||||||
if (ModifiedOn != null)
|
if (ModifiedOn != null)
|
||||||
{
|
{
|
||||||
_text += $" {Localizer["On"]} <b>{UtcToLocal(ModifiedOn).Value.ToString(DateTimeFormat)}</ b >";
|
_text += $" {Localizer["On"]} <b>{UtcToLocal(ModifiedOn).Value.ToString(DateTimeFormat)}</b>";
|
||||||
}
|
}
|
||||||
|
|
||||||
_text += "</p>";
|
_text += "</p>";
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
|
|
||||||
if (DeletedOn != null)
|
if (DeletedOn != null)
|
||||||
{
|
{
|
||||||
_text += $" {Localizer["On"]} <b>{UtcToLocal(DeletedOn).Value.ToString(DateTimeFormat)}</ b >";
|
_text += $" {Localizer["On"]} <b>{UtcToLocal(DeletedOn).Value.ToString(DateTimeFormat)}</b>";
|
||||||
}
|
}
|
||||||
|
|
||||||
_text += "</p>";
|
_text += "</p>";
|
||||||
|
|||||||
@@ -61,6 +61,12 @@
|
|||||||
{
|
{
|
||||||
<input type="file" id="@_fileinputid" name="file" accept="@_filter" />
|
<input type="file" id="@_fileinputid" name="file" accept="@_filter" />
|
||||||
}
|
}
|
||||||
|
@if (MaxUploadFileSize > 0)
|
||||||
|
{
|
||||||
|
<div class="row my-1">
|
||||||
|
<small class="fw-light">@string.Format(Localizer["File.MaxSize"], MaxUploadFileSize)</small>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<button type="button" class="btn btn-success" @onclick="UploadFiles">@SharedLocalizer["Upload"]</button>
|
<button type="button" class="btn btn-success" @onclick="UploadFiles">@SharedLocalizer["Upload"]</button>
|
||||||
@@ -107,7 +113,7 @@
|
|||||||
|
|
||||||
@code {
|
@code {
|
||||||
private bool _initialized = false;
|
private bool _initialized = false;
|
||||||
private List<Folder> _folders;
|
private List<Folder> _folders = new List<Folder>();
|
||||||
private List<File> _files = new List<File>();
|
private List<File> _files = new List<File>();
|
||||||
private string _fileinputid = string.Empty;
|
private string _fileinputid = string.Empty;
|
||||||
private string _progressinfoid = string.Empty;
|
private string _progressinfoid = string.Empty;
|
||||||
@@ -157,9 +163,15 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false
|
public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public bool AnonymizeUploadFilenames { get; set; } = false; // optional - indicate if file names should be anonymized on upload - default false
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public int ChunkSize { get; set; } = 1; // optional - size of file chunks to upload in MB
|
public int ChunkSize { get; set; } = 1; // optional - size of file chunks to upload in MB
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public int MaxUploadFileSize { get; set; } = -1; // optional - maximum upload file size in MB
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public EventCallback<int> OnUpload { get; set; } // optional - executes a method in the calling component when a file is uploaded
|
public EventCallback<int> OnUpload { get; set; } // optional - executes a method in the calling component when a file is uploaded
|
||||||
|
|
||||||
@@ -195,8 +207,10 @@
|
|||||||
Filter = "nupkg";
|
Filter = "nupkg";
|
||||||
ShowSuccess = true;
|
ShowSuccess = true;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if (!string.IsNullOrEmpty(Folder) && Folder != Constants.PackagesFolder)
|
{
|
||||||
|
// folder path specified rather than folderid
|
||||||
|
if (!string.IsNullOrEmpty(Folder))
|
||||||
{
|
{
|
||||||
Folder folder = await FolderService.GetFolderAsync(ModuleState.SiteId, Folder);
|
Folder folder = await FolderService.GetFolderAsync(ModuleState.SiteId, Folder);
|
||||||
if (folder != null)
|
if (folder != null)
|
||||||
@@ -210,6 +224,7 @@
|
|||||||
_messagetype = MessageType.Error;
|
_messagetype = MessageType.Error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (ShowFolders)
|
if (ShowFolders)
|
||||||
{
|
{
|
||||||
@@ -242,25 +257,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await SetImage();
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(Filter))
|
if (!string.IsNullOrEmpty(Filter))
|
||||||
{
|
{
|
||||||
_filter = "." + Filter.Replace(",", ",.");
|
_filter = "." + Filter.Replace(",", ",.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GetFolderPermission();
|
||||||
|
await SetImage();
|
||||||
await GetFiles();
|
await GetFiles();
|
||||||
|
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task GetFiles()
|
private void GetFolderPermission()
|
||||||
{
|
{
|
||||||
_haseditpermission = false;
|
_haseditpermission = false;
|
||||||
if (Folder == Constants.PackagesFolder)
|
if (Folder == Constants.PackagesFolder)
|
||||||
{
|
{
|
||||||
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, RoleNames.Host);
|
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, RoleNames.Host);
|
||||||
_files = new List<File>();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -268,67 +282,12 @@
|
|||||||
if (folder != null)
|
if (folder != null)
|
||||||
{
|
{
|
||||||
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, folder.PermissionList);
|
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, folder.PermissionList);
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Browse, folder.PermissionList))
|
|
||||||
{
|
|
||||||
_files = await FileService.GetFilesAsync(FolderId);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_files = new List<File>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_haseditpermission = false;
|
_haseditpermission = false;
|
||||||
_files = new List<File>();
|
|
||||||
}
|
|
||||||
if (_filter != "*")
|
|
||||||
{
|
|
||||||
List<File> filtered = new List<File>();
|
|
||||||
foreach (File file in _files)
|
|
||||||
{
|
|
||||||
if (_filter.ToUpper().IndexOf("." + file.Extension.ToUpper()) != -1)
|
|
||||||
{
|
|
||||||
filtered.Add(file);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_files = filtered;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task FolderChanged(ChangeEventArgs e)
|
|
||||||
{
|
|
||||||
_message = string.Empty;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
FolderId = int.Parse((string)e.Value);
|
|
||||||
await GetFiles();
|
|
||||||
FileId = -1;
|
|
||||||
_file = null;
|
|
||||||
_image = string.Empty;
|
|
||||||
|
|
||||||
await OnSelectFolder.InvokeAsync(FolderId);
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error Loading Files {Error}", ex.Message);
|
|
||||||
_message = Localizer["Error.File.Load"];
|
|
||||||
_messagetype = MessageType.Error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task FileChanged(ChangeEventArgs e)
|
|
||||||
{
|
|
||||||
_message = string.Empty;
|
|
||||||
FileId = int.Parse((string)e.Value);
|
|
||||||
await SetImage();
|
|
||||||
#pragma warning disable CS0618
|
|
||||||
await OnSelect.InvokeAsync(FileId);
|
|
||||||
#pragma warning restore CS0618
|
|
||||||
await OnSelectFile.InvokeAsync(FileId);
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SetImage()
|
private async Task SetImage()
|
||||||
@@ -354,6 +313,74 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task GetFiles()
|
||||||
|
{
|
||||||
|
if (ShowFiles)
|
||||||
|
{
|
||||||
|
Folder folder = _folders.FirstOrDefault(item => item.FolderId == FolderId);
|
||||||
|
if (folder != null)
|
||||||
|
{
|
||||||
|
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Browse, folder.PermissionList))
|
||||||
|
{
|
||||||
|
_files = await FileService.GetFilesAsync(FolderId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_files = new List<File>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_files = new List<File>();
|
||||||
|
}
|
||||||
|
if (_filter != "*")
|
||||||
|
{
|
||||||
|
List<File> filtered = new List<File>();
|
||||||
|
foreach (File file in _files)
|
||||||
|
{
|
||||||
|
if (_filter.ToUpper().IndexOf("." + file.Extension.ToUpper()) != -1)
|
||||||
|
{
|
||||||
|
filtered.Add(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_files = filtered;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task FolderChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
_message = string.Empty;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
FolderId = int.Parse((string)e.Value);
|
||||||
|
FileId = -1;
|
||||||
|
GetFolderPermission();
|
||||||
|
await SetImage();
|
||||||
|
await GetFiles();
|
||||||
|
await OnSelectFolder.InvokeAsync(FolderId);
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Loading Files {Error}", ex.Message);
|
||||||
|
_message = Localizer["Error.File.Load"];
|
||||||
|
_messagetype = MessageType.Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task FileChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
_message = string.Empty;
|
||||||
|
FileId = int.Parse((string)e.Value);
|
||||||
|
await SetImage();
|
||||||
|
#pragma warning disable CS0618
|
||||||
|
await OnSelect.InvokeAsync(FileId);
|
||||||
|
#pragma warning restore CS0618
|
||||||
|
await OnSelectFile.InvokeAsync(FileId);
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task UploadFiles()
|
private async Task UploadFiles()
|
||||||
{
|
{
|
||||||
_message = string.Empty;
|
_message = string.Empty;
|
||||||
@@ -363,16 +390,39 @@
|
|||||||
if (uploads.Length > 0)
|
if (uploads.Length > 0)
|
||||||
{
|
{
|
||||||
string restricted = "";
|
string restricted = "";
|
||||||
|
string tooLarge = "";
|
||||||
foreach (var upload in uploads)
|
foreach (var upload in uploads)
|
||||||
{
|
{
|
||||||
var filename = upload.Split(':')[0];
|
var fileparts = upload.Split(':');
|
||||||
|
var filename = fileparts[0];
|
||||||
|
|
||||||
|
if (MaxUploadFileSize > 0)
|
||||||
|
{
|
||||||
|
var filesizeBytes = long.Parse(fileparts[1]);
|
||||||
|
var filesizeMB = (double)filesizeBytes / (1024 * 1024);
|
||||||
|
if (filesizeMB > MaxUploadFileSize)
|
||||||
|
{
|
||||||
|
tooLarge += (tooLarge == "" ? "" : ",") + filename;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var extension = (filename.LastIndexOf(".") != -1) ? filename.Substring(filename.LastIndexOf(".") + 1) : "";
|
var extension = (filename.LastIndexOf(".") != -1) ? filename.Substring(filename.LastIndexOf(".") + 1) : "";
|
||||||
if (!PageState.Site.UploadableFiles.Split(',').Contains(extension.ToLower()))
|
if (!PageState.Site.UploadableFiles.Split(',').Contains(extension.ToLower()))
|
||||||
{
|
{
|
||||||
restricted += (restricted == "" ? "" : ",") + extension;
|
restricted += (restricted == "" ? "" : ",") + extension;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (restricted == "")
|
if (restricted != "")
|
||||||
|
{
|
||||||
|
_message = string.Format(Localizer["Message.File.Restricted"], restricted);
|
||||||
|
_messagetype = MessageType.Warning;
|
||||||
|
}
|
||||||
|
else if (tooLarge != "")
|
||||||
|
{
|
||||||
|
_message = string.Format(Localizer["Message.File.TooLarge"], tooLarge, MaxUploadFileSize);
|
||||||
|
_messagetype = MessageType.Warning;
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
CancellationTokenSource tokenSource = new CancellationTokenSource();
|
CancellationTokenSource tokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
@@ -408,7 +458,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// upload files
|
// upload files
|
||||||
var success = await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken, jwt, chunksize, tokenSource.Token);
|
var success = await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken, jwt, chunksize, AnonymizeUploadFilenames, tokenSource.Token);
|
||||||
|
|
||||||
// reset progress indicators
|
// reset progress indicators
|
||||||
if (ShowProgress)
|
if (ShowProgress)
|
||||||
@@ -430,35 +480,35 @@
|
|||||||
_message = Localizer["Success.File.Upload"];
|
_message = Localizer["Success.File.Upload"];
|
||||||
_messagetype = MessageType.Success;
|
_messagetype = MessageType.Success;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await logger.LogError("File Upload Failed {Files}", uploads);
|
|
||||||
_message = Localizer["Error.File.Upload"];
|
|
||||||
_messagetype = MessageType.Error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Folder == Constants.PackagesFolder)
|
FileId = -1;
|
||||||
{
|
if (Folder != Constants.PackagesFolder && !AnonymizeUploadFilenames)
|
||||||
await OnUpload.InvokeAsync(-1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
// set FileId to first file in upload collection
|
// set FileId to first file in upload collection
|
||||||
var file = await FileService.GetFileAsync(int.Parse(folder), uploads[0].Split(":")[0]);
|
var file = await FileService.GetFileAsync(int.Parse(folder), uploads[0].Split(":")[0]);
|
||||||
if (file != null)
|
if (file != null)
|
||||||
{
|
{
|
||||||
FileId = file.FileId;
|
FileId = file.FileId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await SetImage();
|
await SetImage();
|
||||||
|
|
||||||
|
await OnUpload.InvokeAsync(FileId);
|
||||||
#pragma warning disable CS0618
|
#pragma warning disable CS0618
|
||||||
await OnSelect.InvokeAsync(FileId);
|
await OnSelect.InvokeAsync(FileId);
|
||||||
#pragma warning restore CS0618
|
#pragma warning restore CS0618
|
||||||
await OnSelectFile.InvokeAsync(FileId);
|
await OnSelectFile.InvokeAsync(FileId);
|
||||||
await OnUpload.InvokeAsync(FileId);
|
|
||||||
}
|
|
||||||
await GetFiles();
|
await GetFiles();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await logger.LogError("File Upload Failed {Files}", uploads);
|
||||||
|
_message = Localizer["Error.File.Upload"];
|
||||||
|
_messagetype = MessageType.Error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -471,12 +521,6 @@
|
|||||||
finally {
|
finally {
|
||||||
tokenSource.Dispose();
|
tokenSource.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_message = string.Format(Localizer["Message.File.Restricted"], restricted);
|
|
||||||
_messagetype = MessageType.Warning;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -501,13 +545,15 @@
|
|||||||
_messagetype = MessageType.Success;
|
_messagetype = MessageType.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
await GetFiles();
|
|
||||||
FileId = -1;
|
FileId = -1;
|
||||||
await SetImage();
|
await SetImage();
|
||||||
|
|
||||||
#pragma warning disable CS0618
|
#pragma warning disable CS0618
|
||||||
await OnSelect.InvokeAsync(FileId);
|
await OnSelect.InvokeAsync(FileId);
|
||||||
#pragma warning restore CS0618
|
#pragma warning restore CS0618
|
||||||
await OnSelectFile.InvokeAsync(FileId);
|
await OnSelectFile.InvokeAsync(FileId);
|
||||||
|
|
||||||
|
await GetFiles();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -533,15 +579,16 @@
|
|||||||
public async Task Refresh(int fileId)
|
public async Task Refresh(int fileId)
|
||||||
{
|
{
|
||||||
await GetFiles();
|
await GetFiles();
|
||||||
|
FileId = -1;
|
||||||
if (fileId != -1)
|
if (fileId != -1)
|
||||||
{
|
{
|
||||||
var file = _files.Where(item => item.FileId == fileId).FirstOrDefault();
|
var file = _files.Where(item => item.FileId == fileId).FirstOrDefault();
|
||||||
if (file != null)
|
if (file != null)
|
||||||
{
|
{
|
||||||
FileId = file.FileId;
|
FileId = file.FileId;
|
||||||
|
}
|
||||||
|
}
|
||||||
await SetImage();
|
await SetImage();
|
||||||
}
|
|
||||||
}
|
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,29 +4,43 @@
|
|||||||
|
|
||||||
@if (!string.IsNullOrEmpty(Message))
|
@if (!string.IsNullOrEmpty(Message))
|
||||||
{
|
{
|
||||||
|
@if (_style == MessageStyle.Alert)
|
||||||
|
{
|
||||||
<div class="@_classname alert-dismissible fade show mb-3" role="alert">
|
<div class="@_classname alert-dismissible fade show mb-3" role="alert">
|
||||||
@((MarkupString)Message)
|
@((MarkupString)Message)
|
||||||
@if (Type == MessageType.Error && PageState != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
@if (Type == MessageType.Error && PageState != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
<NavLink class="ms-2" href="@NavigateUrl("admin/log")">View Details</NavLink>
|
<NavLink class="ms-2" href="@NavigateUrl("admin/log")">View Details</NavLink>
|
||||||
}
|
}
|
||||||
@if (ModuleState != null)
|
<form method="post" class="app-form-inline" @formname="ModuleMessageForm" @onsubmit="CloseMessage" data-enhance>
|
||||||
{
|
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||||
@if (ModuleState.RenderMode == RenderModes.Static)
|
<button type="submit" class="btn-close" data-dismiss="alert" aria-label="close"></button>
|
||||||
{
|
</form>
|
||||||
<a href="@NavigationManager.Uri" class="btn-close" data-dismiss="alert" aria-label="close"></a>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<button type="button" class="btn-close" data-dismiss="alert" aria-label="close" @onclick="CloseMessage"></button>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (_style == MessageStyle.Toast)
|
||||||
|
{
|
||||||
|
<div class="app-modulemessage-toast bottom-0 end-0">
|
||||||
|
<div class="@_classname alert-dismissible fade show mb-3 rounded-end-0" role="alert">
|
||||||
|
@((MarkupString)Message)
|
||||||
|
@if (Type == MessageType.Error && PageState != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
|
{
|
||||||
|
<NavLink class="ms-2" href="@NavigateUrl("admin/log")">View Details</NavLink>
|
||||||
|
}
|
||||||
|
<form method="post" class="app-form-inline" @formname="ModuleMessageForm" @onsubmit="CloseMessage" data-enhance>
|
||||||
|
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||||
|
<button type="submit" class="btn-close" data-dismiss="alert" aria-label="close"></button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private string _message = string.Empty;
|
private string _message = string.Empty;
|
||||||
private string _classname = string.Empty;
|
private string _classname = string.Empty;
|
||||||
|
private MessageStyle _style;
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Message { get; set; }
|
public string Message { get; set; }
|
||||||
@@ -34,6 +48,9 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public MessageType Type { get; set; }
|
public MessageType Type { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public MessageStyle Style { get; set; } = MessageStyle.Alert;
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public RenderModeBoundary Parent { get; set; }
|
public RenderModeBoundary Parent { get; set; }
|
||||||
|
|
||||||
@@ -43,6 +60,11 @@
|
|||||||
if (!string.IsNullOrEmpty(_message))
|
if (!string.IsNullOrEmpty(_message))
|
||||||
{
|
{
|
||||||
_classname = GetMessageType(Type);
|
_classname = GetMessageType(Type);
|
||||||
|
_style = Style;
|
||||||
|
if (Type == MessageType.Error)
|
||||||
|
{
|
||||||
|
_style = MessageStyle.Alert; // errors should always be displayed as alerts
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,9 +89,10 @@
|
|||||||
|
|
||||||
return classname;
|
return classname;
|
||||||
}
|
}
|
||||||
private void CloseMessage(MouseEventArgs e)
|
|
||||||
|
private void CloseMessage()
|
||||||
{
|
{
|
||||||
if(Parent != null)
|
if (Parent != null)
|
||||||
{
|
{
|
||||||
Parent.DismissMessage();
|
Parent.DismissMessage();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
@if (_permissions != null)
|
@if (_permissions != null)
|
||||||
{
|
{
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<table class="table table-borderless">
|
<table class="table table-borderless">
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
@foreach (var permissionname in _permissionnames)
|
@foreach (var permissionname in _permissionnames)
|
||||||
{
|
{
|
||||||
<td style="text-align: center;">
|
<td style="text-align: center;">
|
||||||
<TriStateCheckBox Value=@GetPermissionValue(permissionname, role.Name, -1) Disabled="@GetPermissionDisabled(permissionname, role.Name)" OnChange="@(e => PermissionChanged(e, permissionname, role.Name, -1))" />
|
<TriStateCheckBox Value="@GetPermissionValue(permissionname, role.Name, -1)" Disabled="@GetPermissionDisabled(permissionname, role.Name)" OnChange="@(e => PermissionChanged(e, permissionname, role.Name, -1))" />
|
||||||
</td>
|
</td>
|
||||||
}
|
}
|
||||||
</tr>
|
</tr>
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
@foreach (var permissionname in _permissionnames)
|
@foreach (var permissionname in _permissionnames)
|
||||||
{
|
{
|
||||||
<td style="text-align: center; width: 1px;">
|
<td style="text-align: center; width: 1px;">
|
||||||
<TriStateCheckBox Value=@GetPermissionValue(permissionname, "", user.UserId) Disabled="@GetPermissionDisabled(permissionname, "")" OnChange="@(e => PermissionChanged(e, permissionname, "", user.UserId))" />
|
<TriStateCheckBox Value="@GetPermissionValue(permissionname, "", user.UserId)" Disabled="@GetPermissionDisabled(permissionname, "")" OnChange="@(e => PermissionChanged(e, permissionname, "", user.UserId))" />
|
||||||
</td>
|
</td>
|
||||||
}
|
}
|
||||||
</tr>
|
</tr>
|
||||||
@@ -88,7 +88,7 @@
|
|||||||
<ModuleMessage Type="MessageType.Warning" Message="@_message" />
|
<ModuleMessage Type="MessageType.Warning" Message="@_message" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@@ -119,10 +119,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
_roles = await RoleService.GetRolesAsync(ModuleState.SiteId, true);
|
_roles = await RoleService.GetRolesAsync(ModuleState.SiteId, true);
|
||||||
if (!UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
_roles.RemoveAll(item => item.Name == RoleNames.Host); // remove host role
|
||||||
{
|
|
||||||
_roles.RemoveAll(item => item.Name == RoleNames.Host);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get permission names
|
// get permission names
|
||||||
if (string.IsNullOrEmpty(PermissionNames))
|
if (string.IsNullOrEmpty(PermissionNames))
|
||||||
@@ -222,24 +219,24 @@
|
|||||||
|
|
||||||
private bool GetPermissionDisabled(string permissionName, string roleName)
|
private bool GetPermissionDisabled(string permissionName, string roleName)
|
||||||
{
|
{
|
||||||
|
var disabled = false;
|
||||||
|
|
||||||
|
// administrator role permissions can only be changed by a host
|
||||||
if (roleName == RoleNames.Admin && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
if (roleName == RoleNames.Admin && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
return true;
|
disabled = true;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (GetEntityName(permissionName) != EntityName && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PermissionChanged(bool? value, string permissionName, string roleName, int userId)
|
// API permissions can only be changed by an administrator
|
||||||
|
if (GetEntityName(permissionName) != EntityName && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
|
{
|
||||||
|
disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool? PermissionChanged(bool? value, string permissionName, string roleName, int userId)
|
||||||
{
|
{
|
||||||
if (roleName != "")
|
if (roleName != "")
|
||||||
{
|
{
|
||||||
@@ -248,6 +245,14 @@
|
|||||||
{
|
{
|
||||||
_permissions.Remove(permission);
|
_permissions.Remove(permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// system roles cannot be denied - only custom roles can be denied
|
||||||
|
var role = _roles.FirstOrDefault(item => item.Name == roleName);
|
||||||
|
if (value != null && !value.Value && role.IsSystem)
|
||||||
|
{
|
||||||
|
value = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionName), GetPermissionName(permissionName), roleName, null, value.Value));
|
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionName), GetPermissionName(permissionName), roleName, null, value.Value));
|
||||||
@@ -265,6 +270,7 @@
|
|||||||
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionName), GetPermissionName(permissionName), null, userId, value.Value));
|
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionName), GetPermissionName(permissionName), null, userId, value.Value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Dictionary<string, string>> GetUsers(string filter)
|
private async Task<Dictionary<string, string>> GetUsers(string filter)
|
||||||
@@ -305,29 +311,20 @@
|
|||||||
|
|
||||||
private void ValidatePermissions()
|
private void ValidatePermissions()
|
||||||
{
|
{
|
||||||
// remove deny all users, unauthenticated, and registered users
|
|
||||||
var permissions = _permissions.Where(item => !item.IsAuthorized &&
|
|
||||||
(item.RoleName == RoleNames.Everyone || item.RoleName == RoleNames.Unauthenticated || item.RoleName == RoleNames.Registered)).ToList();
|
|
||||||
foreach (var permission in permissions)
|
|
||||||
{
|
|
||||||
_permissions.Remove(permission);
|
|
||||||
}
|
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
// remove deny administrators and host users
|
// remove host role permissions
|
||||||
permissions = _permissions.Where(item => !item.IsAuthorized &&
|
var permissions = _permissions.Where(item => item.RoleName == RoleNames.Host).ToList();
|
||||||
(item.RoleName == RoleNames.Admin || item.RoleName == RoleNames.Host)).ToList();
|
|
||||||
foreach (var permission in permissions)
|
foreach (var permission in permissions)
|
||||||
{
|
{
|
||||||
_permissions.Remove(permission);
|
_permissions.Remove(permission);
|
||||||
}
|
}
|
||||||
|
// add host role permissions if administrator role is not assigned (to prevent lockout)
|
||||||
foreach (var permissionname in _permissionnames)
|
foreach (var permissionname in _permissionnames)
|
||||||
{
|
{
|
||||||
// add administrators role if neither host or administrator is assigned
|
if (!_permissions.Any(item => item.EntityName == GetEntityName(permissionname) && item.PermissionName == GetPermissionName(permissionname) && item.RoleName == RoleNames.Admin))
|
||||||
if (!_permissions.Any(item => item.EntityName == GetEntityName(permissionname) && item.PermissionName == GetPermissionName(permissionname) &&
|
|
||||||
(item.RoleName == RoleNames.Admin || item.RoleName == RoleNames.Host)))
|
|
||||||
{
|
{
|
||||||
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionname), GetPermissionName(permissionname), RoleNames.Admin, null, true));
|
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionname), GetPermissionName(permissionname), RoleNames.Host, null, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
@inject ISettingService SettingService
|
@inject ISettingService SettingService
|
||||||
@inject IStringLocalizer<RichTextEditor> Localizer
|
@inject IStringLocalizer<RichTextEditor> Localizer
|
||||||
|
|
||||||
<div class="row" style="margin-bottom: 50px;">
|
<div class="row" style="@_style">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
@_textEditorComponent
|
@_textEditorComponent
|
||||||
</div>
|
</div>
|
||||||
@@ -18,6 +18,8 @@
|
|||||||
private RenderFragment _textEditorComponent;
|
private RenderFragment _textEditorComponent;
|
||||||
private ITextEditor _textEditor;
|
private ITextEditor _textEditor;
|
||||||
|
|
||||||
|
private string _style = "margin-bottom: 50px;";
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Content { get; set; }
|
public string Content { get; set; }
|
||||||
|
|
||||||
@@ -30,6 +32,9 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public string Provider { get; set; }
|
public string Provider { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string Style { get; set; } // optional
|
||||||
|
|
||||||
[Parameter(CaptureUnmatchedValues = true)]
|
[Parameter(CaptureUnmatchedValues = true)]
|
||||||
public Dictionary<string, object> AdditionalAttributes { get; set; } = new Dictionary<string, object>();
|
public Dictionary<string, object> AdditionalAttributes { get; set; } = new Dictionary<string, object>();
|
||||||
|
|
||||||
@@ -40,6 +45,12 @@
|
|||||||
|
|
||||||
protected override void OnParametersSet()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(Style))
|
||||||
|
{
|
||||||
|
_style = Style;
|
||||||
|
}
|
||||||
|
|
||||||
_textEditorComponent = (builder) =>
|
_textEditorComponent = (builder) =>
|
||||||
{
|
{
|
||||||
CreateTextEditor(builder);
|
CreateTextEditor(builder);
|
||||||
|
|||||||
@@ -30,6 +30,12 @@ else
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public SecurityAccessLevel? Security { get; set; } // optional - can be used to specify SecurityAccessLevel
|
public SecurityAccessLevel? Security { get; set; } // optional - can be used to specify SecurityAccessLevel
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string RoleName { get; set; } // optional - can be used to specify Role allowed to view this tab
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string PermissionName { get; set; } // optional - can be used to specify Permission allowed to view this tab
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
base.OnParametersSet();
|
base.OnParametersSet();
|
||||||
|
|||||||
@@ -84,14 +84,63 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if a tab should be visible based on user permissions.
|
||||||
|
/// Authorization follows this hierarchy:
|
||||||
|
/// 1. Host tabs (Security == Host): Only users with Host role can access (Admins excluded)
|
||||||
|
/// 2. Admin users: Bypass all other checks (except Host restrictions)
|
||||||
|
/// 3. SecurityAccessLevel check (null/Anonymous/View/Edit/Host):
|
||||||
|
/// - null: No security level restriction (proceeds to step 4)
|
||||||
|
/// - Anonymous: No authentication required
|
||||||
|
/// - View/Edit: Requires corresponding module permission
|
||||||
|
/// - Host: Only Host role can access
|
||||||
|
/// 4. Additional RoleName requirement (if specified)
|
||||||
|
/// 5. Additional PermissionName requirement (if specified)
|
||||||
|
///
|
||||||
|
/// Important: When Security is null, RoleName and PermissionName checks STILL apply
|
||||||
|
/// (Security = null doesn't mean unrestricted, it means "no security level required")
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tabPanel">The tab panel to check authorization for</param>
|
||||||
|
/// <returns>True if user is authorized to see this tab, false otherwise</returns>
|
||||||
private bool IsAuthorized(TabPanel tabPanel)
|
private bool IsAuthorized(TabPanel tabPanel)
|
||||||
{
|
{
|
||||||
var authorized = false;
|
// Step 1: Check for Host-only restriction
|
||||||
|
if (tabPanel.Security == SecurityAccessLevel.Host)
|
||||||
|
{
|
||||||
|
return UserSecurity.IsAuthorized(PageState.User, RoleNames.Host);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Admin bypass all restrictions except Host
|
||||||
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: If Security is null, check only RoleName and PermissionName
|
||||||
|
if (tabPanel.Security == null)
|
||||||
|
{
|
||||||
|
// Start with authorized = true for null security
|
||||||
|
bool isAuthorized = true;
|
||||||
|
|
||||||
|
// Only apply RoleName check if provided
|
||||||
|
if (!string.IsNullOrEmpty(tabPanel.RoleName))
|
||||||
|
{
|
||||||
|
isAuthorized = UserSecurity.IsAuthorized(PageState.User, tabPanel.RoleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only apply PermissionName check if provided
|
||||||
|
if (isAuthorized && !string.IsNullOrEmpty(tabPanel.PermissionName))
|
||||||
|
{
|
||||||
|
isAuthorized = UserSecurity.IsAuthorized(PageState.User, tabPanel.PermissionName, ModuleState.PermissionList);
|
||||||
|
}
|
||||||
|
|
||||||
|
return isAuthorized;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle other SecurityAccessLevel values
|
||||||
|
bool authorized = false; // Use different variable name or move declaration
|
||||||
switch (tabPanel.Security)
|
switch (tabPanel.Security)
|
||||||
{
|
{
|
||||||
case null: // security not specified - assume SecurityAccessLevel.Anonymous
|
|
||||||
authorized = true;
|
|
||||||
break;
|
|
||||||
case SecurityAccessLevel.Anonymous:
|
case SecurityAccessLevel.Anonymous:
|
||||||
authorized = true;
|
authorized = true;
|
||||||
break;
|
break;
|
||||||
@@ -101,13 +150,23 @@
|
|||||||
case SecurityAccessLevel.Edit:
|
case SecurityAccessLevel.Edit:
|
||||||
authorized = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, ModuleState.PermissionList);
|
authorized = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, ModuleState.PermissionList);
|
||||||
break;
|
break;
|
||||||
case SecurityAccessLevel.Admin:
|
|
||||||
authorized = UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin);
|
|
||||||
break;
|
|
||||||
case SecurityAccessLevel.Host:
|
case SecurityAccessLevel.Host:
|
||||||
authorized = UserSecurity.IsAuthorized(PageState.User, RoleNames.Host);
|
authorized = UserSecurity.IsAuthorized(PageState.User, RoleNames.Host);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Step 4: Additional RoleName requirement
|
||||||
|
if (authorized && !string.IsNullOrEmpty(tabPanel.RoleName))
|
||||||
|
{
|
||||||
|
authorized = UserSecurity.IsAuthorized(PageState.User, tabPanel.RoleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 5: Additional PermissionName requirement
|
||||||
|
if (authorized && !string.IsNullOrEmpty(tabPanel.PermissionName))
|
||||||
|
{
|
||||||
|
authorized = UserSecurity.IsAuthorized(PageState.User, tabPanel.PermissionName, ModuleState.PermissionList);
|
||||||
|
}
|
||||||
|
|
||||||
return authorized;
|
return authorized;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user