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/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
|
||||
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/
|
||||
ENTRYPOINT ["dotnet", "Oqtane.Server.dll"]
|
||||
COPY entrypoint.sh .
|
||||
RUN chmod +x entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["./entrypoint.sh"]
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
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
|
||||
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.Collections.Generic
|
||||
@using System.Net.Http
|
||||
@@ -9,7 +9,9 @@
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using Microsoft.Extensions.Localization
|
||||
@using Microsoft.JSInterop
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
|
||||
@using Oqtane
|
||||
@using Oqtane.Models
|
||||
@using Oqtane.Modules
|
||||
@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",
|
||||
"Database": {
|
||||
"DefaultDBType": ""
|
||||
@@ -22,27 +22,42 @@
|
||||
{
|
||||
"Name": "LocalDB",
|
||||
"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",
|
||||
"ControlType": "Oqtane.Installer.Controls.SqlServerConfig, Oqtane.Client",
|
||||
"DBType": "Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Database.SqlServer"
|
||||
"DBType": "Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Server"
|
||||
},
|
||||
{
|
||||
"Name": "SQLite",
|
||||
"ControlType": "Oqtane.Installer.Controls.SqliteConfig, Oqtane.Client",
|
||||
"DBType": "Oqtane.Database.Sqlite.SqliteDatabase, Oqtane.Database.Sqlite"
|
||||
"DBType": "Oqtane.Database.Sqlite.SqliteDatabase, Oqtane.Server"
|
||||
},
|
||||
{
|
||||
"Name": "MySQL",
|
||||
"ControlType": "Oqtane.Installer.Controls.MySQLConfig, Oqtane.Client",
|
||||
"DBType": "Oqtane.Database.MySQL.MySQLDatabase, Oqtane.Database.MySQL"
|
||||
"DBType": "Oqtane.Database.MySQL.MySQLDatabase, Oqtane.Server"
|
||||
},
|
||||
{
|
||||
"Name": "PostgreSQL",
|
||||
"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]
|
||||
@inherits ModuleBase
|
||||
@inject I[Module]Service [Module]Service
|
||||
@inject I[Module]Service Client[Module]Service
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IStringLocalizer<Edit> Localizer
|
||||
|
||||
@@ -33,11 +33,6 @@
|
||||
|
||||
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 bool validated = false;
|
||||
|
||||
@@ -55,7 +50,7 @@
|
||||
if (PageState.Action == "Edit")
|
||||
{
|
||||
_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)
|
||||
{
|
||||
_name = [Module].Name;
|
||||
@@ -86,14 +81,14 @@
|
||||
[Module] [Module] = new [Module]();
|
||||
[Module].ModuleId = ModuleState.ModuleId;
|
||||
[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]);
|
||||
}
|
||||
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;
|
||||
await [Module]Service.Update[Module]Async([Module]);
|
||||
await Client[Module]Service.Update[Module]Async([Module]);
|
||||
await logger.LogInformation("[Module] Updated {[Module]}", [Module]);
|
||||
}
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
@namespace [Owner].Module.[Module]
|
||||
@inherits ModuleBase
|
||||
@inject I[Module]Service [Module]Service
|
||||
@inject I[Module]Service Client[Module]Service
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
|
||||
@@ -42,8 +42,8 @@ else
|
||||
|
||||
public override List<Resource> Resources => new List<Resource>()
|
||||
{
|
||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" },
|
||||
new Resource { ResourceType = ResourceType.Script, Url = ModulePath() + "Module.js" }
|
||||
new Stylesheet(ModulePath() + "Module.css"),
|
||||
new Script(ModulePath() + "Module.js")
|
||||
};
|
||||
|
||||
List<[Module]> _[Module]s;
|
||||
@@ -52,7 +52,7 @@ else
|
||||
{
|
||||
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)
|
||||
{
|
||||
@@ -65,9 +65,9 @@ else
|
||||
{
|
||||
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]);
|
||||
_[Module]s = await [Module]Service.Get[Module]sAsync(ModuleState.ModuleId);
|
||||
_[Module]s = await Client[Module]Service.Get[Module]sAsync(ModuleState.ModuleId);
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -10,10 +10,7 @@ namespace [Owner].Module.[Module]
|
||||
Name = "[Module]",
|
||||
Description = "[Description]",
|
||||
Version = "1.0.0",
|
||||
ServerManagerType = "[ServerManagerType]",
|
||||
ReleaseVersions = "1.0.0",
|
||||
Dependencies = "[Owner].Module.[Module].Shared.Oqtane",
|
||||
PackageName = "[Owner].Module.[Module]"
|
||||
ServerManagerType = "[ServerManagerType]"
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
@namespace [Owner].Module.[Module]
|
||||
@inherits ModuleBase
|
||||
@implements Oqtane.Interfaces.ISettingsControl
|
||||
@inject ISettingService SettingService
|
||||
@inject IStringLocalizer<Settings> Localizer
|
||||
|
||||
@@ -35,8 +36,8 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
Dictionary<string, string> settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
|
||||
SettingService.SetSetting(settings, "SettingName", _value);
|
||||
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
|
||||
settings = SettingService.SetSetting(settings, "SettingName", _value);
|
||||
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -7,9 +7,10 @@ using Oqtane.Shared;
|
||||
|
||||
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]");
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Linq;
|
||||
using Oqtane.Services;
|
||||
using [Owner].Module.[Module].Services;
|
||||
|
||||
@@ -8,7 +9,10 @@ namespace [Owner].Module.[Module].Startup
|
||||
{
|
||||
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))]
|
||||
[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
|
||||
{
|
||||
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
|
||||
{
|
||||
private readonly IDbContextFactory<[Module]Context> _factory;
|
||||
@@ -7,7 +7,7 @@ using [Owner].Module.[Module].Services;
|
||||
|
||||
namespace [Owner].Module.[Module].Startup
|
||||
{
|
||||
public class ServerStartup : IServerStartup
|
||||
public class [Module]ServerStartup : IServerStartup
|
||||
{
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Oqtane.Models;
|
||||
@@ -6,16 +5,11 @@ using Oqtane.Models;
|
||||
namespace [Owner].Module.[Module].Models
|
||||
{
|
||||
[Table("[Owner][Module]")]
|
||||
public class [Module] : IAuditable
|
||||
public class [Module] : ModelBase
|
||||
{
|
||||
[Key]
|
||||
public int [Module]Id { get; set; }
|
||||
public int ModuleId { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
public string CreatedBy { get; set; }
|
||||
public DateTime CreatedOn { get; set; }
|
||||
public string ModifiedBy { get; set; }
|
||||
public DateTime ModifiedOn { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"Title": "Default Module Template",
|
||||
"Type": "External",
|
||||
"Version": "5.2.0",
|
||||
"Type": "Internal",
|
||||
"Version": "10.0.0",
|
||||
"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,4 +1,4 @@
|
||||
/* Oqtane Styles */
|
||||
/* Oqtane Styles */
|
||||
|
||||
body {
|
||||
padding-top: 7rem;
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"Title": "Default Theme Template",
|
||||
"Type": "External",
|
||||
"Version": "5.2.0",
|
||||
"Type": "Internal",
|
||||
"Version": "10.0.0",
|
||||
"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.Extensions.Localization;
|
||||
using Oqtane.Interfaces;
|
||||
using Oqtane.Providers;
|
||||
using Oqtane.Services;
|
||||
using Oqtane.Shared;
|
||||
using Radzen;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
@@ -23,7 +25,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
services.AddScoped<SiteState>();
|
||||
services.AddScoped<IInstallationService, InstallationService>();
|
||||
services.AddScoped<IModuleDefinitionService, ModuleDefinitionService>();
|
||||
services.AddScoped<IThemeService, ThemeService>();
|
||||
services.AddScoped<IThemeService, Oqtane.Services.ThemeService>();
|
||||
services.AddScoped<IAliasService, AliasService>();
|
||||
services.AddScoped<ITenantService, TenantService>();
|
||||
services.AddScoped<ISiteService, SiteService>();
|
||||
@@ -39,7 +41,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
services.AddScoped<ILogService, LogService>();
|
||||
services.AddScoped<IJobService, JobService>();
|
||||
services.AddScoped<IJobLogService, JobLogService>();
|
||||
services.AddScoped<INotificationService, NotificationService>();
|
||||
services.AddScoped<INotificationService, Oqtane.Services.NotificationService>();
|
||||
services.AddScoped<IFolderService, FolderService>();
|
||||
services.AddScoped<IFileService, FileService>();
|
||||
services.AddScoped<ISiteTemplateService, SiteTemplateService>();
|
||||
@@ -53,12 +55,22 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
services.AddScoped<ISyncService, SyncService>();
|
||||
services.AddScoped<ILocalizationCookieService, LocalizationCookieService>();
|
||||
services.AddScoped<ICookieConsentService, CookieConsentService>();
|
||||
services.AddScoped<IOutputCacheService, OutputCacheService>();
|
||||
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
|
||||
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.QuillJSTextEditor>();
|
||||
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.TextAreaTextEditor>();
|
||||
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.RadzenTextEditor>();
|
||||
|
||||
services.AddRadzenComponents();
|
||||
|
||||
var localizer = services.BuildServiceProvider().GetService<IStringLocalizer<Oqtane.Modules.Controls.RadzenTextEditor>>();
|
||||
Oqtane.Modules.Controls.RadzenEditorDefinitions.Localizer = localizer;
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<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>
|
||||
</div>
|
||||
@@ -182,7 +182,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
_databaseName = "LocalDB";
|
||||
_databaseName = Constants.DefaultDBName;
|
||||
}
|
||||
LoadDatabaseConfigComponent();
|
||||
|
||||
@@ -269,8 +269,8 @@
|
||||
SiteName = Constants.DefaultSite,
|
||||
Register = _register,
|
||||
SiteTemplate = _template,
|
||||
RenderMode = RenderModes.Static,
|
||||
Runtime = Runtimes.Server
|
||||
RenderMode = "", // provided by appsettings.json
|
||||
Runtime = "" // provided by appsettings.json
|
||||
};
|
||||
|
||||
var installation = await InstallationService.Install(config);
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
{
|
||||
string url = NavigateUrl(p.Path);
|
||||
<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>
|
||||
<div class="lead">@((MarkupString)SharedLocalizer[p.Name].ToString().Replace(" ", "<br />"))</div>
|
||||
</NavLink>
|
||||
@@ -24,13 +24,19 @@
|
||||
}
|
||||
|
||||
@code {
|
||||
private List<Page> _pages;
|
||||
Dictionary<string, object> _attributes { get; set; } = new();
|
||||
private List<Page> _pages;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
||||
public override string RenderMode => RenderModes.Static;
|
||||
|
||||
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");
|
||||
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,100 +5,109 @@
|
||||
@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="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 />
|
||||
@if (_initialized)
|
||||
{
|
||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
<div class="container">
|
||||
@if (!_editable)
|
||||
{
|
||||
<ModuleMessage Message="@Localizer["JobNotEditable"]" Type="MessageType.Warning" />
|
||||
}
|
||||
<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>
|
||||
<div class="col-sm-9">
|
||||
<input id="type" class="form-control" @bind="@_jobType" required disabled />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<div class="col-sm-9">
|
||||
<input id="type" class="form-control" @bind="@_jobType" readonly />
|
||||
<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 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>
|
||||
<div class="col-sm-9">
|
||||
<select id="enabled" class="form-select" @bind="@_isEnabled" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="enabled" HelpText="Select whether you want the job enabled or not" ResourceKey="Enabled">Enabled? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="enabled" class="form-select" @bind="@_isEnabled" required disabled="@(!_editable)">
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<div class="col-sm-9">
|
||||
<input id="runs-every" class="form-control mb-1" @bind="@_interval" maxlength="4" required />
|
||||
<select id="runs-every" class="form-select" @bind="@_frequency" required>
|
||||
<option value="m">@Localizer["Minute(s)"]</option>
|
||||
<option value="H">@Localizer["Hour(s)"]</option>
|
||||
<option value="d">@Localizer["Day(s)"]</option>
|
||||
<option value="w">@Localizer["Week(s)"]</option>
|
||||
<option value="M">@Localizer["Month(s)"]</option>
|
||||
<option value="O">@Localizer["Once"]</option>
|
||||
</select>
|
||||
<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>
|
||||
<div class="col-sm-9">
|
||||
<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 disabled="@(!_editable)">
|
||||
<option value="m">@Localizer["Minute(s)"]</option>
|
||||
<option value="H">@Localizer["Hour(s)"]</option>
|
||||
<option value="d">@Localizer["Day(s)"]</option>
|
||||
<option value="w">@Localizer["Week(s)"]</option>
|
||||
<option value="M">@Localizer["Month(s)"]</option>
|
||||
<option value="O">@Localizer["Once"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="retention" HelpText="Number of log entries to retain for this job" ResourceKey="RetentionLog">Retention Log (Items): </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="retention" type="number" min="0" step ="1" class="form-control" @bind="@_retentionHistory" maxlength="3" required />
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="retention" HelpText="Number of log entries to retain for this job" ResourceKey="RetentionLog">Retention Log (Items): </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="retention" type="number" min="0" step="1" class="form-control" @bind="@_retentionHistory" maxlength="3" required disabled="@(!_editable)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="starting" HelpText="Optionally enter the date and time when this job should start executing" ResourceKey="Starting">Starting: </Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<input id="starting" type="date" class="form-control" @bind="@_startDate" />
|
||||
</div>
|
||||
<div class="col">
|
||||
<input id="starting" type="time" class="form-control" placeholder="hh:mm" @bind="@_startTime" />
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="starting" HelpText="Optionally enter the date and time when this job should start executing" ResourceKey="Starting">Starting: </Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<input id="starting" type="date" class="form-control" @bind="@_startDate" disabled="@(!_editable)" />
|
||||
</div>
|
||||
<div class="col">
|
||||
<input id="starting" type="time" class="form-control" @bind="@_startTime" placeholder="hh:mm" required="@(_startDate.HasValue)" disabled="@(!_editable)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="ending" HelpText="Optionally enter the date and time when this job should stop executing" ResourceKey="Ending">Ending: </Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<input id="ending" type="date" class="form-control" @bind="@_endDate" />
|
||||
</div>
|
||||
<div class="col">
|
||||
<input id="ending" type="time" class="form-control" placeholder="hh:mm" @bind="@_endTime" />
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="ending" HelpText="Optionally enter the date and time when this job should stop executing" ResourceKey="Ending">Ending: </Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<input id="ending" type="date" class="form-control" @bind="@_endDate" disabled="@(!_editable)" />
|
||||
</div>
|
||||
<div class="col">
|
||||
<input id="ending" type="time" class="form-control" placeholder="hh:mm" @bind="@_endTime" required="@(_endDate.HasValue)" disabled="@(!_editable)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<div class="col-sm-9">
|
||||
<div class="row">
|
||||
<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 class="row mb-1 align-items-center">
|
||||
<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">
|
||||
<input id="next" class="form-control" @bind="@_nextDate" disabled />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveJob">@SharedLocalizer["Save"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
<br />
|
||||
<br />
|
||||
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
|
||||
</form>
|
||||
<br />
|
||||
@if (_editable)
|
||||
{
|
||||
<button type="button" class="btn btn-success" @onclick="SaveJob">@SharedLocalizer["Save"]</button>
|
||||
}
|
||||
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 />
|
||||
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
|
||||
</form>
|
||||
}
|
||||
|
||||
@code {
|
||||
private ElementReference form;
|
||||
private bool validated = false;
|
||||
private bool _initialized = false;
|
||||
private bool _editable = true;
|
||||
private int _jobId;
|
||||
private string _name = string.Empty;
|
||||
private string _jobType = string.Empty;
|
||||
@@ -113,26 +122,32 @@
|
||||
private DateTime? _nextDate = null;
|
||||
private DateTime? _nextTime = null;
|
||||
private string createdby;
|
||||
private DateTime createdon;
|
||||
private string modifiedby;
|
||||
private DateTime modifiedon;
|
||||
private DateTime createdon;
|
||||
private string modifiedby;
|
||||
private DateTime modifiedon;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_jobId = Int32.Parse(PageState.QueryString["id"]);
|
||||
Job job = await JobService.GetJobAsync(_jobId);
|
||||
if (job != null)
|
||||
{
|
||||
_name = job.Name;
|
||||
_jobType = job.JobType;
|
||||
_isEnabled = job.IsEnabled.ToString();
|
||||
_interval = job.Interval.ToString();
|
||||
_frequency = job.Frequency;
|
||||
_startDate = UtcToLocal(job.StartDate);
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadJob();
|
||||
}
|
||||
|
||||
protected async Task LoadJob()
|
||||
{
|
||||
try
|
||||
{
|
||||
_jobId = Int32.Parse(PageState.QueryString["id"]);
|
||||
Job job = await JobService.GetJobAsync(_jobId);
|
||||
if (job != null)
|
||||
{
|
||||
_editable = !job.IsEnabled && !job.IsExecuting;
|
||||
_name = job.Name;
|
||||
_jobType = job.JobType;
|
||||
_isEnabled = job.IsEnabled.ToString();
|
||||
_interval = job.Interval.ToString();
|
||||
_frequency = job.Frequency;
|
||||
_startDate = UtcToLocal(job.StartDate);
|
||||
_startTime = UtcToLocal(job.StartDate);
|
||||
_endDate = UtcToLocal(job.EndDate);
|
||||
_endTime = UtcToLocal(job.EndDate);
|
||||
@@ -140,63 +155,107 @@
|
||||
_nextDate = UtcToLocal(job.NextExecution);
|
||||
_nextTime = UtcToLocal(job.NextExecution);
|
||||
createdby = job.CreatedBy;
|
||||
createdon = job.CreatedOn;
|
||||
modifiedby = job.ModifiedBy;
|
||||
modifiedon = job.ModifiedOn;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading Job {JobId} {Error}", _jobId, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Job.Load"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
createdon = job.CreatedOn;
|
||||
modifiedby = job.ModifiedBy;
|
||||
modifiedon = job.ModifiedOn;
|
||||
}
|
||||
|
||||
private async Task SaveJob()
|
||||
{
|
||||
if (!Utilities.ValidateEffectiveExpiryDates(_startDate, _endDate))
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.StartEndDateError"], MessageType.Warning);
|
||||
return;
|
||||
_initialized = true;
|
||||
}
|
||||
validated = true;
|
||||
var interop = new Interop(JSRuntime);
|
||||
if (await interop.FormValid(form))
|
||||
{
|
||||
var job = await JobService.GetJobAsync(_jobId);
|
||||
job.Name = _name;
|
||||
job.JobType = _jobType;
|
||||
job.IsEnabled = Boolean.Parse(_isEnabled);
|
||||
job.Frequency = _frequency;
|
||||
if (job.Frequency == "O") // once
|
||||
{
|
||||
job.Interval = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
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));
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading Job {JobId} {Error}", _jobId, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Job.Load"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
job = await JobService.UpdateJobAsync(job);
|
||||
await logger.LogInformation("Job Updated {Job}", job);
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Udate Job {Job} {Error}", job, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Job.Update"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Required.JobInfo"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
private async Task SaveJob()
|
||||
{
|
||||
try
|
||||
{
|
||||
var interop = new Interop(JSRuntime);
|
||||
if (await interop.FormValid(form))
|
||||
{
|
||||
var job = await JobService.GetJobAsync(_jobId);
|
||||
job.Name = _name;
|
||||
job.IsEnabled = bool.Parse(_isEnabled);
|
||||
|
||||
job.Frequency = _frequency;
|
||||
if (job.Frequency == "O") // once
|
||||
{
|
||||
job.Interval = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
job.Interval = int.Parse(_interval);
|
||||
}
|
||||
|
||||
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);
|
||||
await logger.LogInformation("Job Updated {Job}", job);
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.StartDateError"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.StartEndDateError"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,11 +116,19 @@ else
|
||||
{
|
||||
try
|
||||
{
|
||||
await JobService.StartJobAsync(jobId);
|
||||
await logger.LogInformation("Job Started {JobId}", jobId);
|
||||
AddModuleMessage(Localizer["Message.Job.Start"], MessageType.Success);
|
||||
_jobs = await JobService.GetJobsAsync();
|
||||
StateHasChanged();
|
||||
Job _job = await JobService.GetJobAsync(jobId);
|
||||
if (!_job.IsEnabled)
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Job.Disabled"], MessageType.Warning);
|
||||
}
|
||||
else
|
||||
{
|
||||
await JobService.StartJobAsync(jobId);
|
||||
await logger.LogInformation("Job Started {JobId}", jobId);
|
||||
AddModuleMessage(Localizer["Message.Job.Start"], MessageType.Success);
|
||||
_jobs = await JobService.GetJobsAsync();
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -14,93 +14,140 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (!twofactor)
|
||||
{
|
||||
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
<div class="Oqtane-Modules-Admin-Login" @onkeypress="@(e => KeyPressed(e))">
|
||||
@if (_allowexternallogin)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
|
||||
<br />
|
||||
|
||||
<br />
|
||||
}
|
||||
@if (_allowsitelogin)
|
||||
{
|
||||
<div class="form-group">
|
||||
<Label Class="control-label" For="username" HelpText="Please enter your Username" ResourceKey="Username">Username:</Label>
|
||||
<input id="username" type="text" @ref="username" class="form-control" placeholder="@Localizer["Username.Placeholder"]" @bind="@_username" @bind:event="oninput" required />
|
||||
</div>
|
||||
<div class="form-group mt-2">
|
||||
<Label Class="control-label" For="password" HelpText="Please enter your Password" ResourceKey="Password">Password:</Label>
|
||||
<div class="input-group">
|
||||
<input id="password" type="@_passwordtype" name="Password" class="form-control" placeholder="@Localizer["Password.Placeholder"]" @bind="@_password" @bind:event="oninput" required />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
<div class="Oqtane-Modules-Admin-Login" @onkeypress="@(e => KeyPressed(e))">
|
||||
@switch (_action)
|
||||
{
|
||||
case "Login":
|
||||
@if (_allowexternallogin)
|
||||
{
|
||||
<button type="button" class="btn btn-primary col-12" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
|
||||
<hr class="app-rule mt-3 mb-2" />
|
||||
}
|
||||
@if (_allowsitelogin)
|
||||
{
|
||||
<div class="form-group text-center">
|
||||
<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 />
|
||||
</div>
|
||||
<div class="form-group text-center mt-2">
|
||||
<Label Class="control-label" For="password" HelpText="Please enter your Password" ResourceKey="Password">Password:</Label>
|
||||
<div class="input-group">
|
||||
<input id="password" type="@_passwordtype" @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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mt-2">
|
||||
@if (!_alwaysremember)
|
||||
{
|
||||
<div class="form-check">
|
||||
<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>
|
||||
<div class="form-group text-center mt-2">
|
||||
<div>
|
||||
<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">Stay Signed In?</Label>
|
||||
</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 />
|
||||
<button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["ForgotPassword"]</button>
|
||||
<div class="btn-group mt-2 col-12" role="group">
|
||||
<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)
|
||||
{
|
||||
<br />
|
||||
|
||||
<br />
|
||||
<NavLink href="@NavigateUrl("register")">@Localizer["Register"]</NavLink>
|
||||
<hr class="app-rule mt-3" />
|
||||
<div class="text-center mt-2">
|
||||
<NavLink href="@_registerurl">@Localizer["Register"]</NavLink>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
else
|
||||
{
|
||||
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
<div class="container Oqtane-Modules-Admin-Login">
|
||||
<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>
|
||||
<input id="code" class="form-control" @bind="@_code" placeholder="@Localizer["Code.Placeholder"]" maxlength="6" required />
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Cancel"]</button>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
break;
|
||||
case "ForgotPassword":
|
||||
<div class="form-group text-center">
|
||||
<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">
|
||||
<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 />
|
||||
</div>
|
||||
<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-secondary" @onclick="@(() => SetAction("Login"))">@SharedLocalizer["Cancel"]</button>
|
||||
</div>
|
||||
break;
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
|
||||
@code {
|
||||
private bool _allowsitelogin = true;
|
||||
private string _action = "Login";
|
||||
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 bool validated = false;
|
||||
private bool twofactor = false;
|
||||
private string _username = string.Empty;
|
||||
private ElementReference username;
|
||||
private string _password = string.Empty;
|
||||
private string _passwordtype = "password";
|
||||
private string _togglepassword = string.Empty;
|
||||
private ElementReference password;
|
||||
private bool _remember = false;
|
||||
private bool _alwaysremember = false;
|
||||
private string _registerurl = string.Empty;
|
||||
private string _email = string.Empty;
|
||||
private string _code = string.Empty;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
|
||||
public override bool? Prerender => true;
|
||||
|
||||
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()
|
||||
@@ -109,8 +156,22 @@ else
|
||||
{
|
||||
_allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false;
|
||||
_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"));
|
||||
|
||||
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"];
|
||||
|
||||
if (PageState.QueryString.ContainsKey("name"))
|
||||
@@ -126,7 +187,7 @@ else
|
||||
|
||||
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)
|
||||
{
|
||||
await logger.LogInformation(LogFunction.Security, "External Login Linkage Successful For Username {Username}", _username);
|
||||
@@ -158,7 +219,15 @@ else
|
||||
{
|
||||
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
|
||||
if (PageState.User != null && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||
private void SetAction(string action)
|
||||
{
|
||||
_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")
|
||||
{
|
||||
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||
_passwordtype = "text";
|
||||
_togglepassword = SharedLocalizer["HidePassword"];
|
||||
}
|
||||
else
|
||||
{
|
||||
_passwordtype = "password";
|
||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,7 +291,7 @@ else
|
||||
var hybrid = (PageState.Runtime == Shared.Runtime.Hybrid);
|
||||
var user = new User { SiteId = PageState.Site.SiteId, Username = _username, Password = _password, LastIPAddress = SiteState.RemoteIPAddress};
|
||||
|
||||
if (!twofactor)
|
||||
if (_action == "Login")
|
||||
{
|
||||
_remember = _alwaysremember || _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);
|
||||
|
||||
// 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)
|
||||
{
|
||||
// hybrid apps utilize an interactive login
|
||||
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
|
||||
authstateprovider.NotifyAuthenticationChanged();
|
||||
NavigationManager.NavigateTo(NavigateUrl(returnurl, true));
|
||||
NavigationManager.NavigateTo(NavigateUrl(_returnurl, true));
|
||||
}
|
||||
else
|
||||
{
|
||||
// post back to the Login page so that the cookies are set correctly
|
||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = 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/");
|
||||
await interop.SubmitForm(url, fields);
|
||||
}
|
||||
@@ -233,13 +324,13 @@ else
|
||||
{
|
||||
if (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "required" || (user != null && user.TwoFactorRequired))
|
||||
{
|
||||
twofactor = true;
|
||||
_action = "TwoFactor";
|
||||
validated = false;
|
||||
AddModuleMessage(Localizer["Message.TwoFactor"], MessageType.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!twofactor)
|
||||
if (_action != "TwoFactor")
|
||||
{
|
||||
await logger.LogInformation(LogFunction.Security, "Login Failed For Username {Username}", _username);
|
||||
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
|
||||
{
|
||||
if (_username != string.Empty)
|
||||
if (!string.IsNullOrEmpty(_username))
|
||||
{
|
||||
var user = await UserService.GetUserAsync(_username, PageState.Site.SiteId);
|
||||
if (user != null)
|
||||
if (await UserService.ForgotPasswordAsync(_username))
|
||||
{
|
||||
await UserService.ForgotPasswordAsync(user);
|
||||
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
|
||||
{
|
||||
@@ -289,10 +387,8 @@ else
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.ForgotPassword"], MessageType.Info);
|
||||
AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -301,40 +397,114 @@ else
|
||||
}
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
private async Task ForgotUsername()
|
||||
{
|
||||
twofactor = false;
|
||||
_username = "";
|
||||
_password = "";
|
||||
ClearModuleMessage();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task KeyPressed(KeyboardEventArgs e)
|
||||
{
|
||||
if (e.Code == "Enter" || e.Code == "NumpadEnter")
|
||||
try
|
||||
{
|
||||
await Login();
|
||||
if (!string.IsNullOrEmpty(_email))
|
||||
{
|
||||
if (await UserService.ForgotUsernameAsync(_email))
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.ForgotUsername"], MessageType.Info);
|
||||
await logger.LogInformation(LogFunction.Security, "Username Reminder Notification Sent For 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 Username Reminder {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Error.ForgotUsername"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void TogglePassword()
|
||||
private async Task LoginLink()
|
||||
{
|
||||
if (_passwordtype == "password")
|
||||
try
|
||||
{
|
||||
_passwordtype = "text";
|
||||
_togglepassword = SharedLocalizer["HidePassword"];
|
||||
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);
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
_passwordtype = "password";
|
||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||
await logger.LogError(ex, "Error Sending Login Link {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Error.SendLoginLink"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void ExternalLogin()
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + WebUtility.UrlEncode(PageState.ReturnUrl)), true);
|
||||
}
|
||||
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,13 +101,20 @@
|
||||
<small>@SharedLocalizer["Search.By"]:</small> <strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
|
||||
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
|
||||
<br />
|
||||
@if (!string.IsNullOrEmpty(context.PackageUrl))
|
||||
@if (_moduledefinitions.Exists(item => item.PackageName == context.PackageId))
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
|
||||
<button type="button" class="btn btn-info">@SharedLocalizer["Installed"]</button>
|
||||
}
|
||||
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
|
||||
else
|
||||
{
|
||||
<a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
|
||||
@if (!string.IsNullOrEmpty(context.PackageUrl))
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
|
||||
}
|
||||
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
|
||||
{
|
||||
<a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
|
||||
}
|
||||
}
|
||||
<br />
|
||||
</div>
|
||||
@@ -171,6 +178,7 @@
|
||||
|
||||
@code {
|
||||
private bool _initialized = false;
|
||||
private List<ModuleDefinition> _moduledefinitions;
|
||||
private int _page = 1;
|
||||
private List<Package> _packages;
|
||||
private string _price = "free";
|
||||
@@ -187,7 +195,8 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
await LoadModuleDefinitions();
|
||||
_moduledefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
|
||||
await LoadPackages();
|
||||
_initialized = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -197,24 +206,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadModuleDefinitions()
|
||||
private async Task LoadPackages()
|
||||
{
|
||||
ShowProgressIndicator();
|
||||
|
||||
var moduledefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
|
||||
_packages = await PackageService.GetPackagesAsync("module", _search, _price, "", _sort);
|
||||
|
||||
if (_packages != null)
|
||||
{
|
||||
foreach (Package package in _packages.ToArray())
|
||||
{
|
||||
if (moduledefinitions.Exists(item => item.PackageName == package.PackageId))
|
||||
{
|
||||
_packages.Remove(package);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HideProgressIndicator();
|
||||
}
|
||||
|
||||
@@ -222,25 +217,25 @@
|
||||
{
|
||||
_price = price;
|
||||
_sort = "popularity";
|
||||
await LoadModuleDefinitions();
|
||||
await LoadPackages();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task Search()
|
||||
{
|
||||
await LoadModuleDefinitions();
|
||||
await LoadPackages();
|
||||
}
|
||||
|
||||
private async Task Reset()
|
||||
{
|
||||
_page = 1;
|
||||
_search = "";
|
||||
await LoadModuleDefinitions();
|
||||
await LoadPackages();
|
||||
}
|
||||
|
||||
private async Task Refresh()
|
||||
{
|
||||
await LoadModuleDefinitions();
|
||||
await LoadPackages();
|
||||
}
|
||||
|
||||
private void OnPageChange(int page)
|
||||
@@ -251,7 +246,7 @@
|
||||
private async void SortChanged(ChangeEventArgs e)
|
||||
{
|
||||
_sort = (string)e.Value;
|
||||
await LoadModuleDefinitions();
|
||||
await LoadPackages();
|
||||
}
|
||||
|
||||
private void HideModal()
|
||||
@@ -310,6 +305,6 @@
|
||||
|
||||
private void OnUpload()
|
||||
{
|
||||
AddModuleMessage(string.Format(Localizer["Success.Module.Download"], NavigateUrl("admin/system")), MessageType.Success);
|
||||
AddModuleMessage(string.Format(Localizer["Success.Module.Upload"], NavigateUrl("admin/system")), MessageType.Success);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@namespace Oqtane.Modules.Admin.ModuleDefinitions
|
||||
@inherits ModuleBase
|
||||
@using System.Text.RegularExpressions
|
||||
@using System.Reflection
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IModuleDefinitionService ModuleDefinitionService
|
||||
@inject IModuleService ModuleService
|
||||
@@ -42,29 +43,32 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="reference" class="form-select" @bind="@_reference" required>
|
||||
@foreach (string version in _versions)
|
||||
{
|
||||
if (Version.Parse(version).CompareTo(Version.Parse(_minversion)) >= 0)
|
||||
{
|
||||
<option value="@(version)">@(version)</option>
|
||||
}
|
||||
}
|
||||
<option value="local">@SharedLocalizer["LocalVersion"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (!string.IsNullOrEmpty(_location))
|
||||
@if (_type == "External")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="location" HelpText="Location where the module will be created" ResourceKey="Location">Location: </Label>
|
||||
<Label Class="col-sm-3" For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="module" class="form-control" @bind="@_location" readonly />
|
||||
<select id="reference" class="form-select" @bind="@_reference" required>
|
||||
@foreach (string version in _versions)
|
||||
{
|
||||
if (Version.Parse(version).CompareTo(Version.Parse(_minversion)) >= 0)
|
||||
{
|
||||
<option value="@(version)">@(version)</option>
|
||||
}
|
||||
}
|
||||
<option value="local">@SharedLocalizer["LocalVersion"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (!string.IsNullOrEmpty(_location))
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="location" HelpText="Location where the module will be created" ResourceKey="Location">Location: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="module" class="form-control" @bind="@_location" readonly />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<button type="button" class="btn btn-success" @onclick="CreateModule">@Localizer["CreateModule"]</button>
|
||||
@@ -80,9 +84,10 @@
|
||||
private string _description = string.Empty;
|
||||
private List<Template> _templates;
|
||||
private string _template = "-";
|
||||
private string _minversion = "2.0.0";
|
||||
private string _type = "";
|
||||
private string[] _versions;
|
||||
private string _reference = "local";
|
||||
private string _minversion = "2.0.0";
|
||||
private string _location = string.Empty;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
@@ -93,6 +98,16 @@
|
||||
{
|
||||
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()
|
||||
@@ -123,11 +138,18 @@
|
||||
if (string.IsNullOrEmpty(_description)) _description = _module;
|
||||
if (IsValidXML(_description))
|
||||
{
|
||||
if (_type == "Internal")
|
||||
{
|
||||
AddModuleMessage(Localizer["Success.Module.Create.Internal"], MessageType.Success);
|
||||
}
|
||||
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
||||
var moduleDefinition = new ModuleDefinition { Owner = _owner, Name = _module, Description = _description, Template = _template, Version = _reference, ModuleDefinitionName = template.Namespace };
|
||||
moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition);
|
||||
GetLocation();
|
||||
AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success);
|
||||
if (_type == "External")
|
||||
{
|
||||
GetLocation();
|
||||
AddModuleMessage(string.Format(Localizer["Success.Module.Create.External"], NavigateUrl("admin/system")), MessageType.Success);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -153,7 +175,7 @@
|
||||
private bool IsValid(string name)
|
||||
{
|
||||
// must contain letters, underscores and digits and first character must be letter or underscore
|
||||
return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
|
||||
return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_.]*$");
|
||||
}
|
||||
|
||||
private bool IsValidXML(string description)
|
||||
@@ -165,11 +187,16 @@
|
||||
private void TemplateChanged(ChangeEventArgs e)
|
||||
{
|
||||
_template = (string)e.Value;
|
||||
_minversion = "2.0.0";
|
||||
if (_template != "-")
|
||||
{
|
||||
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
||||
_minversion = template.Version;
|
||||
_type = template.Type;
|
||||
}
|
||||
else
|
||||
{
|
||||
_minversion = "2.0.0";
|
||||
_type = "";
|
||||
}
|
||||
GetLocation();
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
@if (_initialized)
|
||||
{
|
||||
<TabStrip>
|
||||
<TabPanel Name="Definition" ResourceKey="Definition" Heading="Definition">
|
||||
<TabPanel Name="Module" ResourceKey="Module" Heading="Module">
|
||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
@@ -46,7 +46,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<Section Name="Information" ResourceKey="Information">
|
||||
<br />
|
||||
<Section Name="Information" ResourceKey="Information">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="moduledefinitionname" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
|
||||
@@ -97,7 +98,13 @@
|
||||
}
|
||||
</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>
|
||||
</Section>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveModuleDefinition">@SharedLocalizer["Save"]</button>
|
||||
@@ -231,16 +238,16 @@
|
||||
private string _url = "";
|
||||
private string _contact = "";
|
||||
private string _license = "";
|
||||
private string _fingerprint = "";
|
||||
private List<Permission> _permissions = null;
|
||||
private string _createdby;
|
||||
private DateTime _createdon;
|
||||
private string _modifiedby;
|
||||
private DateTime _modifiedon;
|
||||
private List<Page> _pagesWithModules;
|
||||
|
||||
#pragma warning disable 649
|
||||
private PermissionGrid _permissionGrid;
|
||||
#pragma warning restore 649
|
||||
|
||||
private List<Page> _pagesWithModules;
|
||||
|
||||
private List<Package> _packages;
|
||||
private List<Language> _languages;
|
||||
@@ -267,6 +274,7 @@
|
||||
_url = moduleDefinition.Url;
|
||||
_contact = moduleDefinition.Contact;
|
||||
_license = moduleDefinition.License;
|
||||
_fingerprint = moduleDefinition.Fingerprint;
|
||||
_permissions = moduleDefinition.PermissionList;
|
||||
_createdby = moduleDefinition.CreatedBy;
|
||||
_createdon = moduleDefinition.CreatedOn;
|
||||
|
||||
@@ -17,8 +17,8 @@ else
|
||||
<div class="row mb-3 align-items-center">
|
||||
<div class="col-sm-6">
|
||||
<ActionLink Action="Add" Text="Install Module" ResourceKey="InstallModule" />
|
||||
<ActionLink Action="Create" Text="Create Module" ResourceKey="CreateModule" Class="btn btn-secondary ps-2" />
|
||||
<button type="button" class="btn btn-secondary pw-2" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
|
||||
<ActionLink Action="Create" Text="Create Module" ResourceKey="CreateModule" Class="btn btn-secondary ms-1" />
|
||||
<button type="button" class="btn btn-secondary ms-1" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<select class="form-select" @onchange="(e => CategoryChanged(e))">
|
||||
@@ -130,7 +130,7 @@ else
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
|
||||
@@ -73,27 +73,29 @@
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<select id="page" class="form-select" @bind="@_pageId" required>
|
||||
@if (PageState.Page.UserId != null)
|
||||
{
|
||||
@if (PageState.Page.UserId != null || _isShared)
|
||||
{
|
||||
<select id="page" class="form-select" @bind="@_pageId" required disabled>
|
||||
<option value="@PageState.Page.PageId">@(PageState.Page.Name)</option>
|
||||
}
|
||||
else
|
||||
</select>
|
||||
}
|
||||
else
|
||||
{
|
||||
<select id="page" class="form-select" @bind="@_pageId" required>
|
||||
@if (_pages != null)
|
||||
{
|
||||
if (_pages != null)
|
||||
foreach (Page p in _pages)
|
||||
{
|
||||
foreach (Page p in _pages)
|
||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, p.PermissionList))
|
||||
{
|
||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, p.PermissionList))
|
||||
{
|
||||
<option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option>
|
||||
}
|
||||
<option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option>
|
||||
}
|
||||
}
|
||||
}
|
||||
</select>
|
||||
</select>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -161,6 +163,7 @@
|
||||
private string _pane;
|
||||
private string _containerType;
|
||||
private string _allPages = "false";
|
||||
private bool _isShared = false;
|
||||
private string _header = "";
|
||||
private string _footer = "";
|
||||
private string _permissionNames = "";
|
||||
@@ -207,6 +210,7 @@
|
||||
_expirydate = Utilities.UtcAsLocalDate(pagemodule.ExpiryDate);
|
||||
|
||||
_allPages = pagemodule.Module.AllPages.ToString();
|
||||
_isShared = pagemodule.Module.IsShared;
|
||||
createdby = pagemodule.Module.CreatedBy;
|
||||
createdon = pagemodule.Module.CreatedOn;
|
||||
modifiedby = pagemodule.Module.ModifiedBy;
|
||||
@@ -276,15 +280,40 @@
|
||||
var interop = new Interop(JSRuntime);
|
||||
if (await interop.FormValid(form))
|
||||
{
|
||||
|
||||
if (!string.IsNullOrEmpty(_title))
|
||||
{
|
||||
if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate))
|
||||
{
|
||||
AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning);
|
||||
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 pageId = pagemodule.PageId; // preserve
|
||||
var pane = pagemodule.Pane; // preserve
|
||||
pagemodule.PageId = int.Parse(_pageId);
|
||||
pagemodule.Title = _title;
|
||||
pagemodule.Pane = _pane;
|
||||
@@ -302,33 +331,21 @@
|
||||
pagemodule.Header = _header;
|
||||
pagemodule.Footer = _footer;
|
||||
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);
|
||||
module.AllPages = bool.Parse(_allPages);
|
||||
module.PageModuleId = ModuleState.PageModuleId;
|
||||
module.PermissionList = _permissionGrid.GetPermissionList();
|
||||
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);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="name" class="form-control" @bind="@_name" maxlength="50" required />
|
||||
<input id="name" class="form-control" @bind="@_name" maxlength="100" required />
|
||||
</div>
|
||||
</div>
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||
@@ -81,21 +81,9 @@
|
||||
</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>
|
||||
<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">
|
||||
<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>
|
||||
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
@@ -110,27 +98,6 @@
|
||||
<input id="url" class="form-control" @bind="@_url" maxlength="500" />
|
||||
</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">
|
||||
<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">
|
||||
@@ -141,15 +108,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Section Name="Appearance" ResourceKey="Appearance" Heading=@Localizer["Appearance.Name"]>
|
||||
<Section Name="Theme" Heading="Theme" ResourceKey="Theme">
|
||||
<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">
|
||||
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
|
||||
<div class="col-sm-9">
|
||||
@@ -181,6 +141,49 @@
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="Appearance" Heading="Appearance" ResourceKey="Appearance">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="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"]>
|
||||
<div class="container">
|
||||
<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)))
|
||||
{
|
||||
_themetype = PageState.Site.DefaultThemeType;
|
||||
_themes = ThemeService.GetThemeControls(PageState.Site.Themes);
|
||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
||||
var themes = new List<Theme>();
|
||||
foreach (var theme in PageState.Site.Themes)
|
||||
{
|
||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Utilize, theme.PermissionList))
|
||||
{
|
||||
themes.Add(theme);
|
||||
}
|
||||
}
|
||||
_themes = ThemeService.GetThemeControls(themes);
|
||||
_containers = ThemeService.GetContainerControls(themes, _themetype);
|
||||
_containertype = PageState.Site.DefaultContainerType;
|
||||
_children = new List<Page>();
|
||||
foreach (Page p in _pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="name" class="form-control" @bind="@_name" maxlength="50" required />
|
||||
<input id="name" class="form-control" @bind="@_name" maxlength="100" required />
|
||||
</div>
|
||||
</div>
|
||||
@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>
|
||||
<div class="col-sm-9">
|
||||
<select id="move" class="form-select" @bind="@_insert" required>
|
||||
@if (_parentid == _currentparentid)
|
||||
@if (_parentid == _currentparentid && !_copy)
|
||||
{
|
||||
<option value="="><@Localizer["ThisLocation.Keep"]></option>
|
||||
}
|
||||
@@ -98,21 +98,9 @@
|
||||
</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>
|
||||
<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">
|
||||
<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>
|
||||
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
@@ -127,27 +115,6 @@
|
||||
<input id="url" class="form-control" @bind="@_url" maxlength="500" />
|
||||
</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">
|
||||
<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">
|
||||
@@ -158,14 +125,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Section Name="Appearance" ResourceKey="Appearance" Heading="Appearance">
|
||||
<Section Name="Theme" ResourceKey="Theme" Heading="Theme">
|
||||
<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">
|
||||
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
|
||||
<div class="col-sm-9">
|
||||
@@ -200,6 +161,49 @@
|
||||
</div>
|
||||
</div>
|
||||
</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">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
@@ -221,7 +225,10 @@
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
<br />
|
||||
<br />
|
||||
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
|
||||
@if (!_copy)
|
||||
{
|
||||
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
|
||||
}
|
||||
</TabPanel>
|
||||
<TabPanel Name="Permissions" ResourceKey="Permissions" Heading="Permissions">
|
||||
<div class="container">
|
||||
@@ -237,36 +244,40 @@
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel Name="PageModules" Heading="Modules" ResourceKey="PageModules">
|
||||
<Pager Items="_pageModules">
|
||||
<Header>
|
||||
@if (!_copy)
|
||||
{
|
||||
<TabPanel Name="PageModules" Heading="Modules" ResourceKey="PageModules">
|
||||
<Pager Items="_pageModules">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@Localizer["ModuleTitle"]</th>
|
||||
<th>@Localizer["ModuleDefinition"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="Settings" Text="Edit" Path="@_actualpath" ModuleId="@context.ModuleId" Security="SecurityAccessLevel.Edit" PermissionList="@context.PermissionList" ResourceKey="ModuleSettings" /></td>
|
||||
<td><ActionDialog Header="Delete Module" Message="Are You Sure You Wish To Delete This Module?" Action="Delete" Security="SecurityAccessLevel.Edit" PermissionList="@context.PermissionList" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" /></td>
|
||||
<td>@context.Title</td>
|
||||
<td>@context.ModuleDefinition?.Name</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
</TabPanel>
|
||||
@if (_themeSettingsType != null)
|
||||
{
|
||||
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
|
||||
@_themeSettingsComponent
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="Settings" Text="Edit" Path="@_actualpath" ModuleId="@context.ModuleId" Security="SecurityAccessLevel.Edit" PermissionList="@context.PermissionList" ResourceKey="ModuleSettings" /></td>
|
||||
<td><ActionDialog Header="Delete Module" Message="Are You Sure You Wish To Delete This Module?" Action="Delete" Security="SecurityAccessLevel.Edit" PermissionList="@context.PermissionList" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" /></td>
|
||||
<td>@context.Title</td>
|
||||
<td>@context.ModuleDefinition?.Name</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
</TabPanel>
|
||||
@if (_themeSettingsType != null)
|
||||
{
|
||||
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
|
||||
@_themeSettingsComponent
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
</TabPanel>
|
||||
}
|
||||
|
||||
}
|
||||
</TabStrip>
|
||||
}
|
||||
@@ -345,6 +356,7 @@
|
||||
private List<ThemeControl> _containers = new List<ThemeControl>();
|
||||
private List<Page> _pages;
|
||||
private int _pageId;
|
||||
private bool _copy = false;
|
||||
private string _name;
|
||||
private string _currentparentid;
|
||||
private string _parentid = "-1";
|
||||
@@ -390,6 +402,10 @@
|
||||
{
|
||||
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||
_pageId = Int32.Parse(PageState.QueryString["id"]);
|
||||
if (PageState.QueryString.ContainsKey("copy"))
|
||||
{
|
||||
_copy = bool.Parse(PageState.QueryString["copy"]);
|
||||
}
|
||||
_page = await PageService.GetPageAsync(_pageId);
|
||||
_icons = await SystemService.GetIconsAsync();
|
||||
_iconresources = Utilities.GetFullTypeName(typeof(IconResources).AssemblyQualifiedName);
|
||||
@@ -409,7 +425,7 @@
|
||||
_children = new List<Page>();
|
||||
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);
|
||||
}
|
||||
@@ -436,6 +452,12 @@
|
||||
_expirydate = Utilities.UtcAsLocalDate(_page.ExpiryDate);
|
||||
_ispersonalizable = _page.IsPersonalizable.ToString();
|
||||
|
||||
if (_copy)
|
||||
{
|
||||
_insert = ">";
|
||||
_childid = _page.PageId;
|
||||
}
|
||||
|
||||
// appearance
|
||||
_title = _page.Title;
|
||||
_themetype = _page.ThemeType;
|
||||
@@ -443,8 +465,16 @@
|
||||
{
|
||||
_themetype = PageState.Site.DefaultThemeType;
|
||||
}
|
||||
_themes = ThemeService.GetThemeControls(PageState.Site.Themes);
|
||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
||||
var themes = new List<Theme>();
|
||||
foreach (var theme in PageState.Site.Themes)
|
||||
{
|
||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Utilize, theme.PermissionList))
|
||||
{
|
||||
themes.Add(theme);
|
||||
}
|
||||
}
|
||||
_themes = ThemeService.GetThemeControls(themes);
|
||||
_containers = ThemeService.GetContainerControls(themes, _themetype);
|
||||
_containertype = _page.DefaultContainerType;
|
||||
if (string.IsNullOrEmpty(_containertype))
|
||||
{
|
||||
@@ -458,6 +488,19 @@
|
||||
// permissions
|
||||
_permissions = _page.PermissionList;
|
||||
_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
|
||||
var modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
|
||||
@@ -472,6 +515,13 @@
|
||||
_deletedon = _page.DeletedOn;
|
||||
|
||||
ThemeSettings();
|
||||
|
||||
if (_copy)
|
||||
{
|
||||
_name = "";
|
||||
_path = "";
|
||||
}
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
else
|
||||
@@ -542,7 +592,7 @@
|
||||
builder.OpenComponent(0, _themeSettingsType);
|
||||
builder.AddAttribute(1, "RenderModeBoundary", RenderModeBoundary);
|
||||
builder.AddComponentReferenceCapture(2, inst => { _themeSettings = Convert.ChangeType(inst, _themeSettingsType); });
|
||||
|
||||
|
||||
builder.CloseComponent();
|
||||
};
|
||||
}
|
||||
@@ -564,10 +614,18 @@
|
||||
await ScrollToPageTop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(_themetype) && _containertype != "-")
|
||||
{
|
||||
string currentPath = _page.Path;
|
||||
|
||||
if (_copy)
|
||||
{
|
||||
_page = new Page();
|
||||
_page.SiteId = PageState.Site.SiteId;
|
||||
currentPath = "";
|
||||
}
|
||||
|
||||
_page.Name = _name;
|
||||
|
||||
if (_parentid == "-1")
|
||||
@@ -623,6 +681,13 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// update theme settings
|
||||
if (_themeSettingsType != null && _themeSettings is ISettingsControl themeSettingsControl)
|
||||
{
|
||||
await themeSettingsControl.UpdateSettings();
|
||||
}
|
||||
|
||||
// default page properties
|
||||
if (_insert != "=")
|
||||
{
|
||||
Page child;
|
||||
@@ -676,7 +741,21 @@
|
||||
_page.UpdateModulePermissions = bool.Parse(_updatemodulepermissions);
|
||||
}
|
||||
|
||||
_page = await PageService.UpdatePageAsync(_page);
|
||||
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);
|
||||
await logger.LogInformation("Page Saved {Page}", _page);
|
||||
}
|
||||
|
||||
// update page order
|
||||
await PageService.UpdatePageOrderAsync(_page.SiteId, _page.PageId, _page.ParentId);
|
||||
if (_currentparentid == string.Empty)
|
||||
{
|
||||
@@ -687,12 +766,6 @@
|
||||
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))
|
||||
{
|
||||
NavigationManager.NavigateTo(PageState.ReturnUrl, true); // redirect to page being edited and reload
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
@inherits ModuleBase
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IProfileService ProfileService
|
||||
@inject ISettingService SettingService
|
||||
@inject IStringLocalizer<Edit> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@@ -56,9 +57,25 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="options" HelpText="A comma delimited list of options the user can select from" ResourceKey="Options">Options: </Label>
|
||||
<Label Class="col-sm-3" For="options" HelpText="A comma delimited list of options. Options can contain a key and value if they are seperated by a colon (ie. key:value). You can also dynamically load your options from Settings." ResourceKey="Options">Options: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="options" class="form-control" @bind="@_options" maxlength="2000" />
|
||||
<div class="input-group">
|
||||
@if (_optiontype == "Settings")
|
||||
{
|
||||
<input id="options" class="form-control" @bind="@_options" maxlength="2000" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<select id="entityName" class="form-select" @bind="@_options">
|
||||
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||
@foreach (var entityname in _entitynames)
|
||||
{
|
||||
<option value="@entityname">@entityname</option>
|
||||
}
|
||||
</select>
|
||||
}
|
||||
<button type="button" class="btn btn-secondary" @onclick="ToggleOptionType">@Localizer[_optiontype]</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
@@ -95,7 +112,7 @@
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveProfile">@SharedLocalizer["Save"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
@if (PageState.QueryString.ContainsKey("id"))
|
||||
@if (PageState.QueryString.ContainsKey("id"))
|
||||
{
|
||||
<br />
|
||||
<br />
|
||||
@@ -116,6 +133,8 @@
|
||||
private string _rows = "1";
|
||||
private string _defaultvalue = string.Empty;
|
||||
private string _options = string.Empty;
|
||||
private string _optiontype = "Settings";
|
||||
private List<string> _entitynames;
|
||||
private string _validation = string.Empty;
|
||||
private string _autocomplete = string.Empty;
|
||||
private string _isrequired = "False";
|
||||
@@ -133,6 +152,8 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
_entitynames = await SettingService.GetEntityNamesAsync();
|
||||
|
||||
if (PageState.QueryString.ContainsKey("id"))
|
||||
{
|
||||
_profileid = Int32.Parse(PageState.QueryString["id"]);
|
||||
@@ -148,6 +169,11 @@
|
||||
_rows = profile.Rows.ToString();
|
||||
_defaultvalue = profile.DefaultValue;
|
||||
_options = profile.Options;
|
||||
if (_options.StartsWith("EntityName:"))
|
||||
{
|
||||
_optiontype = "Options";
|
||||
_options = _options.Substring(11);
|
||||
}
|
||||
_validation = profile.Validation;
|
||||
_autocomplete = profile.Autocomplete;
|
||||
_isrequired = profile.IsRequired.ToString();
|
||||
@@ -166,6 +192,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleOptionType()
|
||||
{
|
||||
if (_optiontype == "Options")
|
||||
{
|
||||
_optiontype = "Settings";
|
||||
}
|
||||
else
|
||||
{
|
||||
_optiontype = "Options";
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveProfile()
|
||||
{
|
||||
validated = true;
|
||||
@@ -193,7 +231,14 @@
|
||||
profile.MaxLength = int.Parse(_maxlength);
|
||||
profile.Rows = int.Parse(_rows);
|
||||
profile.DefaultValue = _defaultvalue;
|
||||
profile.Options = _options;
|
||||
if (_optiontype == "Options" && !string.IsNullOrEmpty(_options))
|
||||
{
|
||||
profile.Options = "EntityName:" + _options;
|
||||
}
|
||||
else
|
||||
{
|
||||
profile.Options = _options;
|
||||
}
|
||||
profile.Validation = _validation;
|
||||
profile.Autocomplete = _autocomplete;
|
||||
profile.IsRequired = (_isrequired == null ? false : Boolean.Parse(_isrequired));
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
@inherits ModuleBase
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IUserService UserService
|
||||
@inject ISettingService SettingService
|
||||
@inject ITimeZoneService TimeZoneService
|
||||
@inject ILanguageService LanguageService
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
@inject ISettingService SettingService
|
||||
|
||||
@if (_initialized)
|
||||
{
|
||||
@@ -71,6 +72,18 @@
|
||||
</select>
|
||||
</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>
|
||||
<br />
|
||||
<button type="button" class="btn btn-primary" @onclick="Register">@Localizer["Register"]</button>
|
||||
@@ -95,6 +108,8 @@
|
||||
@code {
|
||||
private bool _initialized = false;
|
||||
private List<Models.TimeZone> _timezones;
|
||||
private IEnumerable<Models.Language> _languages;
|
||||
|
||||
private string _passwordrequirements;
|
||||
private string _username = string.Empty;
|
||||
private ElementReference form;
|
||||
@@ -106,6 +121,7 @@
|
||||
private string _email = string.Empty;
|
||||
private string _displayname = string.Empty;
|
||||
private string _timezoneid = string.Empty;
|
||||
private string _culturecode = string.Empty;
|
||||
private bool _userCreated = false;
|
||||
private bool _allowsitelogin = true;
|
||||
|
||||
@@ -113,10 +129,13 @@
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
|
||||
|
||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
|
||||
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
||||
_timezoneid = PageState.Site.TimeZoneId;
|
||||
_culturecode = PageState.Site.CultureCode;
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
@@ -147,6 +166,7 @@
|
||||
Email = _email,
|
||||
DisplayName = (_displayname == string.Empty ? _username : _displayname),
|
||||
TimeZoneId = _timezoneid,
|
||||
CultureCode = _culturecode,
|
||||
PhotoFileId = null
|
||||
};
|
||||
user = await UserService.AddUserAsync(user);
|
||||
|
||||
@@ -40,13 +40,16 @@
|
||||
{
|
||||
<Pager Items="@_searchResults?.Results"
|
||||
Format="Grid"
|
||||
PageSize="@_pageSize"
|
||||
DisplayPages="@_displayPages"
|
||||
CurrentPage="@_currentPage"
|
||||
Columns="1"
|
||||
Toolbar="Bottom"
|
||||
Parameters="@($"q={_keywords}")">
|
||||
<Row>
|
||||
<div class="search-item mb-2">
|
||||
<h4 class="mb-1"><a href="@context.Url">@context.Title</a></h4>
|
||||
<p class="mb-0 text-muted">@((MarkupString)context.Snippet)</p>
|
||||
<p class="mb-0 text-body-secondary">@((MarkupString)context.Snippet)</p>
|
||||
</div>
|
||||
</Row>
|
||||
</Pager>
|
||||
@@ -66,6 +69,7 @@
|
||||
|
||||
@code {
|
||||
public override string RenderMode => RenderModes.Static;
|
||||
private const string SearchDefaultPageSize = "10";
|
||||
|
||||
private string _includeEntities;
|
||||
private string _excludeEntities;
|
||||
@@ -75,6 +79,8 @@
|
||||
private string _sortField;
|
||||
private string _sortOrder;
|
||||
private string _bodyLength;
|
||||
private string _currentPage = "0";
|
||||
private string _displayPages = "7";
|
||||
|
||||
private string _keywords;
|
||||
private SearchResults _searchResults;
|
||||
@@ -89,11 +95,16 @@
|
||||
_excludeEntities = SettingService.GetSetting(ModuleState.Settings, "SearchResults_ExcludeEntities", "");
|
||||
_fromDate = SettingService.GetSetting(ModuleState.Settings, "SearchResults_FromDate", DateTime.MinValue.ToString());
|
||||
_toDate = SettingService.GetSetting(ModuleState.Settings, "SearchResults_ToDate", DateTime.MaxValue.ToString());
|
||||
_pageSize = SettingService.GetSetting(ModuleState.Settings, "SearchResults_PageSize", int.MaxValue.ToString());
|
||||
_pageSize = SettingService.GetSetting(ModuleState.Settings, "SearchResults_PageSize", SearchDefaultPageSize);
|
||||
_sortField = SettingService.GetSetting(ModuleState.Settings, "SearchResults_SortField", "Relevance");
|
||||
_sortOrder = SettingService.GetSetting(ModuleState.Settings, "SearchResults_SortOrder", "Descending");
|
||||
_bodyLength = SettingService.GetSetting(ModuleState.Settings, "SearchResults_BodyLength", "255");
|
||||
|
||||
if (PageState.QueryString.ContainsKey("p"))
|
||||
{
|
||||
_currentPage = PageState.QueryString["p"];
|
||||
}
|
||||
|
||||
if (_keywords == null && PageState.QueryString.ContainsKey("q"))
|
||||
{
|
||||
_keywords = WebUtility.UrlDecode(PageState.QueryString["q"]);
|
||||
@@ -122,7 +133,7 @@
|
||||
ExcludeEntities = _excludeEntities,
|
||||
FromDate = (!string.IsNullOrEmpty(_fromDate)) ? DateTime.Parse(_fromDate) : DateTime.MinValue,
|
||||
ToDate = (!string.IsNullOrEmpty(_toDate)) ? DateTime.Parse(_toDate) : DateTime.MaxValue,
|
||||
PageSize = (!string.IsNullOrEmpty(_pageSize)) ? int.Parse(_pageSize) : int.MaxValue,
|
||||
PageSize = int.MaxValue,
|
||||
PageIndex = 0,
|
||||
SortField = (!string.IsNullOrEmpty(_sortField)) ? (SearchSortField)Enum.Parse(typeof(SearchSortField), _sortField) : SearchSortField.Relevance,
|
||||
SortOrder = (!string.IsNullOrEmpty(_sortOrder)) ? (SearchSortOrder)Enum.Parse(typeof(SearchSortOrder), _sortOrder) : SearchSortOrder.Descending,
|
||||
|
||||
226
Oqtane.Client/Modules/Admin/Settings/Add.razor
Normal file
226
Oqtane.Client/Modules/Admin/Settings/Add.razor
Normal file
@@ -0,0 +1,226 @@
|
||||
@namespace Oqtane.Modules.Admin.Settings
|
||||
@inherits ModuleBase
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject ISettingService SettingService
|
||||
@inject IStringLocalizer<Edit> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="entityName" HelpText="Entity Name" ResourceKey="EntityName">Entity:</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
@if (_entityNameElement == "input")
|
||||
{
|
||||
<input id="entityName" class="form-control" @bind="@_entityName" maxlength="256" required />
|
||||
}
|
||||
else
|
||||
{
|
||||
<select class="form-select custom-select" value="@_entityName" @onchange="(e => EntityNameChanged(e))">
|
||||
<option value="-"><@Localizer["Select Entity"]></option>
|
||||
@foreach (var entityName in _entityNames)
|
||||
{
|
||||
<option value="@entityName">@entityName</option>
|
||||
}
|
||||
</select>
|
||||
}
|
||||
<button type="button" class="btn btn-secondary" @onclick="@EntityNameClicked" tabindex="-1">@_entityNameTitle</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="entityId" HelpText="Entity Id" ResourceKey="EntityId">Id:</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
@if (_entityIdElement == "input")
|
||||
{
|
||||
<input id="entityId" class="form-control" @bind="@_entityId" maxlength="256" required />
|
||||
}
|
||||
else
|
||||
{
|
||||
<select class="form-select custom-select" @bind="@_entityId">
|
||||
<option value="-"><@Localizer["Select Id"]></option>
|
||||
@foreach (var entityId in _entityIds)
|
||||
{
|
||||
<option value="@entityId">@entityId</option>
|
||||
}
|
||||
</select>
|
||||
}
|
||||
<button type="button" class="btn btn-secondary" @onclick="@EntityIdClicked" tabindex="-1">@_entityIdTitle</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="settingName" HelpText="Setting Name" ResourceKey="SettingName">Name:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="settingName" class="form-control" @bind="@_settingName" maxlength="256" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="settingValue" HelpText="Setting Value" ResourceKey="SettingValue">Value:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="SettingValue" class="form-control" @bind="@_settingValue" maxlength="256" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="isPrivate" HelpText="Private" ResourceKey="IsPrivate">Private?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="isPrivate" class="form-select" @bind="@_isPrivate">
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<br /><br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveSetting">@SharedLocalizer["Save"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@code {
|
||||
private ElementReference form;
|
||||
private bool validated = false;
|
||||
|
||||
private string _entityName = "-";
|
||||
private List<string> _entityNames = new List<string>();
|
||||
private string _entityNameElement = "select";
|
||||
private string _entityNameTitle = "";
|
||||
private string _entityId = "-";
|
||||
private List<int> _entityIds = new List<int>();
|
||||
private string _entityIdElement = "select";
|
||||
private string _entityIdTitle = "";
|
||||
private string _settingName = "";
|
||||
private string _settingValue = "";
|
||||
private string _isPrivate = "True";
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_entityNameTitle = Localizer["Input"];
|
||||
_entityIdTitle = Localizer["Input"];
|
||||
|
||||
// default entity names
|
||||
_entityNames.Add(EntityNames.Host);
|
||||
_entityNames.Add(EntityNames.Job);
|
||||
_entityNames.Add(EntityNames.ModuleDefinition);
|
||||
_entityNames.Add(EntityNames.Theme);
|
||||
_entityNames.Add(EntityNames.Tenant);
|
||||
_entityNames.Add(EntityNames.Site);
|
||||
_entityNames.Add(EntityNames.Role);
|
||||
_entityNames.Add(EntityNames.Page);
|
||||
_entityNames.Add(EntityNames.Module);
|
||||
_entityNames.Add(EntityNames.Folder);
|
||||
_entityNames.Add(EntityNames.User);
|
||||
_entityNames.Add(EntityNames.Visitor);
|
||||
|
||||
// custom entity names
|
||||
var entityNames = await SettingService.GetEntityNamesAsync();
|
||||
foreach (var entityName in entityNames)
|
||||
{
|
||||
if (!_entityNames.Contains(entityName))
|
||||
{
|
||||
_entityNames.Add(entityName);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading Setting {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Error.LoadSetting"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void EntityNameClicked()
|
||||
{
|
||||
if (_entityNameElement == "select")
|
||||
{
|
||||
_entityName = "";
|
||||
_entityNameElement = "input";
|
||||
_entityNameTitle = Localizer["Select"];
|
||||
_entityId = "";
|
||||
_entityIdElement = "input";
|
||||
_entityIdTitle = Localizer["Select"];
|
||||
}
|
||||
else
|
||||
{
|
||||
_entityName = "-";
|
||||
_entityNameElement = "select";
|
||||
_entityNameTitle = Localizer["Input"];
|
||||
}
|
||||
}
|
||||
|
||||
private void EntityIdClicked()
|
||||
{
|
||||
if (_entityIdElement == "select")
|
||||
{
|
||||
_entityId = "";
|
||||
_entityIdElement = "input";
|
||||
_entityIdTitle = Localizer["Select"];
|
||||
}
|
||||
else
|
||||
{
|
||||
_entityId = "-";
|
||||
_entityIdElement = "select";
|
||||
_entityIdTitle = Localizer["Input"];
|
||||
}
|
||||
}
|
||||
|
||||
private async void EntityNameChanged(ChangeEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_entityName = e.Value.ToString();
|
||||
_entityId = "-";
|
||||
_entityIdElement = "select";
|
||||
_entityIdTitle = Localizer["Input"];
|
||||
if (_entityName != "-")
|
||||
{
|
||||
_entityIds = await SettingService.GetEntityIdsAsync(_entityName);
|
||||
}
|
||||
else
|
||||
{
|
||||
_entityIds = new List<int>();
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error On EntityNameChanged");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveSetting()
|
||||
{
|
||||
validated = true;
|
||||
var interop = new Interop(JSRuntime);
|
||||
if (await interop.FormValid(form) && _entityName != "-" && int.TryParse(_entityId, out int entityId))
|
||||
{
|
||||
var setting = new Setting();
|
||||
setting.EntityName = _entityName;
|
||||
setting.EntityId = entityId;
|
||||
setting.SettingName = _settingName;
|
||||
setting.SettingValue = _settingValue;
|
||||
setting.IsPrivate = (bool.Parse(_isPrivate));
|
||||
|
||||
try
|
||||
{
|
||||
setting = await SettingService.AddSettingAsync(setting);
|
||||
await logger.LogInformation("Setting Saved {Setting}", setting);
|
||||
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Saving Setting {Setting} {Error}", setting, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.SaveSetting"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
122
Oqtane.Client/Modules/Admin/Settings/Edit.razor
Normal file
122
Oqtane.Client/Modules/Admin/Settings/Edit.razor
Normal file
@@ -0,0 +1,122 @@
|
||||
@namespace Oqtane.Modules.Admin.Settings
|
||||
@inherits ModuleBase
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject ISettingService SettingService
|
||||
@inject IStringLocalizer<Edit> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="entityName" HelpText="Entity Name" ResourceKey="EntityName">Entity:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="entityName" class="form-control" @bind="@_entityName" maxlength="256" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="entityId" HelpText="Entity Id" ResourceKey="EntityId">Id:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="entityId" class="form-control" @bind="@_entityId" maxlength="256" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="settingName" HelpText="Setting Name" ResourceKey="SettingName">Name:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="settingName" class="form-control" @bind="@_settingName" maxlength="256" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="settingValue" HelpText="Setting Value" ResourceKey="SettingValue">Value:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="SettingValue" class="form-control" @bind="@_settingValue" maxlength="256" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="isPrivate" HelpText="Private" ResourceKey="IsPrivate">Private?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="isPrivate" class="form-select" @bind="@_isPrivate">
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<br /><br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveSetting">@SharedLocalizer["Save"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||
<br /><br />
|
||||
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@code {
|
||||
private ElementReference form;
|
||||
private bool validated = false;
|
||||
|
||||
private int _settingId;
|
||||
private string _entityName;
|
||||
private string _entityId;
|
||||
private string _settingName;
|
||||
private string _settingValue;
|
||||
private string _isPrivate;
|
||||
private string _createdby;
|
||||
private DateTime _createdon;
|
||||
private string _modifiedby;
|
||||
private DateTime _modifiedon;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_settingId = int.Parse(PageState.QueryString["id"]);
|
||||
_entityName = PageState.QueryString["entity"];
|
||||
|
||||
try
|
||||
{
|
||||
var setting = await SettingService.GetSettingAsync(_entityName, _settingId);
|
||||
if (setting != null)
|
||||
{
|
||||
_entityId = setting.EntityId.ToString();
|
||||
_settingName = setting.SettingName;
|
||||
_settingValue = setting.SettingValue;
|
||||
_isPrivate = setting.IsPrivate.ToString();
|
||||
_createdby = setting.CreatedBy;
|
||||
_createdon = setting.CreatedOn;
|
||||
_modifiedby = setting.ModifiedBy;
|
||||
_modifiedon = setting.ModifiedOn;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading Setting {SettingId} {Error}", _settingId, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.LoadSetting"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveSetting()
|
||||
{
|
||||
validated = true;
|
||||
var interop = new Interop(JSRuntime);
|
||||
if (await interop.FormValid(form))
|
||||
{
|
||||
var setting = await SettingService.GetSettingAsync(_entityName, _settingId);
|
||||
setting.SettingValue = _settingValue;
|
||||
setting.IsPrivate = (_isPrivate != null && Boolean.Parse(_isPrivate));
|
||||
|
||||
try
|
||||
{
|
||||
setting = await SettingService.UpdateSettingAsync(setting);
|
||||
await logger.LogInformation("Setting Saved {Setting}", setting);
|
||||
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Saving Setting {Setting} {Error}", setting, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.SaveSetting"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
56
Oqtane.Client/Modules/Admin/Settings/ImportSettings.razor
Normal file
56
Oqtane.Client/Modules/Admin/Settings/ImportSettings.razor
Normal file
@@ -0,0 +1,56 @@
|
||||
@namespace Oqtane.Modules.Admin.Settings
|
||||
@inherits ModuleBase
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject ISettingService SettingService
|
||||
@inject IStringLocalizer<ImportSettings> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="settings" HelpText="Provide settings in comma delimited format using the column template specified" ResourceKey="Settings">Settings:</Label>
|
||||
<div class="col-sm-9">
|
||||
<textarea id="settings" class="form-control" @bind="@_settings" rows="5" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="Import">@Localizer["Import"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||
|
||||
@code {
|
||||
private string _settings = "Entity,Id,Name,Value,Private\n";
|
||||
|
||||
public override string Title => "Import Settings";
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
|
||||
private async Task Import()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_settings))
|
||||
{
|
||||
ShowProgressIndicator();
|
||||
var result = await SettingService.ImportSettingsAsync(new Result { Message = _settings });
|
||||
if (result.Success)
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Import.Success"], MessageType.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Import.Failure"], MessageType.Error);
|
||||
}
|
||||
HideProgressIndicator();
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Import.Validation"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Importing Settings {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Import"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
146
Oqtane.Client/Modules/Admin/Settings/Index.razor
Normal file
146
Oqtane.Client/Modules/Admin/Settings/Index.razor
Normal file
@@ -0,0 +1,146 @@
|
||||
@namespace Oqtane.Modules.Admin.Settings
|
||||
@inherits ModuleBase
|
||||
@inject ISettingService SettingService
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<div class="col-sm-4">
|
||||
<ActionLink Action="Add" Text="Add Setting" Security="SecurityAccessLevel.Host" ResourceKey="AddSetting" ReturnUrl="@(NavigateUrl(PageState.Page.Path, AddUrlParameters(_entityName, _entityId)))" />
|
||||
<ActionLink Action="ImportSettings" Text="Import" Class="btn btn-secondary ms-1" Security="SecurityAccessLevel.Host" ResourceKey="ImportSettings" ReturnUrl="@(NavigateUrl(PageState.Page.Path, AddUrlParameters(_entityName, _entityId)))" />
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<select class="form-select custom-select" value="@_entityName" @onchange="(e => EntityNameChanged(e))">
|
||||
<option value="-"><@Localizer["Select Entity"]></option>
|
||||
@foreach (var entityName in _entityNames)
|
||||
{
|
||||
<option value="@entityName">@entityName</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<select class="form-select custom-select" value="@_entityId" @onchange="(e => EntityIdChanged(e))">
|
||||
<option value="-"><@Localizer["Select Id"]></option>
|
||||
@foreach (var entityId in _entityIds)
|
||||
{
|
||||
<option value="@entityId">@entityId</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<Pager Items="@_settings" SearchProperties="SettingName,SettingValue">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@Localizer["Name"]</th>
|
||||
<th>@Localizer["Value"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"entity={context.EntityName}&id={context.SettingId}")" Security="SecurityAccessLevel.Host" ResourceKey="EditSetting" ReturnUrl="@(NavigateUrl(PageState.Page.Path, AddUrlParameters(_entityName, _entityId)))" /></td>
|
||||
<td><ActionDialog Header="Delete Setting" Message="@string.Format(Localizer["Confirm.DeleteSetting"], context.SettingName)" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteSetting(context))" ResourceKey="DeleteSetting" /></td>
|
||||
<td>@context.SettingName</td>
|
||||
<td>@context.SettingValue</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
|
||||
@code {
|
||||
private string _entityName = "-";
|
||||
private List<string> _entityNames = new List<string>();
|
||||
private string _entityId = "-";
|
||||
private List<int> _entityIds = new List<int>();
|
||||
private List<Setting> _settings = new List<Setting>();
|
||||
|
||||
public override string UrlParametersTemplate => "/{entityname}/{entityid}";
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
_entityNames = await SettingService.GetEntityNamesAsync();
|
||||
|
||||
if (UrlParameters.ContainsKey("entityname"))
|
||||
{
|
||||
_entityName = UrlParameters["entityname"];
|
||||
await GetEntityIds();
|
||||
}
|
||||
if (UrlParameters.ContainsKey("entityid"))
|
||||
{
|
||||
_entityId = UrlParameters["entityid"];
|
||||
await GetSettings();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GetEntityIds()
|
||||
{
|
||||
if (_entityName != "-")
|
||||
{
|
||||
_entityIds = await SettingService.GetEntityIdsAsync(_entityName);
|
||||
}
|
||||
else
|
||||
{
|
||||
_entityIds = new List<int>();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GetSettings()
|
||||
{
|
||||
if (_entityName != "-" && _entityId != "-")
|
||||
{
|
||||
_settings = await SettingService.GetSettingsAsync(_entityName, int.Parse(_entityId), "");
|
||||
_settings = _settings.OrderBy(item => item.SettingName).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
_settings = new List<Setting>();
|
||||
}
|
||||
}
|
||||
|
||||
private async void EntityNameChanged(ChangeEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_entityName = e.Value.ToString();
|
||||
_entityId = "-";
|
||||
await GetEntityIds();
|
||||
await GetSettings();
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error On EntityNameChanged");
|
||||
}
|
||||
}
|
||||
|
||||
private async void EntityIdChanged(ChangeEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_entityId = e.Value.ToString();
|
||||
await GetSettings();
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error On EntityIdChanged");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteSetting(Setting setting)
|
||||
{
|
||||
try
|
||||
{
|
||||
await SettingService.DeleteSettingAsync(setting.EntityName, setting.EntityId, setting.SettingName);
|
||||
await logger.LogInformation("Setting Deleted {Setting}", setting);
|
||||
await GetSettings();
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Deleting Setting {Setting} {Error}", setting, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.DeleteSetting"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,6 @@
|
||||
@inject ITenantService TenantService
|
||||
@inject IAliasService AliasService
|
||||
@inject ISiteService SiteService
|
||||
@inject IThemeService ThemeService
|
||||
@inject ISiteTemplateService SiteTemplateService
|
||||
@inject IUserService UserService
|
||||
@inject IInstallationService InstallationService
|
||||
@@ -29,33 +28,9 @@ else
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<textarea id="alias" class="form-control" @bind="@_urls" rows="3" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="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>
|
||||
<input id="alias" class="form-control" @bind="@_urls" required></input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
@@ -70,26 +45,6 @@ else
|
||||
</select>
|
||||
</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">
|
||||
<Label Class="col-sm-3" For="tenant" HelpText="Select the database for the site" ResourceKey="Tenant">Database: </Label>
|
||||
<div class="col-sm-9">
|
||||
@@ -118,42 +73,42 @@ else
|
||||
<Label Class="col-sm-3" For="databaseType" HelpText="Select the database type" ResourceKey="DatabaseType">Type: </Label>
|
||||
<div class="col-sm-9">
|
||||
@if (_databases != null)
|
||||
{
|
||||
<div class="input-group">
|
||||
<select id="databaseType" class="form-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))" required>
|
||||
@foreach (var database in _databases)
|
||||
{
|
||||
<option value="@database.Name">@Localizer[@database.Name]</option>
|
||||
}
|
||||
</select>
|
||||
@if (!_showConnectionString)
|
||||
{
|
||||
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionString"]</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionParameters"]</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
{
|
||||
<div class="input-group">
|
||||
<select id="databaseType" class="form-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))" required>
|
||||
@foreach (var database in _databases)
|
||||
{
|
||||
<option value="@database.Name">@Localizer[@database.Name]</option>
|
||||
}
|
||||
</select>
|
||||
@if (!_showConnectionString)
|
||||
{
|
||||
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionString"]</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionParameters"]</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@if (!_showConnectionString)
|
||||
{
|
||||
if (_databaseConfigType != null)
|
||||
{
|
||||
@DatabaseConfigComponent
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="connectionstring" HelpText="Enter a complete connection string including all parameters and delimiters" ResourceKey="ConnectionString">Settings:</Label>
|
||||
<div class="col-sm-9">
|
||||
<textarea id="connectionstring" class="form-control" @bind="@_connectionString" rows="3"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (!_showConnectionString)
|
||||
{
|
||||
if (_databaseConfigType != null)
|
||||
{
|
||||
@DatabaseConfigComponent
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="connectionstring" HelpText="Enter a complete connection string including all parameters and delimiters" ResourceKey="ConnectionString">Settings:</Label>
|
||||
<div class="col-sm-9">
|
||||
<textarea id="connectionstring" class="form-control" @bind="@_connectionString" rows="3"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="hostUsername" HelpText="Enter the username of an existing host user" ResourceKey="HostUsername">Host Username:</Label>
|
||||
<div class="col-sm-9">
|
||||
@@ -186,9 +141,6 @@ else
|
||||
private bool _showConnectionString = false;
|
||||
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<Tenant> _tenants;
|
||||
private string _tenantid = "-";
|
||||
@@ -200,11 +152,7 @@ else
|
||||
|
||||
private string _name = string.Empty;
|
||||
private string _urls = string.Empty;
|
||||
private string _themetype = "-";
|
||||
private string _containertype = "-";
|
||||
private string _sitetemplatetype = "-";
|
||||
private string _rendermode = RenderModes.Static;
|
||||
private string _runtime = Runtimes.Server;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
|
||||
@@ -215,19 +163,11 @@ else
|
||||
{
|
||||
_tenantid = _tenants.First(item => item.Name == TenantNames.Master).TenantId.ToString();
|
||||
}
|
||||
_urls = PageState.Alias.Name;
|
||||
_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;
|
||||
}
|
||||
_urls = PageState.Alias.Name + "/sitename";
|
||||
_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();
|
||||
@@ -237,7 +177,7 @@ else
|
||||
}
|
||||
else
|
||||
{
|
||||
_databaseName = "LocalDB";
|
||||
_databaseName = Constants.DefaultDBName;
|
||||
}
|
||||
LoadDatabaseConfigComponent();
|
||||
}
|
||||
@@ -281,37 +221,13 @@ else
|
||||
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()
|
||||
{
|
||||
validated = true;
|
||||
var interop = new Interop(JSRuntime);
|
||||
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", ",");
|
||||
var duplicates = new List<string>();
|
||||
@@ -390,6 +306,8 @@ else
|
||||
config.DatabaseType = tenant.DBType;
|
||||
config.ConnectionString = tenant.DBConnectionString;
|
||||
config.IsNewTenant = false;
|
||||
config.HostEmail = PageState.User.Email;
|
||||
config.HostName = PageState.User.DisplayName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,12 +315,13 @@ else
|
||||
{
|
||||
config.SiteName = _name;
|
||||
config.Aliases = _urls;
|
||||
config.DefaultTheme = _themetype;
|
||||
config.DefaultContainer = _containertype;
|
||||
config.DefaultTheme = Constants.DefaultTheme;
|
||||
config.DefaultContainer = Constants.DefaultContainer;
|
||||
config.DefaultAdminContainer = "";
|
||||
config.SiteTemplate = _sitetemplatetype;
|
||||
config.RenderMode = _rendermode;
|
||||
config.Runtime = _runtime;
|
||||
config.RenderMode = RenderModes.Static;
|
||||
config.Runtime = Runtimes.Server;
|
||||
config.Register = false;
|
||||
|
||||
ShowProgressIndicator();
|
||||
|
||||
|
||||
@@ -83,14 +83,14 @@ else
|
||||
{
|
||||
@if (_connection != "-")
|
||||
{
|
||||
@if (!string.IsNullOrEmpty(_tenant))
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="databasetype" HelpText="The database type" ResourceKey="DatabaseType">Type: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="databasetype" class="form-control" @bind="@_databasetype" readonly />
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="databasetype" HelpText="The database type" ResourceKey="DatabaseType">Type: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="databasetype" class="form-control" @bind="@_databasetype" readonly />
|
||||
</div>
|
||||
</div>
|
||||
@if (!string.IsNullOrEmpty(_tenant))
|
||||
{
|
||||
<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>
|
||||
<div class="col-sm-9">
|
||||
@@ -150,129 +150,149 @@ else
|
||||
}
|
||||
|
||||
@code {
|
||||
private string _connection = "-";
|
||||
private Dictionary<string, object> _connections;
|
||||
private List<Tenant> _tenants;
|
||||
private List<Database> _databases;
|
||||
private string _connection = "-";
|
||||
private Dictionary<string, object> _connections;
|
||||
private List<Tenant> _tenants;
|
||||
private List<Database> _databases;
|
||||
|
||||
private string _name = string.Empty;
|
||||
private string _databasetype = string.Empty;
|
||||
private Type _databaseConfigType;
|
||||
private object _databaseConfig;
|
||||
private RenderFragment DatabaseConfigComponent { get; set; }
|
||||
private bool _showConnectionString = false;
|
||||
private string _tenant = string.Empty;
|
||||
private string _connectionstring = string.Empty;
|
||||
private string _connectionstringtype = "password";
|
||||
private string _connectionstringtoggle = string.Empty;
|
||||
private string _sql = string.Empty;
|
||||
private List<Dictionary<string, string>> _results;
|
||||
private string _name = string.Empty;
|
||||
private string _databasetype = string.Empty;
|
||||
private Type _databaseConfigType;
|
||||
private object _databaseConfig;
|
||||
private RenderFragment DatabaseConfigComponent { get; set; }
|
||||
private bool _showConnectionString = false;
|
||||
private string _tenant = string.Empty;
|
||||
private string _connectionstring = string.Empty;
|
||||
private string _connectionstringtype = "password";
|
||||
private string _connectionstringtoggle = string.Empty;
|
||||
private string _sql = string.Empty;
|
||||
private List<Dictionary<string, string>> _results;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_connections = await SystemService.GetSystemInfoAsync("connectionstrings");
|
||||
_tenants = await TenantService.GetTenantsAsync();
|
||||
_databases = await DatabaseService.GetDatabasesAsync();
|
||||
_connectionstringtoggle = SharedLocalizer["ShowPassword"];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading Tenants {Error}", ex.Message);
|
||||
AddModuleMessage(ex.Message, MessageType.Error);
|
||||
}
|
||||
}
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_connections = await SystemService.GetSystemInfoAsync("connectionstrings");
|
||||
_tenants = await TenantService.GetTenantsAsync();
|
||||
_databases = await DatabaseService.GetDatabasesAsync();
|
||||
_connectionstringtoggle = SharedLocalizer["ShowPassword"];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading Tenants {Error}", ex.Message);
|
||||
AddModuleMessage(ex.Message, MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async void ConnectionChanged(ChangeEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_connection = (string)e.Value;
|
||||
if (_connection != "-" && _connection != "+")
|
||||
{
|
||||
_connectionstring = _connections[_connection].ToString();
|
||||
_tenant = "";
|
||||
_databasetype = "";
|
||||
var tenant = _tenants.FirstOrDefault(item => item.DBConnectionString == _connection);
|
||||
if (tenant != null)
|
||||
{
|
||||
_tenant = tenant.Name;
|
||||
_databasetype = _databases.FirstOrDefault(item => item.DBType == tenant.DBType && item.Name != "LocalDB").Name;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_databases.Exists(item => item.IsDefault))
|
||||
{
|
||||
_databasetype = _databases.Find(item => item.IsDefault).Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
_databasetype = "LocalDB";
|
||||
}
|
||||
_showConnectionString = false;
|
||||
LoadDatabaseConfigComponent();
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading Connection {Connection} {Error}", _connection, ex.Message);
|
||||
AddModuleMessage(ex.Message, MessageType.Error);
|
||||
}
|
||||
}
|
||||
private async void ConnectionChanged(ChangeEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_connection = (string)e.Value;
|
||||
if (_connection != "-" && _connection != "+")
|
||||
{
|
||||
_connectionstring = _connections[_connection].ToString();
|
||||
_tenant = "";
|
||||
_databasetype = "";
|
||||
var tenant = _tenants.FirstOrDefault(item => item.DBConnectionString == _connection);
|
||||
if (tenant != null)
|
||||
{
|
||||
_tenant = tenant.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
|
||||
{
|
||||
if (_databases.Exists(item => item.IsDefault))
|
||||
{
|
||||
_databasetype = _databases.Find(item => item.IsDefault).Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
_databasetype = Constants.DefaultDBName;
|
||||
}
|
||||
_showConnectionString = false;
|
||||
LoadDatabaseConfigComponent();
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading Connection {Connection} {Error}", _connection, ex.Message);
|
||||
AddModuleMessage(ex.Message, MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void DatabaseTypeChanged(ChangeEventArgs eventArgs)
|
||||
{
|
||||
try
|
||||
{
|
||||
_databasetype = (string)eventArgs.Value;
|
||||
_showConnectionString = false;
|
||||
LoadDatabaseConfigComponent();
|
||||
}
|
||||
catch
|
||||
{
|
||||
AddModuleMessage(Localizer["Error.Database.LoadConfig"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
private void DatabaseTypeChanged(ChangeEventArgs eventArgs)
|
||||
{
|
||||
try
|
||||
{
|
||||
_databasetype = (string)eventArgs.Value;
|
||||
_showConnectionString = false;
|
||||
LoadDatabaseConfigComponent();
|
||||
}
|
||||
catch
|
||||
{
|
||||
AddModuleMessage(Localizer["Error.Database.LoadConfig"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadDatabaseConfigComponent()
|
||||
{
|
||||
var database = _databases.SingleOrDefault(d => d.Name == _databasetype);
|
||||
if (database != null)
|
||||
{
|
||||
_databaseConfigType = Type.GetType(database.ControlType);
|
||||
DatabaseConfigComponent = builder =>
|
||||
{
|
||||
builder.OpenComponent(0, _databaseConfigType);
|
||||
builder.AddComponentReferenceCapture(1, inst => { _databaseConfig = Convert.ChangeType(inst, _databaseConfigType); });
|
||||
builder.CloseComponent();
|
||||
};
|
||||
}
|
||||
}
|
||||
private void LoadDatabaseConfigComponent()
|
||||
{
|
||||
var database = _databases.SingleOrDefault(d => d.Name == _databasetype);
|
||||
if (database != null)
|
||||
{
|
||||
_databaseConfigType = Type.GetType(database.ControlType);
|
||||
DatabaseConfigComponent = builder =>
|
||||
{
|
||||
builder.OpenComponent(0, _databaseConfigType);
|
||||
builder.AddComponentReferenceCapture(1, inst => { _databaseConfig = Convert.ChangeType(inst, _databaseConfigType); });
|
||||
builder.CloseComponent();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowConnectionString()
|
||||
{
|
||||
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
|
||||
{
|
||||
_connectionstring = databaseConfigControl.GetConnectionString();
|
||||
}
|
||||
_showConnectionString = !_showConnectionString;
|
||||
}
|
||||
private void ShowConnectionString()
|
||||
{
|
||||
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
|
||||
{
|
||||
_connectionstring = databaseConfigControl.GetConnectionString();
|
||||
}
|
||||
_showConnectionString = !_showConnectionString;
|
||||
}
|
||||
|
||||
private async Task Add()
|
||||
{
|
||||
var connectionstring = _connectionstring;
|
||||
if (!_showConnectionString && _databaseConfig is IDatabaseConfigControl databaseConfigControl)
|
||||
{
|
||||
connectionstring = databaseConfigControl.GetConnectionString();
|
||||
}
|
||||
if (!string.IsNullOrEmpty(_name) && !string.IsNullOrEmpty(connectionstring))
|
||||
{
|
||||
var settings = new Dictionary<string, object>();
|
||||
private async Task Add()
|
||||
{
|
||||
var connectionstring = _connectionstring;
|
||||
if (!_showConnectionString && _databaseConfig is IDatabaseConfigControl databaseConfigControl)
|
||||
{
|
||||
connectionstring = databaseConfigControl.GetConnectionString();
|
||||
}
|
||||
if (!string.IsNullOrEmpty(_name) && !string.IsNullOrEmpty(connectionstring))
|
||||
{
|
||||
_name = _name + " (" + _databasetype +")";
|
||||
var settings = new Dictionary<string, object>();
|
||||
settings.Add($"{SettingKeys.ConnectionStringsSection}:{_name}", connectionstring);
|
||||
await SystemService.UpdateSystemInfoAsync(settings);
|
||||
_connections = await SystemService.GetSystemInfoAsync("connectionstrings");
|
||||
|
||||
@@ -2,241 +2,280 @@
|
||||
@inherits ModuleBase
|
||||
@inject ISystemService SystemService
|
||||
@inject IInstallationService InstallationService
|
||||
@inject IMigrationHistoryService MigrationHistoryService
|
||||
@inject ITenantService TenantService
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
<TabStrip>
|
||||
<TabPanel Name="Info" Heading="Info" ResourceKey="Info">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="version" HelpText="Framework Version" ResourceKey="FrameworkVersion">Framework Version: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="version" class="form-control" @bind="@_version" readonly />
|
||||
@if (_initialized)
|
||||
{
|
||||
<TabStrip>
|
||||
<TabPanel Name="Info" Heading="Info" ResourceKey="Info">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="version" HelpText="Framework Version" ResourceKey="FrameworkVersion">Framework Version: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="version" class="form-control" @bind="@_version" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="clrversion" HelpText="Common Language Runtime Version" ResourceKey="CLRVersion">CLR Version: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="clrversion" class="form-control" @bind="@_clrversion" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="osversion" HelpText="Operating System Version" ResourceKey="OSVersion">OS Version: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="osversion" class="form-control" @bind="@_osversion" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="process" HelpText="Indicates if the current process is 32 bit or 64 bit" ResourceKey="Process">Process: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="process" class="form-control" @bind="@_process" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="machinename" HelpText="Machine Name" ResourceKey="MachineName">Machine Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="machinename" class="form-control" @bind="@_machinename" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="ipaddress" HelpText="Server IP Address" ResourceKey="IPAddress">IP Address: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="ipaddress" class="form-control" @bind="@_ipaddress" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="environment" HelpText="Environment name" ResourceKey="Environment">Environment: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="environment" class="form-control" @bind="@_environment" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="contentrootpath" HelpText="Root Path" ResourceKey="ContentRootPath">Root Path: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="contentrootpath" class="form-control" @bind="@_contentrootpath" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="webrootpath" HelpText="Web Path" ResourceKey="WebRootPath">Web Path: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="webrootpath" class="form-control" @bind="@_webrootpath" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="servertime" HelpText="Server Date/Time (in UTC)" ResourceKey="ServerTime">Server Date/Time: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="servertime" class="form-control" @bind="@_servertime" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="workingset" HelpText="Memory Allocation Of Service (in MB)" ResourceKey="WorkingSet">Memory Allocation: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="workingset" class="form-control" @bind="@_workingset" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="installationid" HelpText="The Unique Identifier For Your Installation" ResourceKey="InstallationId">Installation ID: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="installationid" class="form-control" @bind="@_installationid" readonly />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="clrversion" HelpText="Common Language Runtime Version" ResourceKey="CLRVersion">CLR Version: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="clrversion" class="form-control" @bind="@_clrversion" readonly />
|
||||
<br /><br />
|
||||
<ActionDialog Header="Restart Application" Message="Are You Sure You Wish To Restart The Application?" Action="Restart Application" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await RestartApplication())" ResourceKey="RestartApplication" />
|
||||
</TabPanel>
|
||||
<TabPanel Name="Options" Heading="Options" ResourceKey="Options">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="detailederrors" HelpText="Specify If Detailed Errors Are Enabled For Blazor. This Option Should Not Not Be Enabled In Production." ResourceKey="DetailedErrors">Detailed Errors? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="detailederrors" class="form-select" @bind="@_detailederrors">
|
||||
<option value="true">@SharedLocalizer["True"]</option>
|
||||
<option value="false">@SharedLocalizer["False"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="logginglevel" HelpText="The Minimum Logging Level For The Event Log. This Option Can Be Used To Control The Volume Of Items Stored In Your Event Log." ResourceKey="LoggingLevel">Logging Level: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="logginglevel" class="form-select" @bind="@_logginglevel">
|
||||
<option value="Trace">@Localizer["Trace"]</option>
|
||||
<option value="Debug">@Localizer["Debug"]</option>
|
||||
<option value="Information">@Localizer["Information"]</option>
|
||||
<option value="Warning">@Localizer["Warning"]</option>
|
||||
<option value="Error">@Localizer["Error"]</option>
|
||||
<option value="Critical">@Localizer["Critical"]</option>
|
||||
<option value="None">@Localizer["None"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="notificationlevel" HelpText="The Minimum Logging Level For Which Notifications Should Be Sent To Host Users." ResourceKey="NotificationLevel">Notification Level: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="notificationlevel" class="form-select" @bind="@_notificationlevel">
|
||||
<option value="Trace">@Localizer["Trace"]</option>
|
||||
<option value="Debug">@Localizer["Debug"]</option>
|
||||
<option value="Information">@Localizer["Information"]</option>
|
||||
<option value="Warning">@Localizer["Warning"]</option>
|
||||
<option value="Error">@Localizer["Error"]</option>
|
||||
<option value="Critical">@Localizer["Critical"]</option>
|
||||
<option value="None">@Localizer["None"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="swagger" HelpText="Specify If Swagger Is Enabled For Your Server API" ResourceKey="Swagger">Swagger Enabled? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="swagger" class="form-select" @bind="@_swagger">
|
||||
<option value="true">@SharedLocalizer["True"]</option>
|
||||
<option value="false">@SharedLocalizer["False"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="cachecontrol" HelpText="Provide a Cache-Control directive for static assets. For example 'public, max-age=60' indicates that static assets should be cached for 60 seconds. A blank value indicates caching is not enabled." ResourceKey="CacheControl">Static Asset Caching: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="cachecontrol" class="form-control" @bind="@_cachecontrol" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="packageregistryurl" HelpText="Specify The Url Of The Package Manager Service For Installing Modules, Themes, And Translations. If This Field Is Blank It Means The Package Manager Service Is Disabled For This Installation." ResourceKey="PackageManager">Package Manager Url: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="packageregistryurl" class="form-control" @bind="@_packageregistryurl" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="packageregistryemail" HelpText="Specify The Email Address Of The User Account Used For Interacting With The Package Manager Service. This Account Is Used For Managing Packages Across Multiple Installations." ResourceKey="PackageManagerEmail">Package Manager Email: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="packageregistryemail" class="form-control" @bind="@_packageregistryemail" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="osversion" HelpText="Operating System Version" ResourceKey="OSVersion">OS Version: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="osversion" class="form-control" @bind="@_osversion" readonly />
|
||||
<br /><br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveConfig">@SharedLocalizer["Save"]</button>
|
||||
<ActionDialog Header="Restart Application" Message="Are You Sure You Wish To Restart The Application?" Action="Restart Application" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await RestartApplication())" ResourceKey="RestartApplication" />
|
||||
<br /><br />
|
||||
<a class="btn btn-primary" href="swagger/index.html" target="_new">@Localizer["Swagger"]</a>
|
||||
<a class="btn btn-secondary" href="api/endpoint" target="_new">@Localizer["Endpoints"]</a>
|
||||
</TabPanel>
|
||||
<TabPanel Name="Log" Heading="Log" ResourceKey="Log">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="log" HelpText="System log information for current day" ResourceKey="Log">Log: </Label>
|
||||
<div class="col-sm-9">
|
||||
<textarea id="log" class="form-control" rows="10" @bind="@_log" readonly />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="process" HelpText="Indicates if the current process is 32 bit or 64 bit" ResourceKey="Process">Process: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="process" class="form-control" @bind="@_process" readonly />
|
||||
<br /><br />
|
||||
<button type="button" class="btn btn-danger" @onclick="ClearLog">@Localizer["Clear"]</button>
|
||||
</TabPanel>
|
||||
<TabPanel Name="Migrations" Heading="Migrations" ResourceKey="Migrations">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="tenant" HelpText="The name of the current database. Note that this is not the physical database name but rather the tenant name which is used within the framework to identify a database." ResourceKey="Tenant">Database: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="tenant" class="form-control" @bind="@_tenant" readonly />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="machinename" HelpText="Machine Name" ResourceKey="MachineName">Machine Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="machinename" class="form-control" @bind="@_machinename" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="ipaddress" HelpText="Server IP Address" ResourceKey="IPAddress">IP Address: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="ipaddress" class="form-control" @bind="@_ipaddress" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="environment" HelpText="Environment name" ResourceKey="Environment">Environment: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="environment" class="form-control" @bind="@_environment" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="contentrootpath" HelpText="Root Path" ResourceKey="ContentRootPath">Root Path: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="contentrootpath" class="form-control" @bind="@_contentrootpath" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="webrootpath" HelpText="Web Path" ResourceKey="WebRootPath">Web Path: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="webrootpath" class="form-control" @bind="@_webrootpath" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="servertime" HelpText="Server Date/Time (in UTC)" ResourceKey="ServerTime">Server Date/Time: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="servertime" class="form-control" @bind="@_servertime" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="workingset" HelpText="Memory Allocation Of Service (in MB)" ResourceKey="WorkingSet">Memory Allocation: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="workingset" class="form-control" @bind="@_workingset" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="installationid" HelpText="The Unique Identifier For Your Installation" ResourceKey="InstallationId">Installation ID: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="installationid" class="form-control" @bind="@_installationid" readonly />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br /><br />
|
||||
<ActionDialog Header="Restart Application" Message="Are You Sure You Wish To Restart The Application?" Action="Restart Application" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await RestartApplication())" ResourceKey="RestartApplication" />
|
||||
</TabPanel>
|
||||
<TabPanel Name="Options" Heading="Options" ResourceKey="Options">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="detailederrors" HelpText="Specify If Detailed Errors Are Enabled For Blazor. This Option Should Not Not Be Enabled In Production." ResourceKey="DetailedErrors">Detailed Errors? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="detailederrors" class="form-select" @bind="@_detailederrors">
|
||||
<option value="true">@SharedLocalizer["True"]</option>
|
||||
<option value="false">@SharedLocalizer["False"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="logginglevel" HelpText="The Minimum Logging Level For The Event Log. This Option Can Be Used To Control The Volume Of Items Stored In Your Event Log." ResourceKey="LoggingLevel">Logging Level: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="logginglevel" class="form-select" @bind="@_logginglevel">
|
||||
<option value="Trace">@Localizer["Trace"]</option>
|
||||
<option value="Debug">@Localizer["Debug"]</option>
|
||||
<option value="Information">@Localizer["Information"]</option>
|
||||
<option value="Warning">@Localizer["Warning"]</option>
|
||||
<option value="Error">@Localizer["Error"]</option>
|
||||
<option value="Critical">@Localizer["Critical"]</option>
|
||||
<option value="None">@Localizer["None"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="notificationlevel" HelpText="The Minimum Logging Level For Which Notifications Should Be Sent To Host Users." ResourceKey="NotificationLevel">Notification Level: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="notificationlevel" class="form-select" @bind="@_notificationlevel">
|
||||
<option value="Trace">@Localizer["Trace"]</option>
|
||||
<option value="Debug">@Localizer["Debug"]</option>
|
||||
<option value="Information">@Localizer["Information"]</option>
|
||||
<option value="Warning">@Localizer["Warning"]</option>
|
||||
<option value="Error">@Localizer["Error"]</option>
|
||||
<option value="Critical">@Localizer["Critical"]</option>
|
||||
<option value="None">@Localizer["None"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="swagger" HelpText="Specify If Swagger Is Enabled For Your Server API" ResourceKey="Swagger">Swagger Enabled? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="swagger" class="form-select" @bind="@_swagger">
|
||||
<option value="true">@SharedLocalizer["True"]</option>
|
||||
<option value="false">@SharedLocalizer["False"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="cachecontrol" HelpText="Provide a Cache-Control directive for static assets. For example 'public, max-age=60' indicates that static assets should be cached for 60 seconds. A blank value indicates caching is not enabled." ResourceKey="CacheControl">Static Asset Caching: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="cachecontrol" class="form-control" @bind="@_cachecontrol" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="packageregistryurl" HelpText="Specify The Url Of The Package Manager Service For Installing Modules, Themes, And Translations. If This Field Is Blank It Means The Package Manager Service Is Disabled For This Installation." ResourceKey="PackageManager">Package Manager Url: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="packageregistryurl" class="form-control" @bind="@_packageregistryurl" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="packageregistryemail" HelpText="Specify The Email Address Of The User Account Used For Interacting With The Package Manager Service. This Account Is Used For Managing Packages Across Multiple Installations." ResourceKey="PackageManagerEmail">Package Manager Email: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="packageregistryemail" class="form-control" @bind="@_packageregistryemail" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br /><br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveConfig">@SharedLocalizer["Save"]</button>
|
||||
<ActionDialog Header="Restart Application" Message="Are You Sure You Wish To Restart The Application?" Action="Restart Application" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await RestartApplication())" ResourceKey="RestartApplication" />
|
||||
<br /><br />
|
||||
<a class="btn btn-primary" href="swagger/index.html" target="_new">@Localizer["Swagger"]</a>
|
||||
<a class="btn btn-secondary" href="api/endpoint" target="_new">@Localizer["Endpoints"]</a>
|
||||
</TabPanel>
|
||||
<TabPanel Name="Log" Heading="Log" ResourceKey="Log">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="log" HelpText="System log information for current day" ResourceKey="Log">Log: </Label>
|
||||
<div class="col-sm-9">
|
||||
<textarea id="log" class="form-control" rows="10" @bind="@_log" readonly />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br /><br />
|
||||
<button type="button" class="btn btn-danger" @onclick="ClearLog">@Localizer["Clear"]</button>
|
||||
</TabPanel>
|
||||
</TabStrip>
|
||||
<br /><br />
|
||||
<br />
|
||||
<Pager Items="@_history" SearchProperties="MigrationId">
|
||||
<Header>
|
||||
<th>@Localizer["Migration"]</th>
|
||||
<th>@Localizer["Date"]</th>
|
||||
<th>@Localizer["Version"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td>@context.MigrationId</td>
|
||||
<td>@UtcToLocal(context.AppliedDate)</td>
|
||||
<td>@context.AppliedVersion</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
</TabPanel>
|
||||
</TabStrip>
|
||||
<br /><br />
|
||||
}
|
||||
|
||||
@code {
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
|
||||
private string _version = string.Empty;
|
||||
private string _clrversion = string.Empty;
|
||||
private string _osversion = string.Empty;
|
||||
private bool _initialized = false;
|
||||
|
||||
private string _version = string.Empty;
|
||||
private string _clrversion = string.Empty;
|
||||
private string _osversion = string.Empty;
|
||||
private string _process = string.Empty;
|
||||
private string _machinename = string.Empty;
|
||||
private string _ipaddress = string.Empty;
|
||||
private string _environment = string.Empty;
|
||||
private string _contentrootpath = string.Empty;
|
||||
private string _webrootpath = string.Empty;
|
||||
private string _servertime = string.Empty;
|
||||
private string _workingset = string.Empty;
|
||||
private string _installationid = string.Empty;
|
||||
private string _ipaddress = string.Empty;
|
||||
private string _environment = string.Empty;
|
||||
private string _contentrootpath = string.Empty;
|
||||
private string _webrootpath = string.Empty;
|
||||
private string _servertime = string.Empty;
|
||||
private string _workingset = string.Empty;
|
||||
private string _installationid = string.Empty;
|
||||
|
||||
private string _detailederrors = string.Empty;
|
||||
private string _logginglevel = string.Empty;
|
||||
private string _notificationlevel = string.Empty;
|
||||
private string _swagger = string.Empty;
|
||||
private string _detailederrors = string.Empty;
|
||||
private string _logginglevel = string.Empty;
|
||||
private string _notificationlevel = string.Empty;
|
||||
private string _swagger = string.Empty;
|
||||
private string _cachecontrol = string.Empty;
|
||||
private string _packageregistryurl = string.Empty;
|
||||
private string _packageregistryemail = string.Empty;
|
||||
|
||||
private string _log = string.Empty;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_version = Constants.Version;
|
||||
private string _tenant = string.Empty;
|
||||
private List<MigrationHistory> _history;
|
||||
|
||||
var systeminfo = await SystemService.GetSystemInfoAsync("environment");
|
||||
if (systeminfo != null)
|
||||
{
|
||||
_clrversion = systeminfo["CLRVersion"].ToString();
|
||||
_osversion = systeminfo["OSVersion"].ToString();
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_version = Constants.Version;
|
||||
|
||||
var systeminfo = await SystemService.GetSystemInfoAsync("environment");
|
||||
if (systeminfo != null)
|
||||
{
|
||||
_clrversion = systeminfo["CLRVersion"].ToString();
|
||||
_osversion = systeminfo["OSVersion"].ToString();
|
||||
_process = systeminfo["Process"].ToString();
|
||||
_machinename = systeminfo["MachineName"].ToString();
|
||||
_ipaddress = systeminfo["IPAddress"].ToString();
|
||||
_environment = systeminfo["Environment"].ToString();
|
||||
_contentrootpath = systeminfo["ContentRootPath"].ToString();
|
||||
_webrootpath = systeminfo["WebRootPath"].ToString();
|
||||
_servertime = systeminfo["ServerTime"].ToString() + " UTC";
|
||||
_workingset = (Convert.ToInt64(systeminfo["WorkingSet"].ToString()) / 1000000).ToString() + " MB";
|
||||
}
|
||||
_ipaddress = systeminfo["IPAddress"].ToString();
|
||||
_environment = systeminfo["Environment"].ToString();
|
||||
_contentrootpath = systeminfo["ContentRootPath"].ToString();
|
||||
_webrootpath = systeminfo["WebRootPath"].ToString();
|
||||
_servertime = systeminfo["ServerTime"].ToString() + " UTC";
|
||||
_workingset = (Convert.ToInt64(systeminfo["WorkingSet"].ToString()) / 1000000).ToString() + " MB";
|
||||
}
|
||||
|
||||
systeminfo = await SystemService.GetSystemInfoAsync("configuration");
|
||||
if (systeminfo != null)
|
||||
{
|
||||
_installationid = systeminfo["InstallationId"].ToString();
|
||||
_detailederrors = systeminfo["DetailedErrors"].ToString();
|
||||
_logginglevel = systeminfo["Logging:LogLevel:Default"].ToString();
|
||||
_notificationlevel = systeminfo["Logging:LogLevel:Notify"].ToString();
|
||||
systeminfo = await SystemService.GetSystemInfoAsync("configuration");
|
||||
if (systeminfo != null)
|
||||
{
|
||||
_installationid = systeminfo["InstallationId"].ToString();
|
||||
_detailederrors = systeminfo["DetailedErrors"].ToString();
|
||||
_logginglevel = systeminfo["Logging:LogLevel:Default"].ToString();
|
||||
_notificationlevel = systeminfo["Logging:LogLevel:Notify"].ToString();
|
||||
_swagger = systeminfo["UseSwagger"].ToString();
|
||||
_cachecontrol = systeminfo["CacheControl"].ToString();
|
||||
_packageregistryurl = systeminfo["PackageRegistryUrl"].ToString();
|
||||
_packageregistryemail = systeminfo["PackageRegistryEmail"].ToString();
|
||||
}
|
||||
|
||||
systeminfo = await SystemService.GetSystemInfoAsync("log");
|
||||
if (systeminfo != null)
|
||||
{
|
||||
_log = systeminfo["Log"].ToString();
|
||||
}
|
||||
}
|
||||
systeminfo = await SystemService.GetSystemInfoAsync("log");
|
||||
if (systeminfo != null)
|
||||
{
|
||||
_log = systeminfo["Log"].ToString();
|
||||
}
|
||||
|
||||
var tenants = await TenantService.GetTenantsAsync();
|
||||
_tenant = tenants.Find(item => item.TenantId == PageState.Alias.TenantId).Name;
|
||||
_history = await MigrationHistoryService.GetMigrationHistoryAsync();
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
private async Task SaveConfig()
|
||||
{
|
||||
|
||||
@@ -101,13 +101,20 @@
|
||||
<small>@SharedLocalizer["Search.By"]:</small> <strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
|
||||
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
|
||||
<br />
|
||||
@if (!string.IsNullOrEmpty(context.PackageUrl))
|
||||
@if (_themes.Exists(item => item.PackageName == context.PackageId))
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
|
||||
<button type="button" class="btn btn-info">@SharedLocalizer["Installed"]</button>
|
||||
}
|
||||
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
|
||||
else
|
||||
{
|
||||
<a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
|
||||
@if (!string.IsNullOrEmpty(context.PackageUrl))
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
|
||||
}
|
||||
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
|
||||
{
|
||||
<a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
|
||||
}
|
||||
}
|
||||
<br />
|
||||
</div>
|
||||
@@ -171,6 +178,7 @@
|
||||
|
||||
@code {
|
||||
private bool _initialized = false;
|
||||
private List<Theme> _themes;
|
||||
private int _page = 1;
|
||||
private List<Package> _packages;
|
||||
private string _price = "free";
|
||||
@@ -187,7 +195,8 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
await LoadThemes();
|
||||
_themes = await ThemeService.GetThemesAsync(PageState.Site.SiteId);
|
||||
await LoadPackages();
|
||||
_initialized = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -197,24 +206,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadThemes()
|
||||
private async Task LoadPackages()
|
||||
{
|
||||
ShowProgressIndicator();
|
||||
|
||||
var themes = await ThemeService.GetThemesAsync();
|
||||
ShowProgressIndicator();
|
||||
_packages = await PackageService.GetPackagesAsync("theme", _search, _price, "", _sort);
|
||||
|
||||
if (_packages != null)
|
||||
{
|
||||
foreach (Package package in _packages.ToArray())
|
||||
{
|
||||
if (themes.Exists(item => item.PackageName == package.PackageId))
|
||||
{
|
||||
_packages.Remove(package);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HideProgressIndicator();
|
||||
}
|
||||
|
||||
@@ -222,25 +217,25 @@
|
||||
{
|
||||
_price = price;
|
||||
_sort = "popularity";
|
||||
await LoadThemes();
|
||||
await LoadPackages();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task Search()
|
||||
{
|
||||
await LoadThemes();
|
||||
await LoadPackages();
|
||||
}
|
||||
|
||||
private async Task Reset()
|
||||
{
|
||||
_page = 1;
|
||||
_search = "";
|
||||
await LoadThemes();
|
||||
await LoadPackages();
|
||||
}
|
||||
|
||||
private async Task Refresh()
|
||||
{
|
||||
await LoadThemes();
|
||||
await LoadPackages();
|
||||
}
|
||||
|
||||
private void OnPageChange(int page)
|
||||
@@ -251,7 +246,7 @@
|
||||
private async void SortChanged(ChangeEventArgs e)
|
||||
{
|
||||
_sort = (string)e.Value;
|
||||
await LoadThemes();
|
||||
await LoadPackages();
|
||||
}
|
||||
|
||||
private void HideModal()
|
||||
@@ -310,6 +305,6 @@
|
||||
|
||||
private void OnUpload()
|
||||
{
|
||||
AddModuleMessage(string.Format(Localizer["Success.Theme.Download"], NavigateUrl("admin/system")), MessageType.Success);
|
||||
AddModuleMessage(string.Format(Localizer["Success.Theme.Upload"], NavigateUrl("admin/system")), MessageType.Success);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@namespace Oqtane.Modules.Admin.Themes
|
||||
@inherits ModuleBase
|
||||
@using System.Text.RegularExpressions
|
||||
@using System.Reflection
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IThemeService ThemeService
|
||||
@inject IModuleService ModuleService
|
||||
@@ -36,30 +37,33 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="reference" class="form-select" @bind="@_reference">
|
||||
@foreach (string version in _versions)
|
||||
{
|
||||
if (Version.Parse(version).CompareTo(Version.Parse(_minversion)) >= 0)
|
||||
{
|
||||
<option value="@(version)">@(version)</option>
|
||||
}
|
||||
}
|
||||
<option value="local">@SharedLocalizer["LocalVersion"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (!string.IsNullOrEmpty(_location))
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="location" HelpText="Location where the theme will be created" ResourceKey="Location">Location: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="module" class="form-control" @bind="@_location" readonly />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_type == "External")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="reference" class="form-select" @bind="@_reference">
|
||||
@foreach (string version in _versions)
|
||||
{
|
||||
if (Version.Parse(version).CompareTo(Version.Parse(_minversion)) >= 0)
|
||||
{
|
||||
<option value="@(version)">@(version)</option>
|
||||
}
|
||||
}
|
||||
<option value="local">@SharedLocalizer["LocalVersion"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (!string.IsNullOrEmpty(_location))
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="location" HelpText="Location where the theme will be created" ResourceKey="Location">Location: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="module" class="form-control" @bind="@_location" readonly />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="CreateTheme">@Localizer["Theme.Create"]</button>
|
||||
@@ -71,9 +75,10 @@
|
||||
private string _theme = string.Empty;
|
||||
private List<Template> _templates;
|
||||
private string _template = "-";
|
||||
private string _minversion = "2.0.0";
|
||||
private string _type = "";
|
||||
private string[] _versions;
|
||||
private string _reference = "local";
|
||||
private string _minversion = "2.0.0";
|
||||
private string _location = string.Empty;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
@@ -84,9 +89,19 @@
|
||||
{
|
||||
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()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -105,11 +120,18 @@
|
||||
{
|
||||
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 theme = new Theme { Owner = _owner, Name = _theme, Template = _template, Version = _reference, ThemeName = template.Namespace };
|
||||
theme = await ThemeService.CreateThemeAsync(theme);
|
||||
GetLocation();
|
||||
AddModuleMessage(string.Format(Localizer["Success.Theme.Create"], NavigateUrl("admin/system")), MessageType.Success);
|
||||
if (_type == "External")
|
||||
{
|
||||
GetLocation();
|
||||
AddModuleMessage(string.Format(Localizer["Success.Theme.Create.External"], NavigateUrl("admin/system")), MessageType.Success);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -125,17 +147,22 @@
|
||||
private bool IsValid(string name)
|
||||
{
|
||||
// must contain letters, underscores and digits and first character must be letter or underscore
|
||||
return !string.IsNullOrEmpty(name) && name.ToLower() != "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)
|
||||
{
|
||||
_template = (string)e.Value;
|
||||
_minversion = "2.0.0";
|
||||
if (_template != "-")
|
||||
{
|
||||
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
||||
_minversion = template.Version;
|
||||
_type = template.Type;
|
||||
}
|
||||
else
|
||||
{
|
||||
_minversion = "2.0.0";
|
||||
_type = "";
|
||||
}
|
||||
GetLocation();
|
||||
}
|
||||
|
||||
@@ -9,84 +9,105 @@
|
||||
|
||||
@if (_initialized)
|
||||
{
|
||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="name" HelpText="The name of the module" ResourceKey="Name">Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="name" class="form-control" @bind="@_name" />
|
||||
<TabStrip>
|
||||
<TabPanel Name="Theme" ResourceKey="Theme" Heading="Theme">
|
||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="name" HelpText="The name of the theme" ResourceKey="Name">Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="name" class="form-control" @bind="@_name" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="isenabled" HelpText="Is theme enabled for this site?" ResourceKey="IsEnabled">Enabled? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="isenabled" class="form-select" @bind="@_isenabled" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<br />
|
||||
<Section Name="Information" ResourceKey="Information" Heading="Information">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="themename" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="themename" class="form-control" @bind="@_themeName" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="version" HelpText="The version of the theme" ResourceKey="Version">Version: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="version" class="form-control" @bind="@_version" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this theme was installed. This value must be specified within the theme's ITheme interface specification." ResourceKey="PackageName">Package Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="owner" HelpText="The owner or creator of the theme" ResourceKey="Owner">Owner: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="owner" class="form-control" @bind="@_owner" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="url" HelpText="The url of the theme" ResourceKey="Url">Url: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="url" class="form-control" @bind="@_url" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="contact" HelpText="The contact for the theme" ResourceKey="Contact">Contact: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="contact" class="form-control" @bind="@_contact" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="license" HelpText="The license of the theme" ResourceKey="License">License: </Label>
|
||||
<div class="col-sm-9">
|
||||
@if (_license.StartsWith("http") || _license.StartsWith("/") || _license.StartsWith("~"))
|
||||
{
|
||||
<a href="@_license.Replace("~", PageState?.Alias.BaseUrl + "/Themes/" + Utilities.GetTypeName(_themeName))" class="btn btn-info" style="text-decoration: none !important" target="_new">@Localizer["View License"]</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div 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>
|
||||
</Section>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveTheme">@SharedLocalizer["Save"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
<br />
|
||||
<br />
|
||||
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
|
||||
</TabPanel>
|
||||
<TabPanel Name="Permissions" ResourceKey="Permissions" Heading="Permissions">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<PermissionGrid EntityName="@EntityNames.Theme" PermissionNames="@PermissionNames.Utilize" PermissionList="@_permissions" @ref="_permissionGrid" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="isenabled" HelpText="Is theme enabled for this site?" ResourceKey="IsEnabled">Enabled? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="isenabled" class="form-select" @bind="@_isenabled" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<Section Name="Information" ResourceKey="Information" Heading="Information">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="themename" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="themename" class="form-control" @bind="@_themeName" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="version" HelpText="The version of the theme" ResourceKey="Version">Version: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="version" class="form-control" @bind="@_version" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this theme was installed. This value must be specified within the theme's ITheme interface specification." ResourceKey="PackageName">Package Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="owner" HelpText="The owner or creator of the theme" ResourceKey="Owner">Owner: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="owner" class="form-control" @bind="@_owner" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="url" HelpText="The url of the theme" ResourceKey="Url">Url: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="url" class="form-control" @bind="@_url" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="contact" HelpText="The contact for the theme" ResourceKey="Contact">Contact: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="contact" class="form-control" @bind="@_contact" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="license" HelpText="The license of the theme" ResourceKey="License">License: </Label>
|
||||
<div class="col-sm-9">
|
||||
@if (_license.StartsWith("http") || _license.StartsWith("/") || _license.StartsWith("~"))
|
||||
{
|
||||
<a href="@_license.Replace("~", PageState?.Alias.BaseUrl + "/Themes/" + Utilities.GetTypeName(_themeName))" class="btn btn-info" style="text-decoration: none !important" target="_new">@Localizer["View License"]</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveTheme">@SharedLocalizer["Save"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
<br />
|
||||
<br />
|
||||
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveTheme">@SharedLocalizer["Save"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
</TabPanel>
|
||||
</TabStrip>
|
||||
}
|
||||
|
||||
@code {
|
||||
@@ -103,11 +124,15 @@
|
||||
private string _url = "";
|
||||
private string _contact = "";
|
||||
private string _license = "";
|
||||
private string _fingerprint = "";
|
||||
private List<Permission> _permissions = null;
|
||||
private string _createdby;
|
||||
private DateTime _createdon;
|
||||
private string _modifiedby;
|
||||
private DateTime _modifiedon;
|
||||
|
||||
private PermissionGrid _permissionGrid;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
@@ -126,6 +151,8 @@
|
||||
_url = theme.Url;
|
||||
_contact = theme.Contact;
|
||||
_license = theme.License;
|
||||
_fingerprint = theme.Fingerprint;
|
||||
_permissions = theme.PermissionList;
|
||||
_createdby = theme.CreatedBy;
|
||||
_createdon = theme.CreatedOn;
|
||||
_modifiedby = theme.ModifiedBy;
|
||||
@@ -152,6 +179,7 @@
|
||||
var theme = await ThemeService.GetThemeAsync(_themeId, ModuleState.SiteId);
|
||||
theme.Name = _name;
|
||||
theme.IsEnabled = (_isenabled == null ? true : bool.Parse(_isenabled));
|
||||
theme.PermissionList = _permissionGrid.GetPermissionList();
|
||||
await ThemeService.UpdateThemeAsync(theme);
|
||||
await logger.LogInformation("Theme Saved {Theme}", theme);
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
|
||||
@@ -15,12 +15,11 @@
|
||||
else
|
||||
{
|
||||
<ActionLink Action="Add" Text="Install Theme" ResourceKey="InstallTheme" />
|
||||
<ActionLink Action="Create" Text="Create Theme" ResourceKey="CreateTheme" Class="btn btn-secondary ps-2" />
|
||||
<button type="button" class="btn btn-secondary pw-2" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
|
||||
<ActionLink Action="Create" Text="Create Theme" ResourceKey="CreateTheme" Class="btn btn-secondary ms-1" />
|
||||
<button type="button" class="btn btn-secondary ms-1" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
|
||||
|
||||
<Pager Items="@_themes">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </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" />
|
||||
}
|
||||
</td>
|
||||
<td><NavLink class="btn btn-secondary" href="@NavigateUrl("admin/site")">@Localizer["Assign"]</NavLink></td>
|
||||
<td>@context.Name</td>
|
||||
<td>@context.Version</td>
|
||||
<td>
|
||||
@@ -80,7 +78,7 @@ else
|
||||
{
|
||||
try
|
||||
{
|
||||
_themes = await ThemeService.GetThemesAsync();
|
||||
_themes = await ThemeService.GetThemesAsync(PageState.Site.SiteId);
|
||||
_packages = await PackageService.GetPackageUpdatesAsync("theme");
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -163,7 +161,7 @@ else
|
||||
{
|
||||
try
|
||||
{
|
||||
await ThemeService.DeleteThemeAsync(Theme.ThemeName);
|
||||
await ThemeService.DeleteThemeAsync(Theme.ThemeId, PageState.Site.SiteId);
|
||||
AddModuleMessage(Localizer["Success.Theme.Delete"], MessageType.Success);
|
||||
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ else
|
||||
<th>@Localizer["Url"]</th>
|
||||
<th>@Localizer["Requests"]</th>
|
||||
<th>@Localizer["Requested"]</th>
|
||||
<th>@Localizer["Referrer"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.UrlMappingId.ToString())" ResourceKey="Edit" /></td>
|
||||
@@ -49,7 +50,8 @@ else
|
||||
</td>
|
||||
<td>@context.Requests</td>
|
||||
<td>@UtcToLocal(context.RequestedOn)</td>
|
||||
</Row>
|
||||
<td>@context.Referrer</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
</TabPanel>
|
||||
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
@inject IFileService FileService
|
||||
@inject IFolderService FolderService
|
||||
@inject ITimeZoneService TimeZoneService
|
||||
@inject ILanguageService LanguageService
|
||||
@inject IJSRuntime jsRuntime
|
||||
@inject IServiceProvider ServiceProvider
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@@ -26,8 +27,7 @@
|
||||
<br />
|
||||
}
|
||||
<TabStrip>
|
||||
<TabPanel Name="Identity" ResourceKey="Identity">
|
||||
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||
<TabPanel Name="Identity" Heading="Identity" ResourceKey="Identity">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="username" HelpText="Your username. Note that this field can not be modified." ResourceKey="Username"></Label>
|
||||
@@ -35,36 +35,6 @@
|
||||
<input id="username" class="form-control" @bind="@_username" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="password" HelpText="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">
|
||||
<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">
|
||||
@@ -89,6 +59,18 @@
|
||||
</select>
|
||||
</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">
|
||||
<Label Class="col-sm-3" For="@_photofileid.ToString()" HelpText="A photo of yourself" ResourceKey="Photo"></Label>
|
||||
<div class="col-sm-9">
|
||||
@@ -99,9 +81,113 @@
|
||||
<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>
|
||||
<button type="button" class="btn btn-danger" @onclick="Logout">@Localizer["Logout Everywhere"]</button>
|
||||
</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="row mb-1 align-items-center">
|
||||
@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">
|
||||
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
|
||||
var values = option.Split(':');
|
||||
var name = values[0];
|
||||
var value = values.Length > 1 ? values[1] : values[0];
|
||||
@if (GetProfileValue(p.Name, "") == name || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == name))
|
||||
{
|
||||
<option value="@option" selected>@option</option>
|
||||
<option value="@name" selected>@value</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@option">@option</option>
|
||||
<option value="@name">@value</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
@@ -142,13 +231,16 @@
|
||||
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
|
||||
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
|
||||
var values = option.Split(':');
|
||||
var name = values[0];
|
||||
var value = values.Length > 1 ? values[1] : values[0];
|
||||
@if (GetProfileValue(p.Name, "") == name || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == name))
|
||||
{
|
||||
<option value="@option" selected>@option</option>
|
||||
<option value="@name" selected>@value</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@option">@option</option>
|
||||
<option value="@name">@value</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
@@ -224,11 +316,11 @@
|
||||
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
</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")" />
|
||||
<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="from">@Localizer["Items.Sent"]</option>
|
||||
</select>
|
||||
@@ -247,7 +339,7 @@
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" /></td>
|
||||
<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)
|
||||
{
|
||||
@@ -292,7 +384,7 @@
|
||||
else
|
||||
{
|
||||
<div class="no-notifications-text">
|
||||
@Localizer["NoNotificationsReceived.Text"]
|
||||
@Localizer["NoNotificationsReceived"]
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -310,7 +402,7 @@
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" /></td>
|
||||
<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)
|
||||
{
|
||||
@@ -356,7 +448,7 @@
|
||||
else
|
||||
{
|
||||
<div class="no-notifications-text">
|
||||
@Localizer["NoNotificationsSent.Text"]
|
||||
@Localizer["NoNotificationsSent"]
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -367,25 +459,38 @@
|
||||
}
|
||||
|
||||
@code {
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
||||
|
||||
private List<Models.TimeZone> _timezones;
|
||||
private IEnumerable<Language> _languages;
|
||||
|
||||
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 string _twofactor = "False";
|
||||
private bool _allowpasskeys = false;
|
||||
private bool _allowexternallogin = false;
|
||||
|
||||
private string _username = string.Empty;
|
||||
private string _email = string.Empty;
|
||||
private string _displayname = string.Empty;
|
||||
private FileManager _filemanager;
|
||||
private int _folderid = -1;
|
||||
private string _timezoneid = string.Empty;
|
||||
private string _culturecode = string.Empty;
|
||||
private int _photofileid = -1;
|
||||
private File _photo = null;
|
||||
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 Dictionary<string, string> _userSettings;
|
||||
private string _category = string.Empty;
|
||||
@@ -394,38 +499,32 @@
|
||||
private List<Notification> _notifications;
|
||||
private string _notificationSummary = string.Empty;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||
_allowtwofactor = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "true");
|
||||
_profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
||||
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
||||
_allowpasskeys = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:Passkeys", "false") == "true");
|
||||
_allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false;
|
||||
|
||||
if (PageState.User != null)
|
||||
{
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
|
||||
|
||||
// identity section
|
||||
_username = PageState.User.Username;
|
||||
_twofactor = PageState.User.TwoFactorRequired.ToString();
|
||||
_email = PageState.User.Email;
|
||||
_displayname = PageState.User.DisplayName;
|
||||
_timezoneid = PageState.User.TimeZoneId;
|
||||
|
||||
if (string.IsNullOrEmpty(_email))
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.User.NoEmail"], MessageType.Warning);
|
||||
}
|
||||
|
||||
// get user folder
|
||||
_timezoneid = PageState.User.TimeZoneId;
|
||||
_culturecode = PageState.User.CultureCode;
|
||||
var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath);
|
||||
if (folder != null)
|
||||
{
|
||||
_folderid = folder.FolderId;
|
||||
}
|
||||
|
||||
_imagefiles = SettingService.GetSetting(PageState.Site.Settings, "ImageFiles", Constants.ImageFiles);
|
||||
_imagefiles = (string.IsNullOrEmpty(_imagefiles)) ? Constants.ImageFiles : _imagefiles;
|
||||
if (PageState.User.PhotoFileId != null)
|
||||
{
|
||||
_photofileid = PageState.User.PhotoFileId.Value;
|
||||
@@ -437,11 +536,27 @@
|
||||
_photo = null;
|
||||
}
|
||||
|
||||
_userSettings = PageState.User.Settings;
|
||||
var sitesettings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
_imagefiles = SettingService.GetSetting(_userSettings, "ImageFiles", Constants.ImageFiles);
|
||||
_imagefiles = (string.IsNullOrEmpty(_imagefiles)) ? Constants.ImageFiles : _imagefiles;
|
||||
// security section
|
||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||
_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();
|
||||
|
||||
_initialized = true;
|
||||
@@ -458,22 +573,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadNotificationsAsync()
|
||||
{
|
||||
_notifications = await NotificationService.GetNotificationsAsync(PageState.Site.SiteId, _filter, PageState.User.UserId);
|
||||
_notifications = _notifications.Where(item => item.DeletedBy != PageState.User.Username).ToList();
|
||||
}
|
||||
|
||||
private string GetProfileValue(string SettingName, string DefaultValue)
|
||||
{
|
||||
string value = SettingService.GetSetting(_userSettings, SettingName, DefaultValue);
|
||||
if (value.Contains("]"))
|
||||
{
|
||||
value = value.Substring(value.IndexOf("]") + 1);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// identity methods
|
||||
private async Task Save()
|
||||
{
|
||||
try
|
||||
@@ -491,6 +591,7 @@
|
||||
user.Email = _email;
|
||||
user.DisplayName = (_displayname == string.Empty ? _username : _displayname);
|
||||
user.TimeZoneId = _timezoneid;
|
||||
user.CultureCode = _culturecode;
|
||||
user.PhotoFileId = _filemanager.GetFileId();
|
||||
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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
foreach (Profile profile in _profiles)
|
||||
@@ -605,18 +842,22 @@
|
||||
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;
|
||||
_userSettings = SettingService.SetSetting(_userSettings, SettingName, value);
|
||||
_filter = (string)e.Value;
|
||||
await LoadNotificationsAsync();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task Delete(Notification Notification)
|
||||
private async Task DeleteNotification(Notification Notification)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -641,13 +882,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
private async void FilterChanged(ChangeEventArgs e)
|
||||
{
|
||||
_filter = (string)e.Value;
|
||||
await LoadNotificationsAsync();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task DeleteAllNotifications()
|
||||
{
|
||||
try
|
||||
@@ -679,18 +913,4 @@
|
||||
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 ISettingService SettingService
|
||||
@inject ITimeZoneService TimeZoneService
|
||||
@inject ILanguageService LanguageService
|
||||
@inject IStringLocalizer<Add> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@@ -28,6 +29,15 @@
|
||||
<input id="email" class="form-control" @bind="@_email" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="confirmed" HelpText="Indicates if the user's email is verified" ResourceKey="Confirmed">Verified?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="confirmed" class="form-select" @bind="@_confirmed">
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="displayname" HelpText="The full name of the user" ResourceKey="DisplayName"></Label>
|
||||
<div class="col-sm-9">
|
||||
@@ -46,6 +56,18 @@
|
||||
</select>
|
||||
</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">
|
||||
<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">
|
||||
@@ -79,13 +101,16 @@
|
||||
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
|
||||
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
|
||||
var values = option.Split(':');
|
||||
var name = values[0];
|
||||
var value = values.Length > 1 ? values[1] : values[0];
|
||||
@if (GetProfileValue(p.Name, "") == name || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == name))
|
||||
{
|
||||
<option value="@option" selected>@option</option>
|
||||
<option value="@name" selected>@value</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@option">@option</option>
|
||||
<option value="@name">@value</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
@@ -117,11 +142,15 @@
|
||||
|
||||
@code {
|
||||
private List<Models.TimeZone> _timezones;
|
||||
private IEnumerable<Models.Language> _languages;
|
||||
|
||||
private bool _initialized = false;
|
||||
private string _username = string.Empty;
|
||||
private string _email = string.Empty;
|
||||
private string _confirmed = "True";
|
||||
private string _displayname = string.Empty;
|
||||
private string _timezoneid = string.Empty;
|
||||
private string _culturecode = string.Empty;
|
||||
private string _notify = "True";
|
||||
private List<Profile> _profiles;
|
||||
private Dictionary<string, string> _settings;
|
||||
@@ -133,10 +162,25 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
||||
_profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
||||
_settings = new Dictionary<string, string>();
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
|
||||
|
||||
_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;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -169,8 +213,10 @@
|
||||
user.Username = _username;
|
||||
user.Password = ""; // will be auto generated
|
||||
user.Email = _email;
|
||||
user.EmailConfirmed = bool.Parse(_confirmed);
|
||||
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
||||
user.TimeZoneId = _timezoneid;
|
||||
user.CultureCode = _culturecode;
|
||||
user.PhotoFileId = null;
|
||||
user.SuppressNotification = !bool.Parse(_notify);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
@inject ISettingService SettingService
|
||||
@inject IFileService FileService
|
||||
@inject ITimeZoneService TimeZoneService
|
||||
@inject ILanguageService LanguageService
|
||||
@inject IServiceProvider ServiceProvider
|
||||
@inject IStringLocalizer<Edit> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
@@ -14,8 +15,7 @@
|
||||
@if (_initialized)
|
||||
{
|
||||
<TabStrip>
|
||||
<TabPanel Name="Identity" ResourceKey="Identity">
|
||||
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||
<TabPanel Name="Identity" Heading="Identity" ResourceKey="Identity">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="username" HelpText="The unique username for a user. Note that this field can not be modified." ResourceKey="Username">Username:</Label>
|
||||
@@ -23,24 +23,6 @@
|
||||
<input id="username" class="form-control" @bind="@_username" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password">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">
|
||||
<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">
|
||||
@@ -48,7 +30,7 @@
|
||||
</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">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">
|
||||
<select id="confirmed" class="form-select" @bind="@_confirmed">
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
@@ -74,6 +56,18 @@
|
||||
</select>
|
||||
</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))
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
@@ -100,7 +94,77 @@
|
||||
</div>
|
||||
</div>
|
||||
</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="row mb-1 align-items-center">
|
||||
@foreach (Profile profile in _profiles)
|
||||
@@ -121,13 +185,16 @@
|
||||
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
|
||||
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
|
||||
var values = option.Split(':');
|
||||
var name = values[0];
|
||||
var value = values.Length > 1 ? values[1] : values[0];
|
||||
@if (GetProfileValue(p.Name, "") == name || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == name))
|
||||
{
|
||||
<option value="@option" selected>@option</option>
|
||||
<option value="@name" selected>@value</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@option">@option</option>
|
||||
<option value="@name">@value</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
@@ -153,36 +220,46 @@
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
|
||||
<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>
|
||||
}
|
||||
@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 />
|
||||
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
|
||||
}
|
||||
|
||||
@code {
|
||||
private List<Models.TimeZone> _timezones;
|
||||
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 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 _confirmed = string.Empty;
|
||||
private string _displayname = string.Empty;
|
||||
private string _timezoneid = string.Empty;
|
||||
private string _culturecode = string.Empty;
|
||||
private string _isdeleted;
|
||||
private string _lastlogin;
|
||||
private string _lastipaddress;
|
||||
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 Dictionary<string, string> _settings;
|
||||
@@ -201,10 +278,8 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||
_profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
|
||||
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
||||
_allowpasskeys = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:Passkeys", "false") == "true");
|
||||
_allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false;
|
||||
|
||||
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);
|
||||
if (user != null)
|
||||
{
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
|
||||
|
||||
_username = user.Username;
|
||||
_email = user.Email;
|
||||
_confirmed = user.EmailConfirmed.ToString();
|
||||
_displayname = user.DisplayName;
|
||||
_timezoneid = PageState.User.TimeZoneId;
|
||||
_culturecode = PageState.User.CultureCode;
|
||||
_isdeleted = user.IsDeleted.ToString();
|
||||
_candelete = user.IsDeleted;
|
||||
_lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", UtcToLocal(user.LastLoginOn));
|
||||
_lastipaddress = user.LastIPAddress;
|
||||
_ishost = UserSecurity.ContainsRole(user.Roles, RoleNames.Host);
|
||||
|
||||
_settings = user.Settings;
|
||||
_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;
|
||||
|
||||
_createdby = user.CreatedBy;
|
||||
_createdon = user.CreatedOn;
|
||||
_modifiedby = user.ModifiedBy;
|
||||
@@ -269,6 +365,7 @@
|
||||
user.EmailConfirmed = bool.Parse(_confirmed);
|
||||
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
||||
user.TimeZoneId = _timezoneid;
|
||||
user.CultureCode = _culturecode;
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
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()
|
||||
{
|
||||
foreach (Profile profile in _profiles)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
@namespace Oqtane.Modules.Admin.Users
|
||||
@inherits ModuleBase
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IUserService UserService
|
||||
@inject ISiteTaskService SiteTaskService
|
||||
@inject IStringLocalizer<Users> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@@ -43,17 +43,9 @@
|
||||
var fileid = _filemanager.GetFileId();
|
||||
if (fileid != -1)
|
||||
{
|
||||
ShowProgressIndicator();
|
||||
var results = await UserService.ImportUsersAsync(PageState.Site.SiteId, fileid, bool.Parse(_notify));
|
||||
if (bool.Parse(results["Success"]))
|
||||
{
|
||||
AddModuleMessage(string.Format(Localizer["Message.Import.Success"], results["Users"]), MessageType.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Import.Failure"], MessageType.Error);
|
||||
}
|
||||
HideProgressIndicator();
|
||||
var siteTask = new SiteTask(PageState.Site.SiteId, "Import Users", "Oqtane.Infrastructure.ImportUsersTask, Oqtane.Server", $"{fileid}:{_notify}");
|
||||
await SiteTaskService.AddSiteTaskAsync(siteTask);
|
||||
AddModuleMessage(Localizer["Message.Import.Success"], MessageType.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -35,11 +35,11 @@
|
||||
{
|
||||
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
|
||||
{
|
||||
<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)
|
||||
{
|
||||
<button type="button" class="@Class" disabled>@((MarkupString)_openIconSpan) @_openText</button>
|
||||
<button type="button" title="@AltText" class="@Class" disabled>@((MarkupString)_openIconSpan) @_openText</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<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" />
|
||||
<button type="submit" class="@Class">@((MarkupString)_openIconSpan) @_openText</button>
|
||||
<button type="submit" title="@AltText" class="@Class">@((MarkupString)_openIconSpan) @_openText</button>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
@@ -112,6 +112,9 @@ else
|
||||
|
||||
[Parameter]
|
||||
public string Text { get; set; } // optional - defaults to Action if not specified
|
||||
|
||||
[Parameter]
|
||||
public string AltText { get; set; } // optional
|
||||
|
||||
[Parameter]
|
||||
public string Action { get; set; } // optional
|
||||
|
||||
@@ -8,17 +8,17 @@
|
||||
{
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
<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]
|
||||
public string Text { get; set; } // optional - defaults to Action if not specified
|
||||
|
||||
[Parameter]
|
||||
public string AltText { get; set; } // optional
|
||||
|
||||
[Parameter]
|
||||
public int ModuleId { get; set; } = -1; // optional - allows the link to target a specific moduleid
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
|
||||
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>";
|
||||
@@ -69,7 +69,7 @@
|
||||
|
||||
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>";
|
||||
@@ -86,7 +86,7 @@
|
||||
|
||||
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>";
|
||||
|
||||
@@ -61,6 +61,12 @@
|
||||
{
|
||||
<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 class="col-auto">
|
||||
<button type="button" class="btn btn-success" @onclick="UploadFiles">@SharedLocalizer["Upload"]</button>
|
||||
@@ -107,7 +113,7 @@
|
||||
|
||||
@code {
|
||||
private bool _initialized = false;
|
||||
private List<Folder> _folders;
|
||||
private List<Folder> _folders = new List<Folder>();
|
||||
private List<File> _files = new List<File>();
|
||||
private string _fileinputid = string.Empty;
|
||||
private string _progressinfoid = string.Empty;
|
||||
@@ -157,9 +163,15 @@
|
||||
[Parameter]
|
||||
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]
|
||||
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]
|
||||
public EventCallback<int> OnUpload { get; set; } // optional - executes a method in the calling component when a file is uploaded
|
||||
|
||||
@@ -195,19 +207,22 @@
|
||||
Filter = "nupkg";
|
||||
ShowSuccess = true;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(Folder) && Folder != Constants.PackagesFolder)
|
||||
else
|
||||
{
|
||||
Folder folder = await FolderService.GetFolderAsync(ModuleState.SiteId, Folder);
|
||||
if (folder != null)
|
||||
// folder path specified rather than folderid
|
||||
if (!string.IsNullOrEmpty(Folder))
|
||||
{
|
||||
FolderId = folder.FolderId;
|
||||
}
|
||||
else
|
||||
{
|
||||
FolderId = -1;
|
||||
_message = "Folder Path " + Folder + " Does Not Exist";
|
||||
_messagetype = MessageType.Error;
|
||||
Folder folder = await FolderService.GetFolderAsync(ModuleState.SiteId, Folder);
|
||||
if (folder != null)
|
||||
{
|
||||
FolderId = folder.FolderId;
|
||||
}
|
||||
else
|
||||
{
|
||||
FolderId = -1;
|
||||
_message = "Folder Path " + Folder + " Does Not Exist";
|
||||
_messagetype = MessageType.Error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,25 +257,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
await SetImage();
|
||||
|
||||
if (!string.IsNullOrEmpty(Filter))
|
||||
{
|
||||
_filter = "." + Filter.Replace(",", ",.");
|
||||
}
|
||||
|
||||
GetFolderPermission();
|
||||
await SetImage();
|
||||
await GetFiles();
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
private async Task GetFiles()
|
||||
private void GetFolderPermission()
|
||||
{
|
||||
_haseditpermission = false;
|
||||
if (Folder == Constants.PackagesFolder)
|
||||
{
|
||||
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, RoleNames.Host);
|
||||
_files = new List<File>();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -268,69 +282,14 @@
|
||||
if (folder != null)
|
||||
{
|
||||
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, folder.PermissionList);
|
||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Browse, folder.PermissionList))
|
||||
{
|
||||
_files = await FileService.GetFilesAsync(FolderId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_files = new List<File>();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_haseditpermission = false;
|
||||
_files = new List<File>();
|
||||
}
|
||||
if (_filter != "*")
|
||||
{
|
||||
List<File> filtered = new List<File>();
|
||||
foreach (File file in _files)
|
||||
{
|
||||
if (_filter.ToUpper().IndexOf("." + file.Extension.ToUpper()) != -1)
|
||||
{
|
||||
filtered.Add(file);
|
||||
}
|
||||
}
|
||||
_files = filtered;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task FolderChanged(ChangeEventArgs e)
|
||||
{
|
||||
_message = string.Empty;
|
||||
try
|
||||
{
|
||||
FolderId = int.Parse((string)e.Value);
|
||||
await GetFiles();
|
||||
FileId = -1;
|
||||
_file = null;
|
||||
_image = string.Empty;
|
||||
|
||||
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()
|
||||
{
|
||||
_image = string.Empty;
|
||||
@@ -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()
|
||||
{
|
||||
_message = string.Empty;
|
||||
@@ -363,16 +390,39 @@
|
||||
if (uploads.Length > 0)
|
||||
{
|
||||
string restricted = "";
|
||||
string tooLarge = "";
|
||||
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) : "";
|
||||
if (!PageState.Site.UploadableFiles.Split(',').Contains(extension.ToLower()))
|
||||
{
|
||||
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();
|
||||
|
||||
@@ -408,7 +458,7 @@
|
||||
}
|
||||
|
||||
// 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
|
||||
if (ShowProgress)
|
||||
@@ -430,6 +480,28 @@
|
||||
_message = Localizer["Success.File.Upload"];
|
||||
_messagetype = MessageType.Success;
|
||||
}
|
||||
|
||||
FileId = -1;
|
||||
if (Folder != Constants.PackagesFolder && !AnonymizeUploadFilenames)
|
||||
{
|
||||
// set FileId to first file in upload collection
|
||||
var file = await FileService.GetFileAsync(int.Parse(folder), uploads[0].Split(":")[0]);
|
||||
if (file != null)
|
||||
{
|
||||
FileId = file.FileId;
|
||||
}
|
||||
}
|
||||
|
||||
await SetImage();
|
||||
|
||||
await OnUpload.InvokeAsync(FileId);
|
||||
#pragma warning disable CS0618
|
||||
await OnSelect.InvokeAsync(FileId);
|
||||
#pragma warning restore CS0618
|
||||
await OnSelectFile.InvokeAsync(FileId);
|
||||
|
||||
await GetFiles();
|
||||
StateHasChanged();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -437,28 +509,6 @@
|
||||
_message = Localizer["Error.File.Upload"];
|
||||
_messagetype = MessageType.Error;
|
||||
}
|
||||
|
||||
if (Folder == Constants.PackagesFolder)
|
||||
{
|
||||
await OnUpload.InvokeAsync(-1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// set FileId to first file in upload collection
|
||||
var file = await FileService.GetFileAsync(int.Parse(folder), uploads[0].Split(":")[0]);
|
||||
if (file != null)
|
||||
{
|
||||
FileId = file.FileId;
|
||||
await SetImage();
|
||||
#pragma warning disable CS0618
|
||||
await OnSelect.InvokeAsync(FileId);
|
||||
#pragma warning restore CS0618
|
||||
await OnSelectFile.InvokeAsync(FileId);
|
||||
await OnUpload.InvokeAsync(FileId);
|
||||
}
|
||||
await GetFiles();
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -471,12 +521,6 @@
|
||||
finally {
|
||||
tokenSource.Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
_message = string.Format(Localizer["Message.File.Restricted"], restricted);
|
||||
_messagetype = MessageType.Warning;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -501,47 +545,50 @@
|
||||
_messagetype = MessageType.Success;
|
||||
}
|
||||
|
||||
await GetFiles();
|
||||
FileId = -1;
|
||||
await SetImage();
|
||||
FileId = -1;
|
||||
await SetImage();
|
||||
|
||||
#pragma warning disable CS0618
|
||||
await OnSelect.InvokeAsync(FileId);
|
||||
#pragma warning restore CS0618
|
||||
await OnSelectFile.InvokeAsync(FileId);
|
||||
|
||||
await GetFiles();
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Deleting File {File} {Error}", FileId, ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Deleting File {File} {Error}", FileId, ex.Message);
|
||||
|
||||
_message = Localizer["Error.File.Delete"];
|
||||
_messagetype = MessageType.Error;
|
||||
}
|
||||
}
|
||||
_message = Localizer["Error.File.Delete"];
|
||||
_messagetype = MessageType.Error;
|
||||
}
|
||||
}
|
||||
|
||||
public int GetFileId() => FileId;
|
||||
public int GetFileId() => FileId;
|
||||
|
||||
public int GetFolderId() => FolderId;
|
||||
public int GetFolderId() => FolderId;
|
||||
|
||||
public File GetFile() => _file;
|
||||
public File GetFile() => _file;
|
||||
|
||||
public async Task Refresh()
|
||||
{
|
||||
await Refresh(-1);
|
||||
}
|
||||
public async Task Refresh()
|
||||
{
|
||||
await Refresh(-1);
|
||||
}
|
||||
|
||||
public async Task Refresh(int fileId)
|
||||
{
|
||||
await GetFiles();
|
||||
if (fileId != -1)
|
||||
public async Task Refresh(int fileId)
|
||||
{
|
||||
await GetFiles();
|
||||
FileId = -1;
|
||||
if (fileId != -1)
|
||||
{
|
||||
var file = _files.Where(item => item.FileId == fileId).FirstOrDefault();
|
||||
if (file != null)
|
||||
{
|
||||
FileId = file.FileId;
|
||||
await SetImage();
|
||||
}
|
||||
}
|
||||
StateHasChanged();
|
||||
await SetImage();
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,29 +4,43 @@
|
||||
|
||||
@if (!string.IsNullOrEmpty(Message))
|
||||
{
|
||||
<div class="@_classname alert-dismissible fade show mb-3" role="alert">
|
||||
@((MarkupString)Message)
|
||||
@if (Type == MessageType.Error && PageState != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
<NavLink class="ms-2" href="@NavigateUrl("admin/log")">View Details</NavLink>
|
||||
}
|
||||
@if (ModuleState != null)
|
||||
{
|
||||
@if (ModuleState.RenderMode == RenderModes.Static)
|
||||
@if (_style == MessageStyle.Alert)
|
||||
{
|
||||
<div class="@_classname alert-dismissible fade show mb-3" role="alert">
|
||||
@((MarkupString)Message)
|
||||
@if (Type == MessageType.Error && PageState != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
<a href="@NavigationManager.Uri" class="btn-close" data-dismiss="alert" aria-label="close"></a>
|
||||
<NavLink class="ms-2" href="@NavigateUrl("admin/log")">View Details</NavLink>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="btn-close" data-dismiss="alert" aria-label="close" @onclick="CloseMessage"></button>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<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>
|
||||
}
|
||||
|
||||
@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 {
|
||||
private string _message = string.Empty;
|
||||
private string _classname = string.Empty;
|
||||
private MessageStyle _style;
|
||||
|
||||
[Parameter]
|
||||
public string Message { get; set; }
|
||||
@@ -34,6 +48,9 @@
|
||||
[Parameter]
|
||||
public MessageType Type { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public MessageStyle Style { get; set; } = MessageStyle.Alert;
|
||||
|
||||
[Parameter]
|
||||
public RenderModeBoundary Parent { get; set; }
|
||||
|
||||
@@ -43,6 +60,11 @@
|
||||
if (!string.IsNullOrEmpty(_message))
|
||||
{
|
||||
_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;
|
||||
}
|
||||
private void CloseMessage(MouseEventArgs e)
|
||||
|
||||
private void CloseMessage()
|
||||
{
|
||||
if(Parent != null)
|
||||
if (Parent != null)
|
||||
{
|
||||
Parent.DismissMessage();
|
||||
}
|
||||
|
||||
@@ -9,62 +9,26 @@
|
||||
|
||||
@if (_permissions != null)
|
||||
{
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<table class="table table-borderless">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="col">@Localizer["Role"]</th>
|
||||
@foreach (var permissionname in _permissionnames)
|
||||
{
|
||||
<th style="text-align: center; width: 1px;">@((MarkupString)DisplayPermissionName(permissionname).Replace(" ", "<br />"))</th>
|
||||
}
|
||||
</tr>
|
||||
@foreach (Role role in _roles)
|
||||
{
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<table class="table table-borderless">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>@role.Name</td>
|
||||
<th scope="col">@Localizer["Role"]</th>
|
||||
@foreach (var permissionname in _permissionnames)
|
||||
{
|
||||
<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))" />
|
||||
</td>
|
||||
<th style="text-align: center; width: 1px;">@((MarkupString)DisplayPermissionName(permissionname).Replace(" ", "<br />"))</th>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
@if (_users.Count != 0)
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-borderless">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">@Localizer["User"]</th>
|
||||
@foreach (var permissionname in _permissionnames)
|
||||
{
|
||||
<th style="text-align: center; width: 1px;">@((MarkupString)DisplayPermissionName(permissionname).Replace(" ", "<br />"))</th>
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (User user in _users)
|
||||
@foreach (Role role in _roles)
|
||||
{
|
||||
<tr>
|
||||
<td>@user.DisplayName (@user.Username)</td>
|
||||
<td>@role.Name</td>
|
||||
@foreach (var permissionname in _permissionnames)
|
||||
{
|
||||
<td style="text-align: center; width: 1px;">
|
||||
<TriStateCheckBox Value=@GetPermissionValue(permissionname, "", user.UserId) Disabled="@GetPermissionDisabled(permissionname, "")" OnChange="@(e => PermissionChanged(e, permissionname, "", user.UserId))" />
|
||||
<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))" />
|
||||
</td>
|
||||
}
|
||||
</tr>
|
||||
@@ -72,200 +36,242 @@
|
||||
</tbody>
|
||||
</table>
|
||||
<br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
@if (_users.Count != 0)
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-borderless">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">@Localizer["User"]</th>
|
||||
@foreach (var permissionname in _permissionnames)
|
||||
{
|
||||
<th style="text-align: center; width: 1px;">@((MarkupString)DisplayPermissionName(permissionname).Replace(" ", "<br />"))</th>
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (User user in _users)
|
||||
{
|
||||
<tr>
|
||||
<td>@user.DisplayName (@user.Username)</td>
|
||||
@foreach (var permissionname in _permissionnames)
|
||||
{
|
||||
<td style="text-align: center; width: 1px;">
|
||||
<TriStateCheckBox Value="@GetPermissionValue(permissionname, "", user.UserId)" Disabled="@GetPermissionDisabled(permissionname, "")" OnChange="@(e => PermissionChanged(e, permissionname, "", user.UserId))" />
|
||||
</td>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-11">
|
||||
<AutoComplete OnSearch="GetUsers" Placeholder="@Localizer["Username.Enter"]" @ref="_user" />
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<button type="button" class="btn btn-primary" @onclick="AddUser">@SharedLocalizer["Add"]</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<ModuleMessage Type="MessageType.Warning" Message="@_message" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-11">
|
||||
<AutoComplete OnSearch="GetUsers" Placeholder="@Localizer["Username.Enter"]" @ref="_user" />
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<button type="button" class="btn btn-primary" @onclick="AddUser">@SharedLocalizer["Add"]</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<ModuleMessage Type="MessageType.Warning" Message="@_message" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
private List<string> _permissionnames;
|
||||
private List<Permission> _permissions;
|
||||
private List<Role> _roles;
|
||||
private List<User> _users = new List<User>();
|
||||
private AutoComplete _user;
|
||||
private string _message = string.Empty;
|
||||
private List<string> _permissionnames;
|
||||
private List<Permission> _permissions;
|
||||
private List<Role> _roles;
|
||||
private List<User> _users = new List<User>();
|
||||
private AutoComplete _user;
|
||||
private string _message = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public string EntityName { get; set; }
|
||||
[Parameter]
|
||||
public string EntityName { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string PermissionNames { get; set; }
|
||||
[Parameter]
|
||||
public string PermissionNames { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Permissions { get; set; } // deprecated - use PermissionList instead
|
||||
[Parameter]
|
||||
public string Permissions { get; set; } // deprecated - use PermissionList instead
|
||||
|
||||
[Parameter]
|
||||
public List<Permission> PermissionList { get; set; }
|
||||
[Parameter]
|
||||
public List<Permission> PermissionList { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Permissions))
|
||||
{
|
||||
PermissionList = JsonSerializer.Deserialize<List<Permission>>(Permissions);
|
||||
}
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Permissions))
|
||||
{
|
||||
PermissionList = JsonSerializer.Deserialize<List<Permission>>(Permissions);
|
||||
}
|
||||
|
||||
_roles = await RoleService.GetRolesAsync(ModuleState.SiteId, true);
|
||||
if (!UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
_roles.RemoveAll(item => item.Name == RoleNames.Host);
|
||||
}
|
||||
_roles = await RoleService.GetRolesAsync(ModuleState.SiteId, true);
|
||||
_roles.RemoveAll(item => item.Name == RoleNames.Host); // remove host role
|
||||
|
||||
// get permission names
|
||||
if (string.IsNullOrEmpty(PermissionNames))
|
||||
{
|
||||
_permissionnames = new List<string>();
|
||||
_permissionnames.Add(Shared.PermissionNames.View);
|
||||
_permissionnames.Add(Shared.PermissionNames.Edit);
|
||||
}
|
||||
else
|
||||
{
|
||||
_permissionnames = PermissionNames.Split(',', StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||
}
|
||||
// get permission names
|
||||
if (string.IsNullOrEmpty(PermissionNames))
|
||||
{
|
||||
_permissionnames = new List<string>();
|
||||
_permissionnames.Add(Shared.PermissionNames.View);
|
||||
_permissionnames.Add(Shared.PermissionNames.Edit);
|
||||
}
|
||||
else
|
||||
{
|
||||
_permissionnames = PermissionNames.Split(',', StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||
}
|
||||
|
||||
// initialize permissions
|
||||
_permissions = new List<Permission>();
|
||||
if (PermissionList != null && PermissionList.Any())
|
||||
{
|
||||
foreach (var permission in PermissionList)
|
||||
{
|
||||
_permissions.Add(permission);
|
||||
if (permission.UserId != null)
|
||||
{
|
||||
if (!_users.Any(item => item.UserId == permission.UserId.Value))
|
||||
{
|
||||
_users.Add(await UserService.GetUserAsync(permission.UserId.Value, ModuleState.SiteId));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (string permissionname in _permissionnames)
|
||||
{
|
||||
// permission names can be in the form of "EntityName:PermissionName:Roles"
|
||||
if (permissionname.Contains(":"))
|
||||
{
|
||||
var segments = permissionname.Split(':');
|
||||
if (segments.Length == 3)
|
||||
{
|
||||
foreach (var role in segments[2].Split(';'))
|
||||
{
|
||||
_permissions.Add(new Permission(ModuleState.SiteId, segments[0], segments[1], role, null, true));
|
||||
}
|
||||
// ensure admin access
|
||||
if (!_permissions.Any(item => item.EntityName == segments[0] && item.PermissionName == segments[1] && item.RoleName == RoleNames.Admin))
|
||||
{
|
||||
_permissions.Add(new Permission(ModuleState.SiteId, segments[0], segments[1], RoleNames.Admin, null, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_permissions.Add(new Permission(ModuleState.SiteId, EntityName, permissionname, RoleNames.Admin, null, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// initialize permissions
|
||||
_permissions = new List<Permission>();
|
||||
if (PermissionList != null && PermissionList.Any())
|
||||
{
|
||||
foreach (var permission in PermissionList)
|
||||
{
|
||||
_permissions.Add(permission);
|
||||
if (permission.UserId != null)
|
||||
{
|
||||
if (!_users.Any(item => item.UserId == permission.UserId.Value))
|
||||
{
|
||||
_users.Add(await UserService.GetUserAsync(permission.UserId.Value, ModuleState.SiteId));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (string permissionname in _permissionnames)
|
||||
{
|
||||
// permission names can be in the form of "EntityName:PermissionName:Roles"
|
||||
if (permissionname.Contains(":"))
|
||||
{
|
||||
var segments = permissionname.Split(':');
|
||||
if (segments.Length == 3)
|
||||
{
|
||||
foreach (var role in segments[2].Split(';'))
|
||||
{
|
||||
_permissions.Add(new Permission(ModuleState.SiteId, segments[0], segments[1], role, null, true));
|
||||
}
|
||||
// ensure admin access
|
||||
if (!_permissions.Any(item => item.EntityName == segments[0] && item.PermissionName == segments[1] && item.RoleName == RoleNames.Admin))
|
||||
{
|
||||
_permissions.Add(new Permission(ModuleState.SiteId, segments[0], segments[1], RoleNames.Admin, null, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_permissions.Add(new Permission(ModuleState.SiteId, EntityName, permissionname, RoleNames.Admin, null, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string GetPermissionName(string permissionName)
|
||||
{
|
||||
return (permissionName.Contains(":")) ? permissionName.Split(':')[1] : permissionName;
|
||||
}
|
||||
private string GetPermissionName(string permissionName)
|
||||
{
|
||||
return (permissionName.Contains(":")) ? permissionName.Split(':')[1] : permissionName;
|
||||
}
|
||||
|
||||
private string GetEntityName(string permissionName)
|
||||
{
|
||||
return (permissionName.Contains(":")) ? permissionName.Split(':')[0] : EntityName;
|
||||
}
|
||||
private string GetEntityName(string permissionName)
|
||||
{
|
||||
return (permissionName.Contains(":")) ? permissionName.Split(':')[0] : EntityName;
|
||||
}
|
||||
|
||||
private string DisplayPermissionName(string permissionName)
|
||||
{
|
||||
var name = Localizer[GetPermissionName(permissionName)].ToString();
|
||||
name += " " + Localizer[GetEntityName(permissionName)].ToString();
|
||||
return name;
|
||||
}
|
||||
private string DisplayPermissionName(string permissionName)
|
||||
{
|
||||
var name = Localizer[GetPermissionName(permissionName)].ToString();
|
||||
name += " " + Localizer[GetEntityName(permissionName)].ToString();
|
||||
return name;
|
||||
}
|
||||
|
||||
private bool? GetPermissionValue(string permissionName, string roleName, int userId)
|
||||
{
|
||||
bool? isauthorized = null;
|
||||
if (roleName != "")
|
||||
{
|
||||
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.RoleName == roleName);
|
||||
if (permission != null)
|
||||
{
|
||||
isauthorized = permission.IsAuthorized;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.UserId == userId);
|
||||
if (permission != null)
|
||||
{
|
||||
isauthorized = permission.IsAuthorized;
|
||||
}
|
||||
}
|
||||
return isauthorized;
|
||||
}
|
||||
private bool? GetPermissionValue(string permissionName, string roleName, int userId)
|
||||
{
|
||||
bool? isauthorized = null;
|
||||
if (roleName != "")
|
||||
{
|
||||
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.RoleName == roleName);
|
||||
if (permission != null)
|
||||
{
|
||||
isauthorized = permission.IsAuthorized;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.UserId == userId);
|
||||
if (permission != null)
|
||||
{
|
||||
isauthorized = permission.IsAuthorized;
|
||||
}
|
||||
}
|
||||
return isauthorized;
|
||||
}
|
||||
|
||||
private bool GetPermissionDisabled(string permissionName, string roleName)
|
||||
{
|
||||
if (roleName == RoleNames.Admin && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GetEntityName(permissionName) != EntityName && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
private bool GetPermissionDisabled(string permissionName, string roleName)
|
||||
{
|
||||
var disabled = false;
|
||||
|
||||
private void PermissionChanged(bool? value, string permissionName, string roleName, int userId)
|
||||
{
|
||||
if (roleName != "")
|
||||
{
|
||||
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.RoleName == roleName);
|
||||
if (permission != null)
|
||||
{
|
||||
_permissions.Remove(permission);
|
||||
}
|
||||
if (value != null)
|
||||
{
|
||||
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionName), GetPermissionName(permissionName), roleName, null, value.Value));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.UserId == userId);
|
||||
if (permission != null)
|
||||
{
|
||||
_permissions.Remove(permission);
|
||||
}
|
||||
if (value != null)
|
||||
{
|
||||
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionName), GetPermissionName(permissionName), null, userId, value.Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
// administrator role permissions can only be changed by a host
|
||||
if (roleName == RoleNames.Admin && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
disabled = true;
|
||||
}
|
||||
|
||||
// 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 != "")
|
||||
{
|
||||
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.RoleName == roleName);
|
||||
if (permission != null)
|
||||
{
|
||||
_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)
|
||||
{
|
||||
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionName), GetPermissionName(permissionName), roleName, null, value.Value));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.UserId == userId);
|
||||
if (permission != null)
|
||||
{
|
||||
_permissions.Remove(permission);
|
||||
}
|
||||
if (value != null)
|
||||
{
|
||||
_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)
|
||||
{
|
||||
@@ -305,29 +311,20 @@
|
||||
|
||||
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))
|
||||
{
|
||||
// remove deny administrators and host users
|
||||
permissions = _permissions.Where(item => !item.IsAuthorized &&
|
||||
(item.RoleName == RoleNames.Admin || item.RoleName == RoleNames.Host)).ToList();
|
||||
foreach (var permission in permissions)
|
||||
// remove host role permissions
|
||||
var permissions = _permissions.Where(item => item.RoleName == RoleNames.Host).ToList();
|
||||
foreach (var permission in permissions)
|
||||
{
|
||||
_permissions.Remove(permission);
|
||||
}
|
||||
// add host role permissions if administrator role is not assigned (to prevent lockout)
|
||||
foreach (var permissionname in _permissionnames)
|
||||
{
|
||||
_permissions.Remove(permission);
|
||||
}
|
||||
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 || item.RoleName == RoleNames.Host)))
|
||||
if (!_permissions.Any(item => item.EntityName == GetEntityName(permissionname) && item.PermissionName == GetPermissionName(permissionname) && item.RoleName == RoleNames.Admin))
|
||||
{
|
||||
_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 IStringLocalizer<RichTextEditor> Localizer
|
||||
|
||||
<div class="row" style="margin-bottom: 50px;">
|
||||
<div class="row" style="@_style">
|
||||
<div class="col">
|
||||
@_textEditorComponent
|
||||
</div>
|
||||
@@ -18,6 +18,8 @@
|
||||
private RenderFragment _textEditorComponent;
|
||||
private ITextEditor _textEditor;
|
||||
|
||||
private string _style = "margin-bottom: 50px;";
|
||||
|
||||
[Parameter]
|
||||
public string Content { get; set; }
|
||||
|
||||
@@ -30,6 +32,9 @@
|
||||
[Parameter]
|
||||
public string Provider { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Style { get; set; } // optional
|
||||
|
||||
[Parameter(CaptureUnmatchedValues = true)]
|
||||
public Dictionary<string, object> AdditionalAttributes { get; set; } = new Dictionary<string, object>();
|
||||
|
||||
@@ -40,6 +45,12 @@
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
|
||||
if (!string.IsNullOrEmpty(Style))
|
||||
{
|
||||
_style = Style;
|
||||
}
|
||||
|
||||
_textEditorComponent = (builder) =>
|
||||
{
|
||||
CreateTextEditor(builder);
|
||||
|
||||
@@ -30,6 +30,12 @@ else
|
||||
[Parameter]
|
||||
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()
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
case null: // security not specified - assume SecurityAccessLevel.Anonymous
|
||||
authorized = true;
|
||||
break;
|
||||
case SecurityAccessLevel.Anonymous:
|
||||
authorized = true;
|
||||
break;
|
||||
@@ -101,13 +150,23 @@
|
||||
case SecurityAccessLevel.Edit:
|
||||
authorized = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, ModuleState.PermissionList);
|
||||
break;
|
||||
case SecurityAccessLevel.Admin:
|
||||
authorized = UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin);
|
||||
break;
|
||||
case SecurityAccessLevel.Host:
|
||||
authorized = UserSecurity.IsAuthorized(PageState.User, RoleNames.Host);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user