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>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<Configurations>Debug;Release</Configurations>
|
<Configurations>Debug;Release</Configurations>
|
||||||
<Version>6.2.1</Version>
|
<Version>10.1.2</Version>
|
||||||
<Product>Oqtane</Product>
|
<Product>Oqtane</Product>
|
||||||
<Authors>Shaun Walker</Authors>
|
<Authors>Shaun Walker</Authors>
|
||||||
<Company>.NET Foundation</Company>
|
<Company>.NET Foundation</Company>
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
<Copyright>.NET Foundation</Copyright>
|
<Copyright>.NET Foundation</Copyright>
|
||||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
<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>
|
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||||
<RepositoryType>Git</RepositoryType>
|
<RepositoryType>Git</RepositoryType>
|
||||||
</PropertyGroup>
|
</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
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2018-2025 .NET Foundation
|
Copyright (c) 2018-2026 .NET Foundation
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -18,11 +18,6 @@
|
|||||||
},
|
},
|
||||||
"sourceName": "Oqtane.Application",
|
"sourceName": "Oqtane.Application",
|
||||||
"preferNameDirectory": true,
|
"preferNameDirectory": true,
|
||||||
"guids": [
|
|
||||||
"04B05448-788F-433D-92C0-FED35122D45A",
|
|
||||||
"AA8E58A1-CD09-4208-BF66-A8BB341FD669",
|
|
||||||
"18D73F73-D7BE-4388-85BA-FBD9AC96FCA2"
|
|
||||||
],
|
|
||||||
"symbols": {
|
"symbols": {
|
||||||
"Framework": {
|
"Framework": {
|
||||||
"type": "parameter",
|
"type": "parameter",
|
||||||
@@ -30,12 +25,12 @@
|
|||||||
"datatype": "choice",
|
"datatype": "choice",
|
||||||
"choices": [
|
"choices": [
|
||||||
{
|
{
|
||||||
"choice": "net9.0",
|
"choice": "net10.0",
|
||||||
"description": "Target net9.0"
|
"description": "Target net10.0"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"replaces": "net9.0",
|
"replaces": "net10.0",
|
||||||
"defaultValue": "net9.0"
|
"defaultValue": "net10.0"
|
||||||
},
|
},
|
||||||
"HttpPort": {
|
"HttpPort": {
|
||||||
"type": "parameter",
|
"type": "parameter",
|
||||||
@@ -80,7 +75,7 @@
|
|||||||
},
|
},
|
||||||
"primaryOutputs": [
|
"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">
|
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<Version>1.0.0</Version>
|
<Version>1.0.0</Version>
|
||||||
<AssemblyName>Oqtane.Application.Client.Oqtane</AssemblyName>
|
<AssemblyName>Oqtane.Application.Client.Oqtane</AssemblyName>
|
||||||
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
|
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
|
||||||
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
|
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
|
||||||
<PublishTrimmed>false</PublishTrimmed>
|
<PublishTrimmed>false</PublishTrimmed>
|
||||||
<BlazorEnableCompression>false</BlazorEnableCompression>
|
<BlazorEnableCompression>false</BlazorEnableCompression>
|
||||||
|
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.9" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.5" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.9" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="10.0.5" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.9" />
|
<PackageReference Include="Microsoft.Extensions.Localization" Version="10.0.5" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.9" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.5" />
|
||||||
<PackageReference Include="System.Net.Http.Json" Version="9.0.9" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -24,7 +23,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Oqtane.Client" Version="6.2.1" />
|
<PackageReference Include="Oqtane.Client" Version="10.1.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</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.AspNetCore.Components.Web
|
||||||
@using Microsoft.Extensions.Localization
|
@using Microsoft.Extensions.Localization
|
||||||
@using Microsoft.JSInterop
|
@using Microsoft.JSInterop
|
||||||
|
@using Microsoft.AspNetCore.Authorization
|
||||||
|
|
||||||
@using Oqtane
|
@using Oqtane
|
||||||
@using Oqtane.Models
|
@using Oqtane.Models
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
|
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>Oqtane.Application.Template</id>
|
<id>Oqtane.Application.Template</id>
|
||||||
<version>6.2.1</version>
|
<version>10.1.2</version>
|
||||||
<title>Oqtane Application Template For Blazor</title>
|
<title>Oqtane Application Template For Blazor</title>
|
||||||
<authors>Shaun Walker</authors>
|
<authors>Shaun Walker</authors>
|
||||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
<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">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<Version>1.0.0</Version>
|
<Version>1.0.0</Version>
|
||||||
<AssemblyName>Oqtane.Application.Server.Oqtane</AssemblyName>
|
<AssemblyName>Oqtane.Application.Server.Oqtane</AssemblyName>
|
||||||
<PreserveCompilationContext>true</PreserveCompilationContext>
|
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||||
<SatelliteResourceLanguages>none</SatelliteResourceLanguages>
|
<SatelliteResourceLanguages>none</SatelliteResourceLanguages>
|
||||||
<CompressionEnabled>false</CompressionEnabled>
|
<CompressionEnabled>false</CompressionEnabled>
|
||||||
<StaticWebAssetsFingerprintContent>false</StaticWebAssetsFingerprintContent>
|
<StaticWebAssetsFingerprintContent>false</StaticWebAssetsFingerprintContent>
|
||||||
|
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
|
||||||
|
<RequiresAspNetWebAssets>true</RequiresAspNetWebAssets>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.9" />
|
<Compile Remove="wwwroot\Modules\Templates\**" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.9" />
|
<Compile Remove="wwwroot\Themes\Templates\**" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.9" />
|
<Content Remove="wwwroot\Modules\Templates\**" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.9" />
|
<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>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -23,7 +33,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Oqtane.Server" Version="6.2.1" />
|
<PackageReference Include="Oqtane.Server" Version="10.1.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
using Microsoft.AspNetCore.Hosting;
|
using System;
|
||||||
using Microsoft.AspNetCore;
|
using System.IO;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Cors.Infrastructure;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Oqtane.Infrastructure;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Oqtane.Extensions;
|
||||||
|
using Oqtane.Infrastructure;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
|
||||||
namespace Oqtane.Application.Server
|
namespace Oqtane.Application.Server
|
||||||
{
|
{
|
||||||
@@ -11,32 +15,41 @@ namespace Oqtane.Application.Server
|
|||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
// defer server startup to Oqtane - do not modify
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
var host = BuildWebHost(args);
|
|
||||||
var databaseManager = host.Services.GetService<IDatabaseManager>();
|
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();
|
var install = databaseManager.Install();
|
||||||
if (!string.IsNullOrEmpty(install.Message))
|
if (!string.IsNullOrEmpty(install.Message))
|
||||||
{
|
{
|
||||||
var filelogger = host.Services.GetRequiredService<ILogger<Program>>();
|
var filelogger = app.Services.GetRequiredService<ILogger<Program>>();
|
||||||
if (filelogger != null)
|
if (filelogger != null)
|
||||||
{
|
{
|
||||||
filelogger.LogError($"[Oqtane.Application.Server.Program.Main] {install.Message}");
|
filelogger.LogError($"[Oqtane.Server.Program.Main] {install.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
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.Modules.Controls
|
||||||
@using Oqtane.Application.Services
|
@using [Owner].Module.[Module].Services
|
||||||
@using Oqtane.Application.Models
|
@using [Owner].Module.[Module].Models
|
||||||
|
|
||||||
@namespace Oqtane.Application.MyModule
|
@namespace [Owner].Module.[Module]
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject IMyModuleService MyModuleService
|
@inject I[Module]Service Client[Module]Service
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IStringLocalizer<Edit> Localizer
|
@inject IStringLocalizer<Edit> Localizer
|
||||||
|
|
||||||
@@ -31,12 +31,7 @@
|
|||||||
|
|
||||||
public override string Actions => "Add,Edit";
|
public override string Actions => "Add,Edit";
|
||||||
|
|
||||||
public override string Title => "Manage MyModule";
|
public override string Title => "Manage [Module]";
|
||||||
|
|
||||||
public override List<Resource> Resources => new List<Resource>()
|
|
||||||
{
|
|
||||||
new Stylesheet(ModulePath() + "Module.css")
|
|
||||||
};
|
|
||||||
|
|
||||||
private ElementReference form;
|
private ElementReference form;
|
||||||
private bool validated = false;
|
private bool validated = false;
|
||||||
@@ -55,20 +50,20 @@
|
|||||||
if (PageState.Action == "Edit")
|
if (PageState.Action == "Edit")
|
||||||
{
|
{
|
||||||
_id = Int32.Parse(PageState.QueryString["id"]);
|
_id = Int32.Parse(PageState.QueryString["id"]);
|
||||||
MyModule MyModule = await MyModuleService.GetMyModuleAsync(_id, ModuleState.ModuleId);
|
[Module] [Module] = await Client[Module]Service.Get[Module]Async(_id, ModuleState.ModuleId);
|
||||||
if (MyModule != null)
|
if ([Module] != null)
|
||||||
{
|
{
|
||||||
_name = MyModule.Name;
|
_name = [Module].Name;
|
||||||
_createdby = MyModule.CreatedBy;
|
_createdby = [Module].CreatedBy;
|
||||||
_createdon = MyModule.CreatedOn;
|
_createdon = [Module].CreatedOn;
|
||||||
_modifiedby = MyModule.ModifiedBy;
|
_modifiedby = [Module].ModifiedBy;
|
||||||
_modifiedon = MyModule.ModifiedOn;
|
_modifiedon = [Module].ModifiedOn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
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);
|
AddModuleMessage(Localizer["Message.LoadError"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,18 +78,18 @@
|
|||||||
{
|
{
|
||||||
if (PageState.Action == "Add")
|
if (PageState.Action == "Add")
|
||||||
{
|
{
|
||||||
MyModule MyModule = new MyModule();
|
[Module] [Module] = new [Module]();
|
||||||
MyModule.ModuleId = ModuleState.ModuleId;
|
[Module].ModuleId = ModuleState.ModuleId;
|
||||||
MyModule.Name = _name;
|
[Module].Name = _name;
|
||||||
MyModule = await MyModuleService.AddMyModuleAsync(MyModule);
|
[Module] = await Client[Module]Service.Add[Module]Async([Module]);
|
||||||
await logger.LogInformation("MyModule Added {MyModule}", MyModule);
|
await logger.LogInformation("[Module] Added {[Module]}", [Module]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
MyModule MyModule = await MyModuleService.GetMyModuleAsync(_id, ModuleState.ModuleId);
|
[Module] [Module] = await Client[Module]Service.Get[Module]Async(_id, ModuleState.ModuleId);
|
||||||
MyModule.Name = _name;
|
[Module].Name = _name;
|
||||||
await MyModuleService.UpdateMyModuleAsync(MyModule);
|
await Client[Module]Service.Update[Module]Async([Module]);
|
||||||
await logger.LogInformation("MyModule Updated {MyModule}", MyModule);
|
await logger.LogInformation("[Module] Updated {[Module]}", [Module]);
|
||||||
}
|
}
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
}
|
}
|
||||||
@@ -105,7 +100,7 @@
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
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);
|
AddModuleMessage(Localizer["Message.SaveError"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,32 +1,32 @@
|
|||||||
@using Oqtane.Application.Services
|
@using [Owner].Module.[Module].Services
|
||||||
@using Oqtane.Application.Models
|
@using [Owner].Module.[Module].Models
|
||||||
|
|
||||||
@namespace Oqtane.Application.MyModule
|
@namespace [Owner].Module.[Module]
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject IMyModuleService MyModuleService
|
@inject I[Module]Service Client[Module]Service
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
|
|
||||||
@if (_MyModules == null)
|
@if (_[Module]s == null)
|
||||||
{
|
{
|
||||||
<p><em>Loading...</em></p>
|
<p><em>Loading...</em></p>
|
||||||
}
|
}
|
||||||
else
|
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 />
|
||||||
<br />
|
<br />
|
||||||
@if (@_MyModules.Count != 0)
|
@if (@_[Module]s.Count != 0)
|
||||||
{
|
{
|
||||||
<Pager Items="@_MyModules">
|
<Pager Items="@_[Module]s">
|
||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th>@Localizer["Name"]</th>
|
<th>@Localizer["Name"]</th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.MyModuleId.ToString())" ResourceKey="Edit" /></td>
|
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.[Module]Id.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><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>
|
<td>@context.Name</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
@@ -38,39 +38,41 @@ else
|
|||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
public override string RenderMode => RenderModes.Static;
|
||||||
|
|
||||||
public override List<Resource> Resources => new List<Resource>()
|
public override List<Resource> Resources => new List<Resource>()
|
||||||
{
|
{
|
||||||
new Stylesheet(ModulePath() + "Module.css"),
|
new Stylesheet(ModulePath() + "Module.css"),
|
||||||
new Script(ModulePath() + "Module.js")
|
new Script(ModulePath() + "Module.js")
|
||||||
};
|
};
|
||||||
|
|
||||||
List<Models.MyModule> _MyModules;
|
List<[Module]> _[Module]s;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_MyModules = await MyModuleService.GetMyModulesAsync(ModuleState.ModuleId);
|
_[Module]s = await Client[Module]Service.Get[Module]sAsync(ModuleState.ModuleId);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
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);
|
AddModuleMessage(Localizer["Message.LoadError"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Delete(MyModule MyModule)
|
private async Task Delete([Module] [Module])
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await MyModuleService.DeleteMyModuleAsync(MyModule.MyModuleId, ModuleState.ModuleId);
|
await Client[Module]Service.Delete[Module]Async([Module].[Module]Id, ModuleState.ModuleId);
|
||||||
await logger.LogInformation("MyModule Deleted {MyModule}", MyModule);
|
await logger.LogInformation("[Module] Deleted {[Module]}", [Module]);
|
||||||
_MyModules = await MyModuleService.GetMyModulesAsync(ModuleState.ModuleId);
|
_[Module]s = await Client[Module]Service.Get[Module]sAsync(ModuleState.ModuleId);
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
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);
|
AddModuleMessage(Localizer["Message.DeleteError"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Oqtane.Application
|
namespace [Owner].Module.[Module]
|
||||||
{
|
{
|
||||||
public class Interop
|
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
|
@inherits ModuleBase
|
||||||
|
@implements Oqtane.Interfaces.ISettingsControl
|
||||||
@inject ISettingService SettingService
|
@inject ISettingService SettingService
|
||||||
@inject IStringLocalizer<Settings> Localizer
|
@inject IStringLocalizer<Settings> Localizer
|
||||||
|
|
||||||
@@ -13,8 +14,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private string resourceType = "Oqtane.Application.MyModule.Settings, Oqtane.Application.Client.Oqtane"; // for localization
|
private string resourceType = "[Owner].Module.[Module].Settings, [Owner].Module.[Module].Client.Oqtane"; // for localization
|
||||||
public override string Title => "MyModdule Settings";
|
public override string Title => "[Module] Settings";
|
||||||
|
|
||||||
string _value;
|
string _value;
|
||||||
|
|
||||||
@@ -35,8 +36,8 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Dictionary<string, string> settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
|
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
|
||||||
SettingService.SetSetting(settings, "SettingName", _value);
|
settings = SettingService.SetSetting(settings, "SettingName", _value);
|
||||||
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
|
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -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>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
@@ -7,22 +7,10 @@ using Oqtane.Shared;
|
|||||||
|
|
||||||
namespace [Owner].Module.[Module].Services
|
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);
|
public Client[Module]Service(HttpClient http, SiteState siteState) : base(http, siteState) { }
|
||||||
|
|
||||||
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) { }
|
|
||||||
|
|
||||||
private string Apiurl => CreateApiUrl("[Module]");
|
private string Apiurl => CreateApiUrl("[Module]");
|
||||||
|
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Oqtane.Services;
|
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 class ClientStartup : IClientStartup
|
||||||
{
|
{
|
||||||
public void ConfigureServices(IServiceCollection services)
|
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.Shared;
|
||||||
using Oqtane.Enums;
|
using Oqtane.Enums;
|
||||||
using Oqtane.Infrastructure;
|
using Oqtane.Infrastructure;
|
||||||
using Oqtane.Application.Services;
|
using [Owner].Module.[Module].Services;
|
||||||
using Oqtane.Controllers;
|
using Oqtane.Controllers;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Oqtane.Application.Controllers
|
namespace [Owner].Module.[Module].Controllers
|
||||||
{
|
{
|
||||||
[Route(ControllerRoutes.ApiRoute)]
|
[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
|
// GET: api/<controller>?moduleid=x
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Authorize(Policy = PolicyNames.ViewModule)]
|
[Authorize(Policy = PolicyNames.ViewModule)]
|
||||||
public async Task<IEnumerable<Models.MyModule>> Get(string moduleid)
|
public async Task<IEnumerable<Models.[Module]>> Get(string moduleid)
|
||||||
{
|
{
|
||||||
int ModuleId;
|
int ModuleId;
|
||||||
if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, 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
|
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;
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -43,16 +43,16 @@ namespace Oqtane.Application.Controllers
|
|||||||
// GET api/<controller>/5
|
// GET api/<controller>/5
|
||||||
[HttpGet("{id}/{moduleid}")]
|
[HttpGet("{id}/{moduleid}")]
|
||||||
[Authorize(Policy = PolicyNames.ViewModule)]
|
[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);
|
Models.[Module] [Module] = await _[Module]Service.Get[Module]Async(id, moduleid);
|
||||||
if (MyModule != null && IsAuthorizedEntityId(EntityNames.Module, MyModule.ModuleId))
|
if ([Module] != null && IsAuthorizedEntityId(EntityNames.Module, [Module].ModuleId))
|
||||||
{
|
{
|
||||||
return MyModule;
|
return [Module];
|
||||||
}
|
}
|
||||||
else
|
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;
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -61,37 +61,37 @@ namespace Oqtane.Application.Controllers
|
|||||||
// POST api/<controller>
|
// POST api/<controller>
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Authorize(Policy = PolicyNames.EditModule)]
|
[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
|
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;
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
MyModule = null;
|
[Module] = null;
|
||||||
}
|
}
|
||||||
return MyModule;
|
return [Module];
|
||||||
}
|
}
|
||||||
|
|
||||||
// PUT api/<controller>/5
|
// PUT api/<controller>/5
|
||||||
[HttpPut("{id}")]
|
[HttpPut("{id}")]
|
||||||
[Authorize(Policy = PolicyNames.EditModule)]
|
[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
|
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;
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
MyModule = null;
|
[Module] = null;
|
||||||
}
|
}
|
||||||
return MyModule;
|
return [Module];
|
||||||
}
|
}
|
||||||
|
|
||||||
// DELETE api/<controller>/5
|
// DELETE api/<controller>/5
|
||||||
@@ -99,14 +99,14 @@ namespace Oqtane.Application.Controllers
|
|||||||
[Authorize(Policy = PolicyNames.EditModule)]
|
[Authorize(Policy = PolicyNames.EditModule)]
|
||||||
public async Task Delete(int id, int moduleid)
|
public async Task Delete(int id, int moduleid)
|
||||||
{
|
{
|
||||||
Models.MyModule MyModule = await _MyModuleService.GetMyModuleAsync(id, moduleid);
|
Models.[Module] [Module] = await _[Module]Service.Get[Module]Async(id, moduleid);
|
||||||
if (MyModule != null && IsAuthorizedEntityId(EntityNames.Module, MyModule.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
|
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;
|
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;
|
||||||
using Oqtane.Migrations.EntityBuilders;
|
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 const string _entityTableName = "[Owner][Module]";
|
||||||
private readonly PrimaryKey<MyModuleEntityBuilder> _primaryKey = new("PK_MyModule", x => x.MyModuleId);
|
private readonly PrimaryKey<[Module]EntityBuilder> _primaryKey = new("PK_[Owner][Module]", x => x.[Module]Id);
|
||||||
private readonly ForeignKey<MyModuleEntityBuilder> _moduleForeignKey = new("FK_MyModule_Module", x => x.ModuleId, "Module", "ModuleId", ReferentialAction.Cascade);
|
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;
|
EntityTableName = _entityTableName;
|
||||||
PrimaryKey = _primaryKey;
|
PrimaryKey = _primaryKey;
|
||||||
ForeignKeys.Add(_moduleForeignKey);
|
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");
|
ModuleId = AddIntegerColumn(table,"ModuleId");
|
||||||
Name = AddMaxStringColumn(table,"Name");
|
Name = AddMaxStringColumn(table,"Name");
|
||||||
AddAuditableColumns(table);
|
AddAuditableColumns(table);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public OperationBuilder<AddColumnOperation> MyModuleId { get; set; }
|
public OperationBuilder<AddColumnOperation> [Module]Id { get; set; }
|
||||||
public OperationBuilder<AddColumnOperation> ModuleId { get; set; }
|
public OperationBuilder<AddColumnOperation> ModuleId { get; set; }
|
||||||
public OperationBuilder<AddColumnOperation> Name { 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.AspNetCore.Hosting;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Oqtane.Infrastructure;
|
using Oqtane.Infrastructure;
|
||||||
using Oqtane.Application.Repository;
|
using [Owner].Module.[Module].Repository;
|
||||||
using Oqtane.Application.Services;
|
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)
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||||
{
|
{
|
||||||
@@ -21,8 +21,8 @@ namespace Oqtane.Application.Startup
|
|||||||
|
|
||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddTransient<IMyModuleService, ServerMyModuleService>();
|
services.AddTransient<I[Module]Service, Server[Module]Service>();
|
||||||
services.AddDbContextFactory<Context>(opt => { }, ServiceLifetime.Transient);
|
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>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
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>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
@namespace Oqtane.Application.MyTheme
|
@namespace [Owner].Theme.[Theme]
|
||||||
@inherits ContainerBase
|
@inherits ContainerBase
|
||||||
@inject ISettingService SettingService
|
@inject ISettingService SettingService
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
public override string Name => "Container";
|
public override string Name => "[Owner] [Theme] - Container1";
|
||||||
|
|
||||||
private bool _title = true;
|
private bool _title = true;
|
||||||
private string _classes = "container-fluid";
|
private string _classes = "container-fluid";
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
@namespace Oqtane.Application.MyTheme
|
@namespace [Owner].Theme.[Theme]
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@implements Oqtane.Interfaces.ISettingsControl
|
@implements Oqtane.Interfaces.ISettingsControl
|
||||||
@inject ISettingService SettingService
|
@inject ISettingService SettingService
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@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";
|
private string _title = "true";
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
@namespace Oqtane.Application.MyTheme
|
@namespace [Owner].Theme.[Theme]
|
||||||
@inherits ThemeBase
|
@inherits ThemeBase
|
||||||
@inject ISettingService SettingService
|
@inject ISettingService SettingService
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
@code {
|
@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";
|
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.Themes;
|
||||||
using Oqtane.Shared;
|
using Oqtane.Shared;
|
||||||
|
|
||||||
namespace Oqtane.Application.MyTheme
|
namespace [Owner].Theme.[Theme]
|
||||||
{
|
{
|
||||||
public class ThemeInfo : ITheme
|
public class ThemeInfo : ITheme
|
||||||
{
|
{
|
||||||
public Oqtane.Models.Theme Theme => new Oqtane.Models.Theme
|
public Oqtane.Models.Theme Theme => new Oqtane.Models.Theme
|
||||||
{
|
{
|
||||||
Name = "MyTheme",
|
Name = "[Owner] [Theme]",
|
||||||
Version = "1.0.0",
|
Version = "1.0.0",
|
||||||
PackageName = "Oqtane.Application",
|
PackageName = "[Owner].Theme.[Theme]",
|
||||||
ThemeSettingsType = "Oqtane.Application.MyTheme.ThemeSettings, Oqtane.Application.Client.Oqtane",
|
ThemeSettingsType = "[Owner].Theme.[Theme].ThemeSettings, [Owner].Theme.[Theme].Client.Oqtane",
|
||||||
ContainerSettingsType = "Oqtane.Application.MyTheme.ContainerSettings, Oqtane.Application.Client.Oqtane",
|
ContainerSettingsType = "[Owner].Theme.[Theme].ContainerSettings, [Owner].Theme.[Theme].Client.Oqtane",
|
||||||
Resources = new List<Resource>()
|
Resources = new List<Resource>()
|
||||||
{
|
{
|
||||||
|
// obtained from https://cdnjs.com/libraries
|
||||||
new Stylesheet(Constants.BootstrapStylesheetUrl, Constants.BootstrapStylesheetIntegrity, "anonymous"),
|
new Stylesheet(Constants.BootstrapStylesheetUrl, Constants.BootstrapStylesheetIntegrity, "anonymous"),
|
||||||
new Stylesheet("~/Theme.css"),
|
new Stylesheet("~/Theme.css"),
|
||||||
new Script(Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous")
|
new Script(Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
@namespace Oqtane.Application.MyTheme
|
@namespace [Owner].Theme.[Theme]
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@implements Oqtane.Interfaces.ISettingsControl
|
@implements Oqtane.Interfaces.ISettingsControl
|
||||||
@inject ISettingService SettingService
|
@inject ISettingService SettingService
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
|
|
||||||
@code {
|
@code {
|
||||||
private int pageId = -1;
|
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 _scope = "page";
|
||||||
private string _login = "-";
|
private string _login = "-";
|
||||||
private string _register = "-";
|
private string _register = "-";
|
||||||
@@ -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">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<Version>1.0.0</Version>
|
<Version>1.0.0</Version>
|
||||||
<AssemblyName>Oqtane.Application.Shared.Oqtane</AssemblyName>
|
<AssemblyName>Oqtane.Application.Shared.Oqtane</AssemblyName>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Oqtane.Shared" Version="6.2.1" />
|
<PackageReference Include="Oqtane.Shared" Version="10.1.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||||||
services.AddScoped<ICookieConsentService, CookieConsentService>();
|
services.AddScoped<ICookieConsentService, CookieConsentService>();
|
||||||
services.AddScoped<ITimeZoneService, TimeZoneService>();
|
services.AddScoped<ITimeZoneService, TimeZoneService>();
|
||||||
services.AddScoped<IMigrationHistoryService, MigrationHistoryService>();
|
services.AddScoped<IMigrationHistoryService, MigrationHistoryService>();
|
||||||
|
services.AddScoped<ISiteGroupService, SiteGroupService>();
|
||||||
|
services.AddScoped<ISiteGroupMemberService, SiteGroupMemberService>();
|
||||||
|
services.AddScoped<ISiteTaskService, SiteTaskService>();
|
||||||
services.AddScoped<IOutputCacheService, OutputCacheService>();
|
services.AddScoped<IOutputCacheService, OutputCacheService>();
|
||||||
|
|
||||||
// providers
|
// providers
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
{
|
{
|
||||||
string url = NavigateUrl(p.Path);
|
string url = NavigateUrl(p.Path);
|
||||||
<div class="col-md-2 mx-auto text-center my-3">
|
<div class="col-md-2 mx-auto text-center my-3">
|
||||||
<NavLink class="nav-link text-body" href="@url" Match="NavLinkMatch.All">
|
<NavLink class="nav-link text-body" href="@url" Match="NavLinkMatch.All" @attributes="_attributes">
|
||||||
<h2><span class="@p.Icon" aria-hidden="true"></span></h2>
|
<h2><span class="@p.Icon" aria-hidden="true"></span></h2>
|
||||||
<div class="lead">@((MarkupString)SharedLocalizer[p.Name].ToString().Replace(" ", "<br />"))</div>
|
<div class="lead">@((MarkupString)SharedLocalizer[p.Name].ToString().Replace(" ", "<br />"))</div>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
Dictionary<string, object> _attributes { get; set; } = new();
|
||||||
private List<Page> _pages;
|
private List<Page> _pages;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
||||||
@@ -31,6 +32,11 @@
|
|||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
|
if (PageState.RenderMode == RenderModes.Static && !PageState.Site.EnhancedNavigation)
|
||||||
|
{
|
||||||
|
_attributes.Add("data-enhance-nav", "true"); // Admin Dashboard utilizes enhanced navigation
|
||||||
|
}
|
||||||
|
|
||||||
var admin = PageState.Pages.FirstOrDefault(item => item.Path == "admin");
|
var admin = PageState.Pages.FirstOrDefault(item => item.Path == "admin");
|
||||||
if (admin != null)
|
if (admin != null)
|
||||||
{
|
{
|
||||||
|
|||||||
117
Oqtane.Client/Modules/Admin/GlobalReplace/Index.razor
Normal file
117
Oqtane.Client/Modules/Admin/GlobalReplace/Index.razor
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
@namespace Oqtane.Modules.Admin.GlobalReplace
|
||||||
|
@using System.Text.Json
|
||||||
|
@inherits ModuleBase
|
||||||
|
@inject ISiteTaskService SiteTaskService
|
||||||
|
@inject IStringLocalizer<Index> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="find" HelpText="Specify the content which needs to be replaced" ResourceKey="Find">Find What: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="find" class="form-control" @bind="@_find" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="replace" HelpText="Specify the replacement content" ResourceKey="Replace">Replace With: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="replace" class="form-control" @bind="@_replace" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="casesensitive" HelpText="Specify if the replacement operation should be case sensitive" ResourceKey="CaseSensitive">Match Case? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="casesensitive" class="form-select" @bind="@_caseSensitive">
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="site" HelpText="Specify if site information should be updated (ie. name, head content, body content, settings)" ResourceKey="Site">Site Info? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="site" class="form-select" @bind="@_site">
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="pages" HelpText="Specify if page information should be updated (ie. name, title, head content, body content, settings)" ResourceKey="Pages">Page Info? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="pages" class="form-select" @bind="@_pages">
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="modules" HelpText="Specify if module information should be updated (ie. title, header, footer, settings)" ResourceKey="Modules">Module Info? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="modules" class="form-select" @bind="@_modules">
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="content" HelpText="Specify if module content should be updated" ResourceKey="Content">Module Content? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="content" class="form-select" @bind="@_content">
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br /><br />
|
||||||
|
<ActionDialog Header="Global Replace" Message="This Operation is Permanent. Are You Sure You Wish To Proceed?" Action="Replace" Class="btn btn-primary" OnClick="@(async () => await Save())" ResourceKey="GlobalReplace" />
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
@code {
|
||||||
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
public override string Title => "Global Replace";
|
||||||
|
|
||||||
|
private string _find;
|
||||||
|
private string _replace;
|
||||||
|
private string _caseSensitive = "True";
|
||||||
|
private string _site = "True";
|
||||||
|
private string _pages = "True";
|
||||||
|
private string _modules = "True";
|
||||||
|
private string _content = "True";
|
||||||
|
|
||||||
|
private async Task Save()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(_find) && !string.IsNullOrEmpty(_replace))
|
||||||
|
{
|
||||||
|
var replace = new GlobalReplace
|
||||||
|
{
|
||||||
|
Find = _find,
|
||||||
|
Replace = _replace,
|
||||||
|
CaseSensitive = bool.Parse(_caseSensitive),
|
||||||
|
Site = bool.Parse(_site),
|
||||||
|
Pages = bool.Parse(_pages),
|
||||||
|
Modules = bool.Parse(_modules),
|
||||||
|
Content = bool.Parse(_content)
|
||||||
|
};
|
||||||
|
|
||||||
|
var siteTask = new SiteTask(PageState.Site.SiteId, "Global Replace", "Oqtane.Infrastructure.GlobalReplaceTask, Oqtane.Server", JsonSerializer.Serialize(replace));
|
||||||
|
await SiteTaskService.AddSiteTaskAsync(siteTask);
|
||||||
|
|
||||||
|
AddModuleMessage(Localizer["Success.Save"], MessageType.Success);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Saving Global Replace Settings {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.Save"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,24 +5,30 @@
|
|||||||
@inject IStringLocalizer<Edit> Localizer
|
@inject IStringLocalizer<Edit> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
@if (_initialized)
|
||||||
|
{
|
||||||
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
@if (!_editable)
|
||||||
<Label Class="col-sm-3" For="name" HelpText="Enter the job name" ResourceKey="Name">Name: </Label>
|
{
|
||||||
<div class="col-sm-9">
|
<ModuleMessage Message="@Localizer["JobNotEditable"]" Type="MessageType.Warning" />
|
||||||
<input id="name" class="form-control" @bind="@_name" maxlength="200" required />
|
}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="type" HelpText="The fully qualified job type name" ResourceKey="Type">Type: </Label>
|
<Label Class="col-sm-3" For="type" HelpText="The fully qualified job type name" ResourceKey="Type">Type: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="type" class="form-control" @bind="@_jobType" readonly />
|
<input id="type" class="form-control" @bind="@_jobType" required disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="name" HelpText="Enter the job name" ResourceKey="Name">Name: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="name" class="form-control" @bind="@_name" maxlength="200" required disabled="@(!_editable)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="enabled" HelpText="Select whether you want the job enabled or not" ResourceKey="Enabled">Enabled? </Label>
|
<Label Class="col-sm-3" For="enabled" HelpText="Select whether you want the job enabled or not" ResourceKey="Enabled">Enabled? </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="enabled" class="form-select" @bind="@_isEnabled" required>
|
<select id="enabled" class="form-select" @bind="@_isEnabled" required disabled="@(!_editable)">
|
||||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
<option value="False">@SharedLocalizer["No"]</option>
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
</select>
|
</select>
|
||||||
@@ -31,8 +37,8 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="runs-every" HelpText="Select how often you want the job to run" ResourceKey="RunsEvery">Runs Every: </Label>
|
<Label Class="col-sm-3" For="runs-every" HelpText="Select how often you want the job to run" ResourceKey="RunsEvery">Runs Every: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="runs-every" class="form-control mb-1" @bind="@_interval" maxlength="4" required />
|
<input id="runs-every" class="form-control mb-1" @bind="@_interval" maxlength="4" required disabled="@(!_editable)" />
|
||||||
<select id="runs-every" class="form-select" @bind="@_frequency" required>
|
<select id="runs-every" class="form-select" @bind="@_frequency" required disabled="@(!_editable)">
|
||||||
<option value="m">@Localizer["Minute(s)"]</option>
|
<option value="m">@Localizer["Minute(s)"]</option>
|
||||||
<option value="H">@Localizer["Hour(s)"]</option>
|
<option value="H">@Localizer["Hour(s)"]</option>
|
||||||
<option value="d">@Localizer["Day(s)"]</option>
|
<option value="d">@Localizer["Day(s)"]</option>
|
||||||
@@ -45,7 +51,7 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="retention" HelpText="Number of log entries to retain for this job" ResourceKey="RetentionLog">Retention Log (Items): </Label>
|
<Label Class="col-sm-3" For="retention" HelpText="Number of log entries to retain for this job" ResourceKey="RetentionLog">Retention Log (Items): </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="retention" type="number" min="0" step ="1" class="form-control" @bind="@_retentionHistory" maxlength="3" required />
|
<input id="retention" type="number" min="0" step="1" class="form-control" @bind="@_retentionHistory" maxlength="3" required disabled="@(!_editable)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -53,10 +59,10 @@
|
|||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<input id="starting" type="date" class="form-control" @bind="@_startDate" />
|
<input id="starting" type="date" class="form-control" @bind="@_startDate" disabled="@(!_editable)" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<input id="starting" type="time" class="form-control" @bind="@_startTime" placeholder="hh:mm" required="@(_startDate.HasValue)" />
|
<input id="starting" type="time" class="form-control" @bind="@_startTime" placeholder="hh:mm" required="@(_startDate.HasValue)" disabled="@(!_editable)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -66,39 +72,42 @@
|
|||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<input id="ending" type="date" class="form-control" @bind="@_endDate" />
|
<input id="ending" type="date" class="form-control" @bind="@_endDate" disabled="@(!_editable)" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<input id="ending" type="time" class="form-control" placeholder="hh:mm" @bind="@_endTime" required="@(_endDate.HasValue)" />
|
<input id="ending" type="time" class="form-control" placeholder="hh:mm" @bind="@_endTime" required="@(_endDate.HasValue)" disabled="@(!_editable)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="next" HelpText="Optionally modify the date and time when this job should execute next" ResourceKey="NextExecution">Next Execution: </Label>
|
<Label Class="col-sm-3" For="next" HelpText="The date and time when this job will execute next. This value cannot be modified. Use the settings above to control the execution of the job." ResourceKey="NextExecution">Next Execution: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="row">
|
<input id="next" class="form-control" @bind="@_nextDate" disabled />
|
||||||
<div class="col">
|
|
||||||
<input id="next" type="date" class="form-control" @bind="@_nextDate" />
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
<input id="next" type="time" class="form-control" placeholder="hh:mm" @bind="@_nextTime" required="@(_nextDate.HasValue)" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
|
@if (_editable)
|
||||||
|
{
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveJob">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveJob">@SharedLocalizer["Save"]</button>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-danger" @onclick="DisableJob">@Localizer["Disable"]</button>
|
||||||
|
}
|
||||||
|
<NavLink class="btn btn-secondary ms-1" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
|
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
|
||||||
</form>
|
</form>
|
||||||
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private ElementReference form;
|
private ElementReference form;
|
||||||
private bool validated = false;
|
private bool validated = false;
|
||||||
|
private bool _initialized = false;
|
||||||
|
private bool _editable = true;
|
||||||
private int _jobId;
|
private int _jobId;
|
||||||
private string _name = string.Empty;
|
private string _name = string.Empty;
|
||||||
private string _jobType = string.Empty;
|
private string _jobType = string.Empty;
|
||||||
@@ -120,6 +129,11 @@
|
|||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await LoadJob();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task LoadJob()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -127,6 +141,7 @@
|
|||||||
Job job = await JobService.GetJobAsync(_jobId);
|
Job job = await JobService.GetJobAsync(_jobId);
|
||||||
if (job != null)
|
if (job != null)
|
||||||
{
|
{
|
||||||
|
_editable = !job.IsEnabled && !job.IsExecuting;
|
||||||
_name = job.Name;
|
_name = job.Name;
|
||||||
_jobType = job.JobType;
|
_jobType = job.JobType;
|
||||||
_isEnabled = job.IsEnabled.ToString();
|
_isEnabled = job.IsEnabled.ToString();
|
||||||
@@ -144,6 +159,8 @@
|
|||||||
modifiedby = job.ModifiedBy;
|
modifiedby = job.ModifiedBy;
|
||||||
modifiedon = job.ModifiedOn;
|
modifiedon = job.ModifiedOn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_initialized = true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -154,19 +171,15 @@
|
|||||||
|
|
||||||
private async Task SaveJob()
|
private async Task SaveJob()
|
||||||
{
|
{
|
||||||
if (!Utilities.ValidateEffectiveExpiryDates(_startDate, _endDate))
|
try
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Message.StartEndDateError"], MessageType.Warning);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
validated = true;
|
|
||||||
var interop = new Interop(JSRuntime);
|
var interop = new Interop(JSRuntime);
|
||||||
if (await interop.FormValid(form))
|
if (await interop.FormValid(form))
|
||||||
{
|
{
|
||||||
var job = await JobService.GetJobAsync(_jobId);
|
var job = await JobService.GetJobAsync(_jobId);
|
||||||
job.Name = _name;
|
job.Name = _name;
|
||||||
job.JobType = _jobType;
|
job.IsEnabled = bool.Parse(_isEnabled);
|
||||||
job.IsEnabled = Boolean.Parse(_isEnabled);
|
|
||||||
job.Frequency = _frequency;
|
job.Frequency = _frequency;
|
||||||
if (job.Frequency == "O") // once
|
if (job.Frequency == "O") // once
|
||||||
{
|
{
|
||||||
@@ -176,6 +189,7 @@
|
|||||||
{
|
{
|
||||||
job.Interval = int.Parse(_interval);
|
job.Interval = int.Parse(_interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
job.StartDate = _startDate.HasValue && _startTime.HasValue
|
job.StartDate = _startDate.HasValue && _startTime.HasValue
|
||||||
? LocalToUtc(_startDate.GetValueOrDefault().Date.Add(_startTime.GetValueOrDefault().TimeOfDay))
|
? LocalToUtc(_startDate.GetValueOrDefault().Date.Add(_startTime.GetValueOrDefault().TimeOfDay))
|
||||||
: null;
|
: null;
|
||||||
@@ -184,21 +198,25 @@
|
|||||||
? LocalToUtc(_endDate.GetValueOrDefault().Date.Add(_endTime.GetValueOrDefault().TimeOfDay))
|
? LocalToUtc(_endDate.GetValueOrDefault().Date.Add(_endTime.GetValueOrDefault().TimeOfDay))
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
job.NextExecution = _nextDate.HasValue && _nextTime.HasValue
|
|
||||||
? LocalToUtc(_nextDate.GetValueOrDefault().Date.Add(_nextTime.GetValueOrDefault().TimeOfDay))
|
|
||||||
: null;
|
|
||||||
job.RetentionHistory = int.Parse(_retentionHistory);
|
job.RetentionHistory = int.Parse(_retentionHistory);
|
||||||
|
|
||||||
try
|
if (!job.IsEnabled || Utilities.ValidateEffectiveExpiryDates(job.StartDate, job.EndDate))
|
||||||
{
|
{
|
||||||
|
if (!job.IsEnabled || (job.StartDate >= DateTime.UtcNow || job.StartDate == null))
|
||||||
|
{
|
||||||
|
job.NextExecution = null;
|
||||||
job = await JobService.UpdateJobAsync(job);
|
job = await JobService.UpdateJobAsync(job);
|
||||||
await logger.LogInformation("Job Updated {Job}", job);
|
await logger.LogInformation("Job Updated {Job}", job);
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
else
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Udate Job {Job} {Error}", job, ex.Message);
|
AddModuleMessage(Localizer["Message.StartDateError"], MessageType.Warning);
|
||||||
AddModuleMessage(Localizer["Error.Job.Update"], MessageType.Error);
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.StartEndDateError"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -206,4 +224,38 @@
|
|||||||
AddModuleMessage(Localizer["Message.Required.JobInfo"], MessageType.Warning);
|
AddModuleMessage(Localizer["Message.Required.JobInfo"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Updating Job {JobId} {Error}", _jobId, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.Job.Update"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DisableJob()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var job = await JobService.GetJobAsync(_jobId);
|
||||||
|
if (job != null)
|
||||||
|
{
|
||||||
|
if (job.IsExecuting)
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.ExecutingError"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
job.IsEnabled = false;
|
||||||
|
job.NextExecution = null;
|
||||||
|
job = await JobService.UpdateJobAsync(job);
|
||||||
|
await logger.LogInformation("Job Updated {Job}", job);
|
||||||
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Updating Job {JobId} {Error}", _jobId, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.Job.Update"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,83 +14,136 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@if (!twofactor)
|
|
||||||
{
|
|
||||||
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<div class="Oqtane-Modules-Admin-Login" @onkeypress="@(e => KeyPressed(e))">
|
<div class="Oqtane-Modules-Admin-Login" @onkeypress="@(e => KeyPressed(e))">
|
||||||
|
@switch (_action)
|
||||||
|
{
|
||||||
|
case "Login":
|
||||||
@if (_allowexternallogin)
|
@if (_allowexternallogin)
|
||||||
{
|
{
|
||||||
<button type="button" class="btn btn-primary" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
|
<button type="button" class="btn btn-primary col-12" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
|
||||||
<br /><br />
|
<hr class="app-rule mt-3 mb-2" />
|
||||||
}
|
}
|
||||||
@if (_allowsitelogin)
|
@if (_allowsitelogin)
|
||||||
{
|
{
|
||||||
<div class="form-group">
|
<div class="form-group text-center">
|
||||||
<Label Class="control-label" For="username" HelpText="Please enter your Username" ResourceKey="Username">Username:</Label>
|
<Label Class="control-label" For="username" HelpText="Please enter your Username" ResourceKey="Username">Username:</Label>
|
||||||
<input id="username" type="text" @ref="username" class="form-control" placeholder="@Localizer["Username.Placeholder"]" @bind="@_username" @bind:event="oninput" required />
|
<input id="username" type="text" @ref="username" class="form-control" placeholder="@Localizer["Username.Placeholder"]" @bind="@_username" @bind:event="oninput" required />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group mt-2">
|
<div class="form-group text-center mt-2">
|
||||||
<Label Class="control-label" For="password" HelpText="Please enter your Password" ResourceKey="Password">Password:</Label>
|
<Label Class="control-label" For="password" HelpText="Please enter your Password" ResourceKey="Password">Password:</Label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="password" type="@_passwordtype" name="Password" class="form-control" placeholder="@Localizer["Password.Placeholder"]" @bind="@_password" @bind:event="oninput" required />
|
<input id="password" type="@_passwordtype" @ref="password" name="Password" class="form-control" placeholder="@Localizer["Password.Placeholder"]" @bind="@_password" @bind:event="oninput" required />
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group mt-2">
|
|
||||||
@if (!_alwaysremember)
|
@if (!_alwaysremember)
|
||||||
{
|
{
|
||||||
<div class="form-check">
|
<div class="form-group text-center mt-2">
|
||||||
|
<div>
|
||||||
<input id="remember" type="checkbox" class="form-check-input" @bind="@_remember" />
|
<input id="remember" type="checkbox" class="form-check-input" @bind="@_remember" />
|
||||||
<Label Class="control-label" For="remember" HelpText="Specify if you would like to be signed back in automatically the next time you visit this site" ResourceKey="Remember">Remember Me?</Label>
|
<Label Class="control-label" For="remember" HelpText="Specify if you would like to be signed back in automatically the next time you visit this site" ResourceKey="Remember">Stay Signed In?</Label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<div 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>
|
</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>
|
<button type="button" class="btn btn-secondary col-12 mt-4" @onclick="@(() => SetAction("ForgotPassword"))">@Localizer["ForgotPassword"]</button>
|
||||||
<br /><br />
|
}
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["ForgotPassword"]</button>
|
|
||||||
|
@if (_allowloginlink)
|
||||||
|
{
|
||||||
|
<hr class="app-rule mt-3" />
|
||||||
|
<button type="button" class="btn btn-primary col-12 mt-2" @onclick="@(() => SetAction("LoginLink"))">@Localizer["UseLoginLink"]</button>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (_allowpasskeys)
|
||||||
|
{
|
||||||
|
<hr class="app-rule mt-3" />
|
||||||
|
<button type="button" class="btn btn-primary col-12 mt-2" @onclick="PasskeyLogin">@Localizer["Passkey"]</button>
|
||||||
|
}
|
||||||
|
|
||||||
@if (PageState.Site.AllowRegistration)
|
@if (PageState.Site.AllowRegistration)
|
||||||
{
|
{
|
||||||
<br /><br />
|
<hr class="app-rule mt-3" />
|
||||||
<NavLink href="@NavigateUrl("register")">@Localizer["Register"]</NavLink>
|
<div class="text-center mt-2">
|
||||||
}
|
<NavLink href="@_registerurl">@Localizer["Register"]</NavLink>
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
|
||||||
}
|
}
|
||||||
else
|
break;
|
||||||
{
|
case "ForgotPassword":
|
||||||
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
<div class="form-group text-center">
|
||||||
<div class="container Oqtane-Modules-Admin-Login">
|
<Label Class="control-label" For="username" HelpText="Please enter your Username" ResourceKey="Username">Username:</Label>
|
||||||
|
<input id="username" type="text" class="form-control" placeholder="@Localizer["Username.Placeholder"]" @bind="@_username" @bind:event="oninput" required />
|
||||||
|
</div>
|
||||||
|
<div class="btn-group mt-4 col-12" role="group">
|
||||||
|
<button type="button" class="btn btn-primary col-6" @onclick="ForgotPassword">@SharedLocalizer["Send"]</button>
|
||||||
|
<button type="button" class="btn btn-secondary col-6" @onclick="@(() => SetAction("Login"))">@SharedLocalizer["Cancel"]</button>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-secondary col-12 mt-4" @onclick="@(() => SetAction("ForgotUsername"))">@Localizer["ForgotUsername"]</button>
|
||||||
|
break;
|
||||||
|
case "ForgotUsername":
|
||||||
|
<div class="form-group text-center">
|
||||||
|
<Label Class="control-label" For="email" HelpText="Please enter your Email" ResourceKey="Email">Email:</Label>
|
||||||
|
<input id="email" type="text" class="form-control" placeholder="@Localizer["Email.Placeholder"]" @bind="@_email" required />
|
||||||
|
</div>
|
||||||
|
<div class="btn-group mt-4 col-12" role="group">
|
||||||
|
<button type="button" class="btn btn-primary col-6" @onclick="ForgotUsername">@SharedLocalizer["Send"]</button>
|
||||||
|
<button type="button" class="btn btn-secondary col-6" @onclick="@(() => SetAction("Login"))">@SharedLocalizer["Cancel"]</button>
|
||||||
|
</div>
|
||||||
|
break;
|
||||||
|
case "LoginLink":
|
||||||
|
<div class="form-group text-center">
|
||||||
|
<Label Class="control-label" For="email" HelpText="Please enter your Email" ResourceKey="Email">Email:</Label>
|
||||||
|
<input id="email" type="text" class="form-control" placeholder="@Localizer["Email.Placeholder"]" @bind="@_email" required />
|
||||||
|
</div>
|
||||||
|
<div class="btn-group mt-4 col-12" role="group">
|
||||||
|
<button type="button" class="btn btn-primary col-6" @onclick="LoginLink">@SharedLocalizer["Send"]</button>
|
||||||
|
<button type="button" class="btn btn-secondary col-6" @onclick="@(() => SetAction("Login"))">@SharedLocalizer["Cancel"]</button>
|
||||||
|
</div>
|
||||||
|
break;
|
||||||
|
case "TwoFactor":
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<Label Class="control-label" For="code" HelpText="Please enter the secure verification code which was sent to you by email" ResourceKey="Code">Verification Code:</Label>
|
<Label Class="control-label" For="code" HelpText="Please enter the secure verification code which was sent to you by email" ResourceKey="Code">Verification Code:</Label>
|
||||||
<input id="code" class="form-control" @bind="@_code" placeholder="@Localizer["Code.Placeholder"]" maxlength="6" required />
|
<input id="code" class="form-control" @bind="@_code" placeholder="@Localizer["Code.Placeholder"]" maxlength="6" required />
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<div class="btn-group mt-4 col-12" role="group">
|
||||||
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
|
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Cancel"]</button>
|
<button type="button" class="btn btn-secondary" @onclick="@(() => SetAction("Login"))">@SharedLocalizer["Cancel"]</button>
|
||||||
|
</div>
|
||||||
|
break;
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private bool _allowsitelogin = true;
|
private string _action = "Login";
|
||||||
private bool _allowexternallogin = false;
|
private bool _allowexternallogin = false;
|
||||||
|
private bool _allowsitelogin = true;
|
||||||
|
private bool _allowloginlink = false;
|
||||||
|
private bool _allowpasskeys = false;
|
||||||
|
private string _returnurl = string.Empty;
|
||||||
|
|
||||||
private ElementReference login;
|
private ElementReference login;
|
||||||
private bool validated = false;
|
private bool validated = false;
|
||||||
private bool twofactor = false;
|
|
||||||
private string _username = string.Empty;
|
private string _username = string.Empty;
|
||||||
private ElementReference username;
|
private ElementReference username;
|
||||||
private string _password = string.Empty;
|
private string _password = string.Empty;
|
||||||
private string _passwordtype = "password";
|
private string _passwordtype = "password";
|
||||||
private string _togglepassword = string.Empty;
|
private string _togglepassword = string.Empty;
|
||||||
|
private ElementReference password;
|
||||||
private bool _remember = false;
|
private bool _remember = false;
|
||||||
private bool _alwaysremember = false;
|
private bool _alwaysremember = false;
|
||||||
|
private string _registerurl = string.Empty;
|
||||||
|
private string _email = string.Empty;
|
||||||
private string _code = string.Empty;
|
private string _code = string.Empty;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
|
||||||
public override bool? Prerender => true;
|
|
||||||
|
|
||||||
public override List<Resource> Resources => new List<Resource>()
|
public override List<Resource> Resources => new List<Resource>()
|
||||||
{
|
{
|
||||||
@@ -103,8 +156,22 @@ else
|
|||||||
{
|
{
|
||||||
_allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false;
|
_allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false;
|
||||||
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
|
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
|
||||||
|
_allowloginlink = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:LoginLink", "false"));
|
||||||
|
_allowpasskeys = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:Passkeys", "false"));
|
||||||
_alwaysremember = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AlwaysRemember", "false"));
|
_alwaysremember = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AlwaysRemember", "false"));
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:RegisterUrl", "")))
|
||||||
|
{
|
||||||
|
_registerurl = SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:RegisterUrl", "");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_registerurl = NavigateUrl("register");
|
||||||
|
}
|
||||||
|
|
||||||
|
// PageState.ReturnUrl is not specified if user navigated directly to login page
|
||||||
|
_returnurl = (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : PageState.Alias.Path;
|
||||||
|
|
||||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
|
|
||||||
if (PageState.QueryString.ContainsKey("name"))
|
if (PageState.QueryString.ContainsKey("name"))
|
||||||
@@ -120,7 +187,7 @@ else
|
|||||||
|
|
||||||
if (PageState.QueryString.ContainsKey("key"))
|
if (PageState.QueryString.ContainsKey("key"))
|
||||||
{
|
{
|
||||||
user = await UserService.LinkUserAsync(user, PageState.QueryString["token"], PageState.Site.Settings["ExternalLogin:ProviderType"], PageState.QueryString["key"], PageState.Site.Settings["ExternalLogin:ProviderName"]);
|
user = await UserService.AddLoginAsync(user, PageState.QueryString["token"], PageState.Site.Settings["ExternalLogin:ProviderType"], PageState.QueryString["key"], PageState.Site.Settings["ExternalLogin:ProviderName"]);
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
await logger.LogInformation(LogFunction.Security, "External Login Linkage Successful For Username {Username}", _username);
|
await logger.LogInformation(LogFunction.Security, "External Login Linkage Successful For Username {Username}", _username);
|
||||||
@@ -152,7 +219,15 @@ else
|
|||||||
{
|
{
|
||||||
if (PageState.QueryString.ContainsKey("status"))
|
if (PageState.QueryString.ContainsKey("status"))
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["ExternalLoginStatus." + PageState.QueryString["status"]], MessageType.Info);
|
AddModuleMessage(Localizer["ExternalLoginStatus." + PageState.QueryString["status"]], MessageType.Warning);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_allowexternallogin && !_allowsitelogin)
|
||||||
|
{
|
||||||
|
// external login
|
||||||
|
ExternalLogin();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
private void SetAction(string action)
|
||||||
if (PageState.User != null && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
_action = action;
|
||||||
|
_username = "";
|
||||||
|
_password = "";
|
||||||
|
_email = "";
|
||||||
|
ClearModuleMessage();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExternalLogin()
|
||||||
|
{
|
||||||
|
NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + WebUtility.UrlEncode(_returnurl)), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TogglePassword()
|
||||||
|
{
|
||||||
|
if (_passwordtype == "password")
|
||||||
|
{
|
||||||
|
_passwordtype = "text";
|
||||||
|
_togglepassword = SharedLocalizer["HidePassword"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_passwordtype = "password";
|
||||||
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +291,7 @@ else
|
|||||||
var hybrid = (PageState.Runtime == Shared.Runtime.Hybrid);
|
var hybrid = (PageState.Runtime == Shared.Runtime.Hybrid);
|
||||||
var user = new User { SiteId = PageState.Site.SiteId, Username = _username, Password = _password, LastIPAddress = SiteState.RemoteIPAddress};
|
var user = new User { SiteId = PageState.Site.SiteId, Username = _username, Password = _password, LastIPAddress = SiteState.RemoteIPAddress};
|
||||||
|
|
||||||
if (!twofactor)
|
if (_action == "Login")
|
||||||
{
|
{
|
||||||
_remember = _alwaysremember || _remember;
|
_remember = _alwaysremember || _remember;
|
||||||
user = await UserService.LoginUserAsync(user, hybrid, _remember);
|
user = await UserService.LoginUserAsync(user, hybrid, _remember);
|
||||||
@@ -205,20 +305,17 @@ else
|
|||||||
{
|
{
|
||||||
await logger.LogInformation(LogFunction.Security, "Login Successful For {Username} From IP Address {IPAddress}", _username, SiteState.RemoteIPAddress);
|
await logger.LogInformation(LogFunction.Security, "Login Successful For {Username} From IP Address {IPAddress}", _username, SiteState.RemoteIPAddress);
|
||||||
|
|
||||||
// return url is not specified if user navigated directly to login page
|
|
||||||
var returnurl = (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : PageState.Alias.Path;
|
|
||||||
|
|
||||||
if (hybrid)
|
if (hybrid)
|
||||||
{
|
{
|
||||||
// hybrid apps utilize an interactive login
|
// hybrid apps utilize an interactive login
|
||||||
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
|
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
|
||||||
authstateprovider.NotifyAuthenticationChanged();
|
authstateprovider.NotifyAuthenticationChanged();
|
||||||
NavigationManager.NavigateTo(NavigateUrl(returnurl, true));
|
NavigationManager.NavigateTo(NavigateUrl(_returnurl, true));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// post back to the Login page so that the cookies are set correctly
|
// post back to the Login page so that the cookies are set correctly
|
||||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = WebUtility.UrlEncode(returnurl) };
|
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = WebUtility.UrlEncode(_returnurl) };
|
||||||
string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/");
|
string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/");
|
||||||
await interop.SubmitForm(url, fields);
|
await interop.SubmitForm(url, fields);
|
||||||
}
|
}
|
||||||
@@ -227,13 +324,13 @@ else
|
|||||||
{
|
{
|
||||||
if (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "required" || (user != null && user.TwoFactorRequired))
|
if (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "required" || (user != null && user.TwoFactorRequired))
|
||||||
{
|
{
|
||||||
twofactor = true;
|
_action = "TwoFactor";
|
||||||
validated = false;
|
validated = false;
|
||||||
AddModuleMessage(Localizer["Message.TwoFactor"], MessageType.Info);
|
AddModuleMessage(Localizer["Message.TwoFactor"], MessageType.Info);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!twofactor)
|
if (_action != "TwoFactor")
|
||||||
{
|
{
|
||||||
await logger.LogInformation(LogFunction.Security, "Login Failed For Username {Username}", _username);
|
await logger.LogInformation(LogFunction.Security, "Login Failed For Username {Username}", _username);
|
||||||
AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error);
|
||||||
@@ -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
|
try
|
||||||
{
|
{
|
||||||
if (_username != string.Empty)
|
if (!string.IsNullOrEmpty(_username))
|
||||||
{
|
{
|
||||||
var user = await UserService.GetUserAsync(_username, PageState.Site.SiteId);
|
if (await UserService.ForgotPasswordAsync(_username))
|
||||||
if (user != null)
|
|
||||||
{
|
{
|
||||||
await UserService.ForgotPasswordAsync(user);
|
|
||||||
await logger.LogInformation(LogFunction.Security, "Password Reset Notification Sent For Username {Username}", _username);
|
await logger.LogInformation(LogFunction.Security, "Password Reset Notification Sent For Username {Username}", _username);
|
||||||
AddModuleMessage(Localizer["Message.ForgotUser"], MessageType.Info);
|
AddModuleMessage(Localizer["Message.ForgotPassword"], MessageType.Info);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -283,10 +387,8 @@ else
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Message.ForgotPassword"], MessageType.Info);
|
AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -295,40 +397,114 @@ else
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Reset()
|
private async Task ForgotUsername()
|
||||||
{
|
{
|
||||||
twofactor = false;
|
try
|
||||||
_username = "";
|
|
||||||
_password = "";
|
|
||||||
ClearModuleMessage();
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task KeyPressed(KeyboardEventArgs e)
|
|
||||||
{
|
{
|
||||||
if (e.Code == "Enter" || e.Code == "NumpadEnter")
|
if (!string.IsNullOrEmpty(_email))
|
||||||
{
|
{
|
||||||
await Login();
|
if (await UserService.ForgotUsernameAsync(_email))
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TogglePassword()
|
|
||||||
{
|
{
|
||||||
if (_passwordtype == "password")
|
AddModuleMessage(Localizer["Message.ForgotUsername"], MessageType.Info);
|
||||||
{
|
await logger.LogInformation(LogFunction.Security, "Username Reminder Notification Sent For Email {Email}", _email);
|
||||||
_passwordtype = "text";
|
|
||||||
_togglepassword = SharedLocalizer["HidePassword"];
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_passwordtype = "password";
|
AddModuleMessage(Localizer["Message.UserDoesNotExist"], MessageType.Warning);
|
||||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
private void ExternalLogin()
|
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + WebUtility.UrlEncode(PageState.ReturnUrl)), true);
|
AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Sending Username Reminder {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.ForgotUsername"], MessageType.Error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task LoginLink()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(_email))
|
||||||
|
{
|
||||||
|
if (await UserService.SendLoginLinkAsync(_email, _returnurl))
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.SendLoginLink"], MessageType.Info);
|
||||||
|
await logger.LogInformation(LogFunction.Security, "Login Link Sent To Email {Email}", _email);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.UserDoesNotExist"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Sending Login Link {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.SendLoginLink"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender && PageState.QueryString.ContainsKey("options"))
|
||||||
|
{
|
||||||
|
// user has initiated a passkey login
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
var credential = await interop.RequestCredential(WebUtility.UrlDecode(PageState.QueryString["options"]));
|
||||||
|
if (!string.IsNullOrEmpty(credential))
|
||||||
|
{
|
||||||
|
// post back to the Passkey page so that the cookies are set correctly
|
||||||
|
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, operation = "login", credential = credential, returnurl = _returnurl };
|
||||||
|
string url = Utilities.TenantUrl(PageState.Alias, "/pages/passkey/");
|
||||||
|
await interop.SubmitForm(url, fields);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await logger.LogError("Passkey Login Was Not Successful");
|
||||||
|
AddModuleMessage(Localizer["Error.Passkey.Fail"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Passkey Login Was Not Successful");
|
||||||
|
AddModuleMessage(Localizer["Error.Passkey.Fail"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstRender && PageState.User == null && _allowsitelogin && _action == "Login")
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(_username))
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(username.Id)) // ensure username is visible in UI
|
||||||
|
{
|
||||||
|
await username.FocusAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(password.Id)) // ensure password is visible in UI
|
||||||
|
{
|
||||||
|
await password.FocusAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// redirect logged in user to specified page
|
||||||
|
if (PageState.User != null && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
|
{
|
||||||
|
NavigationManager.NavigateTo(_returnurl);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
@namespace Oqtane.Modules.Admin.ModuleDefinitions
|
@namespace Oqtane.Modules.Admin.ModuleDefinitions
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@using System.Text.RegularExpressions
|
@using System.Text.RegularExpressions
|
||||||
|
@using System.Reflection
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IModuleDefinitionService ModuleDefinitionService
|
@inject IModuleDefinitionService ModuleDefinitionService
|
||||||
@inject IModuleService ModuleService
|
@inject IModuleService ModuleService
|
||||||
@@ -42,6 +43,8 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if (_type == "External")
|
||||||
|
{
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
|
<Label Class="col-sm-3" For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -66,6 +69,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-success" @onclick="CreateModule">@Localizer["CreateModule"]</button>
|
<button type="button" class="btn btn-success" @onclick="CreateModule">@Localizer["CreateModule"]</button>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
@@ -80,9 +84,10 @@
|
|||||||
private string _description = string.Empty;
|
private string _description = string.Empty;
|
||||||
private List<Template> _templates;
|
private List<Template> _templates;
|
||||||
private string _template = "-";
|
private string _template = "-";
|
||||||
|
private string _minversion = "2.0.0";
|
||||||
|
private string _type = "";
|
||||||
private string[] _versions;
|
private string[] _versions;
|
||||||
private string _reference = "local";
|
private string _reference = "local";
|
||||||
private string _minversion = "2.0.0";
|
|
||||||
private string _location = string.Empty;
|
private string _location = string.Empty;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
@@ -93,6 +98,16 @@
|
|||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Info.Module.Development"], MessageType.Info);
|
AddModuleMessage(Localizer["Info.Module.Development"], MessageType.Info);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var entryAssemblyName = Assembly.GetEntryAssembly().GetName().Name;
|
||||||
|
if (entryAssemblyName.EndsWith(".Oqtane"))
|
||||||
|
{
|
||||||
|
// Oqtane Application assemblies end with .Server.Oqtane or .Client.Oqtane
|
||||||
|
string[] segments = entryAssemblyName.Split('.');
|
||||||
|
_owner = string.Join(".", segments, 0, segments.Length - 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
@@ -123,11 +138,18 @@
|
|||||||
if (string.IsNullOrEmpty(_description)) _description = _module;
|
if (string.IsNullOrEmpty(_description)) _description = _module;
|
||||||
if (IsValidXML(_description))
|
if (IsValidXML(_description))
|
||||||
{
|
{
|
||||||
|
if (_type == "Internal")
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Success.Module.Create.Internal"], MessageType.Success);
|
||||||
|
}
|
||||||
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
||||||
var moduleDefinition = new ModuleDefinition { Owner = _owner, Name = _module, Description = _description, Template = _template, Version = _reference, ModuleDefinitionName = template.Namespace };
|
var moduleDefinition = new ModuleDefinition { Owner = _owner, Name = _module, Description = _description, Template = _template, Version = _reference, ModuleDefinitionName = template.Namespace };
|
||||||
moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition);
|
moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition);
|
||||||
|
if (_type == "External")
|
||||||
|
{
|
||||||
GetLocation();
|
GetLocation();
|
||||||
AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success);
|
AddModuleMessage(string.Format(Localizer["Success.Module.Create.External"], NavigateUrl("admin/system")), MessageType.Success);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -153,7 +175,7 @@
|
|||||||
private bool IsValid(string name)
|
private bool IsValid(string name)
|
||||||
{
|
{
|
||||||
// must contain letters, underscores and digits and first character must be letter or underscore
|
// must contain letters, underscores and digits and first character must be letter or underscore
|
||||||
return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
|
return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_.]*$");
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsValidXML(string description)
|
private bool IsValidXML(string description)
|
||||||
@@ -165,11 +187,16 @@
|
|||||||
private void TemplateChanged(ChangeEventArgs e)
|
private void TemplateChanged(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
_template = (string)e.Value;
|
_template = (string)e.Value;
|
||||||
_minversion = "2.0.0";
|
|
||||||
if (_template != "-")
|
if (_template != "-")
|
||||||
{
|
{
|
||||||
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
||||||
_minversion = template.Version;
|
_minversion = template.Version;
|
||||||
|
_type = template.Type;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_minversion = "2.0.0";
|
||||||
|
_type = "";
|
||||||
}
|
}
|
||||||
GetLocation();
|
GetLocation();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<br />
|
||||||
<Section Name="Information" ResourceKey="Information">
|
<Section Name="Information" ResourceKey="Information">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -97,6 +98,12 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="fingerprint" HelpText="A unique identifier for the module's static resources. This value can be changed by clicking the Save option below (ie. cache busting)." ResourceKey="Fingerprint">Fingerprint: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="fingerprint" class="form-control" @bind="@_fingerprint" disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
<br />
|
<br />
|
||||||
@@ -231,6 +238,7 @@
|
|||||||
private string _url = "";
|
private string _url = "";
|
||||||
private string _contact = "";
|
private string _contact = "";
|
||||||
private string _license = "";
|
private string _license = "";
|
||||||
|
private string _fingerprint = "";
|
||||||
private List<Permission> _permissions = null;
|
private List<Permission> _permissions = null;
|
||||||
private string _createdby;
|
private string _createdby;
|
||||||
private DateTime _createdon;
|
private DateTime _createdon;
|
||||||
@@ -266,6 +274,7 @@
|
|||||||
_url = moduleDefinition.Url;
|
_url = moduleDefinition.Url;
|
||||||
_contact = moduleDefinition.Contact;
|
_contact = moduleDefinition.Contact;
|
||||||
_license = moduleDefinition.License;
|
_license = moduleDefinition.License;
|
||||||
|
_fingerprint = moduleDefinition.Fingerprint;
|
||||||
_permissions = moduleDefinition.PermissionList;
|
_permissions = moduleDefinition.PermissionList;
|
||||||
_createdby = moduleDefinition.CreatedBy;
|
_createdby = moduleDefinition.CreatedBy;
|
||||||
_createdon = moduleDefinition.CreatedOn;
|
_createdon = moduleDefinition.CreatedOn;
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ else
|
|||||||
|
|
||||||
private async Task LoadModuleDefinitions()
|
private async Task LoadModuleDefinitions()
|
||||||
{
|
{
|
||||||
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(_category)).ToList();
|
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Split(',', StringSplitOptions.RemoveEmptyEntries).Contains(_category)).ToList();
|
||||||
_packages = await PackageService.GetPackageUpdatesAsync("module");
|
_packages = await PackageService.GetPackageUpdatesAsync("module");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,16 +73,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="page" HelpText="The page that the module is located on" ResourceKey="Page">Page: </Label>
|
<Label Class="col-sm-3" For="page" HelpText="The page that the module is located on. Please note that shared modules cannot be moved to other pages." ResourceKey="Page">Page: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="page" class="form-select" @bind="@_pageId" required>
|
@if (PageState.Page.UserId != null || _isShared)
|
||||||
@if (PageState.Page.UserId != null)
|
|
||||||
{
|
{
|
||||||
|
<select id="page" class="form-select" @bind="@_pageId" required disabled>
|
||||||
<option value="@PageState.Page.PageId">@(PageState.Page.Name)</option>
|
<option value="@PageState.Page.PageId">@(PageState.Page.Name)</option>
|
||||||
|
</select>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (_pages != null)
|
<select id="page" class="form-select" @bind="@_pageId" required>
|
||||||
|
@if (_pages != null)
|
||||||
{
|
{
|
||||||
foreach (Page p in _pages)
|
foreach (Page p in _pages)
|
||||||
{
|
{
|
||||||
@@ -92,8 +94,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
</select>
|
</select>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -161,6 +163,7 @@
|
|||||||
private string _pane;
|
private string _pane;
|
||||||
private string _containerType;
|
private string _containerType;
|
||||||
private string _allPages = "false";
|
private string _allPages = "false";
|
||||||
|
private bool _isShared = false;
|
||||||
private string _header = "";
|
private string _header = "";
|
||||||
private string _footer = "";
|
private string _footer = "";
|
||||||
private string _permissionNames = "";
|
private string _permissionNames = "";
|
||||||
@@ -207,6 +210,7 @@
|
|||||||
_expirydate = Utilities.UtcAsLocalDate(pagemodule.ExpiryDate);
|
_expirydate = Utilities.UtcAsLocalDate(pagemodule.ExpiryDate);
|
||||||
|
|
||||||
_allPages = pagemodule.Module.AllPages.ToString();
|
_allPages = pagemodule.Module.AllPages.ToString();
|
||||||
|
_isShared = pagemodule.Module.IsShared;
|
||||||
createdby = pagemodule.Module.CreatedBy;
|
createdby = pagemodule.Module.CreatedBy;
|
||||||
createdon = pagemodule.Module.CreatedOn;
|
createdon = pagemodule.Module.CreatedOn;
|
||||||
modifiedby = pagemodule.Module.ModifiedBy;
|
modifiedby = pagemodule.Module.ModifiedBy;
|
||||||
@@ -276,7 +280,6 @@
|
|||||||
var interop = new Interop(JSRuntime);
|
var interop = new Interop(JSRuntime);
|
||||||
if (await interop.FormValid(form))
|
if (await interop.FormValid(form))
|
||||||
{
|
{
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(_title))
|
if (!string.IsNullOrEmpty(_title))
|
||||||
{
|
{
|
||||||
if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate))
|
if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate))
|
||||||
@@ -284,7 +287,33 @@
|
|||||||
AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning);
|
AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update module settings first
|
||||||
|
if (_moduleSettingsType != null)
|
||||||
|
{
|
||||||
|
if (_moduleSettings is ISettingsControl moduleSettingsControl)
|
||||||
|
{
|
||||||
|
// module settings updated using explicit interface
|
||||||
|
await moduleSettingsControl.UpdateSettings();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// legacy approach - module settings updated by convention (ie. by calling a public method named "UpdateSettings" in settings component)
|
||||||
|
// this method should be removed however the ISettingsControl declaration was not added to the default module template until version 10.1.2
|
||||||
|
_moduleSettings?.GetType().GetMethod("UpdateSettings")?.Invoke(_moduleSettings, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update container settings
|
||||||
|
if (_containerSettingsType != null && _containerSettings is ISettingsControl containerSettingsControl)
|
||||||
|
{
|
||||||
|
await containerSettingsControl.UpdateSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
// update page module
|
||||||
var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
|
var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
|
||||||
|
var pageId = pagemodule.PageId; // preserve
|
||||||
|
var pane = pagemodule.Pane; // preserve
|
||||||
pagemodule.PageId = int.Parse(_pageId);
|
pagemodule.PageId = int.Parse(_pageId);
|
||||||
pagemodule.Title = _title;
|
pagemodule.Title = _title;
|
||||||
pagemodule.Pane = _pane;
|
pagemodule.Pane = _pane;
|
||||||
@@ -302,33 +331,21 @@
|
|||||||
pagemodule.Header = _header;
|
pagemodule.Header = _header;
|
||||||
pagemodule.Footer = _footer;
|
pagemodule.Footer = _footer;
|
||||||
await PageModuleService.UpdatePageModuleAsync(pagemodule);
|
await PageModuleService.UpdatePageModuleAsync(pagemodule);
|
||||||
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
|
|
||||||
|
|
||||||
|
// update page module order if page or pane changed
|
||||||
|
if (pageId != pagemodule.PageId || pane != pagemodule.Pane)
|
||||||
|
{
|
||||||
|
await PageModuleService.UpdatePageModuleOrderAsync(pageId, pane); // old page/pane
|
||||||
|
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); // new page/pane
|
||||||
|
}
|
||||||
|
|
||||||
|
// update module
|
||||||
var module = await ModuleService.GetModuleAsync(ModuleState.ModuleId);
|
var module = await ModuleService.GetModuleAsync(ModuleState.ModuleId);
|
||||||
module.AllPages = bool.Parse(_allPages);
|
module.AllPages = bool.Parse(_allPages);
|
||||||
module.PageModuleId = ModuleState.PageModuleId;
|
module.PageModuleId = ModuleState.PageModuleId;
|
||||||
module.PermissionList = _permissionGrid.GetPermissionList();
|
module.PermissionList = _permissionGrid.GetPermissionList();
|
||||||
await ModuleService.UpdateModuleAsync(module);
|
await ModuleService.UpdateModuleAsync(module);
|
||||||
|
|
||||||
if (_moduleSettingsType != null)
|
|
||||||
{
|
|
||||||
if (_moduleSettings is ISettingsControl moduleSettingsControl)
|
|
||||||
{
|
|
||||||
// module settings updated using explicit interface
|
|
||||||
await moduleSettingsControl.UpdateSettings();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// legacy support - module settings updated by convention ( ie. by calling a public method named "UpdateSettings" in settings component )
|
|
||||||
_moduleSettings?.GetType().GetMethod("UpdateSettings")?.Invoke(_moduleSettings, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_containerSettingsType != null && _containerSettings is ISettingsControl containerSettingsControl)
|
|
||||||
{
|
|
||||||
await containerSettingsControl.UpdateSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
|
<Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="name" class="form-control" @bind="@_name" maxlength="50" required />
|
<input id="name" class="form-control" @bind="@_name" maxlength="100" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
@@ -81,21 +81,9 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
|
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="navigation" class="form-select" @bind="@_isnavigation" required>
|
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
||||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
|
||||||
<option value="False">@SharedLocalizer["No"]</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="clickable" HelpText="Select whether the link in the site navigation is enabled or disabled" ResourceKey="Clickable">Clickable? </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<select id="clickable" class="form-select" @bind="@_isclickable" required>
|
|
||||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
|
||||||
<option value="False">@SharedLocalizer["No"]</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -110,27 +98,6 @@
|
|||||||
<input id="url" class="form-control" @bind="@_url" maxlength="500" />
|
<input id="url" class="form-control" @bind="@_url" maxlength="500" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<InputList Value="@_icon" ValueChanged="IconChanged" DataList="@_icons" ResourceKey="Icon" ResourceType="@_iconresources" />
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-1">
|
|
||||||
<i class="@_icon"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this page is active" ResourceKey="EffectiveDate">Effective Date: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this page expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label>
|
<Label Class="col-sm-3" For="personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -141,15 +108,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Section Name="Theme" Heading="Theme" ResourceKey="Theme">
|
||||||
<Section Name="Appearance" ResourceKey="Appearance" Heading=@Localizer["Appearance.Name"]>
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
|
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -181,6 +141,49 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
|
<Section Name="Appearance" Heading="Appearance" ResourceKey="Appearance">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<InputList Value="@_icon" ValueChanged="IconChanged" DataList="@_icons" ResourceKey="Icon" ResourceType="@_iconresources" />
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-1">
|
||||||
|
<i class="@_icon"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="navigation" class="form-select" @bind="@_isnavigation" required>
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="clickable" HelpText="Select whether the link in the site navigation is enabled or disabled" ResourceKey="Clickable">Clickable? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="clickable" class="form-select" @bind="@_isclickable" required>
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this page is active" ResourceKey="EffectiveDate">Effective Date: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this page expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
<Section Name="PageContent" ResourceKey="PageContent" Heading=@Localizer["PageContent.Heading"]>
|
<Section Name="PageContent" ResourceKey="PageContent" Heading=@Localizer["PageContent.Heading"]>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
|
<Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="name" class="form-control" @bind="@_name" maxlength="50" required />
|
<input id="name" class="form-control" @bind="@_name" maxlength="100" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
<Label Class="col-sm-3" For="move" HelpText="Select the location where you would like the page to be moved in relation to other pages" ResourceKey="Move">Move: </Label>
|
<Label Class="col-sm-3" For="move" HelpText="Select the location where you would like the page to be moved in relation to other pages" ResourceKey="Move">Move: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="move" class="form-select" @bind="@_insert" required>
|
<select id="move" class="form-select" @bind="@_insert" required>
|
||||||
@if (_parentid == _currentparentid)
|
@if (_parentid == _currentparentid && !_copy)
|
||||||
{
|
{
|
||||||
<option value="="><@Localizer["ThisLocation.Keep"]></option>
|
<option value="="><@Localizer["ThisLocation.Keep"]></option>
|
||||||
}
|
}
|
||||||
@@ -98,21 +98,9 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
|
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="navigation" class="form-select" @bind="@_isnavigation" required>
|
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
||||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
|
||||||
<option value="False">@SharedLocalizer["No"]</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="clickable" HelpText="Select whether the link in the site navigation is enabled or disabled" ResourceKey="Clickable">Clickable? </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<select id="clickable" class="form-select" @bind="@_isclickable" required>
|
|
||||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
|
||||||
<option value="False">@SharedLocalizer["No"]</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -127,27 +115,6 @@
|
|||||||
<input id="url" class="form-control" @bind="@_url" maxlength="500" />
|
<input id="url" class="form-control" @bind="@_url" maxlength="500" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<InputList Value="@_icon" ValueChanged="IconChanged" DataList="@_icons" ResourceKey="Icon" ResourceType="@_iconresources" />
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-1">
|
|
||||||
<i class="@_icon"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this page is active" ResourceKey="EffectiveDate">Effective Date: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this page expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label>
|
<Label Class="col-sm-3" For="personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -158,14 +125,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Section Name="Appearance" ResourceKey="Appearance" Heading="Appearance">
|
<Section Name="Theme" ResourceKey="Theme" Heading="Theme">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
|
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -200,6 +161,49 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
|
<Section Name="Appearance" ResourceKey="Appearance" Heading="Appearance">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<InputList Value="@_icon" ValueChanged="IconChanged" DataList="@_icons" ResourceKey="Icon" ResourceType="@_iconresources" />
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-1">
|
||||||
|
<i class="@_icon"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="navigation" class="form-select" @bind="@_isnavigation" required>
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="clickable" HelpText="Select whether the link in the site navigation is enabled or disabled" ResourceKey="Clickable">Clickable? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="clickable" class="form-select" @bind="@_isclickable" required>
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this page is active" ResourceKey="EffectiveDate">Effective Date: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this page expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
<Section Name="PageContent" Heading="Page Content" ResourceKey="PageContent">
|
<Section Name="PageContent" Heading="Page Content" ResourceKey="PageContent">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -221,7 +225,10 @@
|
|||||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
|
@if (!_copy)
|
||||||
|
{
|
||||||
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
|
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
|
||||||
|
}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Permissions" ResourceKey="Permissions" Heading="Permissions">
|
<TabPanel Name="Permissions" ResourceKey="Permissions" Heading="Permissions">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@@ -243,6 +250,8 @@
|
|||||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
</div>
|
</div>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
@if (!_copy)
|
||||||
|
{
|
||||||
<TabPanel Name="PageModules" Heading="Modules" ResourceKey="PageModules">
|
<TabPanel Name="PageModules" Heading="Modules" ResourceKey="PageModules">
|
||||||
<Pager Items="_pageModules">
|
<Pager Items="_pageModules">
|
||||||
<Header>
|
<Header>
|
||||||
@@ -268,6 +277,8 @@
|
|||||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
</TabStrip>
|
</TabStrip>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -345,6 +356,7 @@
|
|||||||
private List<ThemeControl> _containers = new List<ThemeControl>();
|
private List<ThemeControl> _containers = new List<ThemeControl>();
|
||||||
private List<Page> _pages;
|
private List<Page> _pages;
|
||||||
private int _pageId;
|
private int _pageId;
|
||||||
|
private bool _copy = false;
|
||||||
private string _name;
|
private string _name;
|
||||||
private string _currentparentid;
|
private string _currentparentid;
|
||||||
private string _parentid = "-1";
|
private string _parentid = "-1";
|
||||||
@@ -390,6 +402,10 @@
|
|||||||
{
|
{
|
||||||
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||||
_pageId = Int32.Parse(PageState.QueryString["id"]);
|
_pageId = Int32.Parse(PageState.QueryString["id"]);
|
||||||
|
if (PageState.QueryString.ContainsKey("copy"))
|
||||||
|
{
|
||||||
|
_copy = bool.Parse(PageState.QueryString["copy"]);
|
||||||
|
}
|
||||||
_page = await PageService.GetPageAsync(_pageId);
|
_page = await PageService.GetPageAsync(_pageId);
|
||||||
_icons = await SystemService.GetIconsAsync();
|
_icons = await SystemService.GetIconsAsync();
|
||||||
_iconresources = Utilities.GetFullTypeName(typeof(IconResources).AssemblyQualifiedName);
|
_iconresources = Utilities.GetFullTypeName(typeof(IconResources).AssemblyQualifiedName);
|
||||||
@@ -409,7 +425,7 @@
|
|||||||
_children = new List<Page>();
|
_children = new List<Page>();
|
||||||
foreach (Page p in _pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid, CultureInfo.InvariantCulture))))
|
foreach (Page p in _pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid, CultureInfo.InvariantCulture))))
|
||||||
{
|
{
|
||||||
if (p.PageId != _pageId && UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
if ((p.PageId != _pageId || _copy) && UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
||||||
{
|
{
|
||||||
_children.Add(p);
|
_children.Add(p);
|
||||||
}
|
}
|
||||||
@@ -436,6 +452,12 @@
|
|||||||
_expirydate = Utilities.UtcAsLocalDate(_page.ExpiryDate);
|
_expirydate = Utilities.UtcAsLocalDate(_page.ExpiryDate);
|
||||||
_ispersonalizable = _page.IsPersonalizable.ToString();
|
_ispersonalizable = _page.IsPersonalizable.ToString();
|
||||||
|
|
||||||
|
if (_copy)
|
||||||
|
{
|
||||||
|
_insert = ">";
|
||||||
|
_childid = _page.PageId;
|
||||||
|
}
|
||||||
|
|
||||||
// appearance
|
// appearance
|
||||||
_title = _page.Title;
|
_title = _page.Title;
|
||||||
_themetype = _page.ThemeType;
|
_themetype = _page.ThemeType;
|
||||||
@@ -466,6 +488,19 @@
|
|||||||
// permissions
|
// permissions
|
||||||
_permissions = _page.PermissionList;
|
_permissions = _page.PermissionList;
|
||||||
_updatemodulepermissions = "True";
|
_updatemodulepermissions = "True";
|
||||||
|
if (_copy)
|
||||||
|
{
|
||||||
|
_permissions = _page.PermissionList.Select(item => new Permission
|
||||||
|
{
|
||||||
|
SiteId = item.SiteId,
|
||||||
|
EntityName = item.EntityName,
|
||||||
|
EntityId = -1,
|
||||||
|
PermissionName = item.PermissionName,
|
||||||
|
RoleName = item.RoleName,
|
||||||
|
UserId = item.UserId,
|
||||||
|
IsAuthorized = item.IsAuthorized,
|
||||||
|
}).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
// page modules
|
// page modules
|
||||||
var modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
|
var modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
|
||||||
@@ -480,6 +515,13 @@
|
|||||||
_deletedon = _page.DeletedOn;
|
_deletedon = _page.DeletedOn;
|
||||||
|
|
||||||
ThemeSettings();
|
ThemeSettings();
|
||||||
|
|
||||||
|
if (_copy)
|
||||||
|
{
|
||||||
|
_name = "";
|
||||||
|
_path = "";
|
||||||
|
}
|
||||||
|
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -572,10 +614,18 @@
|
|||||||
await ScrollToPageTop();
|
await ScrollToPageTop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(_themetype) && _containertype != "-")
|
if (!string.IsNullOrEmpty(_themetype) && _containertype != "-")
|
||||||
{
|
{
|
||||||
string currentPath = _page.Path;
|
string currentPath = _page.Path;
|
||||||
|
|
||||||
|
if (_copy)
|
||||||
|
{
|
||||||
|
_page = new Page();
|
||||||
|
_page.SiteId = PageState.Site.SiteId;
|
||||||
|
currentPath = "";
|
||||||
|
}
|
||||||
|
|
||||||
_page.Name = _name;
|
_page.Name = _name;
|
||||||
|
|
||||||
if (_parentid == "-1")
|
if (_parentid == "-1")
|
||||||
@@ -631,6 +681,13 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update theme settings
|
||||||
|
if (_themeSettingsType != null && _themeSettings is ISettingsControl themeSettingsControl)
|
||||||
|
{
|
||||||
|
await themeSettingsControl.UpdateSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
// default page properties
|
||||||
if (_insert != "=")
|
if (_insert != "=")
|
||||||
{
|
{
|
||||||
Page child;
|
Page child;
|
||||||
@@ -684,7 +741,21 @@
|
|||||||
_page.UpdateModulePermissions = bool.Parse(_updatemodulepermissions);
|
_page.UpdateModulePermissions = bool.Parse(_updatemodulepermissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_copy)
|
||||||
|
{
|
||||||
|
// create page
|
||||||
|
_page = await PageService.AddPageAsync(_page);
|
||||||
|
await PageService.CopyPageAsync(_pageId, _page.PageId, bool.Parse(_updatemodulepermissions));
|
||||||
|
await logger.LogInformation("Page Added {Page}", _page);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// update page
|
||||||
_page = await PageService.UpdatePageAsync(_page);
|
_page = await PageService.UpdatePageAsync(_page);
|
||||||
|
await logger.LogInformation("Page Saved {Page}", _page);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update page order
|
||||||
await PageService.UpdatePageOrderAsync(_page.SiteId, _page.PageId, _page.ParentId);
|
await PageService.UpdatePageOrderAsync(_page.SiteId, _page.PageId, _page.ParentId);
|
||||||
if (_currentparentid == string.Empty)
|
if (_currentparentid == string.Empty)
|
||||||
{
|
{
|
||||||
@@ -695,12 +766,6 @@
|
|||||||
await PageService.UpdatePageOrderAsync(_page.SiteId, _page.PageId, int.Parse(_currentparentid));
|
await PageService.UpdatePageOrderAsync(_page.SiteId, _page.PageId, int.Parse(_currentparentid));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_themeSettingsType != null && _themeSettings is ISettingsControl themeSettingsControl)
|
|
||||||
{
|
|
||||||
await themeSettingsControl.UpdateSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
await logger.LogInformation("Page Saved {Page}", _page);
|
|
||||||
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
|
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(PageState.ReturnUrl, true); // redirect to page being edited and reload
|
NavigationManager.NavigateTo(PageState.ReturnUrl, true); // redirect to page being edited and reload
|
||||||
|
|||||||
@@ -3,10 +3,11 @@
|
|||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IUserService UserService
|
@inject IUserService UserService
|
||||||
@inject IStringLocalizer<Index> Localizer
|
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
|
||||||
@inject ISettingService SettingService
|
@inject ISettingService SettingService
|
||||||
@inject ITimeZoneService TimeZoneService
|
@inject ITimeZoneService TimeZoneService
|
||||||
|
@inject ILanguageService LanguageService
|
||||||
|
@inject IStringLocalizer<Index> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@if (_initialized)
|
@if (_initialized)
|
||||||
{
|
{
|
||||||
@@ -71,6 +72,18 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="culture" HelpText="Your preferred language. Note that you will only be able to choose from languages supported on this site." ResourceKey="CultureCode">Language:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="culture" class="form-select" @bind="@_culturecode">
|
||||||
|
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||||
|
@foreach (var language in _languages)
|
||||||
|
{
|
||||||
|
<option value="@language.Code">@language.Name</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<button type="button" class="btn btn-primary" @onclick="Register">@Localizer["Register"]</button>
|
<button type="button" class="btn btn-primary" @onclick="Register">@Localizer["Register"]</button>
|
||||||
@@ -95,6 +108,8 @@
|
|||||||
@code {
|
@code {
|
||||||
private bool _initialized = false;
|
private bool _initialized = false;
|
||||||
private List<Models.TimeZone> _timezones;
|
private List<Models.TimeZone> _timezones;
|
||||||
|
private IEnumerable<Models.Language> _languages;
|
||||||
|
|
||||||
private string _passwordrequirements;
|
private string _passwordrequirements;
|
||||||
private string _username = string.Empty;
|
private string _username = string.Empty;
|
||||||
private ElementReference form;
|
private ElementReference form;
|
||||||
@@ -106,6 +121,7 @@
|
|||||||
private string _email = string.Empty;
|
private string _email = string.Empty;
|
||||||
private string _displayname = string.Empty;
|
private string _displayname = string.Empty;
|
||||||
private string _timezoneid = string.Empty;
|
private string _timezoneid = string.Empty;
|
||||||
|
private string _culturecode = string.Empty;
|
||||||
private bool _userCreated = false;
|
private bool _userCreated = false;
|
||||||
private bool _allowsitelogin = true;
|
private bool _allowsitelogin = true;
|
||||||
|
|
||||||
@@ -113,10 +129,13 @@
|
|||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
|
_timezones = TimeZoneService.GetTimeZones();
|
||||||
|
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
|
||||||
|
|
||||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||||
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
|
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
|
||||||
_timezones = TimeZoneService.GetTimeZones();
|
|
||||||
_timezoneid = PageState.Site.TimeZoneId;
|
_timezoneid = PageState.Site.TimeZoneId;
|
||||||
|
_culturecode = PageState.Site.CultureCode;
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,6 +166,7 @@
|
|||||||
Email = _email,
|
Email = _email,
|
||||||
DisplayName = (_displayname == string.Empty ? _username : _displayname),
|
DisplayName = (_displayname == string.Empty ? _username : _displayname),
|
||||||
TimeZoneId = _timezoneid,
|
TimeZoneId = _timezoneid,
|
||||||
|
CultureCode = _culturecode,
|
||||||
PhotoFileId = null
|
PhotoFileId = null
|
||||||
};
|
};
|
||||||
user = await UserService.AddUserAsync(user);
|
user = await UserService.AddUserAsync(user);
|
||||||
|
|||||||
@@ -11,11 +11,15 @@
|
|||||||
@inject IThemeService ThemeService
|
@inject IThemeService ThemeService
|
||||||
@inject ISettingService SettingService
|
@inject ISettingService SettingService
|
||||||
@inject ITimeZoneService TimeZoneService
|
@inject ITimeZoneService TimeZoneService
|
||||||
|
@inject ILocalizationService LocalizationService
|
||||||
@inject IServiceProvider ServiceProvider
|
@inject IServiceProvider ServiceProvider
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject INotificationService NotificationService
|
@inject INotificationService NotificationService
|
||||||
|
@inject IJobService JobService
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
@inject IOutputCacheService CacheService
|
@inject IOutputCacheService CacheService
|
||||||
|
@inject ISiteGroupService SiteGroupService
|
||||||
|
@inject ISiteGroupMemberService SiteGroupMemberService
|
||||||
|
|
||||||
@if (_initialized)
|
@if (_initialized)
|
||||||
{
|
{
|
||||||
@@ -28,22 +32,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="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>
|
<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="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>
|
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="timezone" class="form-select" @bind="@_timezoneid">
|
<select id="timezone" class="form-select" @bind="@_timezoneid">
|
||||||
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||||
@@ -54,6 +43,18 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</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))
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -207,13 +208,6 @@
|
|||||||
</div>
|
</div>
|
||||||
@if (_smtpenabled == "True" && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
@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">
|
<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>
|
<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">
|
<div class="col-sm-9">
|
||||||
@@ -264,15 +258,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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
|
else
|
||||||
{
|
{
|
||||||
@@ -348,57 +333,6 @@
|
|||||||
</Section>
|
</Section>
|
||||||
@if (_aliases != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
@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">
|
<Section Name="Hosting" Heading="Hosting Model" ResourceKey="Hosting">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -411,8 +345,20 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if (_rendermode == RenderModes.Static)
|
||||||
|
{
|
||||||
<div class="row mb-1 align-items-center">
|
<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="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 hosting model for UI components which require interactivity" ResourceKey="Runtime">Interactivity: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="runtime" class="form-select" @bind="@_runtime" required>
|
<select id="runtime" class="form-select" @bind="@_runtime" required>
|
||||||
<option value="@Runtimes.Server">@(SharedLocalizer["Runtime" + @Runtimes.Server])</option>
|
<option value="@Runtimes.Server">@(SharedLocalizer["Runtime" + @Runtimes.Server])</option>
|
||||||
@@ -441,6 +387,180 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</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">
|
<Section Name="TenantInformation" Heading="Database" ResourceKey="TenantInformation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -481,10 +601,11 @@
|
|||||||
private List<ThemeControl> _containers = new List<ThemeControl>();
|
private List<ThemeControl> _containers = new List<ThemeControl>();
|
||||||
private List<Page> _pages;
|
private List<Page> _pages;
|
||||||
private List<Models.TimeZone> _timezones;
|
private List<Models.TimeZone> _timezones;
|
||||||
|
private IEnumerable<Models.Culture> _cultures;
|
||||||
|
|
||||||
private string _name = string.Empty;
|
private string _name = string.Empty;
|
||||||
private string _homepageid = "-";
|
|
||||||
private string _timezoneid = string.Empty;
|
private string _timezoneid = string.Empty;
|
||||||
|
private string _culturecode = string.Empty;
|
||||||
private string _isdeleted;
|
private string _isdeleted;
|
||||||
private string _sitemap = "";
|
private string _sitemap = "";
|
||||||
private string _siteguid = "";
|
private string _siteguid = "";
|
||||||
@@ -522,7 +643,6 @@
|
|||||||
private string _togglesmtpclientsecret = string.Empty;
|
private string _togglesmtpclientsecret = string.Empty;
|
||||||
private string _smtpscopes = string.Empty;
|
private string _smtpscopes = string.Empty;
|
||||||
private string _smtpsender = string.Empty;
|
private string _smtpsender = string.Empty;
|
||||||
private string _smtprelay = "False";
|
|
||||||
private int _retention = 30;
|
private int _retention = 30;
|
||||||
|
|
||||||
private string _pwaisenabled;
|
private string _pwaisenabled;
|
||||||
@@ -531,15 +651,28 @@
|
|||||||
private int _pwasplashiconfileid = -1;
|
private int _pwasplashiconfileid = -1;
|
||||||
private FileManager _pwasplashiconfilemanager;
|
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 List<Alias> _aliases;
|
||||||
private int _aliasid = -1;
|
private int _aliasid = -1;
|
||||||
private string _aliasname;
|
private string _aliasname;
|
||||||
private string _defaultalias;
|
private string _defaultalias;
|
||||||
|
private bool _addAlias = false;
|
||||||
|
|
||||||
private string _rendermode = RenderModes.Interactive;
|
private List<SiteGroup> _siteGroups = new List<SiteGroup>();
|
||||||
private string _runtime = Runtimes.Server;
|
private List<Site> _sites = new List<Site>();
|
||||||
private string _prerender = "True";
|
private int _siteGroupId = -1;
|
||||||
private string _hybrid = "False";
|
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 _tenant = string.Empty;
|
||||||
private string _database = string.Empty;
|
private string _database = string.Empty;
|
||||||
@@ -567,16 +700,14 @@
|
|||||||
if (site != null)
|
if (site != null)
|
||||||
{
|
{
|
||||||
_timezones = TimeZoneService.GetTimeZones();
|
_timezones = TimeZoneService.GetTimeZones();
|
||||||
|
_cultures = await LocalizationService.GetNeutralCulturesAsync();
|
||||||
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
||||||
|
|
||||||
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||||
|
|
||||||
_name = site.Name;
|
_name = site.Name;
|
||||||
_timezoneid = site.TimeZoneId;
|
_timezoneid = site.TimeZoneId;
|
||||||
if (site.HomePageId != null)
|
_culturecode = site.CultureCode;
|
||||||
{
|
|
||||||
_homepageid = site.HomePageId.Value.ToString();
|
|
||||||
}
|
|
||||||
_isdeleted = site.IsDeleted.ToString();
|
_isdeleted = site.IsDeleted.ToString();
|
||||||
_sitemap = PageState.Alias.Protocol + PageState.Alias.Name + "/sitemap.xml";
|
_sitemap = PageState.Alias.Protocol + PageState.Alias.Name + "/sitemap.xml";
|
||||||
_siteguid = site.SiteGuid;
|
_siteguid = site.SiteGuid;
|
||||||
@@ -640,7 +771,6 @@
|
|||||||
_togglesmtpclientsecret = SharedLocalizer["ShowPassword"];
|
_togglesmtpclientsecret = SharedLocalizer["ShowPassword"];
|
||||||
_smtpscopes = SettingService.GetSetting(settings, "SMTPScopes", string.Empty);
|
_smtpscopes = SettingService.GetSetting(settings, "SMTPScopes", string.Empty);
|
||||||
_smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty);
|
_smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty);
|
||||||
_smtprelay = SettingService.GetSetting(settings, "SMTPRelay", "False");
|
|
||||||
_retention = int.Parse(SettingService.GetSetting(settings, "NotificationRetention", "30"));
|
_retention = int.Parse(SettingService.GetSetting(settings, "NotificationRetention", "30"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -656,10 +786,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// aliases
|
// aliases
|
||||||
await GetAliases();
|
await LoadAliases();
|
||||||
|
|
||||||
// hosting model
|
// hosting model
|
||||||
_rendermode = site.RenderMode;
|
_rendermode = site.RenderMode;
|
||||||
|
_enhancednavigation = site.EnhancedNavigation.ToString();
|
||||||
_runtime = site.Runtime;
|
_runtime = site.Runtime;
|
||||||
_prerender = site.Prerender.ToString();
|
_prerender = site.Prerender.ToString();
|
||||||
_hybrid = site.Hybrid.ToString();
|
_hybrid = site.Hybrid.ToString();
|
||||||
@@ -669,7 +800,7 @@
|
|||||||
{
|
{
|
||||||
var tenants = await TenantService.GetTenantsAsync();
|
var tenants = await TenantService.GetTenantsAsync();
|
||||||
var _databases = await DatabaseService.GetDatabasesAsync();
|
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)
|
if (tenant != null)
|
||||||
{
|
{
|
||||||
_tenant = tenant.Name;
|
_tenant = tenant.Name;
|
||||||
@@ -679,6 +810,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// site groups
|
||||||
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
|
{
|
||||||
|
await LoadSiteGroups();
|
||||||
|
}
|
||||||
|
|
||||||
// audit
|
// audit
|
||||||
_createdby = site.CreatedBy;
|
_createdby = site.CreatedBy;
|
||||||
_createdon = site.CreatedOn;
|
_createdon = site.CreatedOn;
|
||||||
@@ -746,7 +883,7 @@
|
|||||||
{
|
{
|
||||||
site.Name = _name;
|
site.Name = _name;
|
||||||
site.TimeZoneId = _timezoneid;
|
site.TimeZoneId = _timezoneid;
|
||||||
site.HomePageId = (_homepageid != "-" ? int.Parse(_homepageid) : null);
|
site.CultureCode = _culturecode;
|
||||||
site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
|
site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
|
||||||
|
|
||||||
// appearance
|
// appearance
|
||||||
@@ -806,15 +943,13 @@
|
|||||||
|
|
||||||
// hosting model
|
// hosting model
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
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.RenderMode = _rendermode;
|
||||||
|
site.EnhancedNavigation = bool.Parse(_enhancednavigation);
|
||||||
site.Runtime = _runtime;
|
site.Runtime = _runtime;
|
||||||
site.Prerender = bool.Parse(_prerender);
|
site.Prerender = bool.Parse(_prerender);
|
||||||
site.Hybrid = bool.Parse(_hybrid);
|
site.Hybrid = bool.Parse(_hybrid);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
site = await SiteService.UpdateSiteAsync(site);
|
site = await SiteService.UpdateSiteAsync(site);
|
||||||
|
|
||||||
@@ -834,8 +969,18 @@
|
|||||||
settings = SettingService.SetSetting(settings, "SMTPClientSecret", _smtpclientsecret, true);
|
settings = SettingService.SetSetting(settings, "SMTPClientSecret", _smtpclientsecret, true);
|
||||||
settings = SettingService.SetSetting(settings, "SMTPScopes", _smtpscopes, true);
|
settings = SettingService.SetSetting(settings, "SMTPScopes", _smtpscopes, true);
|
||||||
settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
|
settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
|
||||||
settings = SettingService.SetSetting(settings, "SMTPRelay", _smtprelay, true);
|
|
||||||
settings = SettingService.SetSetting(settings, "NotificationRetention", _retention.ToString(), 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
|
//cookie consent
|
||||||
@@ -874,17 +1019,17 @@
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var aliases = await AliasService.GetAliasesAsync();
|
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 SiteService.DeleteSiteAsync(PageState.Site.SiteId);
|
||||||
await logger.LogInformation("Site Deleted {SiteId}", 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);
|
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);
|
NavigationManager.NavigateTo(PageState.Uri.Scheme + "://" + redirect.Name, true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -945,7 +1090,10 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
_smtpenabled = "True";
|
||||||
|
|
||||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
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, "SMTPHost", _smtphost, true);
|
||||||
settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true);
|
settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true);
|
||||||
settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true);
|
settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true);
|
||||||
@@ -960,6 +1108,14 @@
|
|||||||
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||||
await logger.LogInformation("Site SMTP Settings Saved");
|
await logger.LogInformation("Site SMTP Settings Saved");
|
||||||
|
|
||||||
|
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."));
|
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);
|
AddModuleMessage(Localizer["Info.Smtp.SaveSettings"], MessageType.Info);
|
||||||
await ScrollToPageTop();
|
await ScrollToPageTop();
|
||||||
@@ -976,45 +1132,51 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task GetAliases()
|
private async Task LoadAliases()
|
||||||
{
|
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
|
||||||
{
|
{
|
||||||
_aliases = await AliasService.GetAliasesAsync();
|
_aliases = await AliasService.GetAliasesAsync();
|
||||||
_aliases = _aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Site.TenantId).OrderBy(item => item.AliasId).ToList();
|
_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)
|
||||||
|
{
|
||||||
|
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()
|
private void AddAlias()
|
||||||
{
|
{
|
||||||
_aliases.Add(new Alias { AliasId = 0, Name = "", IsDefault = false });
|
_aliasid = -1;
|
||||||
_aliasid = 0;
|
|
||||||
_aliasname = "";
|
_aliasname = "";
|
||||||
_defaultalias = "False";
|
_defaultalias = "False";
|
||||||
|
_addAlias = true;
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EditAlias(Alias alias)
|
private async Task DeleteAlias()
|
||||||
{
|
{
|
||||||
_aliasid = alias.AliasId;
|
await AliasService.DeleteAliasAsync(_aliasid);
|
||||||
_aliasname = alias.Name;
|
await LoadAliases();
|
||||||
_defaultalias = alias.IsDefault.ToString();
|
|
||||||
StateHasChanged();
|
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()
|
private async Task SaveAlias()
|
||||||
{
|
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(_aliasname))
|
if (!string.IsNullOrEmpty(_aliasname))
|
||||||
{
|
{
|
||||||
@@ -1032,9 +1194,9 @@
|
|||||||
|
|
||||||
if (unique)
|
if (unique)
|
||||||
{
|
{
|
||||||
if (_aliasid == 0)
|
if (_aliasid == -1)
|
||||||
{
|
{
|
||||||
alias = new Alias { SiteId = PageState.Site.SiteId, TenantId = PageState.Site.TenantId, Name = _aliasname, IsDefault = bool.Parse(_defaultalias) };
|
alias = new Alias { SiteId = PageState.Site.SiteId, TenantId = PageState.Alias.TenantId, Name = _aliasname, IsDefault = bool.Parse(_defaultalias) };
|
||||||
await AliasService.AddAliasAsync(alias);
|
await AliasService.AddAliasAsync(alias);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -1048,7 +1210,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await GetAliases();
|
await LoadAliases();
|
||||||
_aliasid = -1;
|
_aliasid = -1;
|
||||||
_aliasname = "";
|
_aliasname = "";
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
@@ -1060,11 +1222,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private async Task CancelAlias()
|
private async Task CancelAlias()
|
||||||
{
|
{
|
||||||
await GetAliases();
|
await LoadAliases();
|
||||||
_aliasid = -1;
|
_aliasid = -1;
|
||||||
_aliasname = "";
|
_aliasname = "";
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
@@ -1074,4 +1235,205 @@
|
|||||||
await CacheService.EvictByTag(Constants.SitemapOutputCacheTag);
|
await CacheService.EvictByTag(Constants.SitemapOutputCacheTag);
|
||||||
AddModuleMessage(Localizer["Success.SiteMap.CacheEvicted"], MessageType.Success);
|
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 ITenantService TenantService
|
||||||
@inject IAliasService AliasService
|
@inject IAliasService AliasService
|
||||||
@inject ISiteService SiteService
|
@inject ISiteService SiteService
|
||||||
@inject IThemeService ThemeService
|
|
||||||
@inject ISiteTemplateService SiteTemplateService
|
@inject ISiteTemplateService SiteTemplateService
|
||||||
@inject IUserService UserService
|
@inject IUserService UserService
|
||||||
@inject IInstallationService InstallationService
|
@inject IInstallationService InstallationService
|
||||||
@@ -29,33 +28,9 @@ else
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="alias" HelpText="The urls for the site (comman delimited). This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or virtual folders (ie. domain.com/folder)." ResourceKey="Aliases">Urls: </Label>
|
<Label Class="col-sm-3" For="alias" HelpText="The primary url for the site. This can be a domain name (ie. domain.com), subdomain (ie. sub.domain.com) or a virtual folder (ie. domain.com/folder)." ResourceKey="Aliases">Url: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<textarea id="alias" class="form-control" @bind="@_urls" rows="3" required></textarea>
|
<input id="alias" class="form-control" @bind="@_urls" required></input>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="defaultTheme" HelpText="Select the default theme for the website" ResourceKey="DefaultTheme">Default Theme: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<select id="defaultTheme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
|
|
||||||
<option value="-"><@Localizer["Theme.Select"]></option>
|
|
||||||
@foreach (var theme in _themes)
|
|
||||||
{
|
|
||||||
<option value="@theme.TypeName">@theme.Name</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<select id="defaultContainer" class="form-select" @bind="@_containertype" required>
|
|
||||||
<option value="-"><@Localizer["Container.Select"]></option>
|
|
||||||
@foreach (var container in _containers)
|
|
||||||
{
|
|
||||||
<option value="@container.TypeName">@container.Name</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -70,26 +45,6 @@ else
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="rendermode" HelpText="The default render mode for the site" ResourceKey="Rendermode">Render Mode: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<select id="rendermode" class="form-select" @bind="@_rendermode" required>
|
|
||||||
<option value="@RenderModes.Interactive">@(SharedLocalizer["RenderMode" + @RenderModes.Interactive])</option>
|
|
||||||
<option value="@RenderModes.Static">@(SharedLocalizer["RenderMode" + @RenderModes.Static])</option>
|
|
||||||
<option value="@RenderModes.Headless">@(SharedLocalizer["RenderMode" + @RenderModes.Headless])</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="runtime" HelpText="The render mode for UI components which require interactivity" ResourceKey="Runtime">Interactivity: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<select id="runtime" class="form-select" @bind="@_runtime" required>
|
|
||||||
<option value="@Runtimes.Server">@(SharedLocalizer["Runtime" + @Runtimes.Server])</option>
|
|
||||||
<option value="@Runtimes.WebAssembly">@(SharedLocalizer["Runtime" + @Runtimes.WebAssembly])</option>
|
|
||||||
<option value="@Runtimes.Auto">@(SharedLocalizer["Runtime" + @Runtimes.Auto])</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="tenant" HelpText="Select the database for the site" ResourceKey="Tenant">Database: </Label>
|
<Label Class="col-sm-3" For="tenant" HelpText="Select the database for the site" ResourceKey="Tenant">Database: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -186,9 +141,6 @@ else
|
|||||||
private bool _showConnectionString = false;
|
private bool _showConnectionString = false;
|
||||||
private string _connectionString = string.Empty;
|
private string _connectionString = string.Empty;
|
||||||
|
|
||||||
private List<Theme> _themeList;
|
|
||||||
private List<ThemeControl> _themes = new List<ThemeControl>();
|
|
||||||
private List<ThemeControl> _containers = new List<ThemeControl>();
|
|
||||||
private List<SiteTemplate> _siteTemplates;
|
private List<SiteTemplate> _siteTemplates;
|
||||||
private List<Tenant> _tenants;
|
private List<Tenant> _tenants;
|
||||||
private string _tenantid = "-";
|
private string _tenantid = "-";
|
||||||
@@ -200,11 +152,7 @@ else
|
|||||||
|
|
||||||
private string _name = string.Empty;
|
private string _name = string.Empty;
|
||||||
private string _urls = string.Empty;
|
private string _urls = string.Empty;
|
||||||
private string _themetype = "-";
|
|
||||||
private string _containertype = "-";
|
|
||||||
private string _sitetemplatetype = "-";
|
private string _sitetemplatetype = "-";
|
||||||
private string _rendermode = RenderModes.Static;
|
|
||||||
private string _runtime = Runtimes.Server;
|
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
@@ -215,19 +163,11 @@ else
|
|||||||
{
|
{
|
||||||
_tenantid = _tenants.First(item => item.Name == TenantNames.Master).TenantId.ToString();
|
_tenantid = _tenants.First(item => item.Name == TenantNames.Master).TenantId.ToString();
|
||||||
}
|
}
|
||||||
_urls = PageState.Alias.Name;
|
_urls = PageState.Alias.Name + "/sitename";
|
||||||
_themeList = await ThemeService.GetThemesAsync(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;
|
|
||||||
}
|
|
||||||
_siteTemplates = await SiteTemplateService.GetSiteTemplatesAsync();
|
_siteTemplates = await SiteTemplateService.GetSiteTemplatesAsync();
|
||||||
if (_siteTemplates.Any(item => item.TypeName == Constants.DefaultSiteTemplate))
|
if (_siteTemplates.Any(item => item.TypeName == Constants.EmptySiteTemplate))
|
||||||
{
|
{
|
||||||
_sitetemplatetype = Constants.DefaultSiteTemplate;
|
_sitetemplatetype = Constants.EmptySiteTemplate;
|
||||||
}
|
}
|
||||||
|
|
||||||
_databases = await DatabaseService.GetDatabasesAsync();
|
_databases = await DatabaseService.GetDatabasesAsync();
|
||||||
@@ -281,37 +221,13 @@ else
|
|||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void ThemeChanged(ChangeEventArgs e)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_themetype = (string)e.Value;
|
|
||||||
if (_themetype != "-")
|
|
||||||
{
|
|
||||||
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
|
|
||||||
_containertype = _containers.First().TypeName;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_containers = new List<ThemeControl>();
|
|
||||||
_containertype = "-";
|
|
||||||
}
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error Loading Containers For Theme {ThemeType} {Error}", _themetype, ex.Message);
|
|
||||||
AddModuleMessage(Localizer["Error.Theme.LoadContainers"], MessageType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SaveSite()
|
private async Task SaveSite()
|
||||||
{
|
{
|
||||||
validated = true;
|
validated = true;
|
||||||
var interop = new Interop(JSRuntime);
|
var interop = new Interop(JSRuntime);
|
||||||
if (await interop.FormValid(form))
|
if (await interop.FormValid(form))
|
||||||
{
|
{
|
||||||
if (_tenantid != "-" && _name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-" && _sitetemplatetype != "-")
|
if (_tenantid != "-" && _name != string.Empty && _urls != string.Empty && _sitetemplatetype != "-")
|
||||||
{
|
{
|
||||||
_urls = Regex.Replace(_urls, @"\r\n?|\n", ",");
|
_urls = Regex.Replace(_urls, @"\r\n?|\n", ",");
|
||||||
var duplicates = new List<string>();
|
var duplicates = new List<string>();
|
||||||
@@ -399,12 +315,12 @@ else
|
|||||||
{
|
{
|
||||||
config.SiteName = _name;
|
config.SiteName = _name;
|
||||||
config.Aliases = _urls;
|
config.Aliases = _urls;
|
||||||
config.DefaultTheme = _themetype;
|
config.DefaultTheme = Constants.DefaultTheme;
|
||||||
config.DefaultContainer = _containertype;
|
config.DefaultContainer = Constants.DefaultContainer;
|
||||||
config.DefaultAdminContainer = "";
|
config.DefaultAdminContainer = "";
|
||||||
config.SiteTemplate = _sitetemplatetype;
|
config.SiteTemplate = _sitetemplatetype;
|
||||||
config.RenderMode = _rendermode;
|
config.RenderMode = RenderModes.Static;
|
||||||
config.Runtime = _runtime;
|
config.Runtime = Runtimes.Server;
|
||||||
config.Register = false;
|
config.Register = false;
|
||||||
|
|
||||||
ShowProgressIndicator();
|
ShowProgressIndicator();
|
||||||
|
|||||||
@@ -83,14 +83,14 @@ else
|
|||||||
{
|
{
|
||||||
@if (_connection != "-")
|
@if (_connection != "-")
|
||||||
{
|
{
|
||||||
@if (!string.IsNullOrEmpty(_tenant))
|
|
||||||
{
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="databasetype" HelpText="The database type" ResourceKey="DatabaseType">Type: </Label>
|
<Label Class="col-sm-3" For="databasetype" HelpText="The database type" ResourceKey="DatabaseType">Type: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="databasetype" class="form-control" @bind="@_databasetype" readonly />
|
<input id="databasetype" class="form-control" @bind="@_databasetype" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if (!string.IsNullOrEmpty(_tenant))
|
||||||
|
{
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="tenant" HelpText="The database using this connection" ResourceKey="Tenant">Database: </Label>
|
<Label Class="col-sm-3" For="tenant" HelpText="The database using this connection" ResourceKey="Tenant">Database: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -203,6 +203,24 @@ else
|
|||||||
// hack - there are 3 providers with SqlServerDatabase DBTypes - so we are choosing the last one in alphabetical order
|
// 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;
|
_databasetype = _databases.Where(item => item.DBType == tenant.DBType).OrderBy(item => item.Name).Last()?.Name;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_connection.Contains(" ("))
|
||||||
|
{
|
||||||
|
_databasetype = _connection.Substring(_connection.LastIndexOf(" (") + 2).Replace(")", "");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_databases.Exists(item => item.IsDefault))
|
||||||
|
{
|
||||||
|
_databasetype = _databases.Find(item => item.IsDefault).Name;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_databasetype = Constants.DefaultDBName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -273,6 +291,7 @@ else
|
|||||||
}
|
}
|
||||||
if (!string.IsNullOrEmpty(_name) && !string.IsNullOrEmpty(connectionstring))
|
if (!string.IsNullOrEmpty(_name) && !string.IsNullOrEmpty(connectionstring))
|
||||||
{
|
{
|
||||||
|
_name = _name + " (" + _databasetype +")";
|
||||||
var settings = new Dictionary<string, object>();
|
var settings = new Dictionary<string, object>();
|
||||||
settings.Add($"{SettingKeys.ConnectionStringsSection}:{_name}", connectionstring);
|
settings.Add($"{SettingKeys.ConnectionStringsSection}:{_name}", connectionstring);
|
||||||
await SystemService.UpdateSystemInfoAsync(settings);
|
await SystemService.UpdateSystemInfoAsync(settings);
|
||||||
|
|||||||
@@ -271,7 +271,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tenants = await TenantService.GetTenantsAsync();
|
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();
|
_history = await MigrationHistoryService.GetMigrationHistoryAsync();
|
||||||
|
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
@namespace Oqtane.Modules.Admin.Themes
|
@namespace Oqtane.Modules.Admin.Themes
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@using System.Text.RegularExpressions
|
@using System.Text.RegularExpressions
|
||||||
|
@using System.Reflection
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IThemeService ThemeService
|
@inject IThemeService ThemeService
|
||||||
@inject IModuleService ModuleService
|
@inject IModuleService ModuleService
|
||||||
@@ -36,6 +37,8 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if (_type == "External")
|
||||||
|
{
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
|
<Label Class="col-sm-3" For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -60,6 +63,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="CreateTheme">@Localizer["Theme.Create"]</button>
|
<button type="button" class="btn btn-success" @onclick="CreateTheme">@Localizer["Theme.Create"]</button>
|
||||||
@@ -71,9 +75,10 @@
|
|||||||
private string _theme = string.Empty;
|
private string _theme = string.Empty;
|
||||||
private List<Template> _templates;
|
private List<Template> _templates;
|
||||||
private string _template = "-";
|
private string _template = "-";
|
||||||
|
private string _minversion = "2.0.0";
|
||||||
|
private string _type = "";
|
||||||
private string[] _versions;
|
private string[] _versions;
|
||||||
private string _reference = "local";
|
private string _reference = "local";
|
||||||
private string _minversion = "2.0.0";
|
|
||||||
private string _location = string.Empty;
|
private string _location = string.Empty;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
@@ -84,6 +89,16 @@
|
|||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Info.Theme.CreatorIntent"], MessageType.Info);
|
AddModuleMessage(Localizer["Info.Theme.CreatorIntent"], MessageType.Info);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var entryAssemblyName = Assembly.GetEntryAssembly().GetName().Name;
|
||||||
|
if (entryAssemblyName.EndsWith(".Oqtane"))
|
||||||
|
{
|
||||||
|
// Oqtane Application assemblies end with .Server.Oqtane or .Client.Oqtane
|
||||||
|
string[] segments = entryAssemblyName.Split('.');
|
||||||
|
_owner = string.Join(".", segments, 0, segments.Length - 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
@@ -105,11 +120,18 @@
|
|||||||
{
|
{
|
||||||
if (IsValid(_owner) && IsValid(_theme) && _owner != _theme && _template != "-")
|
if (IsValid(_owner) && IsValid(_theme) && _owner != _theme && _template != "-")
|
||||||
{
|
{
|
||||||
|
if (_type == "Internal")
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Success.Theme.Create.Internal"], MessageType.Success);
|
||||||
|
}
|
||||||
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
||||||
var theme = new Theme { Owner = _owner, Name = _theme, Template = _template, Version = _reference, ThemeName = template.Namespace };
|
var theme = new Theme { Owner = _owner, Name = _theme, Template = _template, Version = _reference, ThemeName = template.Namespace };
|
||||||
theme = await ThemeService.CreateThemeAsync(theme);
|
theme = await ThemeService.CreateThemeAsync(theme);
|
||||||
|
if (_type == "External")
|
||||||
|
{
|
||||||
GetLocation();
|
GetLocation();
|
||||||
AddModuleMessage(string.Format(Localizer["Success.Theme.Create"], NavigateUrl("admin/system")), MessageType.Success);
|
AddModuleMessage(string.Format(Localizer["Success.Theme.Create.External"], NavigateUrl("admin/system")), MessageType.Success);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -125,17 +147,22 @@
|
|||||||
private bool IsValid(string name)
|
private bool IsValid(string name)
|
||||||
{
|
{
|
||||||
// must contain letters, underscores and digits and first character must be letter or underscore
|
// must contain letters, underscores and digits and first character must be letter or underscore
|
||||||
return !string.IsNullOrEmpty(name) && name.ToLower() != "theme" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
|
return !string.IsNullOrEmpty(name) && name.ToLower() != "theme" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_.]*$");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TemplateChanged(ChangeEventArgs e)
|
private void TemplateChanged(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
_template = (string)e.Value;
|
_template = (string)e.Value;
|
||||||
_minversion = "2.0.0";
|
|
||||||
if (_template != "-")
|
if (_template != "-")
|
||||||
{
|
{
|
||||||
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
||||||
_minversion = template.Version;
|
_minversion = template.Version;
|
||||||
|
_type = template.Type;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_minversion = "2.0.0";
|
||||||
|
_type = "";
|
||||||
}
|
}
|
||||||
GetLocation();
|
GetLocation();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<br />
|
||||||
<Section Name="Information" ResourceKey="Information" Heading="Information">
|
<Section Name="Information" ResourceKey="Information" Heading="Information">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -81,6 +82,12 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="fingerprint" HelpText="A unique identifier for the theme's static resources. This value can be changed by clicking the Save option below (ie. cache busting)." ResourceKey="Fingerprint">Fingerprint: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="fingerprint" class="form-control" @bind="@_fingerprint" disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
<br />
|
<br />
|
||||||
@@ -117,6 +124,7 @@
|
|||||||
private string _url = "";
|
private string _url = "";
|
||||||
private string _contact = "";
|
private string _contact = "";
|
||||||
private string _license = "";
|
private string _license = "";
|
||||||
|
private string _fingerprint = "";
|
||||||
private List<Permission> _permissions = null;
|
private List<Permission> _permissions = null;
|
||||||
private string _createdby;
|
private string _createdby;
|
||||||
private DateTime _createdon;
|
private DateTime _createdon;
|
||||||
@@ -143,6 +151,7 @@
|
|||||||
_url = theme.Url;
|
_url = theme.Url;
|
||||||
_contact = theme.Contact;
|
_contact = theme.Contact;
|
||||||
_license = theme.License;
|
_license = theme.License;
|
||||||
|
_fingerprint = theme.Fingerprint;
|
||||||
_permissions = theme.PermissionList;
|
_permissions = theme.PermissionList;
|
||||||
_createdby = theme.CreatedBy;
|
_createdby = theme.CreatedBy;
|
||||||
_createdon = theme.CreatedOn;
|
_createdon = theme.CreatedOn;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ else
|
|||||||
<th>@Localizer["Url"]</th>
|
<th>@Localizer["Url"]</th>
|
||||||
<th>@Localizer["Requests"]</th>
|
<th>@Localizer["Requests"]</th>
|
||||||
<th>@Localizer["Requested"]</th>
|
<th>@Localizer["Requested"]</th>
|
||||||
|
<th>@Localizer["Referrer"]</th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.UrlMappingId.ToString())" ResourceKey="Edit" /></td>
|
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.UrlMappingId.ToString())" ResourceKey="Edit" /></td>
|
||||||
@@ -49,6 +50,7 @@ else
|
|||||||
</td>
|
</td>
|
||||||
<td>@context.Requests</td>
|
<td>@context.Requests</td>
|
||||||
<td>@UtcToLocal(context.RequestedOn)</td>
|
<td>@UtcToLocal(context.RequestedOn)</td>
|
||||||
|
<td>@context.Referrer</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
@inject IFileService FileService
|
@inject IFileService FileService
|
||||||
@inject IFolderService FolderService
|
@inject IFolderService FolderService
|
||||||
@inject ITimeZoneService TimeZoneService
|
@inject ITimeZoneService TimeZoneService
|
||||||
|
@inject ILanguageService LanguageService
|
||||||
@inject IJSRuntime jsRuntime
|
@inject IJSRuntime jsRuntime
|
||||||
@inject IServiceProvider ServiceProvider
|
@inject IServiceProvider ServiceProvider
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@@ -26,8 +27,7 @@
|
|||||||
<br />
|
<br />
|
||||||
}
|
}
|
||||||
<TabStrip>
|
<TabStrip>
|
||||||
<TabPanel Name="Identity" ResourceKey="Identity">
|
<TabPanel Name="Identity" Heading="Identity" ResourceKey="Identity">
|
||||||
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="username" HelpText="Your username. Note that this field can not be modified." ResourceKey="Username"></Label>
|
<Label Class="col-sm-3" For="username" HelpText="Your username. Note that this field can not be modified." ResourceKey="Username"></Label>
|
||||||
@@ -35,36 +35,6 @@
|
|||||||
<input id="username" class="form-control" @bind="@_username" readonly />
|
<input id="username" class="form-control" @bind="@_username" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="password" HelpText="If you wish to change your password you can enter it here. Please choose a sufficiently secure password." ResourceKey="Password"></Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<div class="input-group">
|
|
||||||
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="confirm" HelpText="If you are changing your password you must enter it again to confirm it matches" ResourceKey="Confirm"></Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<div class="input-group">
|
|
||||||
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" />
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@if (_allowtwofactor)
|
|
||||||
{
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="twofactor" HelpText="Indicates if you are using two factor authentication. Two factor authentication requires you to enter a verification code sent via email after you sign in." ResourceKey="TwoFactor"></Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<select id="twofactor" class="form-select" @bind="@_twofactor" required>
|
|
||||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
|
||||||
<option value="False">@SharedLocalizer["No"]</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="email" HelpText="Your email address where you wish to receive notifications" ResourceKey="Email"></Label>
|
<Label Class="col-sm-3" For="email" HelpText="Your email address where you wish to receive notifications" ResourceKey="Email"></Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -89,6 +59,18 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="culture" HelpText="Your preferred language. Note that you will only be able to choose from languages supported on this site." ResourceKey="CultureCode">Language:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="culture" class="form-select" @bind="@_culturecode">
|
||||||
|
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||||
|
@foreach (var language in _languages)
|
||||||
|
{
|
||||||
|
<option value="@language.Code">@language.Name</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="@_photofileid.ToString()" HelpText="A photo of yourself" ResourceKey="Photo"></Label>
|
<Label Class="col-sm-3" For="@_photofileid.ToString()" HelpText="A photo of yourself" ResourceKey="Photo"></Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -99,9 +81,113 @@
|
|||||||
<br />
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
<button type="button" class="btn btn-danger" @onclick="Logout">@Localizer["Logout Everywhere"]</button>
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Profile" ResourceKey="Profile">
|
<TabPanel Name="Security" Heading="Security" ResourceKey="Security">
|
||||||
|
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="password" HelpText="If you wish to change your password you can enter it here. Please choose a sufficiently secure password." ResourceKey="Password"></Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="confirm" HelpText="If you are changing your password you must enter it again to confirm it matches" ResourceKey="Confirm"></Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" />
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
|
<br /><br />
|
||||||
|
@if (_allowtwofactor)
|
||||||
|
{
|
||||||
|
<Section Name="MFA" Heading="Multi-Factor Authentication" ResourceKey="MFA">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="twofactor" HelpText="Indicates if you are using two factor authentication. Two factor authentication requires you to enter a verification code sent via email after you sign in." ResourceKey="TwoFactor"></Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="twofactor" class="form-select" @bind="@_twofactor" required>
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
<br />
|
||||||
|
}
|
||||||
|
@if (_allowpasskeys)
|
||||||
|
{
|
||||||
|
<Section Name="Passkeys" Heading="Passkeys" ResourceKey="Passkeys" Expanded="@((_passkeys.Count > 0).ToString())">
|
||||||
|
<button type="button" class="btn btn-primary" @onclick="AddPasskey">@SharedLocalizer["Add"]</button>
|
||||||
|
@if (_passkeys.Count > 0)
|
||||||
|
{
|
||||||
|
<Pager Items="@_passkeys">
|
||||||
|
<Header>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
|
<th>@Localizer["Passkey"]</th>
|
||||||
|
</Header>
|
||||||
|
<Row>
|
||||||
|
@if (context.CredentialId != _passkeyId)
|
||||||
|
{
|
||||||
|
<td><button type="button" class="btn btn-primary" @onclick="@(() => EditPasskey(context))">@SharedLocalizer["Edit"]</button></td>
|
||||||
|
<td><ActionDialog Action="Delete" OnClick="@(async () => await DeletePasskey(context))" ResourceKey="DeletePasskey" Class="btn btn-danger" Header="Delete Passkey" Message="@string.Format(Localizer["Confirm.Passkey.Delete", context.Name])" /></td>
|
||||||
|
<td>@context.Name</td>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<td><button type="button" class="btn btn-success" @onclick="@(async () => await SavePasskey())">@SharedLocalizer["Save"]</button></td>
|
||||||
|
<td><button type="button" class="btn btn-secondary" @onclick="@(async () => await CancelPasskey())">@SharedLocalizer["Cancel"]</button></td>
|
||||||
|
<td><input id="passkeyname" class="form-control" @bind="@_passkeyName" /></td>
|
||||||
|
}
|
||||||
|
</Row>
|
||||||
|
</Pager>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="mt-2">@Localizer["Message.Passkeys.None"]</div>
|
||||||
|
}
|
||||||
|
</Section>
|
||||||
|
<br />
|
||||||
|
}
|
||||||
|
@if (_allowexternallogin)
|
||||||
|
{
|
||||||
|
<Section Name="Logins" Heading="Logins" ResourceKey="Logins" Expanded="@((_logins.Count > 0).ToString())">
|
||||||
|
@if (_logins.Count > 0)
|
||||||
|
{
|
||||||
|
<Pager Items="@_logins">
|
||||||
|
<Header>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
|
<th>@Localizer["Login"]</th>
|
||||||
|
</Header>
|
||||||
|
<Row>
|
||||||
|
<td><ActionDialog Action="Delete" OnClick="@(async () => await DeleteLogin(context))" ResourceKey="DeleteLogin" Class="btn btn-danger" Header="Delete Login" Message="@string.Format(Localizer["Confirm.Login.Delete", context.Name])" /></td>
|
||||||
|
<td>@context.Name</td>
|
||||||
|
</Row>
|
||||||
|
</Pager>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="mt-2">@Localizer["Message.Logins.None"]</div>
|
||||||
|
}
|
||||||
|
</Section>
|
||||||
|
<br />
|
||||||
|
}
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-danger" @onclick="Logout">@Localizer["Logout Everywhere"]</button>
|
||||||
|
<br />
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel Name="Profile" Heading="Profile" ResourceKey="Profile">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@foreach (Profile profile in _profiles)
|
@foreach (Profile profile in _profiles)
|
||||||
@@ -230,11 +316,11 @@
|
|||||||
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Notifications" ResourceKey="Notifications">
|
<TabPanel Name="Notifications" Heading="Notifications" ResourceKey="Notifications">
|
||||||
<ActionLink Action="Add" Text="Send Notification" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="SendNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" />
|
<ActionLink Action="Add" Text="Send Notification" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="SendNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" />
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<select class="form-select" @onchange="(e => FilterChanged(e))">
|
<select class="form-select" @onchange="(e => FilterNotifications(e))">
|
||||||
<option value="to">@Localizer["Inbox"]</option>
|
<option value="to">@Localizer["Inbox"]</option>
|
||||||
<option value="from">@Localizer["Items.Sent"]</option>
|
<option value="from">@Localizer["Items.Sent"]</option>
|
||||||
</select>
|
</select>
|
||||||
@@ -253,7 +339,7 @@
|
|||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" /></td>
|
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" /></td>
|
||||||
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
|
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await DeleteNotification(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
|
||||||
|
|
||||||
@if (context.IsRead)
|
@if (context.IsRead)
|
||||||
{
|
{
|
||||||
@@ -298,7 +384,7 @@
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="no-notifications-text">
|
<div class="no-notifications-text">
|
||||||
@Localizer["NoNotificationsReceived.Text"]
|
@Localizer["NoNotificationsReceived"]
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -316,7 +402,7 @@
|
|||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" /></td>
|
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" /></td>
|
||||||
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
|
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await DeleteNotification(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
|
||||||
|
|
||||||
@if (context.IsRead)
|
@if (context.IsRead)
|
||||||
{
|
{
|
||||||
@@ -362,7 +448,7 @@
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="no-notifications-text">
|
<div class="no-notifications-text">
|
||||||
@Localizer["NoNotificationsSent.Text"]
|
@Localizer["NoNotificationsSent"]
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -373,25 +459,38 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
||||||
|
|
||||||
|
private List<Models.TimeZone> _timezones;
|
||||||
|
private IEnumerable<Language> _languages;
|
||||||
|
|
||||||
private bool _initialized = false;
|
private bool _initialized = false;
|
||||||
private string _passwordrequirements;
|
|
||||||
private string _username = string.Empty;
|
|
||||||
private string _password = string.Empty;
|
|
||||||
private string _passwordtype = "password";
|
|
||||||
private string _togglepassword = string.Empty;
|
|
||||||
private string _confirm = string.Empty;
|
|
||||||
private bool _allowtwofactor = false;
|
private bool _allowtwofactor = false;
|
||||||
private string _twofactor = "False";
|
private bool _allowpasskeys = false;
|
||||||
|
private bool _allowexternallogin = false;
|
||||||
|
|
||||||
|
private string _username = string.Empty;
|
||||||
private string _email = string.Empty;
|
private string _email = string.Empty;
|
||||||
private string _displayname = string.Empty;
|
private string _displayname = string.Empty;
|
||||||
private FileManager _filemanager;
|
private FileManager _filemanager;
|
||||||
private int _folderid = -1;
|
private int _folderid = -1;
|
||||||
private List<Models.TimeZone> _timezones;
|
|
||||||
private string _timezoneid = string.Empty;
|
private string _timezoneid = string.Empty;
|
||||||
|
private string _culturecode = string.Empty;
|
||||||
private int _photofileid = -1;
|
private int _photofileid = -1;
|
||||||
private File _photo = null;
|
private File _photo = null;
|
||||||
private string _imagefiles = string.Empty;
|
private string _imagefiles = string.Empty;
|
||||||
|
|
||||||
|
private string _passwordrequirements;
|
||||||
|
private string _password = string.Empty;
|
||||||
|
private string _passwordtype = "password";
|
||||||
|
private string _togglepassword = string.Empty;
|
||||||
|
private string _confirm = string.Empty;
|
||||||
|
private string _twofactor = "False";
|
||||||
|
private List<UserPasskey> _passkeys;
|
||||||
|
private byte[] _passkeyId;
|
||||||
|
private string _passkeyName = string.Empty;
|
||||||
|
private List<UserLogin> _logins;
|
||||||
|
|
||||||
private List<Profile> _profiles;
|
private List<Profile> _profiles;
|
||||||
private Dictionary<string, string> _userSettings;
|
private Dictionary<string, string> _userSettings;
|
||||||
private string _category = string.Empty;
|
private string _category = string.Empty;
|
||||||
@@ -400,42 +499,32 @@
|
|||||||
private List<Notification> _notifications;
|
private List<Notification> _notifications;
|
||||||
private string _notificationSummary = string.Empty;
|
private string _notificationSummary = string.Empty;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
|
||||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
|
||||||
_allowtwofactor = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "true");
|
_allowtwofactor = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "true");
|
||||||
_profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
_allowpasskeys = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:Passkeys", "false") == "true");
|
||||||
foreach (var profile in _profiles)
|
_allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false;
|
||||||
{
|
|
||||||
if (profile.Options.ToLower().StartsWith("entityname:"))
|
|
||||||
{
|
|
||||||
var options = await SettingService.GetSettingsAsync(profile.Options.Substring(11), -1);
|
|
||||||
options.Add("", $"<{SharedLocalizer["Not Specified"]}>");
|
|
||||||
profile.Options = string.Join(",", options.OrderBy(item => item.Value).Select(kvp => $"{kvp.Key}:{kvp.Value}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_timezones = TimeZoneService.GetTimeZones();
|
|
||||||
|
|
||||||
if (PageState.User != null)
|
if (PageState.User != null)
|
||||||
{
|
{
|
||||||
|
_timezones = TimeZoneService.GetTimeZones();
|
||||||
|
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
|
||||||
|
|
||||||
|
// identity section
|
||||||
_username = PageState.User.Username;
|
_username = PageState.User.Username;
|
||||||
_twofactor = PageState.User.TwoFactorRequired.ToString();
|
|
||||||
_email = PageState.User.Email;
|
_email = PageState.User.Email;
|
||||||
_displayname = PageState.User.DisplayName;
|
_displayname = PageState.User.DisplayName;
|
||||||
_timezoneid = PageState.User.TimeZoneId;
|
_timezoneid = PageState.User.TimeZoneId;
|
||||||
|
_culturecode = PageState.User.CultureCode;
|
||||||
// get user folder
|
|
||||||
var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath);
|
var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath);
|
||||||
if (folder != null)
|
if (folder != null)
|
||||||
{
|
{
|
||||||
_folderid = folder.FolderId;
|
_folderid = folder.FolderId;
|
||||||
}
|
}
|
||||||
|
_imagefiles = SettingService.GetSetting(PageState.Site.Settings, "ImageFiles", Constants.ImageFiles);
|
||||||
|
_imagefiles = (string.IsNullOrEmpty(_imagefiles)) ? Constants.ImageFiles : _imagefiles;
|
||||||
if (PageState.User.PhotoFileId != null)
|
if (PageState.User.PhotoFileId != null)
|
||||||
{
|
{
|
||||||
_photofileid = PageState.User.PhotoFileId.Value;
|
_photofileid = PageState.User.PhotoFileId.Value;
|
||||||
@@ -447,11 +536,27 @@
|
|||||||
_photo = null;
|
_photo = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_userSettings = PageState.User.Settings;
|
// security section
|
||||||
var sitesettings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||||
_imagefiles = SettingService.GetSetting(_userSettings, "ImageFiles", Constants.ImageFiles);
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
_imagefiles = (string.IsNullOrEmpty(_imagefiles)) ? Constants.ImageFiles : _imagefiles;
|
_twofactor = PageState.User.TwoFactorRequired.ToString();
|
||||||
|
await GetPasskeys();
|
||||||
|
await GetLogins();
|
||||||
|
|
||||||
|
// profile section
|
||||||
|
_profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
||||||
|
foreach (var profile in _profiles)
|
||||||
|
{
|
||||||
|
if (profile.Options.ToLower().StartsWith("entityname:"))
|
||||||
|
{
|
||||||
|
var options = await SettingService.GetSettingsAsync(profile.Options.Substring(11), -1);
|
||||||
|
options.Add("", $"<{SharedLocalizer["Not Specified"]}>");
|
||||||
|
profile.Options = string.Join(",", options.OrderBy(item => item.Value).Select(kvp => $"{kvp.Key}:{kvp.Value}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_userSettings = PageState.User.Settings;
|
||||||
|
|
||||||
|
// notification section
|
||||||
await LoadNotificationsAsync();
|
await LoadNotificationsAsync();
|
||||||
|
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
@@ -468,22 +573,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadNotificationsAsync()
|
// identity methods
|
||||||
{
|
|
||||||
_notifications = await NotificationService.GetNotificationsAsync(PageState.Site.SiteId, _filter, PageState.User.UserId);
|
|
||||||
_notifications = _notifications.Where(item => item.DeletedBy != PageState.User.Username).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetProfileValue(string SettingName, string DefaultValue)
|
|
||||||
{
|
|
||||||
string value = SettingService.GetSetting(_userSettings, SettingName, DefaultValue);
|
|
||||||
if (value.Contains("]"))
|
|
||||||
{
|
|
||||||
value = value.Substring(value.IndexOf("]") + 1);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task Save()
|
private async Task Save()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -501,6 +591,7 @@
|
|||||||
user.Email = _email;
|
user.Email = _email;
|
||||||
user.DisplayName = (_displayname == string.Empty ? _username : _displayname);
|
user.DisplayName = (_displayname == string.Empty ? _username : _displayname);
|
||||||
user.TimeZoneId = _timezoneid;
|
user.TimeZoneId = _timezoneid;
|
||||||
|
user.CultureCode = _culturecode;
|
||||||
user.PhotoFileId = _filemanager.GetFileId();
|
user.PhotoFileId = _filemanager.GetFileId();
|
||||||
if (user.PhotoFileId == -1)
|
if (user.PhotoFileId == -1)
|
||||||
{
|
{
|
||||||
@@ -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()
|
private async Task Logout()
|
||||||
{
|
{
|
||||||
await logger.LogInformation("User Logout Everywhere For Username {Username}", PageState.User?.Username);
|
await logger.LogInformation("User Logout Everywhere For Username {Username}", PageState.User?.Username);
|
||||||
@@ -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()
|
private bool ValidateProfiles()
|
||||||
{
|
{
|
||||||
foreach (Profile profile in _profiles)
|
foreach (Profile profile in _profiles)
|
||||||
@@ -615,18 +842,22 @@
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Cancel()
|
// notification methods
|
||||||
|
|
||||||
|
private async Task LoadNotificationsAsync()
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
_notifications = await NotificationService.GetNotificationsAsync(PageState.Site.SiteId, _filter, PageState.User.UserId);
|
||||||
|
_notifications = _notifications.Where(item => item.DeletedBy != PageState.User.Username).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProfileChanged(ChangeEventArgs e, string SettingName)
|
private async void FilterNotifications(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
var value = (string)e.Value;
|
_filter = (string)e.Value;
|
||||||
_userSettings = SettingService.SetSetting(_userSettings, SettingName, value);
|
await LoadNotificationsAsync();
|
||||||
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Delete(Notification Notification)
|
private async Task DeleteNotification(Notification Notification)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -651,13 +882,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void FilterChanged(ChangeEventArgs e)
|
|
||||||
{
|
|
||||||
_filter = (string)e.Value;
|
|
||||||
await LoadNotificationsAsync();
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task DeleteAllNotifications()
|
private async Task DeleteAllNotifications()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -689,18 +913,4 @@
|
|||||||
HideProgressIndicator();
|
HideProgressIndicator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TogglePassword()
|
|
||||||
{
|
|
||||||
if (_passwordtype == "password")
|
|
||||||
{
|
|
||||||
_passwordtype = "text";
|
|
||||||
_togglepassword = SharedLocalizer["HidePassword"];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_passwordtype = "password";
|
|
||||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
@inject IProfileService ProfileService
|
@inject IProfileService ProfileService
|
||||||
@inject ISettingService SettingService
|
@inject ISettingService SettingService
|
||||||
@inject ITimeZoneService TimeZoneService
|
@inject ITimeZoneService TimeZoneService
|
||||||
|
@inject ILanguageService LanguageService
|
||||||
@inject IStringLocalizer<Add> Localizer
|
@inject IStringLocalizer<Add> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@@ -55,6 +56,18 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="culture" HelpText="The user's preferred language. Note that you will only be able to choose from languages supported on this site." ResourceKey="CultureCode">Language:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="culture" class="form-select" @bind="@_culturecode">
|
||||||
|
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||||
|
@foreach (var language in _languages)
|
||||||
|
{
|
||||||
|
<option value="@language.Code">@language.Name</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="notify" HelpText="Indicate if new users should receive an email notification" ResourceKey="Notify">Notify? </Label>
|
<Label Class="col-sm-3" For="notify" HelpText="Indicate if new users should receive an email notification" ResourceKey="Notify">Notify? </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -129,12 +142,15 @@
|
|||||||
|
|
||||||
@code {
|
@code {
|
||||||
private List<Models.TimeZone> _timezones;
|
private List<Models.TimeZone> _timezones;
|
||||||
|
private IEnumerable<Models.Language> _languages;
|
||||||
|
|
||||||
private bool _initialized = false;
|
private bool _initialized = false;
|
||||||
private string _username = string.Empty;
|
private string _username = string.Empty;
|
||||||
private string _email = string.Empty;
|
private string _email = string.Empty;
|
||||||
private string _confirmed = "True";
|
private string _confirmed = "True";
|
||||||
private string _displayname = string.Empty;
|
private string _displayname = string.Empty;
|
||||||
private string _timezoneid = string.Empty;
|
private string _timezoneid = string.Empty;
|
||||||
|
private string _culturecode = string.Empty;
|
||||||
private string _notify = "True";
|
private string _notify = "True";
|
||||||
private List<Profile> _profiles;
|
private List<Profile> _profiles;
|
||||||
private Dictionary<string, string> _settings;
|
private Dictionary<string, string> _settings;
|
||||||
@@ -147,6 +163,11 @@
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
_timezones = TimeZoneService.GetTimeZones();
|
_timezones = TimeZoneService.GetTimeZones();
|
||||||
|
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
|
||||||
|
|
||||||
|
_timezoneid = PageState.Site.TimeZoneId;
|
||||||
|
_culturecode = PageState.Site.CultureCode;
|
||||||
|
|
||||||
_profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
_profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
||||||
foreach (var profile in _profiles)
|
foreach (var profile in _profiles)
|
||||||
{
|
{
|
||||||
@@ -157,8 +178,9 @@
|
|||||||
profile.Options = string.Join(",", options.OrderBy(item => item.Value).Select(kvp => $"{kvp.Key}:{kvp.Value}"));
|
profile.Options = string.Join(",", options.OrderBy(item => item.Value).Select(kvp => $"{kvp.Key}:{kvp.Value}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_settings = new Dictionary<string, string>();
|
_settings = new Dictionary<string, string>();
|
||||||
_timezoneid = PageState.Site.TimeZoneId;
|
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -194,6 +216,7 @@
|
|||||||
user.EmailConfirmed = bool.Parse(_confirmed);
|
user.EmailConfirmed = bool.Parse(_confirmed);
|
||||||
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
||||||
user.TimeZoneId = _timezoneid;
|
user.TimeZoneId = _timezoneid;
|
||||||
|
user.CultureCode = _culturecode;
|
||||||
user.PhotoFileId = null;
|
user.PhotoFileId = null;
|
||||||
user.SuppressNotification = !bool.Parse(_notify);
|
user.SuppressNotification = !bool.Parse(_notify);
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
@inject ISettingService SettingService
|
@inject ISettingService SettingService
|
||||||
@inject IFileService FileService
|
@inject IFileService FileService
|
||||||
@inject ITimeZoneService TimeZoneService
|
@inject ITimeZoneService TimeZoneService
|
||||||
|
@inject ILanguageService LanguageService
|
||||||
@inject IServiceProvider ServiceProvider
|
@inject IServiceProvider ServiceProvider
|
||||||
@inject IStringLocalizer<Edit> Localizer
|
@inject IStringLocalizer<Edit> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
@@ -14,8 +15,7 @@
|
|||||||
@if (_initialized)
|
@if (_initialized)
|
||||||
{
|
{
|
||||||
<TabStrip>
|
<TabStrip>
|
||||||
<TabPanel Name="Identity" ResourceKey="Identity">
|
<TabPanel Name="Identity" Heading="Identity" ResourceKey="Identity">
|
||||||
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="username" HelpText="The unique username for a user. Note that this field can not be modified." ResourceKey="Username">Username:</Label>
|
<Label Class="col-sm-3" For="username" HelpText="The unique username for a user. Note that this field can not be modified." ResourceKey="Username">Username:</Label>
|
||||||
@@ -23,24 +23,6 @@
|
|||||||
<input id="username" class="form-control" @bind="@_username" readonly />
|
<input id="username" class="form-control" @bind="@_username" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password">Password:</Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<div class="input-group">
|
|
||||||
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm">Confirm Password:</Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<div class="input-group">
|
|
||||||
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" />
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email">Email:</Label>
|
<Label Class="col-sm-3" For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email">Email:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -74,6 +56,18 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="culture" HelpText="The user's preferred language. Note that you will only be able to choose from languages supported on this site." ResourceKey="CultureCode">Language:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="culture" class="form-select" @bind="@_culturecode">
|
||||||
|
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||||
|
@foreach (var language in _languages)
|
||||||
|
{
|
||||||
|
<option value="@language.Code">@language.Name</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -100,7 +94,77 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Profile" ResourceKey="Profile">
|
<TabPanel Name="Security" Heading="Security" ResourceKey="Security">
|
||||||
|
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password">Password:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm">Confirm Password:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" />
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br /><br />
|
||||||
|
@if (_allowpasskeys)
|
||||||
|
{
|
||||||
|
<Section Name="Passkeys" Heading="Passkeys" ResourceKey="Passkeys" Expanded="@((_passkeys.Count > 0).ToString())">
|
||||||
|
@if (_passkeys.Count > 0)
|
||||||
|
{
|
||||||
|
<Pager Items="@_passkeys">
|
||||||
|
<Header>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
|
<th>@Localizer["Passkey"]</th>
|
||||||
|
</Header>
|
||||||
|
<Row>
|
||||||
|
<td><ActionDialog Action="Delete" OnClick="@(async () => await DeletePasskey(context))" ResourceKey="DeletePasskey" Class="btn btn-danger" Header="Delete Passkey" Message="@string.Format(Localizer["Confirm.Passkey.Delete", context.Name])" /></td>
|
||||||
|
<td>@context.Name</td>
|
||||||
|
</Row>
|
||||||
|
</Pager>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="mt-2">@Localizer["Message.Passkeys.None"]</div>
|
||||||
|
}
|
||||||
|
</Section>
|
||||||
|
<br />
|
||||||
|
}
|
||||||
|
@if (_allowexternallogin)
|
||||||
|
{
|
||||||
|
<Section Name="Logins" Heading="Logins" ResourceKey="Logins" Expanded="@((_logins.Count > 0).ToString())">
|
||||||
|
@if (_logins.Count > 0)
|
||||||
|
{
|
||||||
|
<Pager Items="@_logins">
|
||||||
|
<Header>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
|
<th>@Localizer["Login"]</th>
|
||||||
|
</Header>
|
||||||
|
<Row>
|
||||||
|
<td><ActionDialog Action="Delete" OnClick="@(async () => await DeleteLogin(context))" ResourceKey="DeleteLogin" Class="btn btn-danger" Header="Delete Login" Message="@string.Format(Localizer["Confirm.Login.Delete", context.Name])" /></td>
|
||||||
|
<td>@context.Name</td>
|
||||||
|
</Row>
|
||||||
|
</Pager>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="mt-2">@Localizer["Message.Logins.None"]</div>
|
||||||
|
}
|
||||||
|
</Section>
|
||||||
|
<br />
|
||||||
|
}
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel Name="Profile" Heading="Profile" ResourceKey="Profile">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@foreach (Profile profile in _profiles)
|
@foreach (Profile profile in _profiles)
|
||||||
@@ -160,7 +224,7 @@
|
|||||||
{
|
{
|
||||||
<button type="button" class="btn btn-primary ms-1" @onclick="ImpersonateUser">@Localizer["Impersonate"]</button>
|
<button type="button" class="btn btn-primary ms-1" @onclick="ImpersonateUser">@Localizer["Impersonate"]</button>
|
||||||
}
|
}
|
||||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && _isdeleted == "True")
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && _candelete)
|
||||||
{
|
{
|
||||||
<ActionDialog Header="Delete User" Message="Are You Sure You Wish To Permanently Delete This User?" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger ms-1" OnClick="@(async () => await DeleteUser())" ResourceKey="DeleteUser" />
|
<ActionDialog Header="Delete User" Message="Are You Sure You Wish To Permanently Delete This User?" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger ms-1" OnClick="@(async () => await DeleteUser())" ResourceKey="DeleteUser" />
|
||||||
}
|
}
|
||||||
@@ -169,23 +233,33 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private List<Models.TimeZone> _timezones;
|
|
||||||
private bool _initialized = false;
|
private bool _initialized = false;
|
||||||
private string _passwordrequirements;
|
private bool _allowpasskeys = false;
|
||||||
|
private bool _allowexternallogin = false;
|
||||||
|
|
||||||
|
private List<Models.TimeZone> _timezones;
|
||||||
|
private IEnumerable<Language> _languages;
|
||||||
|
|
||||||
private int _userid;
|
private int _userid;
|
||||||
private string _username = string.Empty;
|
private string _username = string.Empty;
|
||||||
private string _password = string.Empty;
|
|
||||||
private string _passwordtype = "password";
|
|
||||||
private string _togglepassword = string.Empty;
|
|
||||||
private string _confirm = string.Empty;
|
|
||||||
private string _email = string.Empty;
|
private string _email = string.Empty;
|
||||||
private string _confirmed = string.Empty;
|
private string _confirmed = string.Empty;
|
||||||
private string _displayname = string.Empty;
|
private string _displayname = string.Empty;
|
||||||
private string _timezoneid = string.Empty;
|
private string _timezoneid = string.Empty;
|
||||||
|
private string _culturecode = string.Empty;
|
||||||
private string _isdeleted;
|
private string _isdeleted;
|
||||||
private string _lastlogin;
|
private string _lastlogin;
|
||||||
private string _lastipaddress;
|
private string _lastipaddress;
|
||||||
private bool _ishost = false;
|
private bool _ishost = false;
|
||||||
|
private bool _candelete = false;
|
||||||
|
|
||||||
|
private string _passwordrequirements;
|
||||||
|
private string _password = string.Empty;
|
||||||
|
private string _passwordtype = "password";
|
||||||
|
private string _togglepassword = string.Empty;
|
||||||
|
private string _confirm = string.Empty;
|
||||||
|
private List<UserPasskey> _passkeys;
|
||||||
|
private List<UserLogin> _logins;
|
||||||
|
|
||||||
private List<Profile> _profiles;
|
private List<Profile> _profiles;
|
||||||
private Dictionary<string, string> _settings;
|
private Dictionary<string, string> _settings;
|
||||||
@@ -204,8 +278,35 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
_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))
|
||||||
|
{
|
||||||
|
_userid = UserId;
|
||||||
|
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);
|
||||||
|
|
||||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
|
await GetPasskeys();
|
||||||
|
await GetLogins();
|
||||||
|
|
||||||
_profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
|
_profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
|
||||||
foreach (var profile in _profiles)
|
foreach (var profile in _profiles)
|
||||||
{
|
{
|
||||||
@@ -216,25 +317,8 @@
|
|||||||
profile.Options = string.Join(",", options.OrderBy(item => item.Value).Select(kvp => $"{kvp.Key}:{kvp.Value}"));
|
profile.Options = string.Join(",", options.OrderBy(item => item.Value).Select(kvp => $"{kvp.Key}:{kvp.Value}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_timezones = TimeZoneService.GetTimeZones();
|
|
||||||
|
|
||||||
if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int UserId))
|
|
||||||
{
|
|
||||||
_userid = UserId;
|
|
||||||
var user = await UserService.GetUserAsync(_userid, PageState.Site.SiteId);
|
|
||||||
if (user != null)
|
|
||||||
{
|
|
||||||
_username = user.Username;
|
|
||||||
_email = user.Email;
|
|
||||||
_confirmed = user.EmailConfirmed.ToString();
|
|
||||||
_displayname = user.DisplayName;
|
|
||||||
_timezoneid = PageState.User.TimeZoneId;
|
|
||||||
_isdeleted = user.IsDeleted.ToString();
|
|
||||||
_lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", UtcToLocal(user.LastLoginOn));
|
|
||||||
_lastipaddress = user.LastIPAddress;
|
|
||||||
_ishost = UserSecurity.ContainsRole(user.Roles, RoleNames.Host);
|
|
||||||
|
|
||||||
_settings = user.Settings;
|
_settings = user.Settings;
|
||||||
|
|
||||||
_createdby = user.CreatedBy;
|
_createdby = user.CreatedBy;
|
||||||
_createdon = user.CreatedOn;
|
_createdon = user.CreatedOn;
|
||||||
_modifiedby = user.ModifiedBy;
|
_modifiedby = user.ModifiedBy;
|
||||||
@@ -281,6 +365,7 @@
|
|||||||
user.EmailConfirmed = bool.Parse(_confirmed);
|
user.EmailConfirmed = bool.Parse(_confirmed);
|
||||||
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
||||||
user.TimeZoneId = _timezoneid;
|
user.TimeZoneId = _timezoneid;
|
||||||
|
user.CultureCode = _culturecode;
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
user.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
|
user.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
|
||||||
@@ -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()
|
private bool ValidateProfiles()
|
||||||
{
|
{
|
||||||
foreach (Profile profile in _profiles)
|
foreach (Profile profile in _profiles)
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ else
|
|||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<Pager Items="@users" RowClass="align-middle" SearchProperties="User.Username,User.Email,User.DisplayName">
|
<Pager Items="@users" CurrentPage="@_page.ToString()" OnPageChange="@((page) => _page = page)" RowClass="align-middle" SearchProperties="User.Username,User.Email,User.DisplayName">
|
||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
@@ -60,9 +60,46 @@ else
|
|||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings" Security="SecurityAccessLevel.Admin">
|
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings" Security="SecurityAccessLevel.Host">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<Section Name="User" Heading="User Settings" ResourceKey="UserSettings">
|
<Section Name="User" Heading="User Settings" ResourceKey="UserSettings">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="allowsitelogin" HelpText="Do you want to allow users to sign in using a username and password that is managed locally on this site? Note that you should only disable this option if you have already successfully configured an alternative login method, or else you may lock yourself out of the site." ResourceKey="AllowSiteLogin">Allow Local Login?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="allowsitelogin" class="form-select" @bind="@_allowsitelogin">
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="twofactor" HelpText="Do you want users to use two factor authentication? Note that you should use the Disabled option until you have successfully verified that the Notification Job in Scheduled Jobs is enabled and your SMTP options in Site Settings are configured or else you will lock yourself out." ResourceKey="TwoFactor">Use 2FA?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="twofactor" class="form-select" @bind="@_twofactor">
|
||||||
|
<option value="false">@Localizer["Disabled"]</option>
|
||||||
|
<option value="true">@Localizer["Optional"]</option>
|
||||||
|
<option value="required">@Localizer["Required"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="loginlink" HelpText="Do you want to allow users to login using a time sensitive link sent by email" ResourceKey="LoginLink">Allow Login Link?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="loginlink" class="form-select" @bind="@_loginlink">
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="passkeys" HelpText="Do you want to allow users to login using passkeys (ie. passwordless authentication using WebAuthn/FIDO2)" ResourceKey="Passkeys">Allow Passkeys?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="passkeys" class="form-select" @bind="@_passkeys">
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="allowregistration" HelpText="Do you want anonymous visitors to be able to register for an account on the site" ResourceKey="AllowRegistration">Allow User Registration?</Label>
|
<Label Class="col-sm-3" For="allowregistration" HelpText="Do you want anonymous visitors to be able to register for an account on the site" ResourceKey="AllowRegistration">Allow User Registration?</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -81,12 +118,6 @@ else
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
<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>
|
<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">
|
<div class="col-sm-9">
|
||||||
@@ -96,16 +127,10 @@ else
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
|
||||||
{
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="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="profileurl" HelpText="Optionally provide a custom profile url" ResourceKey="ProfileUrl">Profile Url:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="twofactor" class="form-select" @bind="@_twofactor">
|
<input id="profileurl" class="form-control" @bind="@_profileurl" />
|
||||||
<option value="false">@Localizer["Disabled"]</option>
|
|
||||||
<option value="true">@Localizer["Optional"]</option>
|
|
||||||
<option value="required">@Localizer["Required"]</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -144,10 +169,7 @@ else
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
</Section>
|
</Section>
|
||||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
|
||||||
{
|
|
||||||
<Section Name="Password" Heading="Password Settings" ResourceKey="PasswordSettings">
|
<Section Name="Password" Heading="Password Settings" ResourceKey="PasswordSettings">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="minimumlength" HelpText="The Minimum Length For A Password" ResourceKey="RequiredLength">Minimum Length:</Label>
|
<Label Class="col-sm-3" For="minimumlength" HelpText="The Minimum Length For A Password" ResourceKey="RequiredLength">Minimum Length:</Label>
|
||||||
@@ -472,15 +494,6 @@ else
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="allowsitelogin" HelpText="Do you want to allow users to sign in using a username and password that is managed locally on this site? Note that you should only disable this option if you have already sucessfully configured an external login provider, or else you may lock yourself out of the site." ResourceKey="AllowSiteLogin">Allow Local Login?</Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<select id="allowsitelogin" class="form-select" @bind="@_allowsitelogin">
|
|
||||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
|
||||||
<option value="false">@SharedLocalizer["No"]</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</Section>
|
</Section>
|
||||||
@@ -522,7 +535,6 @@ else
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||||
@@ -533,12 +545,16 @@ else
|
|||||||
@code {
|
@code {
|
||||||
private List<UserRole> users;
|
private List<UserRole> users;
|
||||||
private string _deleted = "false";
|
private string _deleted = "false";
|
||||||
|
private int _page = 1;
|
||||||
|
|
||||||
|
private string _allowsitelogin;
|
||||||
|
private string _twofactor;
|
||||||
|
private string _loginlink;
|
||||||
|
private string _passkeys;
|
||||||
private string _allowregistration;
|
private string _allowregistration;
|
||||||
private string _registerurl;
|
private string _registerurl;
|
||||||
private string _profileurl;
|
|
||||||
private string _requireconfirmedemail;
|
private string _requireconfirmedemail;
|
||||||
private string _twofactor;
|
private string _profileurl;
|
||||||
private string _cookiename;
|
private string _cookiename;
|
||||||
private string _cookiedomain;
|
private string _cookiedomain;
|
||||||
private string _cookieexpiration;
|
private string _cookieexpiration;
|
||||||
@@ -588,7 +604,6 @@ else
|
|||||||
private string _createusers;
|
private string _createusers;
|
||||||
private string _verifyusers;
|
private string _verifyusers;
|
||||||
private string _allowhostrole;
|
private string _allowhostrole;
|
||||||
private string _allowsitelogin;
|
|
||||||
|
|
||||||
private string _secret;
|
private string _secret;
|
||||||
private string _secrettype = "password";
|
private string _secrettype = "password";
|
||||||
@@ -609,13 +624,16 @@ else
|
|||||||
|
|
||||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
_allowregistration = PageState.Site.AllowRegistration.ToString().ToLower();
|
_allowregistration = PageState.Site.AllowRegistration.ToString().ToLower();
|
||||||
_registerurl = SettingService.GetSetting(settings, "LoginOptions:RegisterUrl", "");
|
|
||||||
_profileurl = SettingService.GetSetting(settings, "LoginOptions:ProfileUrl", "");
|
|
||||||
_requireconfirmedemail = SettingService.GetSetting(settings, "LoginOptions:RequireConfirmedEmail", "true");
|
|
||||||
|
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
|
_allowsitelogin = SettingService.GetSetting(settings, "LoginOptions:AllowSiteLogin", "true");
|
||||||
_twofactor = SettingService.GetSetting(settings, "LoginOptions:TwoFactor", "false");
|
_twofactor = SettingService.GetSetting(settings, "LoginOptions:TwoFactor", "false");
|
||||||
|
_loginlink = SettingService.GetSetting(settings, "LoginOptions:LoginLink", "false");
|
||||||
|
_passkeys = SettingService.GetSetting(settings, "LoginOptions:Passkeys", "false");
|
||||||
|
_registerurl = SettingService.GetSetting(settings, "LoginOptions:RegisterUrl", "");
|
||||||
|
_requireconfirmedemail = SettingService.GetSetting(settings, "LoginOptions:RequireConfirmedEmail", "true");
|
||||||
|
_profileurl = SettingService.GetSetting(settings, "LoginOptions:ProfileUrl", "");
|
||||||
_cookiename = SettingService.GetSetting(settings, "LoginOptions:CookieName", ".AspNetCore.Identity.Application");
|
_cookiename = SettingService.GetSetting(settings, "LoginOptions:CookieName", ".AspNetCore.Identity.Application");
|
||||||
_cookiedomain = SettingService.GetSetting(settings, "LoginOptions:CookieDomain", "");
|
_cookiedomain = SettingService.GetSetting(settings, "LoginOptions:CookieDomain", "");
|
||||||
_cookieexpiration = SettingService.GetSetting(settings, "LoginOptions:CookieExpiration", "");
|
_cookieexpiration = SettingService.GetSetting(settings, "LoginOptions:CookieExpiration", "");
|
||||||
@@ -677,7 +695,6 @@ else
|
|||||||
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
|
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
|
||||||
_verifyusers = SettingService.GetSetting(settings, "ExternalLogin:VerifyUsers", "true");
|
_verifyusers = SettingService.GetSetting(settings, "ExternalLogin:VerifyUsers", "true");
|
||||||
_allowhostrole = SettingService.GetSetting(settings, "ExternalLogin:AllowHostRole", "false");
|
_allowhostrole = SettingService.GetSetting(settings, "ExternalLogin:AllowHostRole", "false");
|
||||||
_allowsitelogin = SettingService.GetSetting(settings, "LoginOptions:AllowSiteLogin", "true");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadUsersAsync()
|
private async Task LoadUsersAsync()
|
||||||
@@ -741,6 +758,8 @@ else
|
|||||||
private async Task SaveSiteSettings()
|
private async Task SaveSiteSettings()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
var site = PageState.Site;
|
var site = PageState.Site;
|
||||||
site.AllowRegistration = bool.Parse(_allowregistration);
|
site.AllowRegistration = bool.Parse(_allowregistration);
|
||||||
@@ -748,12 +767,13 @@ else
|
|||||||
|
|
||||||
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
||||||
|
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
settings = SettingService.SetSetting(settings, "LoginOptions:AllowSiteLogin", _allowsitelogin, false);
|
||||||
{
|
|
||||||
settings = SettingService.SetSetting(settings, "LoginOptions:RegisterUrl", _registerurl, false);
|
|
||||||
settings = SettingService.SetSetting(settings, "LoginOptions:ProfileUrl", _profileurl, false);
|
|
||||||
settings = SettingService.SetSetting(settings, "LoginOptions:RequireConfirmedEmail", _requireconfirmedemail, false);
|
|
||||||
settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false);
|
settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false);
|
||||||
|
settings = SettingService.SetSetting(settings, "LoginOptions:LoginLink", _loginlink, false);
|
||||||
|
settings = SettingService.SetSetting(settings, "LoginOptions:Passkeys", _passkeys, false);
|
||||||
|
settings = SettingService.SetSetting(settings, "LoginOptions:RegisterUrl", _registerurl, false);
|
||||||
|
settings = SettingService.SetSetting(settings, "LoginOptions:RequireConfirmedEmail", _requireconfirmedemail, false);
|
||||||
|
settings = SettingService.SetSetting(settings, "LoginOptions:ProfileUrl", _profileurl, false);
|
||||||
settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true);
|
settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true);
|
||||||
settings = SettingService.SetSetting(settings, "LoginOptions:CookieDomain", _cookiedomain, true);
|
settings = SettingService.SetSetting(settings, "LoginOptions:CookieDomain", _cookiedomain, true);
|
||||||
settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, 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:CreateUsers", _createusers, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:VerifyUsers", _verifyusers, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:VerifyUsers", _verifyusers, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:AllowHostRole", _allowhostrole, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:AllowHostRole", _allowhostrole, true);
|
||||||
settings = SettingService.SetSetting(settings, "LoginOptions:AllowSiteLogin", _allowsitelogin, false);
|
|
||||||
|
|
||||||
settings = SettingService.SetSetting(settings, "JwtOptions:Secret", _secret, true);
|
settings = SettingService.SetSetting(settings, "JwtOptions:Secret", _secret, true);
|
||||||
settings = SettingService.SetSetting(settings, "JwtOptions:Issuer", _issuer, true);
|
settings = SettingService.SetSetting(settings, "JwtOptions:Issuer", _issuer, true);
|
||||||
settings = SettingService.SetSetting(settings, "JwtOptions:Audience", _audience, true);
|
settings = SettingService.SetSetting(settings, "JwtOptions:Audience", _audience, true);
|
||||||
settings = SettingService.SetSetting(settings, "JwtOptions:Lifetime", _lifetime, true);
|
settings = SettingService.SetSetting(settings, "JwtOptions:Lifetime", _lifetime, true);
|
||||||
}
|
|
||||||
|
|
||||||
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
|
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
|
||||||
await SettingService.ClearSiteSettingsCacheAsync();
|
await SettingService.ClearSiteSettingsCacheAsync();
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(_secret))
|
if (!string.IsNullOrEmpty(_secret))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
@namespace Oqtane.Modules.Admin.Users
|
@namespace Oqtane.Modules.Admin.Users
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IUserService UserService
|
@inject ISiteTaskService SiteTaskService
|
||||||
@inject IStringLocalizer<Users> Localizer
|
@inject IStringLocalizer<Users> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@@ -43,17 +43,9 @@
|
|||||||
var fileid = _filemanager.GetFileId();
|
var fileid = _filemanager.GetFileId();
|
||||||
if (fileid != -1)
|
if (fileid != -1)
|
||||||
{
|
{
|
||||||
ShowProgressIndicator();
|
var siteTask = new SiteTask(PageState.Site.SiteId, "Import Users", "Oqtane.Infrastructure.ImportUsersTask, Oqtane.Server", $"{fileid}:{_notify}");
|
||||||
var results = await UserService.ImportUsersAsync(PageState.Site.SiteId, fileid, bool.Parse(_notify));
|
await SiteTaskService.AddSiteTaskAsync(siteTask);
|
||||||
if (bool.Parse(results["Success"]))
|
AddModuleMessage(Localizer["Message.Import.Success"], MessageType.Success);
|
||||||
{
|
|
||||||
AddModuleMessage(string.Format(Localizer["Message.Import.Success"], results["Users"]), MessageType.Success);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AddModuleMessage(Localizer["Message.Import.Failure"], MessageType.Error);
|
|
||||||
}
|
|
||||||
HideProgressIndicator();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -35,11 +35,11 @@
|
|||||||
{
|
{
|
||||||
if (Disabled)
|
if (Disabled)
|
||||||
{
|
{
|
||||||
<button type="button" class="@Class" disabled>@((MarkupString)_openIconSpan) @_openText</button>
|
<button type="button" class="@Class" title="@AltText" disabled>@((MarkupString)_openIconSpan) @_openText</button>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<button type="button" class="@Class" @onclick="DisplayModal">@((MarkupString)_openIconSpan) @_openText</button>
|
<button type="button" class="@Class" title="@AltText" @onclick="DisplayModal">@((MarkupString)_openIconSpan) @_openText</button>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,13 +83,13 @@ else
|
|||||||
{
|
{
|
||||||
if (Disabled)
|
if (Disabled)
|
||||||
{
|
{
|
||||||
<button type="button" class="@Class" disabled>@((MarkupString)_openIconSpan) @_openText</button>
|
<button type="button" title="@AltText" class="@Class" disabled>@((MarkupString)_openIconSpan) @_openText</button>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<form method="post" class="app-form-inline" @formname="@($"ActionDialogActionForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="DisplayModal" data-enhance>
|
<form method="post" class="app-form-inline" @formname="@($"ActionDialogActionForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="DisplayModal" data-enhance>
|
||||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||||
<button type="submit" class="@Class">@((MarkupString)_openIconSpan) @_openText</button>
|
<button type="submit" title="@AltText" class="@Class">@((MarkupString)_openIconSpan) @_openText</button>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,6 +113,9 @@ else
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public string Text { get; set; } // optional - defaults to Action if not specified
|
public string Text { get; set; } // optional - defaults to Action if not specified
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string AltText { get; set; } // optional
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Action { get; set; } // optional
|
public string Action { get; set; } // optional
|
||||||
|
|
||||||
|
|||||||
@@ -8,17 +8,17 @@
|
|||||||
{
|
{
|
||||||
if (Disabled)
|
if (Disabled)
|
||||||
{
|
{
|
||||||
<NavLink class="@($"{_classname} disabled")" href="@_url" style="@_style">@((MarkupString)_iconSpan) @_text</NavLink>
|
<NavLink class="@($"{_classname} disabled")" title="@AltText" href="@_url" style="@_style">@((MarkupString)_iconSpan) @_text</NavLink>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (OnClick == null)
|
if (OnClick == null)
|
||||||
{
|
{
|
||||||
<NavLink class="@_classname" href="@_url" style="@_style">@((MarkupString)_iconSpan) @_text</NavLink>
|
<NavLink class="@_classname" title="@AltText" href="@_url" style="@_style">@((MarkupString)_iconSpan) @_text</NavLink>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<button type="button" class="@_classname" style="@_style" onclick="@OnClick">@((MarkupString)_iconSpan) @_text</button>
|
<button type="button" class="@_classname" title="@AltText" style="@_style" onclick="@OnClick">@((MarkupString)_iconSpan) @_text</button>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,6 +42,9 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public string Text { get; set; } // optional - defaults to Action if not specified
|
public string Text { get; set; } // optional - defaults to Action if not specified
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string AltText { get; set; } // optional
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public int ModuleId { get; set; } = -1; // optional - allows the link to target a specific moduleid
|
public int ModuleId { get; set; } = -1; // optional - allows the link to target a specific moduleid
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,12 @@
|
|||||||
{
|
{
|
||||||
<input type="file" id="@_fileinputid" name="file" accept="@_filter" />
|
<input type="file" id="@_fileinputid" name="file" accept="@_filter" />
|
||||||
}
|
}
|
||||||
|
@if (MaxUploadFileSize > 0)
|
||||||
|
{
|
||||||
|
<div class="row my-1">
|
||||||
|
<small class="fw-light">@string.Format(Localizer["File.MaxSize"], MaxUploadFileSize)</small>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<button type="button" class="btn btn-success" @onclick="UploadFiles">@SharedLocalizer["Upload"]</button>
|
<button type="button" class="btn btn-success" @onclick="UploadFiles">@SharedLocalizer["Upload"]</button>
|
||||||
@@ -163,6 +169,9 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public int ChunkSize { get; set; } = 1; // optional - size of file chunks to upload in MB
|
public int ChunkSize { get; set; } = 1; // optional - size of file chunks to upload in MB
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public int MaxUploadFileSize { get; set; } = -1; // optional - maximum upload file size in MB
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public EventCallback<int> OnUpload { get; set; } // optional - executes a method in the calling component when a file is uploaded
|
public EventCallback<int> OnUpload { get; set; } // optional - executes a method in the calling component when a file is uploaded
|
||||||
|
|
||||||
@@ -381,16 +390,39 @@
|
|||||||
if (uploads.Length > 0)
|
if (uploads.Length > 0)
|
||||||
{
|
{
|
||||||
string restricted = "";
|
string restricted = "";
|
||||||
|
string tooLarge = "";
|
||||||
foreach (var upload in uploads)
|
foreach (var upload in uploads)
|
||||||
{
|
{
|
||||||
var filename = upload.Split(':')[0];
|
var fileparts = upload.Split(':');
|
||||||
|
var filename = fileparts[0];
|
||||||
|
|
||||||
|
if (MaxUploadFileSize > 0)
|
||||||
|
{
|
||||||
|
var filesizeBytes = long.Parse(fileparts[1]);
|
||||||
|
var filesizeMB = (double)filesizeBytes / (1024 * 1024);
|
||||||
|
if (filesizeMB > MaxUploadFileSize)
|
||||||
|
{
|
||||||
|
tooLarge += (tooLarge == "" ? "" : ",") + filename;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var extension = (filename.LastIndexOf(".") != -1) ? filename.Substring(filename.LastIndexOf(".") + 1) : "";
|
var extension = (filename.LastIndexOf(".") != -1) ? filename.Substring(filename.LastIndexOf(".") + 1) : "";
|
||||||
if (!PageState.Site.UploadableFiles.Split(',').Contains(extension.ToLower()))
|
if (!PageState.Site.UploadableFiles.Split(',').Contains(extension.ToLower()))
|
||||||
{
|
{
|
||||||
restricted += (restricted == "" ? "" : ",") + extension;
|
restricted += (restricted == "" ? "" : ",") + extension;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (restricted == "")
|
if (restricted != "")
|
||||||
|
{
|
||||||
|
_message = string.Format(Localizer["Message.File.Restricted"], restricted);
|
||||||
|
_messagetype = MessageType.Warning;
|
||||||
|
}
|
||||||
|
else if (tooLarge != "")
|
||||||
|
{
|
||||||
|
_message = string.Format(Localizer["Message.File.TooLarge"], tooLarge, MaxUploadFileSize);
|
||||||
|
_messagetype = MessageType.Warning;
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
CancellationTokenSource tokenSource = new CancellationTokenSource();
|
CancellationTokenSource tokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
@@ -490,11 +522,6 @@
|
|||||||
tokenSource.Dispose();
|
tokenSource.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
_message = string.Format(Localizer["Message.File.Restricted"], restricted);
|
|
||||||
_messagetype = MessageType.Warning;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
@if (_style == MessageStyle.Toast)
|
@if (_style == MessageStyle.Toast)
|
||||||
{
|
{
|
||||||
<div class="app-modulemessage-toast bottom-0 end-0">
|
<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)
|
@((MarkupString)Message)
|
||||||
@if (Type == MessageType.Error && PageState != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
@if (Type == MessageType.Error && PageState != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
@inject ISettingService SettingService
|
@inject ISettingService SettingService
|
||||||
@inject IStringLocalizer<RichTextEditor> Localizer
|
@inject IStringLocalizer<RichTextEditor> Localizer
|
||||||
|
|
||||||
<div class="row" style="margin-bottom: 50px;">
|
<div class="row" style="@_style">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
@_textEditorComponent
|
@_textEditorComponent
|
||||||
</div>
|
</div>
|
||||||
@@ -18,6 +18,8 @@
|
|||||||
private RenderFragment _textEditorComponent;
|
private RenderFragment _textEditorComponent;
|
||||||
private ITextEditor _textEditor;
|
private ITextEditor _textEditor;
|
||||||
|
|
||||||
|
private string _style = "margin-bottom: 50px;";
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Content { get; set; }
|
public string Content { get; set; }
|
||||||
|
|
||||||
@@ -30,6 +32,9 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public string Provider { get; set; }
|
public string Provider { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string Style { get; set; } // optional
|
||||||
|
|
||||||
[Parameter(CaptureUnmatchedValues = true)]
|
[Parameter(CaptureUnmatchedValues = true)]
|
||||||
public Dictionary<string, object> AdditionalAttributes { get; set; } = new Dictionary<string, object>();
|
public Dictionary<string, object> AdditionalAttributes { get; set; } = new Dictionary<string, object>();
|
||||||
|
|
||||||
@@ -40,6 +45,12 @@
|
|||||||
|
|
||||||
protected override void OnParametersSet()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(Style))
|
||||||
|
{
|
||||||
|
_style = Style;
|
||||||
|
}
|
||||||
|
|
||||||
_textEditorComponent = (builder) =>
|
_textEditorComponent = (builder) =>
|
||||||
{
|
{
|
||||||
CreateTextEditor(builder);
|
CreateTextEditor(builder);
|
||||||
|
|||||||
@@ -30,6 +30,12 @@ else
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public SecurityAccessLevel? Security { get; set; } // optional - can be used to specify SecurityAccessLevel
|
public SecurityAccessLevel? Security { get; set; } // optional - can be used to specify SecurityAccessLevel
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string RoleName { get; set; } // optional - can be used to specify Role allowed to view this tab
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string PermissionName { get; set; } // optional - can be used to specify Permission allowed to view this tab
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
base.OnParametersSet();
|
base.OnParametersSet();
|
||||||
|
|||||||
@@ -84,14 +84,63 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if a tab should be visible based on user permissions.
|
||||||
|
/// Authorization follows this hierarchy:
|
||||||
|
/// 1. Host tabs (Security == Host): Only users with Host role can access (Admins excluded)
|
||||||
|
/// 2. Admin users: Bypass all other checks (except Host restrictions)
|
||||||
|
/// 3. SecurityAccessLevel check (null/Anonymous/View/Edit/Host):
|
||||||
|
/// - null: No security level restriction (proceeds to step 4)
|
||||||
|
/// - Anonymous: No authentication required
|
||||||
|
/// - View/Edit: Requires corresponding module permission
|
||||||
|
/// - Host: Only Host role can access
|
||||||
|
/// 4. Additional RoleName requirement (if specified)
|
||||||
|
/// 5. Additional PermissionName requirement (if specified)
|
||||||
|
///
|
||||||
|
/// Important: When Security is null, RoleName and PermissionName checks STILL apply
|
||||||
|
/// (Security = null doesn't mean unrestricted, it means "no security level required")
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tabPanel">The tab panel to check authorization for</param>
|
||||||
|
/// <returns>True if user is authorized to see this tab, false otherwise</returns>
|
||||||
private bool IsAuthorized(TabPanel tabPanel)
|
private bool IsAuthorized(TabPanel tabPanel)
|
||||||
{
|
{
|
||||||
var authorized = false;
|
// Step 1: Check for Host-only restriction
|
||||||
|
if (tabPanel.Security == SecurityAccessLevel.Host)
|
||||||
|
{
|
||||||
|
return UserSecurity.IsAuthorized(PageState.User, RoleNames.Host);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Admin bypass all restrictions except Host
|
||||||
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: If Security is null, check only RoleName and PermissionName
|
||||||
|
if (tabPanel.Security == null)
|
||||||
|
{
|
||||||
|
// Start with authorized = true for null security
|
||||||
|
bool isAuthorized = true;
|
||||||
|
|
||||||
|
// Only apply RoleName check if provided
|
||||||
|
if (!string.IsNullOrEmpty(tabPanel.RoleName))
|
||||||
|
{
|
||||||
|
isAuthorized = UserSecurity.IsAuthorized(PageState.User, tabPanel.RoleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only apply PermissionName check if provided
|
||||||
|
if (isAuthorized && !string.IsNullOrEmpty(tabPanel.PermissionName))
|
||||||
|
{
|
||||||
|
isAuthorized = UserSecurity.IsAuthorized(PageState.User, tabPanel.PermissionName, ModuleState.PermissionList);
|
||||||
|
}
|
||||||
|
|
||||||
|
return isAuthorized;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle other SecurityAccessLevel values
|
||||||
|
bool authorized = false; // Use different variable name or move declaration
|
||||||
switch (tabPanel.Security)
|
switch (tabPanel.Security)
|
||||||
{
|
{
|
||||||
case null: // security not specified - assume SecurityAccessLevel.Anonymous
|
|
||||||
authorized = true;
|
|
||||||
break;
|
|
||||||
case SecurityAccessLevel.Anonymous:
|
case SecurityAccessLevel.Anonymous:
|
||||||
authorized = true;
|
authorized = true;
|
||||||
break;
|
break;
|
||||||
@@ -101,13 +150,23 @@
|
|||||||
case SecurityAccessLevel.Edit:
|
case SecurityAccessLevel.Edit:
|
||||||
authorized = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, ModuleState.PermissionList);
|
authorized = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, ModuleState.PermissionList);
|
||||||
break;
|
break;
|
||||||
case SecurityAccessLevel.Admin:
|
|
||||||
authorized = UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin);
|
|
||||||
break;
|
|
||||||
case SecurityAccessLevel.Host:
|
case SecurityAccessLevel.Host:
|
||||||
authorized = UserSecurity.IsAuthorized(PageState.User, RoleNames.Host);
|
authorized = UserSecurity.IsAuthorized(PageState.User, RoleNames.Host);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Step 4: Additional RoleName requirement
|
||||||
|
if (authorized && !string.IsNullOrEmpty(tabPanel.RoleName))
|
||||||
|
{
|
||||||
|
authorized = UserSecurity.IsAuthorized(PageState.User, tabPanel.RoleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 5: Additional PermissionName requirement
|
||||||
|
if (authorized && !string.IsNullOrEmpty(tabPanel.PermissionName))
|
||||||
|
{
|
||||||
|
authorized = UserSecurity.IsAuthorized(PageState.User, tabPanel.PermissionName, ModuleState.PermissionList);
|
||||||
|
}
|
||||||
|
|
||||||
return authorized;
|
return authorized;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@
|
|||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div @ref="@_editorElement"></div>
|
<div @ref="@_editorElement" class="app-editor-resizable"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
@using System.Text.RegularExpressions
|
@using System.Text.RegularExpressions
|
||||||
@using Radzen
|
@using Radzen
|
||||||
@using Radzen.Blazor
|
@using Radzen.Blazor
|
||||||
|
@using System.Reflection
|
||||||
|
|
||||||
@namespace Oqtane.Modules.Controls
|
@namespace Oqtane.Modules.Controls
|
||||||
@inherits ModuleControlBase
|
@inherits ModuleControlBase
|
||||||
@@ -17,7 +18,7 @@
|
|||||||
<RadzenTheme Theme="@RadzenEditorDefinitions.DefaultTheme" />
|
<RadzenTheme Theme="@RadzenEditorDefinitions.DefaultTheme" />
|
||||||
<RadzenComponents />
|
<RadzenComponents />
|
||||||
<RadzenHtmlEditor @ref="_editor" Visible="_visible" Placeholder="@Placeholder" style="@($"height: {Height}px;")"
|
<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>
|
<ChildContent>
|
||||||
@_toolbar
|
@_toolbar
|
||||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
@@ -93,6 +94,17 @@
|
|||||||
}
|
}
|
||||||
await _interop.SetBackgroundColor(_editor.Element, backgroundColor);
|
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);
|
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") },
|
{ "FormatBlock", (builder, sequence) => CreateFragment(builder, sequence, "FormatBlock", "RadzenHtmlEditorFormatBlock") },
|
||||||
{ "Indent", (builder, sequence) => CreateFragment(builder, sequence, "Indent", "RadzenHtmlEditorIndent") },
|
{ "Indent", (builder, sequence) => CreateFragment(builder, sequence, "Indent", "RadzenHtmlEditorIndent") },
|
||||||
{ "InsertImage", (builder, sequence) => CreateFragment(builder, sequence, "InsertImage", "RadzenHtmlEditorCustomTool", "InsertImage", "image") },
|
{ "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") },
|
{ "Italic", (builder, sequence) => CreateFragment(builder, sequence, "Italic", "RadzenHtmlEditorItalic") },
|
||||||
{ "Justify", (builder, sequence) => CreateFragment(builder, sequence, "Justify", "RadzenHtmlEditorJustify") },
|
{ "Justify", (builder, sequence) => CreateFragment(builder, sequence, "Justify", "RadzenHtmlEditorJustify") },
|
||||||
{ "Link", (builder, sequence) => CreateFragment(builder, sequence, "InsertLink", "RadzenHtmlEditorCustomTool", "InsertLink", "insert_link") },
|
{ "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 style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th>@SharedLocalizer["CreatedOn"]</th>
|
<th>@Localizer["CreatedOn"]</th>
|
||||||
<th>@SharedLocalizer["CreatedBy"]</th>
|
<th>@Localizer["CreatedBy"]</th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><ActionLink Action="View" Security="SecurityAccessLevel.Edit" OnClick="@(async () => await View(context))" ResourceKey="View" /></td>
|
<td><ActionLink Action="View" Security="SecurityAccessLevel.Edit" OnClick="@(async () => await View(context))" ResourceKey="View" /></td>
|
||||||
@@ -121,15 +121,11 @@
|
|||||||
private async Task View(Models.HtmlText htmltext)
|
private async Task View(Models.HtmlText htmltext)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
|
||||||
htmltext = await HtmlTextService.GetHtmlTextAsync(htmltext.HtmlTextId, htmltext.ModuleId);
|
|
||||||
if (htmltext != null)
|
|
||||||
{
|
{
|
||||||
_view = htmltext.Content;
|
_view = htmltext.Content;
|
||||||
_view = Utilities.FormatContent(_view, PageState.Alias, "render");
|
_view = Utilities.FormatContent(_view, PageState.Alias, "render");
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Viewing Content {Error}", ex.Message);
|
await logger.LogError(ex, "Error Viewing Content {Error}", ex.Message);
|
||||||
@@ -140,9 +136,6 @@
|
|||||||
private async Task Restore(Models.HtmlText htmltext)
|
private async Task Restore(Models.HtmlText htmltext)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
|
||||||
htmltext = await HtmlTextService.GetHtmlTextAsync(htmltext.HtmlTextId, ModuleState.ModuleId);
|
|
||||||
if (htmltext != null)
|
|
||||||
{
|
{
|
||||||
var content = htmltext.Content;
|
var content = htmltext.Content;
|
||||||
htmltext = new HtmlText();
|
htmltext = new HtmlText();
|
||||||
@@ -154,7 +147,6 @@
|
|||||||
await LoadContent();
|
await LoadContent();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Restoring Content {Error}", ex.Message);
|
await logger.LogError(ex, "Error Restoring Content {Error}", ex.Message);
|
||||||
@@ -165,9 +157,6 @@
|
|||||||
private async Task Delete(Models.HtmlText htmltext)
|
private async Task Delete(Models.HtmlText htmltext)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
|
||||||
htmltext = await HtmlTextService.GetHtmlTextAsync(htmltext.HtmlTextId, ModuleState.ModuleId);
|
|
||||||
if (htmltext != null)
|
|
||||||
{
|
{
|
||||||
await HtmlTextService.DeleteHtmlTextAsync(htmltext.HtmlTextId, htmltext.ModuleId);
|
await HtmlTextService.DeleteHtmlTextAsync(htmltext.HtmlTextId, htmltext.ModuleId);
|
||||||
await logger.LogInformation("Content Deleted {HtmlText}", htmltext);
|
await logger.LogInformation("Content Deleted {HtmlText}", htmltext);
|
||||||
@@ -175,7 +164,6 @@
|
|||||||
await LoadContent();
|
await LoadContent();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Deleting Content {Error}", ex.Message);
|
await logger.LogError(ex, "Error Deleting Content {Error}", ex.Message);
|
||||||
|
|||||||
@@ -7,6 +7,18 @@ using Oqtane.Shared;
|
|||||||
|
|
||||||
namespace Oqtane.Modules.HtmlText.Services
|
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")]
|
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
|
||||||
public class HtmlTextService : ServiceBase, IHtmlTextService, IClientService
|
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));
|
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)
|
public async Task<Models.HtmlText> AddHtmlTextAsync(Models.HtmlText htmlText)
|
||||||
{
|
{
|
||||||
return await PostJsonAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}", EntityNames.Module, htmlText.ModuleId), 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>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@@ -26,12 +32,14 @@
|
|||||||
private bool validated = false;
|
private bool validated = false;
|
||||||
|
|
||||||
private string _dynamictokens;
|
private string _dynamictokens;
|
||||||
|
private string _versions = "5";
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_dynamictokens = SettingService.GetSetting(ModuleState.Settings, "DynamicTokens", "false");
|
_dynamictokens = SettingService.GetSetting(ModuleState.Settings, "DynamicTokens", "false");
|
||||||
|
_versions = SettingService.GetSetting(ModuleState.Settings, "Versions", "5");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -45,6 +53,10 @@
|
|||||||
{
|
{
|
||||||
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
|
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
|
||||||
settings = SettingService.SetSetting(settings, "DynamicTokens", _dynamictokens);
|
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);
|
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ namespace Oqtane.Modules
|
|||||||
private Dictionary<string, string> _urlparameters;
|
private Dictionary<string, string> _urlparameters;
|
||||||
private bool _scriptsloaded = false;
|
private bool _scriptsloaded = false;
|
||||||
|
|
||||||
protected Logger logger => _logger ?? (_logger = new Logger(this));
|
public Logger logger => _logger ?? (_logger = new Logger(this));
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
protected ILogService LoggingService { get; set; }
|
protected ILogService LoggingService { get; set; }
|
||||||
@@ -372,6 +372,11 @@ namespace Oqtane.Modules
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UI methods
|
// 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)
|
public void AddModuleMessage(string message, MessageType type)
|
||||||
{
|
{
|
||||||
AddModuleMessage(message, type, "top");
|
AddModuleMessage(message, type, "top");
|
||||||
@@ -389,21 +394,37 @@ namespace Oqtane.Modules
|
|||||||
|
|
||||||
public void AddModuleMessage(string message, MessageType type, string position, MessageStyle style)
|
public void AddModuleMessage(string message, MessageType type, string position, MessageStyle style)
|
||||||
{
|
{
|
||||||
|
if (RenderModeBoundary == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(RenderModeBoundaryErrorMessage);
|
||||||
|
}
|
||||||
RenderModeBoundary.AddModuleMessage(message, type, position, style);
|
RenderModeBoundary.AddModuleMessage(message, type, position, style);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClearModuleMessage()
|
public void ClearModuleMessage()
|
||||||
{
|
{
|
||||||
|
if (RenderModeBoundary == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(RenderModeBoundaryErrorMessage);
|
||||||
|
}
|
||||||
RenderModeBoundary.AddModuleMessage("", MessageType.Undefined);
|
RenderModeBoundary.AddModuleMessage("", MessageType.Undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ShowProgressIndicator()
|
public void ShowProgressIndicator()
|
||||||
{
|
{
|
||||||
|
if (RenderModeBoundary == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(RenderModeBoundaryErrorMessage);
|
||||||
|
}
|
||||||
RenderModeBoundary.ShowProgressIndicator();
|
RenderModeBoundary.ShowProgressIndicator();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HideProgressIndicator()
|
public void HideProgressIndicator()
|
||||||
{
|
{
|
||||||
|
if (RenderModeBoundary == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(RenderModeBoundaryErrorMessage);
|
||||||
|
}
|
||||||
RenderModeBoundary.HideProgressIndicator();
|
RenderModeBoundary.HideProgressIndicator();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -460,6 +481,11 @@ namespace Oqtane.Modules
|
|||||||
|
|
||||||
public string ReplaceTokens(string content, object obj)
|
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
|
// Using StringBuilder avoids the performance penalty of repeated string allocations
|
||||||
// that occur with string.Replace or string concatenation inside loops.
|
// that occur with string.Replace or string concatenation inside loops.
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<RootNamespace>Oqtane</RootNamespace>
|
<RootNamespace>Oqtane</RootNamespace>
|
||||||
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
|
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
|
||||||
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
|
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
|
||||||
|
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.9" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.5" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.9" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="10.0.5" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.9" />
|
<PackageReference Include="Microsoft.Extensions.Localization" Version="10.0.5" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.9" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.5" />
|
||||||
<PackageReference Include="Radzen.Blazor" Version="7.4.3" />
|
<PackageReference Include="Radzen.Blazor" Version="10.0.6" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -117,31 +117,58 @@
|
|||||||
<resheader name="writer">
|
<resheader name="writer">
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
<data name="Name" xml:space="preserve">
|
<data name="CaseSensitive.Text" xml:space="preserve">
|
||||||
<value>Name</value>
|
<value>Match Case?</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Add.Text" xml:space="preserve">
|
<data name="CaseSensitive.HelpText" xml:space="preserve">
|
||||||
<value>Add MyModule</value>
|
<value>Specify if the replacement operation should be case sensitive</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Edit.Text" xml:space="preserve">
|
<data name="Content.Text" xml:space="preserve">
|
||||||
<value>Edit</value>
|
<value>Module Content?</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Delete.Text" xml:space="preserve">
|
<data name="Content.HelpText" xml:space="preserve">
|
||||||
<value>Delete</value>
|
<value>Specify if module content should be updated</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Delete.Header" xml:space="preserve">
|
<data name="Pages.Text" xml:space="preserve">
|
||||||
<value>Delete MyModule</value>
|
<value>Page Info?</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Delete.Message" xml:space="preserve">
|
<data name="Pages.HelpText" xml:space="preserve">
|
||||||
<value>Are You Sure You Wish To Delete This MyModule?</value>
|
<value>Specify if page information should be updated (ie. name, title, head content, body content settings)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Message.DisplayNone" xml:space="preserve">
|
<data name="Site.Text" xml:space="preserve">
|
||||||
<value>No MyModules To Display</value>
|
<value>Site Info?</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Message.LoadError" xml:space="preserve">
|
<data name="Site.HelpText" xml:space="preserve">
|
||||||
<value>Error Loading MyModule</value>
|
<value>Specify if site information should be updated (ie. name, head content, body content, settings)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Message.DeleteError" xml:space="preserve">
|
<data name="Replace.Text" xml:space="preserve">
|
||||||
<value>Error Deleting MyModule</value>
|
<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>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -139,7 +139,7 @@
|
|||||||
<value>Error Updating Job</value>
|
<value>Error Updating Job</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Message.Required.JobInfo" xml:space="preserve">
|
<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>
|
||||||
<data name="Name.HelpText" xml:space="preserve">
|
<data name="Name.HelpText" xml:space="preserve">
|
||||||
<value>Enter the job name</value>
|
<value>Enter the job name</value>
|
||||||
@@ -154,7 +154,7 @@
|
|||||||
<value>Select how often you want the job to run</value>
|
<value>Select how often you want the job to run</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Starting.HelpText" xml:space="preserve">
|
<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>
|
||||||
<data name="Ending.HelpText" xml:space="preserve">
|
<data name="Ending.HelpText" xml:space="preserve">
|
||||||
<value>Optionally enter the date and time when this job should stop executing</value>
|
<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>
|
<value>Number of log entries to retain for this job</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="NextExecution.HelpText" xml:space="preserve">
|
<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>
|
||||||
<data name="Type.Text" xml:space="preserve">
|
<data name="Type.Text" xml:space="preserve">
|
||||||
<value>Type: </value>
|
<value>Type: </value>
|
||||||
@@ -193,6 +193,15 @@
|
|||||||
<value>Execute Once</value>
|
<value>Execute Once</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Message.StartEndDateError" xml:space="preserve">
|
<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>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -118,7 +118,13 @@
|
|||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
<data name="ForgotPassword" xml:space="preserve">
|
<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>
|
||||||
<data name="Success.Account.Verified" xml:space="preserve">
|
<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>
|
<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>
|
<value>User Account Email Address Could Not Be Verified. Please Contact Your Administrator For Further Instructions.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Success.Account.Linked" xml:space="preserve">
|
<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>
|
||||||
<data name="Message.Account.NotLinked" xml:space="preserve">
|
<data name="Message.Account.NotLinked" xml:space="preserve">
|
||||||
<value>External Login Could Not Be Linked. Please Contact Your Administrator For Further Instructions.</value>
|
<value>External Login Could Not Be Linked. Please Contact Your Administrator For Further Instructions.</value>
|
||||||
@@ -142,16 +148,19 @@
|
|||||||
<value>You Are Already Signed In</value>
|
<value>You Are Already Signed In</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Message.ForgotPassword" xml:space="preserve">
|
<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>
|
<value>Please Check The Email Address Associated To Your User Account For A Password Reset Notification</value>
|
||||||
</data>
|
</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">
|
<data name="Message.UserDoesNotExist" xml:space="preserve">
|
||||||
<value>User Does Not Exist</value>
|
<value>User Does Not Exist For Criteria Specified</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Code.HelpText" xml:space="preserve">
|
<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>
|
||||||
<data name="Code.Placeholder" xml:space="preserve">
|
<data name="Code.Placeholder" xml:space="preserve">
|
||||||
<value>Verification Code</value>
|
<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>
|
<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>
|
||||||
<data name="Password.HelpText" xml:space="preserve">
|
<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>
|
||||||
<data name="Password.Placeholder" xml:space="preserve">
|
<data name="Password.Placeholder" xml:space="preserve">
|
||||||
<value>Password</value>
|
<value>Password</value>
|
||||||
@@ -175,13 +184,13 @@
|
|||||||
<value>Password:</value>
|
<value>Password:</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Remember.HelpText" xml:space="preserve">
|
<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>
|
||||||
<data name="Remember.Text" xml:space="preserve">
|
<data name="Remember.Text" xml:space="preserve">
|
||||||
<value>Remember Me?</value>
|
<value>Stay Signed In?</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Username.HelpText" xml:space="preserve">
|
<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>
|
||||||
<data name="Username.Placeholder" xml:space="preserve">
|
<data name="Username.Placeholder" xml:space="preserve">
|
||||||
<value>Username</value>
|
<value>Username</value>
|
||||||
@@ -200,6 +209,12 @@
|
|||||||
</data>
|
</data>
|
||||||
<data name="Error.ResetPassword" xml:space="preserve">
|
<data name="Error.ResetPassword" xml:space="preserve">
|
||||||
<value>Error Resetting Password</value>
|
<value>Error Resetting Password</value>
|
||||||
|
</data>
|
||||||
|
<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>
|
||||||
<data name="ExternalLoginStatus.DuplicateEmail" xml:space="preserve">
|
<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>
|
<value>Multiple User Accounts Already Exist With The Email Address Of Your External Login. Please Contact Your Administrator For Further Instructions.</value>
|
||||||
@@ -228,7 +243,28 @@
|
|||||||
<data name="ExternalLoginStatus.ReviewClaims" xml:space="preserve">
|
<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>
|
<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>
|
||||||
|
<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">
|
<data name="Register" xml:space="preserve">
|
||||||
<value>Register as new user?</value>
|
<value>Register as new user?</value>
|
||||||
</data>
|
</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>
|
</root>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user