Compare commits
620 Commits
v6.2.1
...
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 | ||
|
|
026d716ece | ||
| 3f90653894 | |||
| ea056165ca | |||
|
|
a85ae69ed1 | ||
|
|
33f525dbda | ||
|
|
4ba7e034b7 | ||
|
|
74a5fb656e | ||
|
|
bc5ce74925 | ||
|
|
338c652635 | ||
|
|
4834761f64 | ||
|
|
d0ef5d0fe3 | ||
|
|
7d9b102ec4 | ||
|
|
5b2dff254f | ||
|
|
986c9d9f72 | ||
|
|
4db37059cd | ||
|
|
378c68be4b | ||
|
|
ebca580a0b | ||
|
|
413df647d3 | ||
|
|
a1011ed709 | ||
|
|
71be6d4ded | ||
|
|
f586401a14 | ||
| 7a9941fe66 | |||
| b3b39f583a | |||
| 92c554e854 | |||
|
|
fa384cb6f3 | ||
|
|
05db1bcbfb | ||
|
|
bdd6d9781c | ||
|
|
d23a5ad91b | ||
|
|
a50e179744 | ||
|
|
874d9f32a9 | ||
| 391827222e | |||
| c1721bd1a1 | |||
| f6630ae241 | |||
| 424cab64a8 |
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
|
||||
@@ -1,8 +1,8 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Version>6.2.1</Version>
|
||||
<Version>10.1.2</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@@ -10,7 +10,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.1</PackageReleaseNotes>
|
||||
<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>
|
||||
|
||||
27
Dockerfile
Normal file
27
Dockerfile
Normal file
@@ -0,0 +1,27 @@
|
||||
# Build
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
|
||||
|
||||
WORKDIR /source
|
||||
|
||||
COPY --link . .
|
||||
|
||||
RUN dotnet restore /source/Oqtane.sln
|
||||
|
||||
RUN dotnet build "/source/Oqtane.sln" -c Release -o /source/build/
|
||||
|
||||
# Publish
|
||||
FROM build AS publish
|
||||
|
||||
RUN dotnet publish "Oqtane.Server/Oqtane.Server.csproj" -c Release -o /source/publish/
|
||||
|
||||
# Deploy
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS deploy
|
||||
|
||||
WORKDIR /codefiles
|
||||
|
||||
COPY --from=publish /source/publish/ /codefiles/
|
||||
|
||||
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
|
||||
|
||||
@@ -18,11 +18,6 @@
|
||||
},
|
||||
"sourceName": "Oqtane.Application",
|
||||
"preferNameDirectory": true,
|
||||
"guids": [
|
||||
"04B05448-788F-433D-92C0-FED35122D45A",
|
||||
"AA8E58A1-CD09-4208-BF66-A8BB341FD669",
|
||||
"18D73F73-D7BE-4388-85BA-FBD9AC96FCA2"
|
||||
],
|
||||
"symbols": {
|
||||
"Framework": {
|
||||
"type": "parameter",
|
||||
@@ -30,12 +25,12 @@
|
||||
"datatype": "choice",
|
||||
"choices": [
|
||||
{
|
||||
"choice": "net9.0",
|
||||
"description": "Target net9.0"
|
||||
"choice": "net10.0",
|
||||
"description": "Target net10.0"
|
||||
}
|
||||
],
|
||||
"replaces": "net9.0",
|
||||
"defaultValue": "net9.0"
|
||||
"replaces": "net10.0",
|
||||
"defaultValue": "net10.0"
|
||||
},
|
||||
"HttpPort": {
|
||||
"type": "parameter",
|
||||
@@ -80,7 +75,7 @@
|
||||
},
|
||||
"primaryOutputs": [
|
||||
{
|
||||
"path": "Oqtane.Application.sln"
|
||||
"path": "Oqtane.Application.slnx"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Modules;
|
||||
|
||||
namespace Oqtane.Application.MyModule
|
||||
{
|
||||
public class ModuleInfo : IModule
|
||||
{
|
||||
public ModuleDefinition ModuleDefinition => new ModuleDefinition
|
||||
{
|
||||
Name = "MyModule",
|
||||
Description = "Example module",
|
||||
Version = "1.0.0",
|
||||
ServerManagerType = "Oqtane.Application.Manager.MyModuleManager, Oqtane.Application.Server.Oqtane",
|
||||
ReleaseVersions = "1.0.0",
|
||||
Dependencies = "Oqtane.Application.Shared.Oqtane",
|
||||
PackageName = "Oqtane.Application"
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<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="9.0.9" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.9" />
|
||||
<PackageReference Include="System.Net.Http.Json" Version="9.0.9" />
|
||||
<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>
|
||||
@@ -24,7 +23,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Oqtane.Client" Version="6.2.1" />
|
||||
<PackageReference Include="Oqtane.Client" Version="10.1.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Name.Text" xml:space="preserve">
|
||||
<value>Name: </value>
|
||||
</data>
|
||||
<data name="Name.HelpText" xml:space="preserve">
|
||||
<value>Enter the name</value>
|
||||
</data>
|
||||
<data name="Save" xml:space="preserve">
|
||||
<value>Save</value>
|
||||
</data>
|
||||
<data name="Cancel" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
<data name="Message.SaveValidation" xml:space="preserve">
|
||||
<value>Please Provide All Required Information</value>
|
||||
</data>
|
||||
<data name="Message.SaveError" xml:space="preserve">
|
||||
<value>Error Saving MyModule</value>
|
||||
</data>
|
||||
<data name="Message.LoadError" xml:space="preserve">
|
||||
<value>Error Loading MyModule</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1,55 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Oqtane.Services;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace Oqtane.Application.Services
|
||||
{
|
||||
public interface IMyModuleService
|
||||
{
|
||||
Task<List<Models.MyModule>> GetMyModulesAsync(int ModuleId);
|
||||
|
||||
Task<Models.MyModule> GetMyModuleAsync(int MyModuleId, int ModuleId);
|
||||
|
||||
Task<Models.MyModule> AddMyModuleAsync(Models.MyModule MyModule);
|
||||
|
||||
Task<Models.MyModule> UpdateMyModuleAsync(Models.MyModule MyModule);
|
||||
|
||||
Task DeleteMyModuleAsync(int MyModuleId, int ModuleId);
|
||||
}
|
||||
|
||||
public class MyModuleService : ServiceBase, IMyModuleService
|
||||
{
|
||||
public MyModuleService(HttpClient http, SiteState siteState) : base(http, siteState) { }
|
||||
|
||||
private string Apiurl => CreateApiUrl("MyModule");
|
||||
|
||||
public async Task<List<Models.MyModule>> GetMyModulesAsync(int ModuleId)
|
||||
{
|
||||
List<Models.MyModule> Tasks = await GetJsonAsync<List<Models.MyModule>>(CreateAuthorizationPolicyUrl($"{Apiurl}?moduleid={ModuleId}", EntityNames.Module, ModuleId), Enumerable.Empty<Models.MyModule>().ToList());
|
||||
return Tasks.OrderBy(item => item.Name).ToList();
|
||||
}
|
||||
|
||||
public async Task<Models.MyModule> GetMyModuleAsync(int MyModuleId, int ModuleId)
|
||||
{
|
||||
return await GetJsonAsync<Models.MyModule>(CreateAuthorizationPolicyUrl($"{Apiurl}/{MyModuleId}/{ModuleId}", EntityNames.Module, ModuleId));
|
||||
}
|
||||
|
||||
public async Task<Models.MyModule> AddMyModuleAsync(Models.MyModule MyModule)
|
||||
{
|
||||
return await PostJsonAsync<Models.MyModule>(CreateAuthorizationPolicyUrl($"{Apiurl}", EntityNames.Module, MyModule.ModuleId), MyModule);
|
||||
}
|
||||
|
||||
public async Task<Models.MyModule> UpdateMyModuleAsync(Models.MyModule MyModule)
|
||||
{
|
||||
return await PutJsonAsync<Models.MyModule>(CreateAuthorizationPolicyUrl($"{Apiurl}/{MyModule.MyModuleId}", EntityNames.Module, MyModule.ModuleId), MyModule);
|
||||
}
|
||||
|
||||
public async Task DeleteMyModuleAsync(int MyModuleId, int ModuleId)
|
||||
{
|
||||
await DeleteAsync(CreateAuthorizationPolicyUrl($"{Apiurl}/{MyModuleId}/{ModuleId}", EntityNames.Module, ModuleId));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using Microsoft.Extensions.Localization
|
||||
@using Microsoft.JSInterop
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
|
||||
@using Oqtane
|
||||
@using Oqtane.Models
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Application.Template</id>
|
||||
<version>6.2.1</version>
|
||||
<version>10.1.2</version>
|
||||
<title>Oqtane Application Template For Blazor</title>
|
||||
<authors>Shaun Walker</authors>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.12.35506.116 d17.12
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Application.Server", "Server\Oqtane.Application.Server.csproj", "{04B05448-788F-433D-92C0-FED35122D45A}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Application.Client", "Client\Oqtane.Application.Client.csproj", "{AA8E58A1-CD09-4208-BF66-A8BB341FD669}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Application.Shared", "Shared\Oqtane.Application.Shared.csproj", "{18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{04B05448-788F-433D-92C0-FED35122D45A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{04B05448-788F-433D-92C0-FED35122D45A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{04B05448-788F-433D-92C0-FED35122D45A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{04B05448-788F-433D-92C0-FED35122D45A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
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>
|
||||
@@ -1,87 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using Oqtane.Modules;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Interfaces;
|
||||
using Oqtane.Enums;
|
||||
using Oqtane.Repository;
|
||||
using Oqtane.Application.Repository;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Oqtane.Application.Manager
|
||||
{
|
||||
public class MyModuleManager : MigratableModuleBase, IInstallable, IPortable, ISearchable
|
||||
{
|
||||
private readonly IMyModuleRepository _MyModuleRepository;
|
||||
private readonly IDBContextDependencies _DBContextDependencies;
|
||||
|
||||
public MyModuleManager(IMyModuleRepository MyModuleRepository, IDBContextDependencies DBContextDependencies)
|
||||
{
|
||||
_MyModuleRepository = MyModuleRepository;
|
||||
_DBContextDependencies = DBContextDependencies;
|
||||
}
|
||||
|
||||
public bool Install(Tenant tenant, string version)
|
||||
{
|
||||
return Migrate(new Context(_DBContextDependencies), tenant, MigrationType.Up);
|
||||
}
|
||||
|
||||
public bool Uninstall(Tenant tenant)
|
||||
{
|
||||
return Migrate(new Context(_DBContextDependencies), tenant, MigrationType.Down);
|
||||
}
|
||||
|
||||
public string ExportModule(Module module)
|
||||
{
|
||||
string content = "";
|
||||
List<Models.MyModule> MyModules = _MyModuleRepository.GetMyModules(module.ModuleId).ToList();
|
||||
if (MyModules != null)
|
||||
{
|
||||
content = JsonSerializer.Serialize(MyModules);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
public void ImportModule(Module module, string content, string version)
|
||||
{
|
||||
List<Models.MyModule> MyModules = null;
|
||||
if (!string.IsNullOrEmpty(content))
|
||||
{
|
||||
MyModules = JsonSerializer.Deserialize<List<Models.MyModule>>(content);
|
||||
}
|
||||
if (MyModules != null)
|
||||
{
|
||||
foreach(var Task in MyModules)
|
||||
{
|
||||
_MyModuleRepository.AddMyModule(new Models.MyModule { ModuleId = module.ModuleId, Name = Task.Name });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task<List<SearchContent>> GetSearchContentsAsync(PageModule pageModule, DateTime lastIndexedOn)
|
||||
{
|
||||
var searchContentList = new List<SearchContent>();
|
||||
|
||||
foreach (var MyModule in _MyModuleRepository.GetMyModules(pageModule.ModuleId))
|
||||
{
|
||||
if (MyModule.ModifiedOn >= lastIndexedOn)
|
||||
{
|
||||
searchContentList.Add(new SearchContent
|
||||
{
|
||||
EntityName = "MyModule",
|
||||
EntityId = MyModule.MyModuleId.ToString(),
|
||||
Title = MyModule.Name,
|
||||
Body = MyModule.Name,
|
||||
ContentModifiedBy = MyModule.ModifiedBy,
|
||||
ContentModifiedOn = MyModule.ModifiedOn
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult(searchContentList);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Oqtane.Databases.Interfaces;
|
||||
using Oqtane.Migrations;
|
||||
using Oqtane.Application.Migrations.EntityBuilders;
|
||||
using Oqtane.Application.Repository;
|
||||
|
||||
namespace Oqtane.Application.Migrations
|
||||
{
|
||||
[DbContext(typeof(Context))]
|
||||
[Migration("Oqtane.Application.01.00.00.00")]
|
||||
public class InitializeModule : MultiDatabaseMigration
|
||||
{
|
||||
public InitializeModule(IDatabase database) : base(database)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var myModuleEntityBuilder = new MyModuleEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
myModuleEntityBuilder.Create();
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var myModuleEntityBuilder = new MyModuleEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
myModuleEntityBuilder.Drop();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<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>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.9" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.9" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.9" />
|
||||
<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>
|
||||
@@ -23,7 +33,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Oqtane.Server" Version="6.2.1" />
|
||||
<PackageReference Include="Oqtane.Server" Version="10.1.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore;
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Cors.Infrastructure;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Oqtane.Infrastructure;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Oqtane.Extensions;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace Oqtane.Application.Server
|
||||
{
|
||||
@@ -11,32 +15,41 @@ namespace Oqtane.Application.Server
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
// defer server startup to Oqtane - do not modify
|
||||
var host = BuildWebHost(args);
|
||||
var databaseManager = host.Services.GetService<IDatabaseManager>();
|
||||
var 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 = host.Services.GetRequiredService<ILogger<Program>>();
|
||||
var filelogger = app.Services.GetRequiredService<ILogger<Program>>();
|
||||
if (filelogger != null)
|
||||
{
|
||||
filelogger.LogError($"[Oqtane.Application.Server.Program.Main] {install.Message}");
|
||||
filelogger.LogError($"[Oqtane.Server.Program.Main] {install.Message}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
host.Run();
|
||||
app.Run();
|
||||
}
|
||||
}
|
||||
|
||||
public static IWebHost BuildWebHost(string[] args) =>
|
||||
WebHost.CreateDefaultBuilder(args)
|
||||
.UseConfiguration(new ConfigurationBuilder()
|
||||
.AddCommandLine(args)
|
||||
.AddEnvironmentVariables()
|
||||
.Build())
|
||||
.UseStartup<Startup>()
|
||||
.ConfigureLocalizationSettings()
|
||||
.Build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Oqtane.Modules;
|
||||
using Oqtane.Repository;
|
||||
using Oqtane.Repository.Databases.Interfaces;
|
||||
|
||||
namespace Oqtane.Application.Repository
|
||||
{
|
||||
public class Context : DBContextBase, ITransientService, IMultiDatabase
|
||||
{
|
||||
public virtual DbSet<Models.MyModule> MyModule { get; set; }
|
||||
|
||||
public Context(IDBContextDependencies DBContextDependencies) : base(DBContextDependencies)
|
||||
{
|
||||
// ContextBase handles multi-tenant database connections
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
base.OnModelCreating(builder);
|
||||
|
||||
builder.Entity<Models.MyModule>().ToTable(ActiveDatabase.RewriteName("MyModule"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Oqtane.Modules;
|
||||
|
||||
namespace Oqtane.Application.Repository
|
||||
{
|
||||
public interface IMyModuleRepository
|
||||
{
|
||||
IEnumerable<Models.MyModule> GetMyModules(int ModuleId);
|
||||
Models.MyModule GetMyModule(int MyModuleId);
|
||||
Models.MyModule GetMyModule(int MyModuleId, bool tracking);
|
||||
Models.MyModule AddMyModule(Models.MyModule MyModule);
|
||||
Models.MyModule UpdateMyModule(Models.MyModule MyModule);
|
||||
void DeleteMyModule(int MyModuleId);
|
||||
}
|
||||
|
||||
public class MyModuleRepository : IMyModuleRepository, ITransientService
|
||||
{
|
||||
private readonly IDbContextFactory<Context> _factory;
|
||||
|
||||
public MyModuleRepository(IDbContextFactory<Context> factory)
|
||||
{
|
||||
_factory = factory;
|
||||
}
|
||||
|
||||
public IEnumerable<Models.MyModule> GetMyModules(int ModuleId)
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
return db.MyModule.Where(item => item.ModuleId == ModuleId).ToList();
|
||||
}
|
||||
|
||||
public Models.MyModule GetMyModule(int MyModuleId)
|
||||
{
|
||||
return GetMyModule(MyModuleId, true);
|
||||
}
|
||||
|
||||
public Models.MyModule GetMyModule(int MyModuleId, bool tracking)
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
if (tracking)
|
||||
{
|
||||
return db.MyModule.Find(MyModuleId);
|
||||
}
|
||||
else
|
||||
{
|
||||
return db.MyModule.AsNoTracking().FirstOrDefault(item => item.MyModuleId == MyModuleId);
|
||||
}
|
||||
}
|
||||
|
||||
public Models.MyModule AddMyModule(Models.MyModule MyModule)
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
db.MyModule.Add(MyModule);
|
||||
db.SaveChanges();
|
||||
return MyModule;
|
||||
}
|
||||
|
||||
public Models.MyModule UpdateMyModule(Models.MyModule MyModule)
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
db.Entry(MyModule).State = EntityState.Modified;
|
||||
db.SaveChanges();
|
||||
return MyModule;
|
||||
}
|
||||
|
||||
public void DeleteMyModule(int MyModuleId)
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
Models.MyModule MyModule = db.MyModule.Find(MyModuleId);
|
||||
db.MyModule.Remove(MyModule);
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Oqtane.Extensions;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Shared;
|
||||
using Microsoft.AspNetCore.Cors.Infrastructure;
|
||||
|
||||
namespace Oqtane.Application.Server
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
private readonly IConfigurationRoot _configuration;
|
||||
private readonly IWebHostEnvironment _environment;
|
||||
|
||||
public Startup(IWebHostEnvironment environment)
|
||||
{
|
||||
AppDomain.CurrentDomain.SetData(Constants.DataDirectory, Path.Combine(environment.ContentRootPath, "Data"));
|
||||
|
||||
var builder = new ConfigurationBuilder()
|
||||
.SetBasePath(environment.ContentRootPath)
|
||||
.AddJsonFile("appsettings.json", false, true)
|
||||
.AddJsonFile($"appsettings.{environment.EnvironmentName}.json", true, true)
|
||||
.AddEnvironmentVariables();
|
||||
|
||||
_configuration = builder.Build();
|
||||
_environment = environment;
|
||||
}
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
// defer server startup to Oqtane - do not modify
|
||||
services.AddOqtane(_configuration, _environment);
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IConfigurationRoot configuration, IWebHostEnvironment environment, ICorsService corsService, ICorsPolicyProvider corsPolicyProvider, ISyncManager sync)
|
||||
{
|
||||
// defer server startup to Oqtane - do not modify
|
||||
app.UseOqtane(configuration, environment, corsService, corsPolicyProvider, sync);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
/* Module Script */
|
||||
var App = App || {};
|
||||
|
||||
App.MyModule = {
|
||||
};
|
||||
@@ -1,10 +1,10 @@
|
||||
@using Oqtane.Modules.Controls
|
||||
@using Oqtane.Application.Services
|
||||
@using Oqtane.Application.Models
|
||||
@using [Owner].Module.[Module].Services
|
||||
@using [Owner].Module.[Module].Models
|
||||
|
||||
@namespace Oqtane.Application.MyModule
|
||||
@namespace [Owner].Module.[Module]
|
||||
@inherits ModuleBase
|
||||
@inject IMyModuleService MyModuleService
|
||||
@inject I[Module]Service Client[Module]Service
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IStringLocalizer<Edit> Localizer
|
||||
|
||||
@@ -31,12 +31,7 @@
|
||||
|
||||
public override string Actions => "Add,Edit";
|
||||
|
||||
public override string Title => "Manage MyModule";
|
||||
|
||||
public override List<Resource> Resources => new List<Resource>()
|
||||
{
|
||||
new Stylesheet(ModulePath() + "Module.css")
|
||||
};
|
||||
public override string Title => "Manage [Module]";
|
||||
|
||||
private ElementReference form;
|
||||
private bool validated = false;
|
||||
@@ -55,20 +50,20 @@
|
||||
if (PageState.Action == "Edit")
|
||||
{
|
||||
_id = Int32.Parse(PageState.QueryString["id"]);
|
||||
MyModule MyModule = await MyModuleService.GetMyModuleAsync(_id, ModuleState.ModuleId);
|
||||
if (MyModule != null)
|
||||
[Module] [Module] = await Client[Module]Service.Get[Module]Async(_id, ModuleState.ModuleId);
|
||||
if ([Module] != null)
|
||||
{
|
||||
_name = MyModule.Name;
|
||||
_createdby = MyModule.CreatedBy;
|
||||
_createdon = MyModule.CreatedOn;
|
||||
_modifiedby = MyModule.ModifiedBy;
|
||||
_modifiedon = MyModule.ModifiedOn;
|
||||
_name = [Module].Name;
|
||||
_createdby = [Module].CreatedBy;
|
||||
_createdon = [Module].CreatedOn;
|
||||
_modifiedby = [Module].ModifiedBy;
|
||||
_modifiedon = [Module].ModifiedOn;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading MyModule {MyModuleId} {Error}", _id, ex.Message);
|
||||
await logger.LogError(ex, "Error Loading [Module] {[Module]Id} {Error}", _id, ex.Message);
|
||||
AddModuleMessage(Localizer["Message.LoadError"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
@@ -83,18 +78,18 @@
|
||||
{
|
||||
if (PageState.Action == "Add")
|
||||
{
|
||||
MyModule MyModule = new MyModule();
|
||||
MyModule.ModuleId = ModuleState.ModuleId;
|
||||
MyModule.Name = _name;
|
||||
MyModule = await MyModuleService.AddMyModuleAsync(MyModule);
|
||||
await logger.LogInformation("MyModule Added {MyModule}", MyModule);
|
||||
[Module] [Module] = new [Module]();
|
||||
[Module].ModuleId = ModuleState.ModuleId;
|
||||
[Module].Name = _name;
|
||||
[Module] = await Client[Module]Service.Add[Module]Async([Module]);
|
||||
await logger.LogInformation("[Module] Added {[Module]}", [Module]);
|
||||
}
|
||||
else
|
||||
{
|
||||
MyModule MyModule = await MyModuleService.GetMyModuleAsync(_id, ModuleState.ModuleId);
|
||||
MyModule.Name = _name;
|
||||
await MyModuleService.UpdateMyModuleAsync(MyModule);
|
||||
await logger.LogInformation("MyModule Updated {MyModule}", MyModule);
|
||||
[Module] [Module] = await Client[Module]Service.Get[Module]Async(_id, ModuleState.ModuleId);
|
||||
[Module].Name = _name;
|
||||
await Client[Module]Service.Update[Module]Async([Module]);
|
||||
await logger.LogInformation("[Module] Updated {[Module]}", [Module]);
|
||||
}
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
@@ -105,7 +100,7 @@
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Saving MyModule {Error}", ex.Message);
|
||||
await logger.LogError(ex, "Error Saving [Module] {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Message.SaveError"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,32 @@
|
||||
@using Oqtane.Application.Services
|
||||
@using Oqtane.Application.Models
|
||||
@using [Owner].Module.[Module].Services
|
||||
@using [Owner].Module.[Module].Models
|
||||
|
||||
@namespace Oqtane.Application.MyModule
|
||||
@namespace [Owner].Module.[Module]
|
||||
@inherits ModuleBase
|
||||
@inject IMyModuleService MyModuleService
|
||||
@inject I[Module]Service Client[Module]Service
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
|
||||
@if (_MyModules == null)
|
||||
@if (_[Module]s == null)
|
||||
{
|
||||
<p><em>Loading...</em></p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<ActionLink Action="Add" Security="SecurityAccessLevel.Edit" Text="Add MyModule" ResourceKey="Add" />
|
||||
<ActionLink Action="Add" Security="SecurityAccessLevel.Edit" Text="Add [Module]" ResourceKey="Add" />
|
||||
<br />
|
||||
<br />
|
||||
@if (@_MyModules.Count != 0)
|
||||
@if (@_[Module]s.Count != 0)
|
||||
{
|
||||
<Pager Items="@_MyModules">
|
||||
<Pager Items="@_[Module]s">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@Localizer["Name"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.MyModuleId.ToString())" ResourceKey="Edit" /></td>
|
||||
<td><ActionDialog Header="Delete MyModule" Message="Are You Sure You Wish To Delete This MyModule?" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" ResourceKey="Delete" Id="@context.MyModuleId.ToString()" /></td>
|
||||
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.[Module]Id.ToString())" ResourceKey="Edit" /></td>
|
||||
<td><ActionDialog Header="Delete [Module]" Message="Are You Sure You Wish To Delete This [Module]?" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" ResourceKey="Delete" Id="@context.[Module]Id.ToString()" /></td>
|
||||
<td>@context.Name</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
@@ -38,39 +38,41 @@ else
|
||||
}
|
||||
|
||||
@code {
|
||||
public override string RenderMode => RenderModes.Static;
|
||||
|
||||
public override List<Resource> Resources => new List<Resource>()
|
||||
{
|
||||
new Stylesheet(ModulePath() + "Module.css"),
|
||||
new Script(ModulePath() + "Module.js")
|
||||
};
|
||||
|
||||
List<Models.MyModule> _MyModules;
|
||||
List<[Module]> _[Module]s;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_MyModules = await MyModuleService.GetMyModulesAsync(ModuleState.ModuleId);
|
||||
_[Module]s = await Client[Module]Service.Get[Module]sAsync(ModuleState.ModuleId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading MyModule {Error}", ex.Message);
|
||||
await logger.LogError(ex, "Error Loading [Module] {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Message.LoadError"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Delete(MyModule MyModule)
|
||||
private async Task Delete([Module] [Module])
|
||||
{
|
||||
try
|
||||
{
|
||||
await MyModuleService.DeleteMyModuleAsync(MyModule.MyModuleId, ModuleState.ModuleId);
|
||||
await logger.LogInformation("MyModule Deleted {MyModule}", MyModule);
|
||||
_MyModules = await MyModuleService.GetMyModulesAsync(ModuleState.ModuleId);
|
||||
await Client[Module]Service.Delete[Module]Async([Module].[Module]Id, ModuleState.ModuleId);
|
||||
await logger.LogInformation("[Module] Deleted {[Module]}", [Module]);
|
||||
_[Module]s = await Client[Module]Service.Get[Module]sAsync(ModuleState.ModuleId);
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Deleting MyModule {MyModule} {Error}", MyModule, ex.Message);
|
||||
await logger.LogError(ex, "Error Deleting [Module] {[Module]} {Error}", [Module], ex.Message);
|
||||
AddModuleMessage(Localizer["Message.DeleteError"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using Microsoft.JSInterop;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Oqtane.Application
|
||||
namespace [Owner].Module.[Module]
|
||||
{
|
||||
public class Interop
|
||||
{
|
||||
@@ -0,0 +1,16 @@
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Modules;
|
||||
|
||||
namespace [Owner].Module.[Module]
|
||||
{
|
||||
public class ModuleInfo : IModule
|
||||
{
|
||||
public ModuleDefinition ModuleDefinition => new ModuleDefinition
|
||||
{
|
||||
Name = "[Module]",
|
||||
Description = "[Description]",
|
||||
Version = "1.0.0",
|
||||
ServerManagerType = "[ServerManagerType]"
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
@namespace Oqtane.Application.MyModule
|
||||
@namespace [Owner].Module.[Module]
|
||||
@inherits ModuleBase
|
||||
@implements Oqtane.Interfaces.ISettingsControl
|
||||
@inject ISettingService SettingService
|
||||
@inject IStringLocalizer<Settings> Localizer
|
||||
|
||||
@@ -13,8 +14,8 @@
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private string resourceType = "Oqtane.Application.MyModule.Settings, Oqtane.Application.Client.Oqtane"; // for localization
|
||||
public override string Title => "MyModdule Settings";
|
||||
private string resourceType = "[Owner].Module.[Module].Settings, [Owner].Module.[Module].Client.Oqtane"; // for localization
|
||||
public override string Title => "[Module] Settings";
|
||||
|
||||
string _value;
|
||||
|
||||
@@ -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)
|
||||
@@ -0,0 +1,141 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Name.Text" xml:space="preserve">
|
||||
<value>Name: </value>
|
||||
</data>
|
||||
<data name="Name.HelpText" xml:space="preserve">
|
||||
<value>Enter the name</value>
|
||||
</data>
|
||||
<data name="Save" xml:space="preserve">
|
||||
<value>Save</value>
|
||||
</data>
|
||||
<data name="Cancel" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
<data name="Message.LoadError" xml:space="preserve">
|
||||
<value>Error Loading [Module]</value>
|
||||
</data>
|
||||
<data name="Message.SaveValidation" xml:space="preserve">
|
||||
<value>Please Provide All Required Information</value>
|
||||
</data>
|
||||
<data name="Message.SaveError" xml:space="preserve">
|
||||
<value>Error Saving [Module]</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,147 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Name" xml:space="preserve">
|
||||
<value>Name</value>
|
||||
</data>
|
||||
<data name="Add.Text" xml:space="preserve">
|
||||
<value>Add [Module]</value>
|
||||
</data>
|
||||
<data name="Edit.Text" xml:space="preserve">
|
||||
<value>Edit</value>
|
||||
</data>
|
||||
<data name="Delete.Text" xml:space="preserve">
|
||||
<value>Delete</value>
|
||||
</data>
|
||||
<data name="Delete.Header" xml:space="preserve">
|
||||
<value>Delete [Module]</value>
|
||||
</data>
|
||||
<data name="Delete.Message" xml:space="preserve">
|
||||
<value>Are You Sure You Wish To Delete This [Module]?</value>
|
||||
</data>
|
||||
<data name="Message.DisplayNone" xml:space="preserve">
|
||||
<value>No [Module]s To Display</value>
|
||||
</data>
|
||||
<data name="Message.LoadError" xml:space="preserve">
|
||||
<value>Error Loading [Module]</value>
|
||||
</data>
|
||||
<data name="Message.DeleteError" xml:space="preserve">
|
||||
<value>Error Deleting [Module]</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
@@ -7,22 +7,10 @@ using Oqtane.Shared;
|
||||
|
||||
namespace [Owner].Module.[Module].Services
|
||||
{
|
||||
public interface I[Module]Service
|
||||
|
||||
public class Client[Module]Service : ServiceBase, I[Module]Service
|
||||
{
|
||||
Task<List<Models.[Module]>> Get[Module]sAsync(int ModuleId);
|
||||
|
||||
Task<Models.[Module]> Get[Module]Async(int [Module]Id, int ModuleId);
|
||||
|
||||
Task<Models.[Module]> Add[Module]Async(Models.[Module] [Module]);
|
||||
|
||||
Task<Models.[Module]> Update[Module]Async(Models.[Module] [Module]);
|
||||
|
||||
Task Delete[Module]Async(int [Module]Id, int ModuleId);
|
||||
}
|
||||
|
||||
public class [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,17 +1,17 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Linq;
|
||||
using Oqtane.Services;
|
||||
using Oqtane.Application.Services;
|
||||
using [Owner].Module.[Module].Services;
|
||||
|
||||
namespace Oqtane.Application.Startup
|
||||
namespace [Owner].Module.[Module].Startup
|
||||
{
|
||||
public class ClientStartup : IClientStartup
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
if (!services.Any(s => s.ServiceType == typeof(IMyModuleService)))
|
||||
if (!services.Any(s => s.ServiceType == typeof(I[Module]Service)))
|
||||
{
|
||||
services.AddScoped<IMyModuleService, MyModuleService>();
|
||||
services.AddScoped<I[Module]Service, Client[Module]Service>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,36 +5,36 @@ using Microsoft.AspNetCore.Http;
|
||||
using Oqtane.Shared;
|
||||
using Oqtane.Enums;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Application.Services;
|
||||
using [Owner].Module.[Module].Services;
|
||||
using Oqtane.Controllers;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Oqtane.Application.Controllers
|
||||
namespace [Owner].Module.[Module].Controllers
|
||||
{
|
||||
[Route(ControllerRoutes.ApiRoute)]
|
||||
public class MyModuleController : ModuleControllerBase
|
||||
public class [Module]Controller : ModuleControllerBase
|
||||
{
|
||||
private readonly IMyModuleService _MyModuleService;
|
||||
private readonly I[Module]Service _[Module]Service;
|
||||
|
||||
public MyModuleController(IMyModuleService MyModuleService, ILogManager logger, IHttpContextAccessor accessor) : base(logger, accessor)
|
||||
public [Module]Controller(I[Module]Service [Module]Service, ILogManager logger, IHttpContextAccessor accessor) : base(logger, accessor)
|
||||
{
|
||||
_MyModuleService = MyModuleService;
|
||||
_[Module]Service = [Module]Service;
|
||||
}
|
||||
|
||||
// GET: api/<controller>?moduleid=x
|
||||
[HttpGet]
|
||||
[Authorize(Policy = PolicyNames.ViewModule)]
|
||||
public async Task<IEnumerable<Models.MyModule>> Get(string moduleid)
|
||||
public async Task<IEnumerable<Models.[Module]>> Get(string moduleid)
|
||||
{
|
||||
int ModuleId;
|
||||
if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId))
|
||||
{
|
||||
return await _MyModuleService.GetMyModulesAsync(ModuleId);
|
||||
return await _[Module]Service.Get[Module]sAsync(ModuleId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized MyModule Get Attempt {ModuleId}", moduleid);
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Get Attempt {ModuleId}", moduleid);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
return null;
|
||||
}
|
||||
@@ -43,16 +43,16 @@ namespace Oqtane.Application.Controllers
|
||||
// GET api/<controller>/5
|
||||
[HttpGet("{id}/{moduleid}")]
|
||||
[Authorize(Policy = PolicyNames.ViewModule)]
|
||||
public async Task<Models.MyModule> Get(int id, int moduleid)
|
||||
public async Task<Models.[Module]> Get(int id, int moduleid)
|
||||
{
|
||||
Models.MyModule MyModule = await _MyModuleService.GetMyModuleAsync(id, moduleid);
|
||||
if (MyModule != null && IsAuthorizedEntityId(EntityNames.Module, MyModule.ModuleId))
|
||||
Models.[Module] [Module] = await _[Module]Service.Get[Module]Async(id, moduleid);
|
||||
if ([Module] != null && IsAuthorizedEntityId(EntityNames.Module, [Module].ModuleId))
|
||||
{
|
||||
return MyModule;
|
||||
return [Module];
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized MyModule Get Attempt {MyModuleId} {ModuleId}", id, moduleid);
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Get Attempt {[Module]Id} {ModuleId}", id, moduleid);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
return null;
|
||||
}
|
||||
@@ -61,37 +61,37 @@ namespace Oqtane.Application.Controllers
|
||||
// POST api/<controller>
|
||||
[HttpPost]
|
||||
[Authorize(Policy = PolicyNames.EditModule)]
|
||||
public async Task<Models.MyModule> Post([FromBody] Models.MyModule MyModule)
|
||||
public async Task<Models.[Module]> Post([FromBody] Models.[Module] [Module])
|
||||
{
|
||||
if (ModelState.IsValid && IsAuthorizedEntityId(EntityNames.Module, MyModule.ModuleId))
|
||||
if (ModelState.IsValid && IsAuthorizedEntityId(EntityNames.Module, [Module].ModuleId))
|
||||
{
|
||||
MyModule = await _MyModuleService.AddMyModuleAsync(MyModule);
|
||||
[Module] = await _[Module]Service.Add[Module]Async([Module]);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized MyModule Post Attempt {MyModule}", MyModule);
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Post Attempt {[Module]}", [Module]);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
MyModule = null;
|
||||
[Module] = null;
|
||||
}
|
||||
return MyModule;
|
||||
return [Module];
|
||||
}
|
||||
|
||||
// PUT api/<controller>/5
|
||||
[HttpPut("{id}")]
|
||||
[Authorize(Policy = PolicyNames.EditModule)]
|
||||
public async Task<Models.MyModule> Put(int id, [FromBody] Models.MyModule MyModule)
|
||||
public async Task<Models.[Module]> Put(int id, [FromBody] Models.[Module] [Module])
|
||||
{
|
||||
if (ModelState.IsValid && MyModule.MyModuleId == id && IsAuthorizedEntityId(EntityNames.Module, MyModule.ModuleId))
|
||||
if (ModelState.IsValid && [Module].[Module]Id == id && IsAuthorizedEntityId(EntityNames.Module, [Module].ModuleId))
|
||||
{
|
||||
MyModule = await _MyModuleService.UpdateMyModuleAsync(MyModule);
|
||||
[Module] = await _[Module]Service.Update[Module]Async([Module]);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized MyModule Put Attempt {MyModule}", MyModule);
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Put Attempt {[Module]}", [Module]);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
MyModule = null;
|
||||
[Module] = null;
|
||||
}
|
||||
return MyModule;
|
||||
return [Module];
|
||||
}
|
||||
|
||||
// DELETE api/<controller>/5
|
||||
@@ -99,14 +99,14 @@ namespace Oqtane.Application.Controllers
|
||||
[Authorize(Policy = PolicyNames.EditModule)]
|
||||
public async Task Delete(int id, int moduleid)
|
||||
{
|
||||
Models.MyModule MyModule = await _MyModuleService.GetMyModuleAsync(id, moduleid);
|
||||
if (MyModule != null && IsAuthorizedEntityId(EntityNames.Module, MyModule.ModuleId))
|
||||
Models.[Module] [Module] = await _[Module]Service.Get[Module]Async(id, moduleid);
|
||||
if ([Module] != null && IsAuthorizedEntityId(EntityNames.Module, [Module].ModuleId))
|
||||
{
|
||||
await _MyModuleService.DeleteMyModuleAsync(id, MyModule.ModuleId);
|
||||
await _[Module]Service.Delete[Module]Async(id, [Module].ModuleId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized v Delete Attempt {MyModuleId} {ModuleId}", id, moduleid);
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Delete Attempt {[Module]Id} {ModuleId}", id, moduleid);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using Oqtane.Modules;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Interfaces;
|
||||
using Oqtane.Enums;
|
||||
using Oqtane.Repository;
|
||||
using [Owner].Module.[Module].Repository;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace [Owner].Module.[Module].Manager
|
||||
{
|
||||
public class [Module]Manager : MigratableModuleBase, IInstallable, IPortable, ISearchable
|
||||
{
|
||||
private readonly I[Module]Repository _[Module]Repository;
|
||||
private readonly IDBContextDependencies _DBContextDependencies;
|
||||
|
||||
public [Module]Manager(I[Module]Repository [Module]Repository, IDBContextDependencies DBContextDependencies)
|
||||
{
|
||||
_[Module]Repository = [Module]Repository;
|
||||
_DBContextDependencies = DBContextDependencies;
|
||||
}
|
||||
|
||||
public bool Install(Tenant tenant, string version)
|
||||
{
|
||||
return Migrate(new [Module]Context(_DBContextDependencies), tenant, MigrationType.Up);
|
||||
}
|
||||
|
||||
public bool Uninstall(Tenant tenant)
|
||||
{
|
||||
return Migrate(new [Module]Context(_DBContextDependencies), tenant, MigrationType.Down);
|
||||
}
|
||||
|
||||
public string ExportModule(Oqtane.Models.Module module)
|
||||
{
|
||||
string content = "";
|
||||
List<Models.[Module]> [Module]s = _[Module]Repository.Get[Module]s(module.ModuleId).ToList();
|
||||
if ([Module]s != null)
|
||||
{
|
||||
content = JsonSerializer.Serialize([Module]s);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
public void ImportModule(Oqtane.Models.Module module, string content, string version)
|
||||
{
|
||||
List<Models.[Module]> [Module]s = null;
|
||||
if (!string.IsNullOrEmpty(content))
|
||||
{
|
||||
[Module]s = JsonSerializer.Deserialize<List<Models.[Module]>>(content);
|
||||
}
|
||||
if ([Module]s != null)
|
||||
{
|
||||
foreach(var [Module] in [Module]s)
|
||||
{
|
||||
_[Module]Repository.Add[Module](new Models.[Module] { ModuleId = module.ModuleId, Name = [Module].Name });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task<List<SearchContent>> GetSearchContentsAsync(PageModule pageModule, DateTime lastIndexedOn)
|
||||
{
|
||||
var searchContentList = new List<SearchContent>();
|
||||
|
||||
foreach (var [Module] in _[Module]Repository.Get[Module]s(pageModule.ModuleId))
|
||||
{
|
||||
if ([Module].ModifiedOn >= lastIndexedOn)
|
||||
{
|
||||
searchContentList.Add(new SearchContent
|
||||
{
|
||||
EntityName = "[Owner][Module]",
|
||||
EntityId = [Module].[Module]Id.ToString(),
|
||||
Title = [Module].Name,
|
||||
Body = [Module].Name,
|
||||
ContentModifiedBy = [Module].ModifiedBy,
|
||||
ContentModifiedOn = [Module].ModifiedOn
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult(searchContentList);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Oqtane.Databases.Interfaces;
|
||||
using Oqtane.Migrations;
|
||||
using [Owner].Module.[Module].Migrations.EntityBuilders;
|
||||
using [Owner].Module.[Module].Repository;
|
||||
|
||||
namespace [Owner].Module.[Module].Migrations
|
||||
{
|
||||
[DbContext(typeof([Module]Context))]
|
||||
[Migration("[Owner].Module.[Module].01.00.00.00")]
|
||||
public class [Module]Initialize : MultiDatabaseMigration
|
||||
{
|
||||
public [Module]Initialize(IDatabase database) : base(database)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var entityBuilder = new [Module]EntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
entityBuilder.Create();
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var entityBuilder = new [Module]EntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
entityBuilder.Drop();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,31 +5,31 @@ using Oqtane.Databases.Interfaces;
|
||||
using Oqtane.Migrations;
|
||||
using Oqtane.Migrations.EntityBuilders;
|
||||
|
||||
namespace Oqtane.Application.Migrations.EntityBuilders
|
||||
namespace [Owner].Module.[Module].Migrations.EntityBuilders
|
||||
{
|
||||
public class MyModuleEntityBuilder : AuditableBaseEntityBuilder<MyModuleEntityBuilder>
|
||||
public class [Module]EntityBuilder : AuditableBaseEntityBuilder<[Module]EntityBuilder>
|
||||
{
|
||||
private const string _entityTableName = "MyModule";
|
||||
private readonly PrimaryKey<MyModuleEntityBuilder> _primaryKey = new("PK_MyModule", x => x.MyModuleId);
|
||||
private readonly ForeignKey<MyModuleEntityBuilder> _moduleForeignKey = new("FK_MyModule_Module", x => x.ModuleId, "Module", "ModuleId", ReferentialAction.Cascade);
|
||||
private const string _entityTableName = "[Owner][Module]";
|
||||
private readonly PrimaryKey<[Module]EntityBuilder> _primaryKey = new("PK_[Owner][Module]", x => x.[Module]Id);
|
||||
private readonly ForeignKey<[Module]EntityBuilder> _moduleForeignKey = new("FK_[Owner][Module]_Module", x => x.ModuleId, "Module", "ModuleId", ReferentialAction.Cascade);
|
||||
|
||||
public MyModuleEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database)
|
||||
public [Module]EntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database)
|
||||
{
|
||||
EntityTableName = _entityTableName;
|
||||
PrimaryKey = _primaryKey;
|
||||
ForeignKeys.Add(_moduleForeignKey);
|
||||
}
|
||||
|
||||
protected override MyModuleEntityBuilder BuildTable(ColumnsBuilder table)
|
||||
protected override [Module]EntityBuilder BuildTable(ColumnsBuilder table)
|
||||
{
|
||||
MyModuleId = AddAutoIncrementColumn(table, "MyModuleId");
|
||||
[Module]Id = AddAutoIncrementColumn(table,"[Module]Id");
|
||||
ModuleId = AddIntegerColumn(table,"ModuleId");
|
||||
Name = AddMaxStringColumn(table,"Name");
|
||||
AddAuditableColumns(table);
|
||||
return this;
|
||||
}
|
||||
|
||||
public OperationBuilder<AddColumnOperation> MyModuleId { get; set; }
|
||||
public OperationBuilder<AddColumnOperation> [Module]Id { get; set; }
|
||||
public OperationBuilder<AddColumnOperation> ModuleId { get; set; }
|
||||
public OperationBuilder<AddColumnOperation> Name { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Oqtane.Modules;
|
||||
using Oqtane.Repository;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Repository.Databases.Interfaces;
|
||||
|
||||
namespace [Owner].Module.[Module].Repository
|
||||
{
|
||||
public class [Module]Context : DBContextBase, ITransientService, IMultiDatabase
|
||||
{
|
||||
public virtual DbSet<Models.[Module]> [Module] { get; set; }
|
||||
|
||||
public [Module]Context(IDBContextDependencies DBContextDependencies) : base(DBContextDependencies)
|
||||
{
|
||||
// ContextBase handles multi-tenant database connections
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
base.OnModelCreating(builder);
|
||||
|
||||
builder.Entity<Models.[Module]>().ToTable(ActiveDatabase.RewriteName("[Owner][Module]"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
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;
|
||||
|
||||
public [Module]Repository(IDbContextFactory<[Module]Context> factory)
|
||||
{
|
||||
_factory = factory;
|
||||
}
|
||||
|
||||
public IEnumerable<Models.[Module]> Get[Module]s(int ModuleId)
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
return db.[Module].Where(item => item.ModuleId == ModuleId).ToList();
|
||||
}
|
||||
|
||||
public Models.[Module] Get[Module](int [Module]Id)
|
||||
{
|
||||
return Get[Module]([Module]Id, true);
|
||||
}
|
||||
|
||||
public Models.[Module] Get[Module](int [Module]Id, bool tracking)
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
if (tracking)
|
||||
{
|
||||
return db.[Module].Find([Module]Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
return db.[Module].AsNoTracking().FirstOrDefault(item => item.[Module]Id == [Module]Id);
|
||||
}
|
||||
}
|
||||
|
||||
public Models.[Module] Add[Module](Models.[Module] [Module])
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
db.[Module].Add([Module]);
|
||||
db.SaveChanges();
|
||||
return [Module];
|
||||
}
|
||||
|
||||
public Models.[Module] Update[Module](Models.[Module] [Module])
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
db.Entry([Module]).State = EntityState.Modified;
|
||||
db.SaveChanges();
|
||||
return [Module];
|
||||
}
|
||||
|
||||
public void Delete[Module](int [Module]Id)
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
Models.[Module] [Module] = db.[Module].Find([Module]Id);
|
||||
db.[Module].Remove([Module]);
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,12 @@ using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Application.Repository;
|
||||
using Oqtane.Application.Services;
|
||||
using [Owner].Module.[Module].Repository;
|
||||
using [Owner].Module.[Module].Services;
|
||||
|
||||
namespace Oqtane.Application.Startup
|
||||
namespace [Owner].Module.[Module].Startup
|
||||
{
|
||||
public class ServerStartup : IServerStartup
|
||||
public class [Module]ServerStartup : IServerStartup
|
||||
{
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
@@ -21,8 +21,8 @@ namespace Oqtane.Application.Startup
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddTransient<IMyModuleService, ServerMyModuleService>();
|
||||
services.AddDbContextFactory<Context>(opt => { }, ServiceLifetime.Transient);
|
||||
services.AddTransient<I[Module]Service, Server[Module]Service>();
|
||||
services.AddDbContextFactory<[Module]Context>(opt => { }, ServiceLifetime.Transient);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/* Module Script */
|
||||
var [Owner] = [Owner] || {};
|
||||
|
||||
[Owner].[Module] = {
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace [Owner].Module.[Module].Services
|
||||
{
|
||||
public interface I[Module]Service
|
||||
{
|
||||
Task<List<Models.[Module]>> Get[Module]sAsync(int ModuleId);
|
||||
|
||||
Task<Models.[Module]> Get[Module]Async(int [Module]Id, int ModuleId);
|
||||
|
||||
Task<Models.[Module]> Add[Module]Async(Models.[Module] [Module]);
|
||||
|
||||
Task<Models.[Module]> Update[Module]Async(Models.[Module] [Module]);
|
||||
|
||||
Task Delete[Module]Async(int [Module]Id, int ModuleId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Oqtane.Models;
|
||||
|
||||
namespace [Owner].Module.[Module].Models
|
||||
{
|
||||
[Table("[Owner][Module]")]
|
||||
public class [Module] : ModelBase
|
||||
{
|
||||
[Key]
|
||||
public int [Module]Id { get; set; }
|
||||
public int ModuleId { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"Title": "Default Module Template",
|
||||
"Type": "Internal",
|
||||
"Version": "10.0.0",
|
||||
"Namespace": "[Owner].Module.[Module]"
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 1.3
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">1.3</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1">this is my long string</data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
[base64 mime encoded serialized .NET Framework object]
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
[base64 mime encoded string representing a byte array form of the .NET Framework object]
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>1.3</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
@@ -0,0 +1,101 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 1.3
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">1.3</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1">this is my long string</data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
[base64 mime encoded serialized .NET Framework object]
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
[base64 mime encoded string representing a byte array form of the .NET Framework object]
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>1.3</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
@@ -1,4 +1,4 @@
|
||||
@namespace Oqtane.Application.MyTheme
|
||||
@namespace [Owner].Theme.[Theme]
|
||||
@inherits ContainerBase
|
||||
@inject ISettingService SettingService
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
</div>
|
||||
|
||||
@code {
|
||||
public override string Name => "Container";
|
||||
public override string Name => "[Owner] [Theme] - Container1";
|
||||
|
||||
private bool _title = true;
|
||||
private string _classes = "container-fluid";
|
||||
@@ -1,4 +1,4 @@
|
||||
@namespace Oqtane.Application.MyTheme
|
||||
@namespace [Owner].Theme.[Theme]
|
||||
@inherits ModuleBase
|
||||
@implements Oqtane.Interfaces.ISettingsControl
|
||||
@inject ISettingService SettingService
|
||||
@@ -17,7 +17,7 @@
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private string resourceType = "Oqtane.Application.MyTheme.ContainerSettings, Oqtane.Application.Client.Oqtane"; // for localization
|
||||
private string resourceType = "[Owner].Theme.[Theme].ContainerSettings, [Owner].Theme.[Theme].Client.Oqtane"; // for localization
|
||||
private string _title = "true";
|
||||
|
||||
protected override void OnInitialized()
|
||||
@@ -1,4 +1,4 @@
|
||||
@namespace Oqtane.Application.MyTheme
|
||||
@namespace [Owner].Theme.[Theme]
|
||||
@inherits ThemeBase
|
||||
@inject ISettingService SettingService
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
</main>
|
||||
|
||||
@code {
|
||||
public override string Name => "MyTheme";
|
||||
public override string Name => "Theme1";
|
||||
|
||||
public override string Panes => PaneNames.Admin + ",Top Full Width,Top 100%,Left 50%,Right 50%,Left 33%,Center 33%,Right 33%,Left Outer 25%,Left Inner 25%,Right Inner 25%,Right Outer 25%,Left 25%,Center 50%,Right 25%,Left Sidebar 66%,Right Sidebar 33%,Left Sidebar 33%,Right Sidebar 66%,Bottom 100%,Bottom Full Width";
|
||||
|
||||
@@ -3,23 +3,25 @@ using Oqtane.Models;
|
||||
using Oqtane.Themes;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace Oqtane.Application.MyTheme
|
||||
namespace [Owner].Theme.[Theme]
|
||||
{
|
||||
public class ThemeInfo : ITheme
|
||||
{
|
||||
public Oqtane.Models.Theme Theme => new Oqtane.Models.Theme
|
||||
{
|
||||
Name = "MyTheme",
|
||||
Name = "[Owner] [Theme]",
|
||||
Version = "1.0.0",
|
||||
PackageName = "Oqtane.Application",
|
||||
ThemeSettingsType = "Oqtane.Application.MyTheme.ThemeSettings, Oqtane.Application.Client.Oqtane",
|
||||
ContainerSettingsType = "Oqtane.Application.MyTheme.ContainerSettings, Oqtane.Application.Client.Oqtane",
|
||||
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 @@
|
||||
@namespace Oqtane.Application.MyTheme
|
||||
@namespace [Owner].Theme.[Theme]
|
||||
@inherits ModuleBase
|
||||
@implements Oqtane.Interfaces.ISettingsControl
|
||||
@inject ISettingService SettingService
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
@code {
|
||||
private int pageId = -1;
|
||||
private string resourceType = "Oqtane.Application.MyTheme.ThemeSettings, Oqtane.Application.Client.Oqtane"; // for localization
|
||||
private string resourceType = "[Owner].Theme.[Theme].ThemeSettings, [Owner].Theme.[Theme].Client.Oqtane"; // for localization
|
||||
private string _scope = "page";
|
||||
private string _login = "-";
|
||||
private string _register = "-";
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Oqtane Styles */
|
||||
/* Oqtane Styles */
|
||||
|
||||
body {
|
||||
padding-top: 7rem;
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"Title": "Default Theme Template",
|
||||
"Type": "Internal",
|
||||
"Version": "10.0.0",
|
||||
"Namespace": "[Owner].Theme.[Theme]"
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Oqtane.Models;
|
||||
|
||||
namespace Oqtane.Application.Models
|
||||
{
|
||||
public class MyModule : IAuditable
|
||||
{
|
||||
[Key]
|
||||
public int MyModuleId { get; set; }
|
||||
public int ModuleId { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
public string CreatedBy { get; set; }
|
||||
public DateTime CreatedOn { get; set; }
|
||||
public string ModifiedBy { get; set; }
|
||||
public DateTime ModifiedOn { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Version>1.0.0</Version>
|
||||
<AssemblyName>Oqtane.Application.Shared.Oqtane</AssemblyName>
|
||||
</PropertyGroup>
|
||||
@@ -11,7 +11,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Oqtane.Shared" Version="6.2.1" />
|
||||
<PackageReference Include="Oqtane.Shared" Version="10.1.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -57,6 +57,9 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
services.AddScoped<ICookieConsentService, CookieConsentService>();
|
||||
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
|
||||
|
||||
@@ -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" @bind="@_startTime" placeholder="hh:mm" required="@(_startDate.HasValue)" />
|
||||
<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" required="@(_endDate.HasValue)" />
|
||||
<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" required="@(_nextDate.HasValue)" />
|
||||
</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,70 +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 = _startDate.HasValue && _startTime.HasValue
|
||||
? LocalToUtc(_startDate.GetValueOrDefault().Date.Add(_startTime.GetValueOrDefault().TimeOfDay))
|
||||
: null;
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading Job {JobId} {Error}", _jobId, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Job.Load"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
job.EndDate = _endDate.HasValue && _endTime.HasValue
|
||||
? LocalToUtc(_endDate.GetValueOrDefault().Date.Add(_endTime.GetValueOrDefault().TimeOfDay))
|
||||
: null;
|
||||
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.NextExecution = _nextDate.HasValue && _nextTime.HasValue
|
||||
? LocalToUtc(_nextDate.GetValueOrDefault().Date.Add(_nextTime.GetValueOrDefault().TimeOfDay))
|
||||
: null;
|
||||
job.RetentionHistory = int.Parse(_retentionHistory);
|
||||
job.Frequency = _frequency;
|
||||
if (job.Frequency == "O") // once
|
||||
{
|
||||
job.Interval = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
job.Interval = int.Parse(_interval);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,83 +14,136 @@
|
||||
}
|
||||
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>()
|
||||
{
|
||||
@@ -103,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"))
|
||||
@@ -120,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);
|
||||
@@ -152,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,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"];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,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);
|
||||
@@ -205,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);
|
||||
}
|
||||
@@ -227,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);
|
||||
@@ -258,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
|
||||
{
|
||||
@@ -283,10 +387,8 @@ else
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.ForgotPassword"], MessageType.Info);
|
||||
AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -295,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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,6 +238,7 @@
|
||||
private string _url = "";
|
||||
private string _contact = "";
|
||||
private string _license = "";
|
||||
private string _fingerprint = "";
|
||||
private List<Permission> _permissions = null;
|
||||
private string _createdby;
|
||||
private DateTime _createdon;
|
||||
@@ -266,6 +274,7 @@
|
||||
_url = moduleDefinition.Url;
|
||||
_contact = moduleDefinition.Contact;
|
||||
_license = moduleDefinition.License;
|
||||
_fingerprint = moduleDefinition.Fingerprint;
|
||||
_permissions = moduleDefinition.PermissionList;
|
||||
_createdby = moduleDefinition.CreatedBy;
|
||||
_createdon = moduleDefinition.CreatedOn;
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
@@ -466,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);
|
||||
@@ -480,6 +515,13 @@
|
||||
_deletedon = _page.DeletedOn;
|
||||
|
||||
ThemeSettings();
|
||||
|
||||
if (_copy)
|
||||
{
|
||||
_name = "";
|
||||
_path = "";
|
||||
}
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
else
|
||||
@@ -550,7 +592,7 @@
|
||||
builder.OpenComponent(0, _themeSettingsType);
|
||||
builder.AddAttribute(1, "RenderModeBoundary", RenderModeBoundary);
|
||||
builder.AddComponentReferenceCapture(2, inst => { _themeSettings = Convert.ChangeType(inst, _themeSettingsType); });
|
||||
|
||||
|
||||
builder.CloseComponent();
|
||||
};
|
||||
}
|
||||
@@ -572,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")
|
||||
@@ -631,6 +681,13 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// update theme settings
|
||||
if (_themeSettingsType != null && _themeSettings is ISettingsControl themeSettingsControl)
|
||||
{
|
||||
await themeSettingsControl.UpdateSettings();
|
||||
}
|
||||
|
||||
// default page properties
|
||||
if (_insert != "=")
|
||||
{
|
||||
Page child;
|
||||
@@ -684,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)
|
||||
{
|
||||
@@ -695,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
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
@inherits ModuleBase
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IUserService UserService
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
@inject ISettingService SettingService
|
||||
@inject ITimeZoneService TimeZoneService
|
||||
@inject ILanguageService LanguageService
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@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 = TimeZoneService.GetTimeZones();
|
||||
_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);
|
||||
|
||||
@@ -11,11 +11,15 @@
|
||||
@inject IThemeService ThemeService
|
||||
@inject ISettingService SettingService
|
||||
@inject ITimeZoneService TimeZoneService
|
||||
@inject ILocalizationService LocalizationService
|
||||
@inject IServiceProvider ServiceProvider
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject INotificationService NotificationService
|
||||
@inject IJobService JobService
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
@inject IOutputCacheService CacheService
|
||||
@inject ISiteGroupService SiteGroupService
|
||||
@inject ISiteGroupMemberService SiteGroupMemberService
|
||||
|
||||
@if (_initialized)
|
||||
{
|
||||
@@ -28,22 +32,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="homepage" HelpText="Select the home page for the site (to be used if there is no page with a path of '/')" ResourceKey="HomePage">Home Page: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="homepage" class="form-select" @bind="@_homepageid" required>
|
||||
<option value="-"><@SharedLocalizer["Not Specified"]></option>
|
||||
@foreach (Page page in _pages)
|
||||
{
|
||||
if (UserSecurity.ContainsRole(page.PermissionList, PermissionNames.View, RoleNames.Everyone))
|
||||
{
|
||||
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="timezone" HelpText="Your time zone" ResourceKey="TimeZone">Time Zone:</Label>
|
||||
<Label Class="col-sm-3" For="timezone" HelpText="The default time zone for the site" ResourceKey="TimeZone">Time Zone:</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="timezone" class="form-select" @bind="@_timezoneid">
|
||||
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||
@@ -54,6 +43,18 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="culture" HelpText="The default language of the site's content" ResourceKey="Culture">Language:</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="culture" class="form-select" @bind="@_culturecode">
|
||||
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||
@foreach (var culture in _cultures)
|
||||
{
|
||||
<option value="@culture.Name">@culture.DisplayName</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
@@ -207,13 +208,6 @@
|
||||
</div>
|
||||
@if (_smtpenabled == "True" && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<div class="col-sm-3">
|
||||
</div>
|
||||
<div class="col-sm-9">
|
||||
<strong>@Localizer["Smtp.Required.EnableNotificationJob"]</strong><br />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="host" HelpText="Enter the host name of the SMTP server" ResourceKey="Host">Host: </Label>
|
||||
<div class="col-sm-9">
|
||||
@@ -264,15 +258,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="relay" HelpText="Only specify this option if you have properly configured an SMTP Relay Service to route your outgoing mail. This option will send notifications from the user's email rather than from the Email Sender specified below." ResourceKey="SmtpRelay">Relay Configured? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="relay" class="form-select" @bind="@_smtprelay" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -348,57 +333,6 @@
|
||||
</Section>
|
||||
@if (_aliases != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
<Section Name="Aliases" Heading="Aliases" ResourceKey="Aliases">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="aliases" HelpText="The urls for the site. This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or virtual folders (ie. domain.com/folder)." ResourceKey="Aliases">Aliases: </Label>
|
||||
<div class="col-sm-9">
|
||||
<button type="button" class="btn btn-primary" @onclick="AddAlias">@SharedLocalizer["Add"]</button>
|
||||
<Pager Items="@_aliases">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@Localizer["AliasName"]</th>
|
||||
<th>@Localizer["AliasDefault"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
@if (context.AliasId != _aliasid)
|
||||
{
|
||||
<td>
|
||||
@if (_aliasid == -1)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick="@(() => EditAlias(context))">@SharedLocalizer["Edit"]</button>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (_aliasid == -1)
|
||||
{
|
||||
<ActionDialog Action="Delete" OnClick="@(async () => await DeleteAlias(context))" ResourceKey="DeleteAlias" Class="btn btn-danger" Header="Delete Alias" Message="@string.Format(Localizer["Confirm.Alias.Delete", context.Name])" />
|
||||
}
|
||||
</td>
|
||||
<td>@context.Name</td>
|
||||
<td>@((context.IsDefault) ? SharedLocalizer["Yes"] : SharedLocalizer["No"])</td>
|
||||
}
|
||||
else
|
||||
{
|
||||
<td><button type="button" class="btn btn-success" @onclick="@(async () => await SaveAlias())">@SharedLocalizer["Save"]</button></td>
|
||||
<td><button type="button" class="btn btn-secondary" @onclick="@(async () => await CancelAlias())">@SharedLocalizer["Cancel"]</button></td>
|
||||
<td>
|
||||
<input id="aliasname" class="form-control" @bind="@_aliasname" />
|
||||
</td>
|
||||
<td>
|
||||
<select id="defaultalias" class="form-select" @bind="@_defaultalias" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</td>
|
||||
}
|
||||
</Row>
|
||||
</Pager>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="Hosting" Heading="Hosting Model" ResourceKey="Hosting">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
@@ -411,8 +345,20 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (_rendermode == RenderModes.Static)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="enhancednavigation" HelpText="Indicates if enhanced navigation should be used with static rendering" ResourceKey="EnhancedNavigation">Enhanced Navigation: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="enhancednavigation" class="form-select" @bind="@_enhancednavigation" 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="runtime" HelpText="The render mode for UI components which require interactivity" ResourceKey="Runtime">Interactivity: </Label>
|
||||
<Label Class="col-sm-3" For="runtime" HelpText="The hosting model 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>
|
||||
@@ -441,6 +387,180 @@
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="Aliases" Heading="Urls" ResourceKey="Aliases">
|
||||
<div class="container">
|
||||
@if (!_addAlias)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="aliases" HelpText="The urls for this site. This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or virtual folders (ie. domain.com/folder)." ResourceKey="Aliases">Urls: </Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<select id="aliases" class="form-select" value="@_aliasid" @onchange="(e => AliasChanged(e))">
|
||||
<option value="-1"><@SharedLocalizer["Not Specified"]></option>
|
||||
@foreach (var alias in _aliases)
|
||||
{
|
||||
<option value="@alias.AliasId">@alias.Name @((alias.IsDefault) ? "(" + Localizer["Default"] + ")" : "")</option>
|
||||
}
|
||||
</select>
|
||||
@if (!_addAlias)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick="AddAlias">@SharedLocalizer["Add"]</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_aliasid != -1 || _addAlias)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="aliasname" HelpText="A url for this site. This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or virtual folders (ie. domain.com/folder)." ResourceKey="AliasName">Url: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="aliasname" class="form-control" @bind="@_aliasname" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="defaultalias" HelpText="The default alias for the site. Requests for non-default aliases will be redirected to the default alias." ResourceKey="DefaultAlias">Default? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="defaultalias" class="form-select" @bind="@_defaultalias">
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<div class="col-sm-3"></div>
|
||||
<div class="col-sm-9">
|
||||
@if (_aliasid != -1 || _addAlias)
|
||||
{
|
||||
<button type="button" class="btn btn-success me-2" @onclick="SaveAlias">@SharedLocalizer["Save"]</button>
|
||||
}
|
||||
@if (_aliasid != -1 && !_addAlias)
|
||||
{
|
||||
<ActionDialog Action="Delete" OnClick="@(async () => await DeleteAlias())" ResourceKey="DeleteAlias" Class="btn btn-danger" Header="Delete Alias" Message="@string.Format(Localizer["Confirm.Alias.Delete", _aliasname])" />
|
||||
}
|
||||
@if (_addAlias)
|
||||
{
|
||||
<button type="button" class="btn btn-secondary" @onclick="CancelAlias">@SharedLocalizer["Cancel"]</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="SiteGroupMembers" Heading="Site Groups" ResourceKey="SiteGroupMembers">
|
||||
<div class="container">
|
||||
@if (!_addSiteGroup)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="group" HelpText="The site groups in this tenant (database)" ResourceKey="SiteGroupMembers">Group: </Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<select id="group" class="form-select" value="@_siteGroupId" @onchange="(e => SiteGroupChanged(e))">
|
||||
<option value="-1"><@SharedLocalizer["Not Specified"]></option>
|
||||
@foreach (var siteGroup in _siteGroups)
|
||||
{
|
||||
<option value="@siteGroup.SiteGroupId">@siteGroup.Name</option>
|
||||
}
|
||||
</select>
|
||||
@if (!_addSiteGroup)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick="AddSiteGroup">@SharedLocalizer["Add"]</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_siteGroupId != -1 || _addSiteGroup)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="groupname" HelpText="Name of the site group" ResourceKey="GroupName">Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="groupname" class="form-control" @bind="@_groupName" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="grouptype" HelpText="Defines the specific behavior of the site group" ResourceKey="GroupType">Type: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="grouptype" class="form-select" @bind="@_groupType">
|
||||
<option value="@SiteGroupTypes.Synchronization">@Localizer[@SiteGroupTypes.Synchronization]</option>
|
||||
<option value="@SiteGroupTypes.ChangeDetection">@Localizer[@SiteGroupTypes.ChangeDetection]</option>
|
||||
<option value="@SiteGroupTypes.Localization">@Localizer[SiteGroupTypes.Localization]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_siteGroupId != -1)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="site" HelpText="The sites which are members of this site group" ResourceKey="Site">Members: </Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<select id="site" class="form-select" value="@_siteId" @onchange="(e => SiteChanged(e))">
|
||||
<option value="-1"><@SharedLocalizer["Not Specified"]></option>
|
||||
@foreach (var site in _sites)
|
||||
{
|
||||
<option value="@site.SiteId">@site.Name</option>
|
||||
}
|
||||
</select>
|
||||
@if (!_addSiteGroupMember)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick="AddSiteGroupMember">@SharedLocalizer["Add"]</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick="AddSiteGroupMember">@SharedLocalizer["Select"]</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_siteGroupId != -1 && _siteId != -1)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="primary" HelpText="Indicates if the selected member is the primary site of the site group" ResourceKey="Primary">Primary? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="primary" class="form-select" @bind="@_primary">
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (_primary == "False" && !string.IsNullOrEmpty(_synchronized) && (_groupType == SiteGroupTypes.Synchronization || _groupType == SiteGroupTypes.ChangeDetection))
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="synchronized" HelpText="The date/time when the site was last synchronized" ResourceKey="Synchronized">Synchronized: </Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="synchronized" class="form-control" @bind="@_synchronized" disabled />
|
||||
@if (!string.IsNullOrEmpty(_synchronized))
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick="ResetSiteGroupMember">@SharedLocalizer["Reset"]</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<div class="col-sm-3"></div>
|
||||
<div class="col-sm-9">
|
||||
@if ((_siteGroupId != -1 || _addSiteGroup))
|
||||
{
|
||||
<button type="button" class="btn btn-success me-2" @onclick="SaveSiteGroupMember">@SharedLocalizer["Save"]</button>
|
||||
}
|
||||
@if (_siteGroupId != -1 && !_addSiteGroup && _siteId != -1 && !_addSiteGroupMember)
|
||||
{
|
||||
<ActionDialog Action="Delete" OnClick="@(async () => await DeleteSiteGroupMember())" ResourceKey="DeleteSiteGroupMember" Class="btn btn-danger" Header="Delete Site Group" Message="@Localizer["Confirm.SiteGroupMember.Delete"]" />
|
||||
}
|
||||
@if (_addSiteGroup)
|
||||
{
|
||||
<button type="button" class="btn btn-secondary" @onclick="CancelSiteGroupMember">@SharedLocalizer["Cancel"]</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="TenantInformation" Heading="Database" ResourceKey="TenantInformation">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
@@ -481,10 +601,11 @@
|
||||
private List<ThemeControl> _containers = new List<ThemeControl>();
|
||||
private List<Page> _pages;
|
||||
private List<Models.TimeZone> _timezones;
|
||||
private IEnumerable<Models.Culture> _cultures;
|
||||
|
||||
private string _name = string.Empty;
|
||||
private string _homepageid = "-";
|
||||
private string _timezoneid = string.Empty;
|
||||
private string _culturecode = string.Empty;
|
||||
private string _isdeleted;
|
||||
private string _sitemap = "";
|
||||
private string _siteguid = "";
|
||||
@@ -522,7 +643,6 @@
|
||||
private string _togglesmtpclientsecret = string.Empty;
|
||||
private string _smtpscopes = string.Empty;
|
||||
private string _smtpsender = string.Empty;
|
||||
private string _smtprelay = "False";
|
||||
private int _retention = 30;
|
||||
|
||||
private string _pwaisenabled;
|
||||
@@ -531,15 +651,28 @@
|
||||
private int _pwasplashiconfileid = -1;
|
||||
private FileManager _pwasplashiconfilemanager;
|
||||
|
||||
private string _rendermode = RenderModes.Interactive;
|
||||
private string _enhancednavigation = "True";
|
||||
private string _runtime = Runtimes.Server;
|
||||
private string _prerender = "True";
|
||||
private string _hybrid = "False";
|
||||
|
||||
private List<Alias> _aliases;
|
||||
private int _aliasid = -1;
|
||||
private string _aliasname;
|
||||
private string _defaultalias;
|
||||
private bool _addAlias = false;
|
||||
|
||||
private string _rendermode = RenderModes.Interactive;
|
||||
private string _runtime = Runtimes.Server;
|
||||
private string _prerender = "True";
|
||||
private string _hybrid = "False";
|
||||
private List<SiteGroup> _siteGroups = new List<SiteGroup>();
|
||||
private List<Site> _sites = new List<Site>();
|
||||
private int _siteGroupId = -1;
|
||||
private int _siteId;
|
||||
private string _groupName = string.Empty;
|
||||
private string _groupType = SiteGroupTypes.Synchronization;
|
||||
private string _primary = "True";
|
||||
private string _synchronized = string.Empty;
|
||||
private bool _addSiteGroup = false;
|
||||
private bool _addSiteGroupMember = false;
|
||||
|
||||
private string _tenant = string.Empty;
|
||||
private string _database = string.Empty;
|
||||
@@ -567,16 +700,14 @@
|
||||
if (site != null)
|
||||
{
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
_cultures = await LocalizationService.GetNeutralCulturesAsync();
|
||||
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
||||
|
||||
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||
|
||||
_name = site.Name;
|
||||
_timezoneid = site.TimeZoneId;
|
||||
if (site.HomePageId != null)
|
||||
{
|
||||
_homepageid = site.HomePageId.Value.ToString();
|
||||
}
|
||||
_culturecode = site.CultureCode;
|
||||
_isdeleted = site.IsDeleted.ToString();
|
||||
_sitemap = PageState.Alias.Protocol + PageState.Alias.Name + "/sitemap.xml";
|
||||
_siteguid = site.SiteGuid;
|
||||
@@ -640,7 +771,6 @@
|
||||
_togglesmtpclientsecret = SharedLocalizer["ShowPassword"];
|
||||
_smtpscopes = SettingService.GetSetting(settings, "SMTPScopes", string.Empty);
|
||||
_smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty);
|
||||
_smtprelay = SettingService.GetSetting(settings, "SMTPRelay", "False");
|
||||
_retention = int.Parse(SettingService.GetSetting(settings, "NotificationRetention", "30"));
|
||||
}
|
||||
|
||||
@@ -656,10 +786,11 @@
|
||||
}
|
||||
|
||||
// aliases
|
||||
await GetAliases();
|
||||
await LoadAliases();
|
||||
|
||||
// hosting model
|
||||
_rendermode = site.RenderMode;
|
||||
_enhancednavigation = site.EnhancedNavigation.ToString();
|
||||
_runtime = site.Runtime;
|
||||
_prerender = site.Prerender.ToString();
|
||||
_hybrid = site.Hybrid.ToString();
|
||||
@@ -669,7 +800,7 @@
|
||||
{
|
||||
var tenants = await TenantService.GetTenantsAsync();
|
||||
var _databases = await DatabaseService.GetDatabasesAsync();
|
||||
var tenant = tenants.Find(item => item.TenantId == site.TenantId);
|
||||
var tenant = tenants.Find(item => item.TenantId == PageState.Alias.TenantId);
|
||||
if (tenant != null)
|
||||
{
|
||||
_tenant = tenant.Name;
|
||||
@@ -679,6 +810,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
// site groups
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
await LoadSiteGroups();
|
||||
}
|
||||
|
||||
// audit
|
||||
_createdby = site.CreatedBy;
|
||||
_createdon = site.CreatedOn;
|
||||
@@ -746,7 +883,7 @@
|
||||
{
|
||||
site.Name = _name;
|
||||
site.TimeZoneId = _timezoneid;
|
||||
site.HomePageId = (_homepageid != "-" ? int.Parse(_homepageid) : null);
|
||||
site.CultureCode = _culturecode;
|
||||
site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
|
||||
|
||||
// appearance
|
||||
@@ -807,13 +944,11 @@
|
||||
// hosting model
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
if (site.RenderMode != _rendermode || site.Runtime != _runtime || site.Prerender != bool.Parse(_prerender) || site.Hybrid != bool.Parse(_hybrid))
|
||||
{
|
||||
site.RenderMode = _rendermode;
|
||||
site.Runtime = _runtime;
|
||||
site.Prerender = bool.Parse(_prerender);
|
||||
site.Hybrid = bool.Parse(_hybrid);
|
||||
}
|
||||
site.RenderMode = _rendermode;
|
||||
site.EnhancedNavigation = bool.Parse(_enhancednavigation);
|
||||
site.Runtime = _runtime;
|
||||
site.Prerender = bool.Parse(_prerender);
|
||||
site.Hybrid = bool.Parse(_hybrid);
|
||||
}
|
||||
|
||||
site = await SiteService.UpdateSiteAsync(site);
|
||||
@@ -834,8 +969,18 @@
|
||||
settings = SettingService.SetSetting(settings, "SMTPClientSecret", _smtpclientsecret, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPScopes", _smtpscopes, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPRelay", _smtprelay, true);
|
||||
settings = SettingService.SetSetting(settings, "NotificationRetention", _retention.ToString(), true);
|
||||
|
||||
if (_smtpenabled == "True")
|
||||
{
|
||||
var jobs = await JobService.GetJobsAsync();
|
||||
var job = jobs.FirstOrDefault(item => item.JobType == "Oqtane.Infrastructure.NotificationJob, Oqtane.Server");
|
||||
if (job != null && !job.IsEnabled)
|
||||
{
|
||||
job.IsEnabled = true;
|
||||
await JobService.UpdateJobAsync(job);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//cookie consent
|
||||
@@ -874,17 +1019,17 @@
|
||||
try
|
||||
{
|
||||
var aliases = await AliasService.GetAliasesAsync();
|
||||
if (aliases.Any(item => item.SiteId != PageState.Site.SiteId || item.TenantId != PageState.Site.TenantId))
|
||||
if (aliases.Any(item => item.SiteId != PageState.Site.SiteId || item.TenantId != PageState.Alias.TenantId))
|
||||
{
|
||||
await SiteService.DeleteSiteAsync(PageState.Site.SiteId);
|
||||
await logger.LogInformation("Site Deleted {SiteId}", PageState.Site.SiteId);
|
||||
|
||||
foreach (Alias alias in aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Site.TenantId))
|
||||
foreach (Alias alias in aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Alias.TenantId))
|
||||
{
|
||||
await AliasService.DeleteAliasAsync(alias.AliasId);
|
||||
}
|
||||
|
||||
var redirect = aliases.First(item => item.SiteId != PageState.Site.SiteId || item.TenantId != PageState.Site.TenantId);
|
||||
var redirect = aliases.First(item => item.SiteId != PageState.Site.SiteId || item.TenantId != PageState.Alias.TenantId);
|
||||
NavigationManager.NavigateTo(PageState.Uri.Scheme + "://" + redirect.Name, true);
|
||||
}
|
||||
else
|
||||
@@ -945,7 +1090,10 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
_smtpenabled = "True";
|
||||
|
||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
settings = SettingService.SetSetting(settings, "SMTPEnabled", _smtpenabled, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPHost", _smtphost, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true);
|
||||
@@ -960,6 +1108,14 @@
|
||||
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||
await logger.LogInformation("Site SMTP Settings Saved");
|
||||
|
||||
var jobs = await JobService.GetJobsAsync();
|
||||
var job = jobs.FirstOrDefault(item => item.JobType == "Oqtane.Infrastructure.NotificationJob, Oqtane.Server");
|
||||
if (job != null && !job.IsEnabled)
|
||||
{
|
||||
job.IsEnabled = true;
|
||||
await JobService.UpdateJobAsync(job);
|
||||
}
|
||||
|
||||
await NotificationService.AddNotificationAsync(new Notification(PageState.Site.SiteId, PageState.User, PageState.Site.Name + " SMTP Configuration Test", "SMTP Server Is Configured Correctly."));
|
||||
AddModuleMessage(Localizer["Info.Smtp.SaveSettings"], MessageType.Info);
|
||||
await ScrollToPageTop();
|
||||
@@ -976,95 +1132,100 @@
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GetAliases()
|
||||
private async Task LoadAliases()
|
||||
{
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
_aliases = await AliasService.GetAliasesAsync();
|
||||
_aliases = _aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Alias.TenantId).OrderBy(item => item.AliasId).ToList();
|
||||
_aliasid = -1;
|
||||
_addAlias = false;
|
||||
}
|
||||
|
||||
private async void AliasChanged(ChangeEventArgs e)
|
||||
{
|
||||
_aliasid = int.Parse(e.Value.ToString());
|
||||
if (_aliasid != -1)
|
||||
{
|
||||
_aliases = await AliasService.GetAliasesAsync();
|
||||
_aliases = _aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Site.TenantId).OrderBy(item => item.AliasId).ToList();
|
||||
var alias = _aliases.FirstOrDefault(item => item.AliasId == _aliasid);
|
||||
if (alias != null)
|
||||
{
|
||||
_aliasname = alias.Name;
|
||||
_defaultalias = alias.IsDefault.ToString();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_aliasname = "";
|
||||
_defaultalias = "False";
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void AddAlias()
|
||||
{
|
||||
_aliases.Add(new Alias { AliasId = 0, Name = "", IsDefault = false });
|
||||
_aliasid = 0;
|
||||
_aliasid = -1;
|
||||
_aliasname = "";
|
||||
_defaultalias = "False";
|
||||
_addAlias = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void EditAlias(Alias alias)
|
||||
private async Task DeleteAlias()
|
||||
{
|
||||
_aliasid = alias.AliasId;
|
||||
_aliasname = alias.Name;
|
||||
_defaultalias = alias.IsDefault.ToString();
|
||||
await AliasService.DeleteAliasAsync(_aliasid);
|
||||
await LoadAliases();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task DeleteAlias(Alias alias)
|
||||
{
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
await AliasService.DeleteAliasAsync(alias.AliasId);
|
||||
await GetAliases();
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveAlias()
|
||||
{
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
if (!string.IsNullOrEmpty(_aliasname))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_aliasname))
|
||||
var aliases = await AliasService.GetAliasesAsync();
|
||||
|
||||
int protocolIndex = _aliasname.IndexOf("://", StringComparison.OrdinalIgnoreCase);
|
||||
if (protocolIndex != -1)
|
||||
{
|
||||
var aliases = await AliasService.GetAliasesAsync();
|
||||
_aliasname = _aliasname.Substring(protocolIndex + 3);
|
||||
}
|
||||
|
||||
int protocolIndex = _aliasname.IndexOf("://", StringComparison.OrdinalIgnoreCase);
|
||||
if (protocolIndex != -1)
|
||||
var alias = aliases.FirstOrDefault(item => item.Name == _aliasname);
|
||||
|
||||
bool unique = (alias == null || alias.AliasId == _aliasid);
|
||||
|
||||
if (unique)
|
||||
{
|
||||
if (_aliasid == -1)
|
||||
{
|
||||
_aliasname = _aliasname.Substring(protocolIndex + 3);
|
||||
alias = new Alias { SiteId = PageState.Site.SiteId, TenantId = PageState.Alias.TenantId, Name = _aliasname, IsDefault = bool.Parse(_defaultalias) };
|
||||
await AliasService.AddAliasAsync(alias);
|
||||
}
|
||||
|
||||
var alias = aliases.FirstOrDefault(item => item.Name == _aliasname);
|
||||
|
||||
bool unique = (alias == null || alias.AliasId == _aliasid);
|
||||
|
||||
if (unique)
|
||||
else
|
||||
{
|
||||
if (_aliasid == 0)
|
||||
alias = _aliases.SingleOrDefault(item => item.AliasId == _aliasid);
|
||||
if (alias != null)
|
||||
{
|
||||
alias = new Alias { SiteId = PageState.Site.SiteId, TenantId = PageState.Site.TenantId, Name = _aliasname, IsDefault = bool.Parse(_defaultalias) };
|
||||
await AliasService.AddAliasAsync(alias);
|
||||
}
|
||||
else
|
||||
{
|
||||
alias = _aliases.SingleOrDefault(item => item.AliasId == _aliasid);
|
||||
if (alias != null)
|
||||
{
|
||||
alias.Name = _aliasname;
|
||||
alias.IsDefault = bool.Parse(_defaultalias);
|
||||
await AliasService.UpdateAliasAsync(alias);
|
||||
}
|
||||
alias.Name = _aliasname;
|
||||
alias.IsDefault = bool.Parse(_defaultalias);
|
||||
await AliasService.UpdateAliasAsync(alias);
|
||||
}
|
||||
}
|
||||
|
||||
await GetAliases();
|
||||
_aliasid = -1;
|
||||
_aliasname = "";
|
||||
StateHasChanged();
|
||||
}
|
||||
else // Duplicate alias
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Aliases.Taken"], MessageType.Warning);
|
||||
await ScrollToPageTop();
|
||||
}
|
||||
await LoadAliases();
|
||||
_aliasid = -1;
|
||||
_aliasname = "";
|
||||
StateHasChanged();
|
||||
}
|
||||
else // Duplicate alias
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Aliases.Taken"], MessageType.Warning);
|
||||
await ScrollToPageTop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CancelAlias()
|
||||
{
|
||||
await GetAliases();
|
||||
await LoadAliases();
|
||||
_aliasid = -1;
|
||||
_aliasname = "";
|
||||
StateHasChanged();
|
||||
@@ -1074,4 +1235,205 @@
|
||||
await CacheService.EvictByTag(Constants.SitemapOutputCacheTag);
|
||||
AddModuleMessage(Localizer["Success.SiteMap.CacheEvicted"], MessageType.Success);
|
||||
}
|
||||
|
||||
private async Task LoadSiteGroups()
|
||||
{
|
||||
_siteGroups = await SiteGroupService.GetSiteGroupsAsync();
|
||||
_siteGroupId = -1;
|
||||
_addSiteGroup = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task SiteGroupChanged(ChangeEventArgs e)
|
||||
{
|
||||
_siteGroupId = int.Parse(e.Value.ToString());
|
||||
if (_siteGroupId != -1)
|
||||
{
|
||||
var group = _siteGroups.FirstOrDefault(item => item.SiteGroupId == _siteGroupId);
|
||||
if (group != null)
|
||||
{
|
||||
_groupName = group.Name;
|
||||
_groupType = group.Type;
|
||||
_siteId = -1;
|
||||
_primary = "False";
|
||||
_addSiteGroupMember = false;
|
||||
|
||||
await LoadSites();
|
||||
}
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task LoadSites()
|
||||
{
|
||||
var siteGroupMembers = await SiteGroupMemberService.GetSiteGroupMembersAsync(-1, _siteGroupId);
|
||||
|
||||
_sites = await SiteService.GetSitesAsync();
|
||||
if (_addSiteGroupMember)
|
||||
{
|
||||
// include sites which are not members
|
||||
_sites = _sites.ExceptBy(siteGroupMembers.Select(item => item.SiteId), item => item.SiteId).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
// include sites which are members
|
||||
_sites = _sites.Where(item => siteGroupMembers.Any(item2 => item2.SiteId == item.SiteId)).ToList();
|
||||
var group = _siteGroups.FirstOrDefault(item => item.SiteGroupId == _siteGroupId);
|
||||
foreach (var site in _sites)
|
||||
{
|
||||
if (group.PrimarySiteId == site.SiteId)
|
||||
{
|
||||
site.Name += $" ({Localizer["Primary"]})";
|
||||
}
|
||||
else
|
||||
{
|
||||
site.Name += $" ({Localizer["Secondary"]})";
|
||||
}
|
||||
}
|
||||
|
||||
var siteGroupMember = siteGroupMembers.FirstOrDefault(item => item.SiteId == _siteId);
|
||||
if (siteGroupMember != null)
|
||||
{
|
||||
_primary = (siteGroupMember.SiteGroup.PrimarySiteId == _siteId) ? "True" : "False";
|
||||
_synchronized = UtcToLocal(siteGroupMember.SynchronizedOn).ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SiteChanged(ChangeEventArgs e)
|
||||
{
|
||||
_siteId = int.Parse(e.Value.ToString());
|
||||
var siteGroupMember = await SiteGroupMemberService.GetSiteGroupMemberAsync(_siteId, _siteGroupId);
|
||||
if (siteGroupMember != null)
|
||||
{
|
||||
_primary = (siteGroupMember.SiteGroup.PrimarySiteId == _siteId) ? "True" : "False";
|
||||
_synchronized = UtcToLocal(siteGroupMember.SynchronizedOn).ToString();
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task AddSiteGroup()
|
||||
{
|
||||
_groupName = "";
|
||||
_siteId = PageState.Site.SiteId;
|
||||
_primary = "True";
|
||||
_synchronized = "";
|
||||
_addSiteGroup = true;
|
||||
}
|
||||
|
||||
private async Task AddSiteGroupMember()
|
||||
{
|
||||
_addSiteGroupMember = !_addSiteGroupMember;
|
||||
_siteId = -1;
|
||||
await LoadSites();
|
||||
}
|
||||
|
||||
private async Task SaveSiteGroupMember()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_groupName))
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Required.GroupName"], MessageType.Warning);
|
||||
await ScrollToPageTop();
|
||||
return;
|
||||
}
|
||||
|
||||
SiteGroup siteGroup = null;
|
||||
|
||||
if (_siteGroupId == -1)
|
||||
{
|
||||
siteGroup = new SiteGroup
|
||||
{
|
||||
Name = _groupName,
|
||||
Type = _groupType,
|
||||
PrimarySiteId = _siteId,
|
||||
Synchronize = false
|
||||
};
|
||||
siteGroup = await SiteGroupService.AddSiteGroupAsync(siteGroup);
|
||||
}
|
||||
else
|
||||
{
|
||||
siteGroup = _siteGroups.FirstOrDefault(item => item.SiteGroupId == _siteGroupId);
|
||||
if (siteGroup != null)
|
||||
{
|
||||
siteGroup.Name = _groupName;
|
||||
siteGroup.Type = _groupType;
|
||||
siteGroup.PrimarySiteId = (_primary == "True") ? _siteId : siteGroup.PrimarySiteId;
|
||||
siteGroup = await SiteGroupService.UpdateSiteGroupAsync(siteGroup);
|
||||
}
|
||||
else
|
||||
{
|
||||
siteGroup = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (siteGroup != null)
|
||||
{
|
||||
if (_siteId != -1)
|
||||
{
|
||||
var siteGroupMember = await SiteGroupMemberService.GetSiteGroupMemberAsync(_siteId, siteGroup.SiteGroupId);
|
||||
if (siteGroupMember == null)
|
||||
{
|
||||
siteGroupMember = new SiteGroupMember
|
||||
{
|
||||
SiteGroupId = siteGroup.SiteGroupId,
|
||||
SiteId = _siteId
|
||||
};
|
||||
await SiteGroupMemberService.AddSiteGroupMemberAsync(siteGroupMember);
|
||||
}
|
||||
else
|
||||
{
|
||||
siteGroupMember.SynchronizedOn = string.IsNullOrEmpty(_synchronized) ? null : siteGroupMember.SynchronizedOn;
|
||||
await SiteGroupMemberService.UpdateSiteGroupMemberAsync(siteGroupMember);
|
||||
}
|
||||
}
|
||||
|
||||
if (siteGroup.Type == SiteGroupTypes.Synchronization)
|
||||
{
|
||||
// enable synchronization job if it is not enabled already
|
||||
var jobs = await JobService.GetJobsAsync();
|
||||
var job = jobs.FirstOrDefault(item => item.JobType == "Oqtane.Infrastructure.SynchronizationJob, Oqtane.Server");
|
||||
if (job != null && !job.IsEnabled)
|
||||
{
|
||||
job.IsEnabled = true;
|
||||
await JobService.UpdateJobAsync(job);
|
||||
}
|
||||
}
|
||||
|
||||
await LoadSiteGroups();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CancelSiteGroupMember()
|
||||
{
|
||||
_groupName = "";
|
||||
await LoadSiteGroups();
|
||||
}
|
||||
|
||||
private async Task DeleteSiteGroupMember()
|
||||
{
|
||||
if (_siteGroupId != -1)
|
||||
{
|
||||
if (_siteId != -1)
|
||||
{
|
||||
var siteGroupMember = await SiteGroupMemberService.GetSiteGroupMemberAsync(_siteId, _siteGroupId);
|
||||
if (siteGroupMember != null)
|
||||
{
|
||||
await SiteGroupMemberService.DeleteSiteGroupMemberAsync(siteGroupMember.SiteGroupMemberId);
|
||||
}
|
||||
}
|
||||
|
||||
var siteGroupMembers = await SiteGroupMemberService.GetSiteGroupMembersAsync(-1, _siteGroupId);
|
||||
if (!siteGroupMembers.Any())
|
||||
{
|
||||
await SiteGroupService.DeleteSiteGroupAsync(_siteGroupId);
|
||||
}
|
||||
|
||||
await LoadSiteGroups();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ResetSiteGroupMember()
|
||||
{
|
||||
_synchronized = "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
@@ -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(PageState.Site.SiteId);
|
||||
_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();
|
||||
@@ -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>();
|
||||
@@ -399,12 +315,12 @@ 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,130 +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;
|
||||
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 (_databases.Exists(item => item.IsDefault))
|
||||
{
|
||||
_databasetype = _databases.Find(item => item.IsDefault).Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
_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");
|
||||
|
||||
@@ -271,7 +271,7 @@
|
||||
}
|
||||
|
||||
var tenants = await TenantService.GetTenantsAsync();
|
||||
_tenant = tenants.Find(item => item.TenantId == PageState.Site.TenantId).Name;
|
||||
_tenant = tenants.Find(item => item.TenantId == PageState.Alias.TenantId).Name;
|
||||
_history = await MigrationHistoryService.GetMigrationHistoryAsync();
|
||||
|
||||
_initialized = true;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<br />
|
||||
<Section Name="Information" ResourceKey="Information" Heading="Information">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
@@ -81,6 +82,12 @@
|
||||
}
|
||||
</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 />
|
||||
@@ -117,6 +124,7 @@
|
||||
private string _url = "";
|
||||
private string _contact = "";
|
||||
private string _license = "";
|
||||
private string _fingerprint = "";
|
||||
private List<Permission> _permissions = null;
|
||||
private string _createdby;
|
||||
private DateTime _createdon;
|
||||
@@ -143,6 +151,7 @@
|
||||
_url = theme.Url;
|
||||
_contact = theme.Contact;
|
||||
_license = theme.License;
|
||||
_fingerprint = theme.Fingerprint;
|
||||
_permissions = theme.PermissionList;
|
||||
_createdby = theme.CreatedBy;
|
||||
_createdon = theme.CreatedOn;
|
||||
|
||||
@@ -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)
|
||||
@@ -230,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>
|
||||
@@ -253,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)
|
||||
{
|
||||
@@ -298,7 +384,7 @@
|
||||
else
|
||||
{
|
||||
<div class="no-notifications-text">
|
||||
@Localizer["NoNotificationsReceived.Text"]
|
||||
@Localizer["NoNotificationsReceived"]
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -316,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)
|
||||
{
|
||||
@@ -362,7 +448,7 @@
|
||||
else
|
||||
{
|
||||
<div class="no-notifications-text">
|
||||
@Localizer["NoNotificationsSent.Text"]
|
||||
@Localizer["NoNotificationsSent"]
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -373,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 List<Models.TimeZone> _timezones;
|
||||
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;
|
||||
@@ -400,42 +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);
|
||||
foreach (var profile in _profiles)
|
||||
{
|
||||
if (profile.Options.ToLower().StartsWith("entityname:"))
|
||||
{
|
||||
var options = await SettingService.GetSettingsAsync(profile.Options.Substring(11), -1);
|
||||
options.Add("", $"<{SharedLocalizer["Not Specified"]}>");
|
||||
profile.Options = string.Join(",", options.OrderBy(item => item.Value).Select(kvp => $"{kvp.Key}:{kvp.Value}"));
|
||||
}
|
||||
}
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
_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;
|
||||
|
||||
// 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;
|
||||
@@ -447,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;
|
||||
@@ -468,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
|
||||
@@ -501,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)
|
||||
{
|
||||
@@ -558,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);
|
||||
@@ -584,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)
|
||||
@@ -615,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
|
||||
{
|
||||
@@ -651,13 +882,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
private async void FilterChanged(ChangeEventArgs e)
|
||||
{
|
||||
_filter = (string)e.Value;
|
||||
await LoadNotificationsAsync();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task DeleteAllNotifications()
|
||||
{
|
||||
try
|
||||
@@ -689,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
|
||||
|
||||
@@ -55,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">
|
||||
@@ -129,12 +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;
|
||||
@@ -147,6 +163,11 @@
|
||||
try
|
||||
{
|
||||
_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)
|
||||
{
|
||||
@@ -157,8 +178,9 @@
|
||||
profile.Options = string.Join(",", options.OrderBy(item => item.Value).Select(kvp => $"{kvp.Key}:{kvp.Value}"));
|
||||
}
|
||||
}
|
||||
|
||||
_settings = new Dictionary<string, string>();
|
||||
_timezoneid = PageState.Site.TimeZoneId;
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -194,6 +216,7 @@
|
||||
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">
|
||||
@@ -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)
|
||||
@@ -160,7 +224,7 @@
|
||||
{
|
||||
<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 ms-1" OnClick="@(async () => await DeleteUser())" ResourceKey="DeleteUser" />
|
||||
}
|
||||
@@ -169,23 +233,33 @@
|
||||
}
|
||||
|
||||
@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;
|
||||
@@ -204,19 +278,8 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||
_profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
|
||||
foreach (var profile in _profiles)
|
||||
{
|
||||
if (profile.Options.ToLower().StartsWith("entityname:"))
|
||||
{
|
||||
var options = await SettingService.GetSettingsAsync(profile.Options.Substring(11), -1);
|
||||
options.Add("", $"<{SharedLocalizer["Not Specified"]}>");
|
||||
profile.Options = string.Join(",", options.OrderBy(item => item.Value).Select(kvp => $"{kvp.Key}:{kvp.Value}"));
|
||||
}
|
||||
}
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
_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))
|
||||
{
|
||||
@@ -224,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;
|
||||
@@ -281,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));
|
||||
@@ -354,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)
|
||||
|
||||
@@ -33,7 +33,7 @@ else
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<Pager Items="@users" RowClass="align-middle" SearchProperties="User.Username,User.Email,User.DisplayName">
|
||||
<Pager Items="@users" CurrentPage="@_page.ToString()" OnPageChange="@((page) => _page = page)" RowClass="align-middle" SearchProperties="User.Username,User.Email,User.DisplayName">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
@@ -60,9 +60,46 @@ else
|
||||
</Row>
|
||||
</Pager>
|
||||
</TabPanel>
|
||||
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings" Security="SecurityAccessLevel.Admin">
|
||||
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings" Security="SecurityAccessLevel.Host">
|
||||
<div class="container">
|
||||
<Section Name="User" Heading="User Settings" ResourceKey="UserSettings">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="allowsitelogin" HelpText="Do you want to allow users to sign in using a username and password that is managed locally on this site? Note that you should only disable this option if you have already successfully configured an alternative login method, or else you may lock yourself out of the site." ResourceKey="AllowSiteLogin">Allow Local Login?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="allowsitelogin" class="form-select" @bind="@_allowsitelogin">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="twofactor" HelpText="Do you want users to use two factor authentication? Note that you should use the Disabled option until you have successfully verified that the Notification Job in Scheduled Jobs is enabled and your SMTP options in Site Settings are configured or else you will lock yourself out." ResourceKey="TwoFactor">Use 2FA?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="twofactor" class="form-select" @bind="@_twofactor">
|
||||
<option value="false">@Localizer["Disabled"]</option>
|
||||
<option value="true">@Localizer["Optional"]</option>
|
||||
<option value="required">@Localizer["Required"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="loginlink" HelpText="Do you want to allow users to login using a time sensitive link sent by email" ResourceKey="LoginLink">Allow Login Link?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="loginlink" class="form-select" @bind="@_loginlink">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="passkeys" HelpText="Do you want to allow users to login using passkeys (ie. passwordless authentication using WebAuthn/FIDO2)" ResourceKey="Passkeys">Allow Passkeys?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="passkeys" class="form-select" @bind="@_passkeys">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="allowregistration" HelpText="Do you want anonymous visitors to be able to register for an account on the site" ResourceKey="AllowRegistration">Allow User Registration?</Label>
|
||||
<div class="col-sm-9">
|
||||
@@ -81,12 +118,6 @@ else
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="profileurl" HelpText="Optionally provide a custom profile url" ResourceKey="ProfileUrl">Profile Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="profileurl" class="form-control" @bind="@_profileurl" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requireconfirmedemail" HelpText="Do you want to require registered users to verify their email address before they are allowed to log in?" ResourceKey="RequireConfirmedEmail">Require Verified Email?</Label>
|
||||
<div class="col-sm-9">
|
||||
@@ -96,433 +127,414 @@ else
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="profileurl" HelpText="Optionally provide a custom profile url" ResourceKey="ProfileUrl">Profile Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="profileurl" class="form-control" @bind="@_profileurl" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="cookiename" HelpText="You can choose to use a custom authentication cookie name for each site. However please be aware that if you want to share an authentication cookie between sites on the same domain they need to use a consistent cookie name. Also be aware that changing the authentication cookie name will logout all current users." ResourceKey="CookieName">Cookie Name:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="cookiename" class="form-control" @bind="@_cookiename" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="cookiedomain" HelpText="If you would like to share cookies across subdomains you will need to specify a root domain with a leading dot (ie. '.example.com')" ResourceKey="CookieDomain">Cookie Domain:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="cookiedomain" class="form-control" @bind="@_cookiedomain" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="cookieexpiration" HelpText="You can choose to use a custom authentication cookie expiration timespan for each site (e.g. '08:00:00' for 8 hours). The default is 14 days if not specified." ResourceKey="CookieExpiration">Cookie Expiration Timespan:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="cookieexpiration" class="form-control" @bind="@_cookieexpiration" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="alwaysremember" HelpText="Enabling this option will set a permanent cookie in conjunction with the Cookie Expiration Timespan, which will automatically sign in users the next time they visit the site. By default the site will use session cookies." ResourceKey="AlwaysRemember">Always Remember User?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="alwaysremember" class="form-select" @bind="@_alwaysremember">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="logouteverywhere" HelpText="Do you want users to be logged out of every active session on any device, or only their current session?" ResourceKey="LogoutEverywhere">Logout Everywhere?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="logouteverywhere" class="form-select" @bind="@_logouteverywhere">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="Password" Heading="Password Settings" ResourceKey="PasswordSettings">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="minimumlength" HelpText="The Minimum Length For A Password" ResourceKey="RequiredLength">Minimum Length:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="minimumlength" class="form-control" @bind="@_minimumlength" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="uniquecharacters" HelpText="The Minimum Number Of Unique Characters Which A Password Must Contain" ResourceKey="UniqueCharacters">Unique Characters:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="uniquecharacters" class="form-control" @bind="@_uniquecharacters" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requiredigit" HelpText="Indicate If Passwords Must Contain A Digit" ResourceKey="RequireDigit">Require Digit?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requiredigit" class="form-select" @bind="@_requiredigit" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requireupper" HelpText="Indicate If Passwords Must Contain An Upper Case Character" ResourceKey="RequireUpper">Require Uppercase?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requireupper" class="form-select" @bind="@_requireupper" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requirelower" HelpText="Indicate If Passwords Must Contain A Lower Case Character" ResourceKey="RequireLower">Require Lowercase?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requirelower" class="form-select" @bind="@_requirelower" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requirepunctuation" HelpText="Indicate if Passwords Must Contain A Non-alphanumeric Character (ie. Punctuation)" ResourceKey="RequirePunctuation">Require Punctuation?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requirepunctuation" class="form-select" @bind="@_requirepunctuation" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="Lockout" Heading="Lockout Settings" ResourceKey="LockoutSettings">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="maximum" HelpText="The Maximum Number Of Sign In Attempts Before A User Is Locked Out" ResourceKey="MaximumFailures">Maximum Failures:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="maximum" class="form-control" @bind="@_maximumfailures" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="lockoutduration" HelpText="The Number Of Minutes A User Should Be Locked Out" ResourceKey="LockoutDuration">Lockout Duration:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="lockoutduration" class="form-control" @bind="@_lockoutduration" required />
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="ExternalLogin" Heading="External Login Settings" ResourceKey="ExternalLoginSettings">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="provider" HelpText="Select the external login provider" ResourceKey="Provider">Provider:</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<select id="provider" class="form-select" value="@_provider" @onchange="(e => ProviderChanged(e))">
|
||||
@foreach (var provider in Shared.ExternalLoginProviders.Providers)
|
||||
{
|
||||
<option value="@provider.Name">@Localizer[provider.Name]</option>
|
||||
}
|
||||
</select>
|
||||
@if (!string.IsNullOrEmpty(_providerurl))
|
||||
{
|
||||
<a href="@_providerurl" class="btn btn-secondary" target="_new">@Localizer["Info"]</a>
|
||||
}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="providertype" HelpText="Select the external login provider type" ResourceKey="ProviderType">Provider Type:</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="providertype" class="form-select" value="@_providertype" @onchange="(e => ProviderTypeChanged(e))">
|
||||
<option value="" selected><@Localizer["Not Specified"]></option>
|
||||
<option value="@AuthenticationProviderTypes.OpenIDConnect">@Localizer["OIDC"]</option>
|
||||
<option value="@AuthenticationProviderTypes.OAuth2">@Localizer["OAuth2"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (_providertype != "")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="twofactor" HelpText="Do you want users to use two factor authentication? Note that you should use the Disabled option until you have successfully verified that the Notification Job in Scheduled Jobs is enabled and your SMTP options in Site Settings are configured or else you will lock yourself out." ResourceKey="TwoFactor">Two Factor Authentication?</Label>
|
||||
<Label Class="col-sm-3" For="providername" HelpText="Specify a friendly name for the external login provider which will be displayed on the Login page" ResourceKey="ProviderName">Provider Name:</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="twofactor" class="form-select" @bind="@_twofactor">
|
||||
<option value="false">@Localizer["Disabled"]</option>
|
||||
<option value="true">@Localizer["Optional"]</option>
|
||||
<option value="required">@Localizer["Required"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="cookiename" HelpText="You can choose to use a custom authentication cookie name for each site. However please be aware that if you want to share an authentication cookie between sites on the same domain they need to use a consistent cookie name. Also be aware that changing the authentication cookie name will logout all current users." ResourceKey="CookieName">Cookie Name:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="cookiename" class="form-control" @bind="@_cookiename" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="cookiedomain" HelpText="If you would like to share cookies across subdomains you will need to specify a root domain with a leading dot (ie. '.example.com')" ResourceKey="CookieDomain">Cookie Domain:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="cookiedomain" class="form-control" @bind="@_cookiedomain" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="cookieexpiration" HelpText="You can choose to use a custom authentication cookie expiration timespan for each site (e.g. '08:00:00' for 8 hours). The default is 14 days if not specified." ResourceKey="CookieExpiration">Cookie Expiration Timespan:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="cookieexpiration" class="form-control" @bind="@_cookieexpiration" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="alwaysremember" HelpText="Enabling this option will set a permanent cookie in conjunction with the Cookie Expiration Timespan, which will automatically sign in users the next time they visit the site. By default the site will use session cookies." ResourceKey="AlwaysRemember">Always Remember User?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="alwaysremember" class="form-select" @bind="@_alwaysremember">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="logouteverywhere" HelpText="Do you want users to be logged out of every active session on any device, or only their current session?" ResourceKey="LogoutEverywhere">Logout Everywhere?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="logouteverywhere" class="form-select" @bind="@_logouteverywhere">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
<input id="providername" class="form-control" @bind="@_providername" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</Section>
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
<Section Name="Password" Heading="Password Settings" ResourceKey="PasswordSettings">
|
||||
@if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="minimumlength" HelpText="The Minimum Length For A Password" ResourceKey="RequiredLength">Minimum Length:</Label>
|
||||
<Label Class="col-sm-3" For="authority" HelpText="The Authority Url or Issuer Url associated with the OpenID Connect provider" ResourceKey="Authority">Authority:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="minimumlength" class="form-control" @bind="@_minimumlength" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="uniquecharacters" HelpText="The Minimum Number Of Unique Characters Which A Password Must Contain" ResourceKey="UniqueCharacters">Unique Characters:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="uniquecharacters" class="form-control" @bind="@_uniquecharacters" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requiredigit" HelpText="Indicate If Passwords Must Contain A Digit" ResourceKey="RequireDigit">Require Digit?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requiredigit" class="form-select" @bind="@_requiredigit" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requireupper" HelpText="Indicate If Passwords Must Contain An Upper Case Character" ResourceKey="RequireUpper">Require Uppercase?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requireupper" class="form-select" @bind="@_requireupper" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requirelower" HelpText="Indicate If Passwords Must Contain A Lower Case Character" ResourceKey="RequireLower">Require Lowercase?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requirelower" class="form-select" @bind="@_requirelower" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
<input id="authority" class="form-control" @bind="@_authority" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requirepunctuation" HelpText="Indicate if Passwords Must Contain A Non-alphanumeric Character (ie. Punctuation)" ResourceKey="RequirePunctuation">Require Punctuation?</Label>
|
||||
<Label Class="col-sm-3" For="metadataurl" HelpText="The discovery endpoint for obtaining metadata for this provider. Only specify if the OpenID Connect provider does not use the standard approach (ie. /.well-known/openid-configuration)" ResourceKey="MetadataUrl">Metadata Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requirepunctuation" class="form-select" @bind="@_requirepunctuation" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
<input id="metadataurl" class="form-control" @bind="@_metadataurl" />
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="Lockout" Heading="Lockout Settings" ResourceKey="LockoutSettings">
|
||||
</div>
|
||||
}
|
||||
@if (_providertype == AuthenticationProviderTypes.OAuth2)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="maximum" HelpText="The Maximum Number Of Sign In Attempts Before A User Is Locked Out" ResourceKey="MaximumFailures">Maximum Failures:</Label>
|
||||
<Label Class="col-sm-3" For="authorizationurl" HelpText="The endpoint for obtaining an Authorization Code" ResourceKey="AuthorizationUrl">Authorization Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="maximum" class="form-control" @bind="@_maximumfailures" required />
|
||||
<input id="authorizationurl" class="form-control" @bind="@_authorizationurl" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="lockoutduration" HelpText="The Number Of Minutes A User Should Be Locked Out" ResourceKey="LockoutDuration">Lockout Duration:</Label>
|
||||
<Label Class="col-sm-3" For="tokenurl" HelpText="The endpoint for obtaining an Auth Token" ResourceKey="TokenUrl">Token Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="lockoutduration" class="form-control" @bind="@_lockoutduration" required />
|
||||
<input id="tokenurl" class="form-control" @bind="@_tokenurl" />
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="ExternalLogin" Heading="External Login Settings" ResourceKey="ExternalLoginSettings">
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="provider" HelpText="Select the external login provider" ResourceKey="Provider">Provider:</Label>
|
||||
<Label Class="col-sm-3" For="userinfourl" HelpText="The endpoint for obtaining user information. This should be an API or Page Url which contains the users email address." ResourceKey="UserInfoUrl">User Info Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="userinfourl" class="form-control" @bind="@_userinfourl" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_providertype != "")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="clientid" HelpText="The Client ID from the provider" ResourceKey="ClientID">Client ID:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="clientid" class="form-control" @bind="@_clientid" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="clientsecret" HelpText="The Client Secret from the provider" ResourceKey="ClientSecret">Client Secret:</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<select id="provider" class="form-select" value="@_provider" @onchange="(e => ProviderChanged(e))">
|
||||
@foreach (var provider in Shared.ExternalLoginProviders.Providers)
|
||||
{
|
||||
<option value="@provider.Name">@Localizer[provider.Name]</option>
|
||||
}
|
||||
</select>
|
||||
@if (!string.IsNullOrEmpty(_providerurl))
|
||||
{
|
||||
<a href="@_providerurl" class="btn btn-secondary" target="_new">@Localizer["Info"]</a>
|
||||
}
|
||||
<input type="@_clientsecrettype" id="clientsecret" class="form-control" @bind="@_clientsecret" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@ToggleClientSecret">@_toggleclientsecret</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="providertype" HelpText="Select the external login provider type" ResourceKey="ProviderType">Provider Type:</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="providertype" class="form-select" value="@_providertype" @onchange="(e => ProviderTypeChanged(e))">
|
||||
<option value="" selected><@Localizer["Not Specified"]></option>
|
||||
<option value="@AuthenticationProviderTypes.OpenIDConnect">@Localizer["OIDC"]</option>
|
||||
<option value="@AuthenticationProviderTypes.OAuth2">@Localizer["OAuth2"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (_providertype != "")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="providername" HelpText="Specify a friendly name for the external login provider which will be displayed on the Login page" ResourceKey="ProviderName">Provider Name:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="providername" class="form-control" @bind="@_providername" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="authority" HelpText="The Authority Url or Issuer Url associated with the OpenID Connect provider" ResourceKey="Authority">Authority:</Label>
|
||||
<Label Class="col-sm-3" For="authresponsetype" HelpText="Specify the authorization response type. The default is Authorization Code which is considered to be the most secure option based on the latest OAuth specification." ResourceKey="AuthResponseType">Authorization Response Type:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="authority" class="form-control" @bind="@_authority" />
|
||||
<select id="authresponsetype" class="form-select" @bind="@_authresponsetype" required>
|
||||
<option value="code">@Localizer["AuthFlow.Code"]</option>
|
||||
<option value="code id_token">@Localizer["AuthFlow.CodeIdToken"]</option>
|
||||
<option value="code id_token token">@Localizer["AuthFlow.CodeIdTokenToken"]</option>
|
||||
<option value="code token">@Localizer["AuthFlow.CodeToken"]</option>
|
||||
<option value="id_token">@Localizer["AuthFlow.IdToken"]</option>
|
||||
<option value="id_token token">@Localizer["AuthFlow.IdTokenToken"]</option>
|
||||
<option value="token">@Localizer["AuthFlow.Token"]</option>
|
||||
<option value="none">@Localizer["AuthFlow.None"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="metadataurl" HelpText="The discovery endpoint for obtaining metadata for this provider. Only specify if the OpenID Connect provider does not use the standard approach (ie. /.well-known/openid-configuration)" ResourceKey="MetadataUrl">Metadata Url:</Label>
|
||||
<Label Class="col-sm-3" For="requirenonce" HelpText="Specify if Nonce validation is required for the ID token (the default is true)" ResourceKey="RequireNonce">Require Nonce?</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="metadataurl" class="form-control" @bind="@_metadataurl" />
|
||||
<select id="requirenonce" class="form-select" @bind="@_requirenonce" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="singlelogout" HelpText="Specify if users should be logged out of both the application and provider (the default is false indicating they will only be logged out of the application)" ResourceKey="SingleLogout">Use Single Logout?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="singlelogout" class="form-select" @bind="@_singlelogout" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_providertype == AuthenticationProviderTypes.OAuth2)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="authorizationurl" HelpText="The endpoint for obtaining an Authorization Code" ResourceKey="AuthorizationUrl">Authorization Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="authorizationurl" class="form-control" @bind="@_authorizationurl" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="tokenurl" HelpText="The endpoint for obtaining an Auth Token" ResourceKey="TokenUrl">Token Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="tokenurl" class="form-control" @bind="@_tokenurl" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="userinfourl" HelpText="The endpoint for obtaining user information. This should be an API or Page Url which contains the users email address." ResourceKey="UserInfoUrl">User Info Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="userinfourl" class="form-control" @bind="@_userinfourl" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_providertype != "")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="clientid" HelpText="The Client ID from the provider" ResourceKey="ClientID">Client ID:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="clientid" class="form-control" @bind="@_clientid" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="clientsecret" HelpText="The Client Secret from the provider" ResourceKey="ClientSecret">Client Secret:</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input type="@_clientsecrettype" id="clientsecret" class="form-control" @bind="@_clientsecret" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@ToggleClientSecret">@_toggleclientsecret</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="authresponsetype" HelpText="Specify the authorization response type. The default is Authorization Code which is considered to be the most secure option based on the latest OAuth specification." ResourceKey="AuthResponseType">Authorization Response Type:</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="authresponsetype" class="form-select" @bind="@_authresponsetype" required>
|
||||
<option value="code">@Localizer["AuthFlow.Code"]</option>
|
||||
<option value="code id_token">@Localizer["AuthFlow.CodeIdToken"]</option>
|
||||
<option value="code id_token token">@Localizer["AuthFlow.CodeIdTokenToken"]</option>
|
||||
<option value="code token">@Localizer["AuthFlow.CodeToken"]</option>
|
||||
<option value="id_token">@Localizer["AuthFlow.IdToken"]</option>
|
||||
<option value="id_token token">@Localizer["AuthFlow.IdTokenToken"]</option>
|
||||
<option value="token">@Localizer["AuthFlow.Token"]</option>
|
||||
<option value="none">@Localizer["AuthFlow.None"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requirenonce" HelpText="Specify if Nonce validation is required for the ID token (the default is true)" ResourceKey="RequireNonce">Require Nonce?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requirenonce" class="form-select" @bind="@_requirenonce" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="singlelogout" HelpText="Specify if users should be logged out of both the application and provider (the default is false indicating they will only be logged out of the application)" ResourceKey="SingleLogout">Use Single Logout?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="singlelogout" class="form-select" @bind="@_singlelogout" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="scopes" HelpText="A list of Scopes to request from the provider (separated by commas). If none are specified, standard Scopes will be used by default." ResourceKey="Scopes">Scopes:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="scopes" class="form-control" @bind="@_scopes" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="parameters" HelpText="Optionally specify any additional parameters as name/value pairs to send to the provider (separated by commas if there are multiple)." ResourceKey="Parameters">Parameters:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="parameters" class="form-control" @bind="@_parameters" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="pkce" HelpText="Indicate if the provider supports Proof Key for Code Exchange (PKCE)" ResourceKey="PKCE">Use PKCE?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="pkce" class="form-select" @bind="@_pkce" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="redirecturl" HelpText="The Redirect Url (or Callback Url) which usually needs to be registered with the provider" ResourceKey="RedirectUrl">Redirect Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="redirecturl" class="form-control" @bind="@_redirecturl" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="reviewclaims" HelpText="This option will record the full list of Claims returned by the Provider in the Event Log. It should only be used for testing purposes. External Login will be restricted when this option is enabled." ResourceKey="ReviewClaims">Review Claims?</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<select id="reviewclaims" class="form-select" @bind="@_reviewclaims" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
@if (_reviewclaims == "true")
|
||||
{
|
||||
<a href="@_externalloginurl" target="_blank" class="btn btn-secondary">@SharedLocalizer["Test"]</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="identifierclaimtype" HelpText="Specify the type name of the unique user identifier claim provided by the provider. The default value is 'sub'." ResourceKey="IdentifierClaimType">Identifier Claim:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="identifierclaimtype" class="form-control" @bind="@_identifierclaimtype" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="nameclaimtype" HelpText="Optionally specify the type name of the user's name claim provided by the provider. The typical value is 'name'." ResourceKey="NameClaimType">Name Claim:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="nameclaimtype" class="form-control" @bind="@_nameclaimtype" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="emailclaimtype" HelpText="Optionally specify the type name of the email address claim provided by the provider. The typical value is 'email'," ResourceKey="EmailClaimType">Email Claim:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="emailclaimtype" class="form-control" @bind="@_emailclaimtype" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="roleclaimtype" HelpText="The name of the roles claim provided by the provider" ResourceKey="RoleClaimType">Roles Claim:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="roleclaimtype" class="form-control" @bind="@_roleclaimtype" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="roleclaimmappings" HelpText="Optionally provide a comma delimited list of role names provided by the identity provider, as well as mappings to your site roles." ResourceKey="RoleClaimMappings">Role Claim Mappings:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="roleclaimmappings" class="form-control" @bind="@_roleclaimmappings" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="synchronizeroles" HelpText="This option will add or remove role assignments so that the site roles exactly match the roles provided by the identity provider" ResourceKey="SynchronizeRoles">Synchronize Roles?</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<select id="synchronizeroles" class="form-select" @bind="@_synchronizeroles" required>
|
||||
<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="profileclaimtypes" HelpText="A comma delimited list of user profile claims provided by the provider, as well as mappings to your user profile definition. For example if the provider includes a 'given_name' claim and you have a 'FirstName' user profile definition you should specify 'given_name:FirstName'." ResourceKey="ProfileClaimTypes">User Profile Claims:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="profileclaimtypes" class="form-control" @bind="@_profileclaimtypes" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="savetokens" HelpText="Specify whether access and refresh tokens should be saved after a successful login. The default is false to reduce the size of the authentication cookie." ResourceKey="SaveTokens">Save Tokens?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="savetokens" class="form-select" @bind="@_savetokens" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="domainfilter" HelpText="Provide any email domain filter criteria (separated by commas). Domains to exclude should be prefixed with an exclamation point (!). For example 'microsoft.com,!hotmail.com' would include microsoft.com email addresses but not hotmail.com email addresses." ResourceKey="DomainFilter">Domain Filter:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="domainfilter" class="form-control" @bind="@_domainfilter" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="createusers" HelpText="Do you want new users to be created automatically? If you disable this option, users must already be registered on the site in order to sign in with their external login." ResourceKey="CreateUsers">Create New Users?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="createusers" class="form-select" @bind="@_createusers">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="verifyusers" HelpText="Do you want existing users to perform an additional email verification step to link their external login? If you disable this option, existing users will be linked automatically." ResourceKey="VerifyUsers">Verify Existing Users?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="verifyusers" class="form-select" @bind="@_verifyusers">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="allowhostrole" HelpText="Indicate if host roles are supported from the identity provider. Please use caution with this option as it allows the host user to administrate every site within your installation." ResourceKey="AllowHostRole">Allow Host Role?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="allowhostrole" class="form-select" @bind="@_allowhostrole" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="allowsitelogin" HelpText="Do you want to allow users to sign in using a username and password that is managed locally on this site? Note that you should only disable this option if you have already sucessfully configured an external login provider, or else you may lock yourself out of the site." ResourceKey="AllowSiteLogin">Allow Local Login?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="allowsitelogin" class="form-select" @bind="@_allowsitelogin">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</Section>
|
||||
<Section Name="Token" Heading="Token Settings" ResourceKey="TokenSettings">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="jwtsecret" HelpText="If you want to want to provide API access, please specify a secret which will be used to encrypt your tokens. The secret should be 16 characters or more to ensure optimal security. Please note that if you change this secret, all existing tokens will become invalid and will need to be regenerated." ResourceKey="Secret">Secret:</Label>
|
||||
<Label Class="col-sm-3" For="scopes" HelpText="A list of Scopes to request from the provider (separated by commas). If none are specified, standard Scopes will be used by default." ResourceKey="Scopes">Scopes:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="scopes" class="form-control" @bind="@_scopes" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="parameters" HelpText="Optionally specify any additional parameters as name/value pairs to send to the provider (separated by commas if there are multiple)." ResourceKey="Parameters">Parameters:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="parameters" class="form-control" @bind="@_parameters" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="pkce" HelpText="Indicate if the provider supports Proof Key for Code Exchange (PKCE)" ResourceKey="PKCE">Use PKCE?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="pkce" class="form-select" @bind="@_pkce" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="redirecturl" HelpText="The Redirect Url (or Callback Url) which usually needs to be registered with the provider" ResourceKey="RedirectUrl">Redirect Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="redirecturl" class="form-control" @bind="@_redirecturl" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="reviewclaims" HelpText="This option will record the full list of Claims returned by the Provider in the Event Log. It should only be used for testing purposes. External Login will be restricted when this option is enabled." ResourceKey="ReviewClaims">Review Claims?</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input type="@_secrettype" id="jwtsecret" class="form-control" @bind="@_secret" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@ToggleSecret">@_togglesecret</button>
|
||||
<select id="reviewclaims" class="form-select" @bind="@_reviewclaims" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
@if (_reviewclaims == "true")
|
||||
{
|
||||
<a href="@_externalloginurl" target="_blank" class="btn btn-secondary">@SharedLocalizer["Test"]</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="issuer" HelpText="Optionally provide the issuer of the token" ResourceKey="Issuer">Issuer:</Label>
|
||||
<Label Class="col-sm-3" For="identifierclaimtype" HelpText="Specify the type name of the unique user identifier claim provided by the provider. The default value is 'sub'." ResourceKey="IdentifierClaimType">Identifier Claim:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="issuer" class="form-control" @bind="@_issuer" />
|
||||
<input id="identifierclaimtype" class="form-control" @bind="@_identifierclaimtype" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="audience" HelpText="Optionally provide the audience for the token" ResourceKey="Audience">Audience:</Label>
|
||||
<Label Class="col-sm-3" For="nameclaimtype" HelpText="Optionally specify the type name of the user's name claim provided by the provider. The typical value is 'name'." ResourceKey="NameClaimType">Name Claim:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="audience" class="form-control" @bind="@_audience" />
|
||||
<input id="nameclaimtype" class="form-control" @bind="@_nameclaimtype" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="lifetime" HelpText="The number of minutes for which a token should be valid" ResourceKey="Lifetime">Lifetime:</Label>
|
||||
<Label Class="col-sm-3" For="emailclaimtype" HelpText="Optionally specify the type name of the email address claim provided by the provider. The typical value is 'email'," ResourceKey="EmailClaimType">Email Claim:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="lifetime" class="form-control" @bind="@_lifetime" />
|
||||
<input id="emailclaimtype" class="form-control" @bind="@_emailclaimtype" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="token" HelpText="Select the Create Token button to generate a long-lived access token (valid for 1 year). Be sure to store this token in a safe location as you will not be able to access it in the future." ResourceKey="Token">Access Token:</Label>
|
||||
<Label Class="col-sm-3" For="roleclaimtype" HelpText="The name of the roles claim provided by the provider" ResourceKey="RoleClaimType">Roles Claim:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="roleclaimtype" class="form-control" @bind="@_roleclaimtype" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="roleclaimmappings" HelpText="Optionally provide a comma delimited list of role names provided by the identity provider, as well as mappings to your site roles." ResourceKey="RoleClaimMappings">Role Claim Mappings:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="roleclaimmappings" class="form-control" @bind="@_roleclaimmappings" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="synchronizeroles" HelpText="This option will add or remove role assignments so that the site roles exactly match the roles provided by the identity provider" ResourceKey="SynchronizeRoles">Synchronize Roles?</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="token" class="form-control" @bind="@_token" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@CreateToken">@Localizer["CreateToken"]</button>
|
||||
<select id="synchronizeroles" class="form-select" @bind="@_synchronizeroles" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
}
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="profileclaimtypes" HelpText="A comma delimited list of user profile claims provided by the provider, as well as mappings to your user profile definition. For example if the provider includes a 'given_name' claim and you have a 'FirstName' user profile definition you should specify 'given_name:FirstName'." ResourceKey="ProfileClaimTypes">User Profile Claims:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="profileclaimtypes" class="form-control" @bind="@_profileclaimtypes" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="savetokens" HelpText="Specify whether access and refresh tokens should be saved after a successful login. The default is false to reduce the size of the authentication cookie." ResourceKey="SaveTokens">Save Tokens?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="savetokens" class="form-select" @bind="@_savetokens" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="domainfilter" HelpText="Provide any email domain filter criteria (separated by commas). Domains to exclude should be prefixed with an exclamation point (!). For example 'microsoft.com,!hotmail.com' would include microsoft.com email addresses but not hotmail.com email addresses." ResourceKey="DomainFilter">Domain Filter:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="domainfilter" class="form-control" @bind="@_domainfilter" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="createusers" HelpText="Do you want new users to be created automatically? If you disable this option, users must already be registered on the site in order to sign in with their external login." ResourceKey="CreateUsers">Create New Users?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="createusers" class="form-select" @bind="@_createusers">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="verifyusers" HelpText="Do you want existing users to perform an additional email verification step to link their external login? If you disable this option, existing users will be linked automatically." ResourceKey="VerifyUsers">Verify Existing Users?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="verifyusers" class="form-select" @bind="@_verifyusers">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="allowhostrole" HelpText="Indicate if host roles are supported from the identity provider. Please use caution with this option as it allows the host user to administrate every site within your installation." ResourceKey="AllowHostRole">Allow Host Role?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="allowhostrole" class="form-select" @bind="@_allowhostrole" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</Section>
|
||||
<Section Name="Token" Heading="Token Settings" ResourceKey="TokenSettings">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="jwtsecret" HelpText="If you want to want to provide API access, please specify a secret which will be used to encrypt your tokens. The secret should be 16 characters or more to ensure optimal security. Please note that if you change this secret, all existing tokens will become invalid and will need to be regenerated." ResourceKey="Secret">Secret:</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input type="@_secrettype" id="jwtsecret" class="form-control" @bind="@_secret" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@ToggleSecret">@_togglesecret</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="issuer" HelpText="Optionally provide the issuer of the token" ResourceKey="Issuer">Issuer:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="issuer" class="form-control" @bind="@_issuer" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="audience" HelpText="Optionally provide the audience for the token" ResourceKey="Audience">Audience:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="audience" class="form-control" @bind="@_audience" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="lifetime" HelpText="The number of minutes for which a token should be valid" ResourceKey="Lifetime">Lifetime:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="lifetime" class="form-control" @bind="@_lifetime" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="token" HelpText="Select the Create Token button to generate a long-lived access token (valid for 1 year). Be sure to store this token in a safe location as you will not be able to access it in the future." ResourceKey="Token">Access Token:</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="token" class="form-control" @bind="@_token" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@CreateToken">@Localizer["CreateToken"]</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||
@@ -533,12 +545,16 @@ else
|
||||
@code {
|
||||
private List<UserRole> users;
|
||||
private string _deleted = "false";
|
||||
private int _page = 1;
|
||||
|
||||
private string _allowsitelogin;
|
||||
private string _twofactor;
|
||||
private string _loginlink;
|
||||
private string _passkeys;
|
||||
private string _allowregistration;
|
||||
private string _registerurl;
|
||||
private string _profileurl;
|
||||
private string _requireconfirmedemail;
|
||||
private string _twofactor;
|
||||
private string _profileurl;
|
||||
private string _cookiename;
|
||||
private string _cookiedomain;
|
||||
private string _cookieexpiration;
|
||||
@@ -588,7 +604,6 @@ else
|
||||
private string _createusers;
|
||||
private string _verifyusers;
|
||||
private string _allowhostrole;
|
||||
private string _allowsitelogin;
|
||||
|
||||
private string _secret;
|
||||
private string _secrettype = "password";
|
||||
@@ -609,13 +624,16 @@ else
|
||||
|
||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
_allowregistration = PageState.Site.AllowRegistration.ToString().ToLower();
|
||||
_registerurl = SettingService.GetSetting(settings, "LoginOptions:RegisterUrl", "");
|
||||
_profileurl = SettingService.GetSetting(settings, "LoginOptions:ProfileUrl", "");
|
||||
_requireconfirmedemail = SettingService.GetSetting(settings, "LoginOptions:RequireConfirmedEmail", "true");
|
||||
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
_allowsitelogin = SettingService.GetSetting(settings, "LoginOptions:AllowSiteLogin", "true");
|
||||
_twofactor = SettingService.GetSetting(settings, "LoginOptions:TwoFactor", "false");
|
||||
_loginlink = SettingService.GetSetting(settings, "LoginOptions:LoginLink", "false");
|
||||
_passkeys = SettingService.GetSetting(settings, "LoginOptions:Passkeys", "false");
|
||||
_registerurl = SettingService.GetSetting(settings, "LoginOptions:RegisterUrl", "");
|
||||
_requireconfirmedemail = SettingService.GetSetting(settings, "LoginOptions:RequireConfirmedEmail", "true");
|
||||
_profileurl = SettingService.GetSetting(settings, "LoginOptions:ProfileUrl", "");
|
||||
_cookiename = SettingService.GetSetting(settings, "LoginOptions:CookieName", ".AspNetCore.Identity.Application");
|
||||
_cookiedomain = SettingService.GetSetting(settings, "LoginOptions:CookieDomain", "");
|
||||
_cookieexpiration = SettingService.GetSetting(settings, "LoginOptions:CookieExpiration", "");
|
||||
@@ -677,7 +695,6 @@ else
|
||||
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
|
||||
_verifyusers = SettingService.GetSetting(settings, "ExternalLogin:VerifyUsers", "true");
|
||||
_allowhostrole = SettingService.GetSetting(settings, "ExternalLogin:AllowHostRole", "false");
|
||||
_allowsitelogin = SettingService.GetSetting(settings, "LoginOptions:AllowSiteLogin", "true");
|
||||
}
|
||||
|
||||
private async Task LoadUsersAsync()
|
||||
@@ -742,18 +759,21 @@ else
|
||||
{
|
||||
try
|
||||
{
|
||||
var site = PageState.Site;
|
||||
site.AllowRegistration = bool.Parse(_allowregistration);
|
||||
await SiteService.UpdateSiteAsync(site);
|
||||
|
||||
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
||||
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:RegisterUrl", _registerurl, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:ProfileUrl", _profileurl, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:RequireConfirmedEmail", _requireconfirmedemail, false);
|
||||
var site = PageState.Site;
|
||||
site.AllowRegistration = bool.Parse(_allowregistration);
|
||||
await SiteService.UpdateSiteAsync(site);
|
||||
|
||||
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
||||
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:AllowSiteLogin", _allowsitelogin, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:LoginLink", _loginlink, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:Passkeys", _passkeys, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:RegisterUrl", _registerurl, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:RequireConfirmedEmail", _requireconfirmedemail, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:ProfileUrl", _profileurl, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:CookieDomain", _cookiedomain, true);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true);
|
||||
@@ -799,16 +819,15 @@ else
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:VerifyUsers", _verifyusers, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:AllowHostRole", _allowhostrole, true);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:AllowSiteLogin", _allowsitelogin, false);
|
||||
|
||||
settings = SettingService.SetSetting(settings, "JwtOptions:Secret", _secret, true);
|
||||
settings = SettingService.SetSetting(settings, "JwtOptions:Issuer", _issuer, true);
|
||||
settings = SettingService.SetSetting(settings, "JwtOptions:Audience", _audience, true);
|
||||
settings = SettingService.SetSetting(settings, "JwtOptions:Lifetime", _lifetime, true);
|
||||
}
|
||||
|
||||
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
|
||||
await SettingService.ClearSiteSettingsCacheAsync();
|
||||
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
|
||||
await SettingService.ClearSiteSettingsCacheAsync();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(_secret))
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -163,6 +169,9 @@
|
||||
[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
|
||||
|
||||
@@ -381,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();
|
||||
|
||||
@@ -490,11 +522,6 @@
|
||||
tokenSource.Dispose();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_message = string.Format(Localizer["Message.File.Restricted"], restricted);
|
||||
_messagetype = MessageType.Warning;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
@if (_style == MessageStyle.Toast)
|
||||
{
|
||||
<div class="app-modulemessage-toast bottom-0 end-0">
|
||||
<div class="@_classname alert-dismissible fade show mb-3" role="alert">
|
||||
<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))
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
<div @ref="@_editorElement"></div>
|
||||
<div @ref="@_editorElement" class="app-editor-resizable"></div>
|
||||
</div>
|
||||
</div>
|
||||
</TabPanel>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
@using System.Text.RegularExpressions
|
||||
@using Radzen
|
||||
@using Radzen.Blazor
|
||||
@using System.Reflection
|
||||
|
||||
@namespace Oqtane.Modules.Controls
|
||||
@inherits ModuleControlBase
|
||||
@@ -17,7 +18,7 @@
|
||||
<RadzenTheme Theme="@RadzenEditorDefinitions.DefaultTheme" />
|
||||
<RadzenComponents />
|
||||
<RadzenHtmlEditor @ref="_editor" Visible="_visible" Placeholder="@Placeholder" style="@($"height: {Height}px;")"
|
||||
@bind-Value="_value" Execute="OnExecute" class="rz-text-editor">
|
||||
@bind-Value="_value" Execute="OnExecute" class="rz-text-editor app-editor-resizable">
|
||||
<ChildContent>
|
||||
@_toolbar
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||
@@ -93,6 +94,17 @@
|
||||
}
|
||||
await _interop.SetBackgroundColor(_editor.Element, backgroundColor);
|
||||
}
|
||||
|
||||
var subscribers = GetEventSubscribers(DialogService, "OnOpen");
|
||||
var dialogSubscibers = subscribers?.Where(s => s.Method.DeclaringType == typeof(RadzenDialog)) ?? Enumerable.Empty<Delegate>();
|
||||
if (dialogSubscibers.Count() > 1)
|
||||
{
|
||||
//clean the event to avoid multiple RadzenDialog instances subscribing to the event
|
||||
dialogSubscibers.Skip(1).ToList().ForEach(s =>
|
||||
{
|
||||
DialogService.OnOpen -= s as Action<string, Type, Dictionary<string, object>, DialogOptions>;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,4 +224,23 @@
|
||||
{
|
||||
await _interop.UpdateDialogLayout(_editor.Element);
|
||||
}
|
||||
|
||||
private Delegate[] GetEventSubscribers(object target, string eventName)
|
||||
{
|
||||
var type = target.GetType();
|
||||
var eventField = type.GetField(eventName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
|
||||
if (eventField == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var eventDelegate = eventField.GetValue(target) as Delegate;
|
||||
if (eventDelegate == null)
|
||||
{
|
||||
return new Delegate[0];
|
||||
}
|
||||
|
||||
return eventDelegate.GetInvocationList();
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ namespace Oqtane.Modules.Controls
|
||||
{ "FormatBlock", (builder, sequence) => CreateFragment(builder, sequence, "FormatBlock", "RadzenHtmlEditorFormatBlock") },
|
||||
{ "Indent", (builder, sequence) => CreateFragment(builder, sequence, "Indent", "RadzenHtmlEditorIndent") },
|
||||
{ "InsertImage", (builder, sequence) => CreateFragment(builder, sequence, "InsertImage", "RadzenHtmlEditorCustomTool", "InsertImage", "image") },
|
||||
{ "Bold", (builder, sequence) => CreateFragment(builder, sequence, "Italic", "RadzenHtmlEditorBold") },
|
||||
{ "Italic", (builder, sequence) => CreateFragment(builder, sequence, "Italic", "RadzenHtmlEditorItalic") },
|
||||
{ "Justify", (builder, sequence) => CreateFragment(builder, sequence, "Justify", "RadzenHtmlEditorJustify") },
|
||||
{ "Link", (builder, sequence) => CreateFragment(builder, sequence, "InsertLink", "RadzenHtmlEditorCustomTool", "InsertLink", "insert_link") },
|
||||
|
||||
@@ -31,8 +31,8 @@
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@SharedLocalizer["CreatedOn"]</th>
|
||||
<th>@SharedLocalizer["CreatedBy"]</th>
|
||||
<th>@Localizer["CreatedOn"]</th>
|
||||
<th>@Localizer["CreatedBy"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="View" Security="SecurityAccessLevel.Edit" OnClick="@(async () => await View(context))" ResourceKey="View" /></td>
|
||||
@@ -122,13 +122,9 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
htmltext = await HtmlTextService.GetHtmlTextAsync(htmltext.HtmlTextId, htmltext.ModuleId);
|
||||
if (htmltext != null)
|
||||
{
|
||||
_view = htmltext.Content;
|
||||
_view = Utilities.FormatContent(_view, PageState.Alias, "render");
|
||||
StateHasChanged();
|
||||
}
|
||||
_view = htmltext.Content;
|
||||
_view = Utilities.FormatContent(_view, PageState.Alias, "render");
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -141,19 +137,15 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
htmltext = await HtmlTextService.GetHtmlTextAsync(htmltext.HtmlTextId, ModuleState.ModuleId);
|
||||
if (htmltext != null)
|
||||
{
|
||||
var content = htmltext.Content;
|
||||
htmltext = new HtmlText();
|
||||
htmltext.ModuleId = ModuleState.ModuleId;
|
||||
htmltext.Content = content;
|
||||
await HtmlTextService.AddHtmlTextAsync(htmltext);
|
||||
await logger.LogInformation("Content Restored {HtmlText}", htmltext);
|
||||
AddModuleMessage(Localizer["Message.Content.Restored"], MessageType.Success);
|
||||
await LoadContent();
|
||||
StateHasChanged();
|
||||
}
|
||||
var content = htmltext.Content;
|
||||
htmltext = new HtmlText();
|
||||
htmltext.ModuleId = ModuleState.ModuleId;
|
||||
htmltext.Content = content;
|
||||
await HtmlTextService.AddHtmlTextAsync(htmltext);
|
||||
await logger.LogInformation("Content Restored {HtmlText}", htmltext);
|
||||
AddModuleMessage(Localizer["Message.Content.Restored"], MessageType.Success);
|
||||
await LoadContent();
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -166,15 +158,11 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
htmltext = await HtmlTextService.GetHtmlTextAsync(htmltext.HtmlTextId, ModuleState.ModuleId);
|
||||
if (htmltext != null)
|
||||
{
|
||||
await HtmlTextService.DeleteHtmlTextAsync(htmltext.HtmlTextId, htmltext.ModuleId);
|
||||
await logger.LogInformation("Content Deleted {HtmlText}", htmltext);
|
||||
AddModuleMessage(Localizer["Message.Content.Deleted"], MessageType.Success);
|
||||
await LoadContent();
|
||||
StateHasChanged();
|
||||
}
|
||||
await HtmlTextService.DeleteHtmlTextAsync(htmltext.HtmlTextId, htmltext.ModuleId);
|
||||
await logger.LogInformation("Content Deleted {HtmlText}", htmltext);
|
||||
AddModuleMessage(Localizer["Message.Content.Deleted"], MessageType.Success);
|
||||
await LoadContent();
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -7,6 +7,18 @@ using Oqtane.Shared;
|
||||
|
||||
namespace Oqtane.Modules.HtmlText.Services
|
||||
{
|
||||
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
|
||||
public interface IHtmlTextService
|
||||
{
|
||||
Task<List<Models.HtmlText>> GetHtmlTextsAsync(int moduleId);
|
||||
|
||||
Task<Models.HtmlText> GetHtmlTextAsync(int moduleId);
|
||||
|
||||
Task<Models.HtmlText> AddHtmlTextAsync(Models.HtmlText htmltext);
|
||||
|
||||
Task DeleteHtmlTextAsync(int htmlTextId, int moduleId);
|
||||
}
|
||||
|
||||
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
|
||||
public class HtmlTextService : ServiceBase, IHtmlTextService, IClientService
|
||||
{
|
||||
@@ -24,11 +36,6 @@ namespace Oqtane.Modules.HtmlText.Services
|
||||
return await GetJsonAsync<Models.HtmlText>(CreateAuthorizationPolicyUrl($"{ApiUrl}/{moduleId}", EntityNames.Module, moduleId));
|
||||
}
|
||||
|
||||
public async Task<Models.HtmlText> GetHtmlTextAsync(int htmlTextId, int moduleId)
|
||||
{
|
||||
return await GetJsonAsync<Models.HtmlText>(CreateAuthorizationPolicyUrl($"{ApiUrl}/{htmlTextId}/{moduleId}", EntityNames.Module, moduleId));
|
||||
}
|
||||
|
||||
public async Task<Models.HtmlText> AddHtmlTextAsync(Models.HtmlText htmlText)
|
||||
{
|
||||
return await PostJsonAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}", EntityNames.Module, htmlText.ModuleId), htmlText);
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Oqtane.Documentation;
|
||||
|
||||
namespace Oqtane.Modules.HtmlText.Services
|
||||
{
|
||||
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
|
||||
public interface IHtmlTextService
|
||||
{
|
||||
Task<List<Models.HtmlText>> GetHtmlTextsAsync(int moduleId);
|
||||
|
||||
Task<Models.HtmlText> GetHtmlTextAsync(int moduleId);
|
||||
|
||||
Task<Models.HtmlText> GetHtmlTextAsync(int htmlTextId, int moduleId);
|
||||
|
||||
Task<Models.HtmlText> AddHtmlTextAsync(Models.HtmlText htmltext);
|
||||
|
||||
Task DeleteHtmlTextAsync(int htmlTextId, int moduleId);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,12 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="versions" ResourceKey="Versions" ResourceType="@resourceType" HelpText="The number of content versions to preserve (note that zero means unlimited)">Versions: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="versions" type="number" min="0" max="9" step="1" class="form-control" @bind="@_versions" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -26,12 +32,14 @@
|
||||
private bool validated = false;
|
||||
|
||||
private string _dynamictokens;
|
||||
private string _versions = "5";
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
try
|
||||
{
|
||||
_dynamictokens = SettingService.GetSetting(ModuleState.Settings, "DynamicTokens", "false");
|
||||
_versions = SettingService.GetSetting(ModuleState.Settings, "Versions", "5");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -45,6 +53,10 @@
|
||||
{
|
||||
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
|
||||
settings = SettingService.SetSetting(settings, "DynamicTokens", _dynamictokens);
|
||||
if (int.TryParse(_versions, out int versions) && versions >= 0 && versions <= 9)
|
||||
{
|
||||
settings = SettingService.SetSetting(settings, "Versions", versions.ToString());
|
||||
}
|
||||
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Oqtane.Modules
|
||||
private Dictionary<string, string> _urlparameters;
|
||||
private bool _scriptsloaded = false;
|
||||
|
||||
protected Logger logger => _logger ?? (_logger = new Logger(this));
|
||||
public Logger logger => _logger ?? (_logger = new Logger(this));
|
||||
|
||||
[Inject]
|
||||
protected ILogService LoggingService { get; set; }
|
||||
@@ -372,6 +372,11 @@ namespace Oqtane.Modules
|
||||
}
|
||||
|
||||
// UI methods
|
||||
private static readonly string RenderModeBoundaryErrorMessage =
|
||||
"RenderModeBoundary is not available. This method requires a RenderModeBoundary parameter. " +
|
||||
"If you are using child components, ensure you pass the RenderModeBoundary property to the child component: " +
|
||||
"<ChildComponent RenderModeBoundary=\"RenderModeBoundary\" />";
|
||||
|
||||
public void AddModuleMessage(string message, MessageType type)
|
||||
{
|
||||
AddModuleMessage(message, type, "top");
|
||||
@@ -389,21 +394,37 @@ namespace Oqtane.Modules
|
||||
|
||||
public void AddModuleMessage(string message, MessageType type, string position, MessageStyle style)
|
||||
{
|
||||
if (RenderModeBoundary == null)
|
||||
{
|
||||
throw new InvalidOperationException(RenderModeBoundaryErrorMessage);
|
||||
}
|
||||
RenderModeBoundary.AddModuleMessage(message, type, position, style);
|
||||
}
|
||||
|
||||
public void ClearModuleMessage()
|
||||
{
|
||||
if (RenderModeBoundary == null)
|
||||
{
|
||||
throw new InvalidOperationException(RenderModeBoundaryErrorMessage);
|
||||
}
|
||||
RenderModeBoundary.AddModuleMessage("", MessageType.Undefined);
|
||||
}
|
||||
|
||||
public void ShowProgressIndicator()
|
||||
{
|
||||
if (RenderModeBoundary == null)
|
||||
{
|
||||
throw new InvalidOperationException(RenderModeBoundaryErrorMessage);
|
||||
}
|
||||
RenderModeBoundary.ShowProgressIndicator();
|
||||
}
|
||||
|
||||
public void HideProgressIndicator()
|
||||
{
|
||||
if (RenderModeBoundary == null)
|
||||
{
|
||||
throw new InvalidOperationException(RenderModeBoundaryErrorMessage);
|
||||
}
|
||||
RenderModeBoundary.HideProgressIndicator();
|
||||
}
|
||||
|
||||
@@ -460,6 +481,11 @@ namespace Oqtane.Modules
|
||||
|
||||
public string ReplaceTokens(string content, object obj)
|
||||
{
|
||||
// check for null or empty content
|
||||
if (string.IsNullOrEmpty(content))
|
||||
{
|
||||
return content;
|
||||
}
|
||||
// Using StringBuilder avoids the performance penalty of repeated string allocations
|
||||
// that occur with string.Replace or string concatenation inside loops.
|
||||
var sb = new StringBuilder();
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>Oqtane</RootNamespace>
|
||||
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
|
||||
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
|
||||
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.9" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.9" />
|
||||
<PackageReference Include="Radzen.Blazor" Version="7.4.3" />
|
||||
<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" />
|
||||
<PackageReference Include="Radzen.Blazor" Version="10.0.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -117,31 +117,58 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Name" xml:space="preserve">
|
||||
<value>Name</value>
|
||||
<data name="CaseSensitive.Text" xml:space="preserve">
|
||||
<value>Match Case?</value>
|
||||
</data>
|
||||
<data name="Add.Text" xml:space="preserve">
|
||||
<value>Add MyModule</value>
|
||||
<data name="CaseSensitive.HelpText" xml:space="preserve">
|
||||
<value>Specify if the replacement operation should be case sensitive</value>
|
||||
</data>
|
||||
<data name="Edit.Text" xml:space="preserve">
|
||||
<value>Edit</value>
|
||||
<data name="Content.Text" xml:space="preserve">
|
||||
<value>Module Content?</value>
|
||||
</data>
|
||||
<data name="Delete.Text" xml:space="preserve">
|
||||
<value>Delete</value>
|
||||
<data name="Content.HelpText" xml:space="preserve">
|
||||
<value>Specify if module content should be updated</value>
|
||||
</data>
|
||||
<data name="Delete.Header" xml:space="preserve">
|
||||
<value>Delete MyModule</value>
|
||||
<data name="Pages.Text" xml:space="preserve">
|
||||
<value>Page Info?</value>
|
||||
</data>
|
||||
<data name="Delete.Message" xml:space="preserve">
|
||||
<value>Are You Sure You Wish To Delete This MyModule?</value>
|
||||
<data name="Pages.HelpText" xml:space="preserve">
|
||||
<value>Specify if page information should be updated (ie. name, title, head content, body content settings)</value>
|
||||
</data>
|
||||
<data name="Message.DisplayNone" xml:space="preserve">
|
||||
<value>No MyModules To Display</value>
|
||||
<data name="Site.Text" xml:space="preserve">
|
||||
<value>Site Info?</value>
|
||||
</data>
|
||||
<data name="Message.LoadError" xml:space="preserve">
|
||||
<value>Error Loading MyModule</value>
|
||||
<data name="Site.HelpText" xml:space="preserve">
|
||||
<value>Specify if site information should be updated (ie. name, head content, body content, settings)</value>
|
||||
</data>
|
||||
<data name="Message.DeleteError" xml:space="preserve">
|
||||
<value>Error Deleting MyModule</value>
|
||||
<data name="Replace.Text" xml:space="preserve">
|
||||
<value>Replace With:</value>
|
||||
</data>
|
||||
<data name="Replace.HelpText" xml:space="preserve">
|
||||
<value>Specify the replacement content</value>
|
||||
</data>
|
||||
<data name="Modules.Text" xml:space="preserve">
|
||||
<value>Module Info?</value>
|
||||
</data>
|
||||
<data name="Modules.HelpText" xml:space="preserve">
|
||||
<value>Specify if module information should be updated (ie. title, header, footer settings)</value>
|
||||
</data>
|
||||
<data name="Success.Save" xml:space="preserve">
|
||||
<value>Your Global Replace Request Has Been Submitted And Will Be Executed Shortly. Please Be Patient.</value>
|
||||
</data>
|
||||
<data name="Error.Save" xml:space="preserve">
|
||||
<value>Error Saving Global Replace</value>
|
||||
</data>
|
||||
<data name="Find.HelpText" xml:space="preserve">
|
||||
<value>Specify the content which needs to be replaced</value>
|
||||
</data>
|
||||
<data name="Find.Text" xml:space="preserve">
|
||||
<value>Find What:</value>
|
||||
</data>
|
||||
<data name="GlobalReplace.Header" xml:space="preserve">
|
||||
<value>Global Replace</value>
|
||||
</data>
|
||||
<data name="GlobalReplace.Message" xml:space="preserve">
|
||||
<value>This Operation is Permanent. Are You Sure You Wish To Proceed?</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -139,7 +139,7 @@
|
||||
<value>Error Updating Job</value>
|
||||
</data>
|
||||
<data name="Message.Required.JobInfo" xml:space="preserve">
|
||||
<value>You Must Provide The Job Name, Type, Frequency, and Retention</value>
|
||||
<value>You Must Provide The Job Name, Frequency, and Retention</value>
|
||||
</data>
|
||||
<data name="Name.HelpText" xml:space="preserve">
|
||||
<value>Enter the job name</value>
|
||||
@@ -154,7 +154,7 @@
|
||||
<value>Select how often you want the job to run</value>
|
||||
</data>
|
||||
<data name="Starting.HelpText" xml:space="preserve">
|
||||
<value>Optionally enter the date and time when this job should start executing</value>
|
||||
<value>Optionally enter the date and time when this job should start executing. If no date or time is specified, the job will execute immediately.</value>
|
||||
</data>
|
||||
<data name="Ending.HelpText" xml:space="preserve">
|
||||
<value>Optionally enter the date and time when this job should stop executing</value>
|
||||
@@ -163,7 +163,7 @@
|
||||
<value>Number of log entries to retain for this job</value>
|
||||
</data>
|
||||
<data name="NextExecution.HelpText" xml:space="preserve">
|
||||
<value>Optionally modify the date and time when this job should execute next</value>
|
||||
<value>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.</value>
|
||||
</data>
|
||||
<data name="Type.Text" xml:space="preserve">
|
||||
<value>Type: </value>
|
||||
@@ -193,6 +193,15 @@
|
||||
<value>Execute Once</value>
|
||||
</data>
|
||||
<data name="Message.StartEndDateError" xml:space="preserve">
|
||||
<value>Start Date cannot be after End Date.</value>
|
||||
<value>The Start Date Cannot Be Later Than The End Date</value>
|
||||
</data>
|
||||
<data name="Message.StartDateError" xml:space="preserve">
|
||||
<value>The Start Date Cannot Be Prior To The Current Date</value>
|
||||
</data>
|
||||
<data name="Message.ExecutingError" xml:space="preserve">
|
||||
<value>The Job Is Currently Executing. You Must Wait Until The Job Has Completed.</value>
|
||||
</data>
|
||||
<data name="JobNotEditable" xml:space="preserve">
|
||||
<value>The Job Cannot Be Modified As It Is Currently Enabled. You Must Disable The Job To Change Its Settings.</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -118,7 +118,13 @@
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="ForgotPassword" xml:space="preserve">
|
||||
<value>Forgot Password</value>
|
||||
<value>Forgot Password?</value>
|
||||
</data>
|
||||
<data name="ForgotUsername" xml:space="preserve">
|
||||
<value>Forgot Username?</value>
|
||||
</data>
|
||||
<data name="UseLoginLink" xml:space="preserve">
|
||||
<value>Use Login Link</value>
|
||||
</data>
|
||||
<data name="Success.Account.Verified" xml:space="preserve">
|
||||
<value>User Account Email Address Verified Successfully. You Can Now Login With Your Username And Password.</value>
|
||||
@@ -127,7 +133,7 @@
|
||||
<value>User Account Email Address Could Not Be Verified. Please Contact Your Administrator For Further Instructions.</value>
|
||||
</data>
|
||||
<data name="Success.Account.Linked" xml:space="preserve">
|
||||
<value>User Account Linked Successfully. You Can Now Login With Your External Login Below.</value>
|
||||
<value>External Login Linked Successfully. You Can Now Login.</value>
|
||||
</data>
|
||||
<data name="Message.Account.NotLinked" xml:space="preserve">
|
||||
<value>External Login Could Not Be Linked. Please Contact Your Administrator For Further Instructions.</value>
|
||||
@@ -142,16 +148,19 @@
|
||||
<value>You Are Already Signed In</value>
|
||||
</data>
|
||||
<data name="Message.ForgotPassword" xml:space="preserve">
|
||||
<value>Please Enter The Username Related To Your Account And Then Select The Forgot Password Option Again</value>
|
||||
</data>
|
||||
<data name="Message.ForgotUser" xml:space="preserve">
|
||||
<value>Please Check The Email Address Associated To Your User Account For A Password Reset Notification</value>
|
||||
</data>
|
||||
<data name="Message.ForgotUsername" xml:space="preserve">
|
||||
<value>Please Check Your Email For A Username Reminder Notification</value>
|
||||
</data>
|
||||
<data name="Message.SendLoginLink" xml:space="preserve">
|
||||
<value>A Login Link Has Been Sent To Your Email Address. The Link Is Only Valid For A Limited Amount Of Time.</value>
|
||||
</data>
|
||||
<data name="Message.UserDoesNotExist" xml:space="preserve">
|
||||
<value>User Does Not Exist</value>
|
||||
<value>User Does Not Exist For Criteria Specified</value>
|
||||
</data>
|
||||
<data name="Code.HelpText" xml:space="preserve">
|
||||
<value>Please Enter The Secure Verification Code Which Was Sent To You By Email.</value>
|
||||
<value>Please enter the secure verification code which was sent to you by email</value>
|
||||
</data>
|
||||
<data name="Code.Placeholder" xml:space="preserve">
|
||||
<value>Verification Code</value>
|
||||
@@ -166,7 +175,7 @@
|
||||
<value>A Secure Verification Code Has Been Sent To Your Email Address. Please Enter The Code That You Received. If You Do Not Receive The Code Or You Have Lost Access To Your Email, Please Contact Your Administrator.</value>
|
||||
</data>
|
||||
<data name="Password.HelpText" xml:space="preserve">
|
||||
<value>Please Enter The Password Related To Your Account. Remember That Passwords Are Case Sensitive. If You Attempt Unsuccessfully To Log In To Your Account Multiple Times, You Will Be Locked Out For A Period Of Time.</value>
|
||||
<value>Please enter the password related to your account. Remember that passwords are sase sensitive. If you attempt to login to your account multiple times unsuccessfully, you will be locked out for a period of time.</value>
|
||||
</data>
|
||||
<data name="Password.Placeholder" xml:space="preserve">
|
||||
<value>Password</value>
|
||||
@@ -175,13 +184,13 @@
|
||||
<value>Password:</value>
|
||||
</data>
|
||||
<data name="Remember.HelpText" xml:space="preserve">
|
||||
<value>Specify If You Would Like To Be Signed Back In Automatically The Next Time You Visit This Site</value>
|
||||
<value>Specify if you would like to be signed back in automatically the next time you visit this site</value>
|
||||
</data>
|
||||
<data name="Remember.Text" xml:space="preserve">
|
||||
<value>Remember Me?</value>
|
||||
<value>Stay Signed In?</value>
|
||||
</data>
|
||||
<data name="Username.HelpText" xml:space="preserve">
|
||||
<value>Please Enter The Username Related To Your Account</value>
|
||||
<value>Please enter the username related to your account</value>
|
||||
</data>
|
||||
<data name="Username.Placeholder" xml:space="preserve">
|
||||
<value>Username</value>
|
||||
@@ -201,7 +210,13 @@
|
||||
<data name="Error.ResetPassword" xml:space="preserve">
|
||||
<value>Error Resetting Password</value>
|
||||
</data>
|
||||
<data name="ExternalLoginStatus.DuplicateEmail" xml:space="preserve">
|
||||
<data name="Error.ForgotUsername" xml:space="preserve">
|
||||
<value>Error Sending Username Reminder</value>
|
||||
</data>
|
||||
<data name="Error.SendLoginLink" xml:space="preserve">
|
||||
<value>Error Sending Login Link</value>
|
||||
</data>
|
||||
<data name="ExternalLoginStatus.DuplicateEmail" xml:space="preserve">
|
||||
<value>Multiple User Accounts Already Exist With The Email Address Of Your External Login. Please Contact Your Administrator For Further Instructions.</value>
|
||||
</data>
|
||||
<data name="ExternalLoginStatus.MissingClaims" xml:space="preserve">
|
||||
@@ -228,7 +243,28 @@
|
||||
<data name="ExternalLoginStatus.ReviewClaims" xml:space="preserve">
|
||||
<value>The Review Claims Option Was Enabled In External Login Settings. Please Visit The Event Log To View The Claims Returned By The Provider.</value>
|
||||
</data>
|
||||
<data name="ExternalLoginStatus.LoginLinkFailed" xml:space="preserve">
|
||||
<value>Login Links Are Time Sensitive. Please Request Another Login Link To Complete The Login Process.</value>
|
||||
</data>
|
||||
<data name="ExternalLoginStatus.PasskeyFailed" xml:space="preserve">
|
||||
<value>Passkey Login Was Unsuccessful. Please Ensure You Selected The Correct Passkey For This Site.</value>
|
||||
</data>
|
||||
<data name="Register" xml:space="preserve">
|
||||
<value>Register as new user?</value>
|
||||
</data>
|
||||
<data name="Passkey" xml:space="preserve">
|
||||
<value>Use Passkey</value>
|
||||
</data>
|
||||
<data name="Error.Passkey.Fail" xml:space="preserve">
|
||||
<value>Passkey Login Was Not Successful</value>
|
||||
</data>
|
||||
<data name="Email.HelpText" xml:space="preserve">
|
||||
<value>Please enter the email address related to your account</value>
|
||||
</data>
|
||||
<data name="Email.Placeholder" xml:space="preserve">
|
||||
<value>Email Address</value>
|
||||
</data>
|
||||
<data name="Email.Text" xml:space="preserve">
|
||||
<value>Email:</value>
|
||||
</data>
|
||||
</root>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user