Compare commits
2521 Commits
v4.0.4
...
7a9941fe66
| Author | SHA1 | Date | |
|---|---|---|---|
| 7a9941fe66 | |||
| b3b39f583a | |||
| 92c554e854 | |||
|
|
29fe3dfd0b | ||
|
|
985e50d415 | ||
|
|
11150b6a10 | ||
|
|
c499acdc4a | ||
|
|
b24e3252d9 | ||
|
|
e15787b1e4 | ||
|
|
d5f19d97e2 | ||
|
|
5543a4aeed | ||
|
|
9c333232e2 | ||
|
|
d52b95ea23 | ||
|
|
ef4fbcbb8a | ||
|
|
aa454b411f | ||
|
|
543e9339c7 | ||
|
|
7fff5c0d18 | ||
|
|
fa79f3f6fa | ||
|
|
c098839881 | ||
|
|
8d0d88c1b9 | ||
|
|
2b6ba0f410 | ||
|
|
11235009c0 | ||
|
|
4b05f7fdad | ||
|
|
338b0ae509 | ||
|
|
a437082952 | ||
|
|
ca9aba7b3b | ||
|
|
fe9f189734 | ||
|
|
018ac612f4 | ||
|
|
5bde40ec2b | ||
|
|
4d5780c192 | ||
|
|
ff6a810ad5 | ||
|
|
feec01ba00 | ||
|
|
1f05d12ef5 | ||
|
|
31aba14507 | ||
|
|
bbd6f13f36 | ||
|
|
68edbbbdb9 | ||
|
|
eb5a0dc1c9 | ||
|
|
7cea4f1792 | ||
|
|
c57c6abb1b | ||
|
|
a25b706c7b | ||
|
|
5d077e843d | ||
|
|
65bf3e9899 | ||
|
|
51ba3a01f5 | ||
|
|
f9ca611b8b | ||
|
|
a49b8728fd | ||
|
|
9234b91089 | ||
|
|
f3fcef52dd | ||
|
|
7f8b741981 | ||
|
|
f1791a709c | ||
|
|
30307fb05e | ||
|
|
57d443be8d | ||
|
|
84844c5043 | ||
|
|
9000f05961 | ||
|
|
6bef9b5c6e | ||
|
|
ffef1f4820 | ||
|
|
10c7bdbcaa | ||
|
|
e8f9888a41 | ||
|
|
8bac702be6 | ||
|
|
128bcecfe3 | ||
|
|
1390b3c489 | ||
|
|
a0f41341ac | ||
|
|
8ffa7ef7ff | ||
|
|
deb4607081 | ||
|
|
49c62f80e8 | ||
|
|
139793f3c0 | ||
|
|
f237cb9655 | ||
|
|
9f18c460d8 | ||
|
|
306a41b442 | ||
|
|
b53f54295d | ||
|
|
7e4f066694 | ||
|
|
90d72489d9 | ||
|
|
045c455324 | ||
|
|
60da903360 | ||
|
|
6d2a71f37e | ||
|
|
c8f60a12a4 | ||
|
|
11b2d3aa43 | ||
|
|
3d9c81d850 | ||
|
|
8ccb1a24f8 | ||
|
|
48c6796128 | ||
|
|
fc403f920b | ||
|
|
ca19496b5c | ||
|
|
0ae38f8a40 | ||
|
|
ad868ba841 | ||
|
|
f2aa39aa85 | ||
|
|
e76e0fc351 | ||
|
|
a348913888 | ||
|
|
5507006c53 | ||
|
|
994429f098 | ||
|
|
8efdcb9c49 | ||
|
|
db9a40db2b | ||
|
|
25667499e6 | ||
|
|
a728cd2d91 | ||
|
|
3f5f3ef10b | ||
|
|
0d708124c2 | ||
|
|
d31f73df14 | ||
|
|
3fed45438b | ||
|
|
8aa967fa1b | ||
|
|
6eaa3e342c | ||
|
|
6fc9e60f62 | ||
|
|
c39ffcf51c | ||
|
|
6f60a91f4c | ||
|
|
8031df6f28 | ||
|
|
da1e859fda | ||
|
|
8d4d25f1d1 | ||
|
|
6aff27778d | ||
|
|
ca2dcbfec0 | ||
|
|
24b666a382 | ||
|
|
9e34295529 | ||
|
|
10c55d056b | ||
|
|
753ab3bdd7 | ||
|
|
feee8def6f | ||
|
|
c208f12f8c | ||
|
|
dc926bf838 | ||
|
|
55a76b5204 | ||
|
|
53b837e763 | ||
|
|
3a551cdf25 | ||
|
|
c0c7f87dc9 | ||
|
|
ac77fd138b | ||
|
|
30d6e9d67c | ||
|
|
47db4334a1 | ||
|
|
fce97179e1 | ||
|
|
4f16cd2d01 | ||
|
|
fa587691b1 | ||
|
|
e0044658f9 | ||
|
|
53de1ddb36 | ||
|
|
da7b046092 | ||
|
|
d888d83a98 | ||
|
|
430f83e8e9 | ||
|
|
e7acd14faa | ||
|
|
1b00fa74bc | ||
|
|
4d572d8173 | ||
|
|
dbda85d8d9 | ||
|
|
95cb5dd66c | ||
|
|
f64e1c3a6a | ||
|
|
26a686c412 | ||
|
|
2505383f53 | ||
|
|
1a1e9ac6be | ||
|
|
7f20a3179e | ||
|
|
46431f0187 | ||
|
|
1ff8ec78c9 | ||
|
|
7840230c62 | ||
|
|
523db0a005 | ||
|
|
cc906d49ba | ||
|
|
fc23af89d3 | ||
|
|
5d8829ba63 | ||
|
|
31ccd80894 | ||
|
|
bac2234616 | ||
|
|
bd61db76a3 | ||
|
|
bc99e3b992 | ||
|
|
b7314b0813 | ||
|
|
4759bd569f | ||
|
|
b88c28f864 | ||
|
|
774ccb05f8 | ||
|
|
0ac48cba34 | ||
|
|
e36880fe3a | ||
|
|
713cf5de2c | ||
|
|
0fa336411f | ||
|
|
8ebdb09d68 | ||
|
|
40bc53001e | ||
|
|
b1656d1eea | ||
|
|
7aa54bf979 | ||
|
|
231f9bca84 | ||
|
|
e4f8596c19 | ||
|
|
020b7233d0 | ||
|
|
85fc0b3e2f | ||
|
|
5dcc7c14f3 | ||
|
|
7993d27b11 | ||
|
|
1f8c54ce74 | ||
|
|
73a414a34b | ||
|
|
8fa19c4a51 | ||
|
|
0667ae3e15 | ||
|
|
db1d00cd07 | ||
|
|
b27f092bef | ||
|
|
4eaea8e586 | ||
|
|
89cd7d3bbb | ||
|
|
2fff1d8d21 | ||
|
|
850631f00e | ||
|
|
1cea8846cf | ||
|
|
af48a48559 | ||
|
|
655c1762aa | ||
|
|
f706ccfd87 | ||
|
|
71e4c7f117 | ||
|
|
ad6182f4bd | ||
|
|
86bf0f65b0 | ||
|
|
7742f7747d | ||
|
|
eb998c41f2 | ||
|
|
657bd7c97c | ||
|
|
c8286148c1 | ||
|
|
e6ba2cce62 | ||
|
|
6105ff44b4 | ||
|
|
72da77be01 | ||
|
|
4c29b31f1b | ||
|
|
6e640108ed | ||
|
|
157322441d | ||
|
|
61d967e6af | ||
|
|
99f2158e55 | ||
|
|
1cba78cc4e | ||
|
|
1770c1ee11 | ||
|
|
a57fbea0cc | ||
|
|
f0c27c83f1 | ||
|
|
7873ca564c | ||
|
|
5ebc1fec24 | ||
|
|
f2559b7d4d | ||
|
|
1ee92a248e | ||
|
|
8376f98f21 | ||
|
|
810a3e0171 | ||
|
|
2eac9c3795 | ||
|
|
75f2425668 | ||
|
|
2dd1d7e926 | ||
|
|
5bb05a0a51 | ||
|
|
bc2c5b00c6 | ||
|
|
09f5e158dd | ||
|
|
4656471a0a | ||
|
|
69d58a4273 | ||
|
|
53a27677d4 | ||
|
|
f243ad0348 | ||
|
|
b4ce6bbb42 | ||
|
|
fa32937045 | ||
|
|
812e5f3c8e | ||
|
|
e2981e802c | ||
|
|
4ae4705c73 | ||
|
|
fbf4b12713 | ||
|
|
e4ece3e0dc | ||
|
|
9f231421be | ||
| 391827222e | |||
| c1721bd1a1 | |||
| f6630ae241 | |||
| 424cab64a8 | |||
|
|
b4fdbb5e48 | ||
|
|
fbf62ca30d | ||
|
|
05d2096fb8 | ||
|
|
7683af81bc | ||
|
|
bad10b3812 | ||
|
|
9f9522c2ed | ||
|
|
62879c3e52 | ||
|
|
45610f8dd7 | ||
|
|
18102cbd78 | ||
|
|
262d6a1529 | ||
|
|
1124ddaf90 | ||
|
|
981add3872 | ||
|
|
8d4b30140e | ||
|
|
b9c59137a8 | ||
|
|
0b1c7e06ca | ||
|
|
fcaf80cba6 | ||
|
|
6358b9eabb | ||
|
|
70a3fab1ff | ||
|
|
bdf86ace86 | ||
|
|
d57132d1e4 | ||
|
|
a6e87abf99 | ||
|
|
f1771610fe | ||
|
|
a88ea9780f | ||
|
|
bca0866d72 | ||
|
|
cebed93abf | ||
|
|
ee2b2e3569 | ||
|
|
cb8e9ee244 | ||
|
|
486184b16c | ||
|
|
9f9bd1988f | ||
|
|
ba1bfd1bc0 | ||
|
|
e12926e971 | ||
|
|
5b4db0de3b | ||
|
|
70ff55faa6 | ||
|
|
f2bd47d8bc | ||
|
|
e2b9c9e98e | ||
|
|
b0791a594f | ||
|
|
ea5eaa6ed2 | ||
|
|
ec3fd1d585 | ||
|
|
d76de22977 | ||
|
|
c0e3483cc7 | ||
|
|
0994cdf3b6 | ||
|
|
a76fd82262 | ||
|
|
2f919c7d69 | ||
|
|
f12592731b | ||
|
|
4a20e1a25d | ||
|
|
cc7111c3ff | ||
|
|
81972aed62 | ||
|
|
5e2092c6d4 | ||
|
|
d136f8ac91 | ||
|
|
5b23917940 | ||
|
|
2cda0a3798 | ||
|
|
f315ad1ce9 | ||
|
|
49f1c273c2 | ||
|
|
8518476c87 | ||
|
|
a34ed756db | ||
|
|
a48232c4e3 | ||
|
|
ac65e38390 | ||
|
|
eab3a753f5 | ||
|
|
9e5922e121 | ||
|
|
bf57b23776 | ||
|
|
8f4a20fd46 | ||
|
|
b3716da5ac | ||
|
|
2c129fd800 | ||
|
|
6fb18e7a25 | ||
|
|
38d28d6944 | ||
|
|
a187e1a7a2 | ||
|
|
7d7a19c7c2 | ||
|
|
6a2ae2153a | ||
|
|
f50ba1a91e | ||
|
|
b09575dbd6 | ||
|
|
c52ee3d91d | ||
|
|
1ced5c0425 | ||
|
|
e399a5c9b1 | ||
|
|
08dff5fb67 | ||
|
|
912760f2a7 | ||
|
|
4b62fdbf93 | ||
|
|
df593d43a7 | ||
|
|
89b1fba771 | ||
|
|
5505c91ae0 | ||
|
|
cc720ff399 | ||
|
|
29f07f6c56 | ||
|
|
a69e197a1f | ||
|
|
6dddd8eff8 | ||
|
|
51aada8922 | ||
|
|
b47bf40e8f | ||
|
|
48151bf365 | ||
|
|
659950996d | ||
|
|
6e656a4d0a | ||
|
|
bf308dd13d | ||
|
|
982f3b1943 | ||
|
|
7a4ea8cf1b | ||
|
|
7c0482a87c | ||
|
|
101ededd89 | ||
|
|
a8cbc0040e | ||
|
|
ed91bb445b | ||
|
|
f158a222f4 | ||
|
|
46bcad1fca | ||
|
|
5e147afb9f | ||
|
|
b061d4593f | ||
|
|
3fa520b4ef | ||
|
|
2df05b4afd | ||
|
|
e0569a6748 | ||
|
|
2e6ab398d9 | ||
|
|
94b03d2a6b | ||
|
|
f84fe30bb6 | ||
|
|
049ddef531 | ||
|
|
a1a214c742 | ||
|
|
c40a483ffa | ||
|
|
aff99acfae | ||
|
|
628129c08d | ||
|
|
679d34dfdf | ||
|
|
b2f65903ae | ||
|
|
2daefe0382 | ||
|
|
1214a11704 | ||
|
|
e55e0118c2 | ||
|
|
a1ac81e907 | ||
|
|
14ad68bf69 | ||
|
|
e3118c6e99 | ||
|
|
b41aeab8f8 | ||
|
|
1a738b358e | ||
|
|
f4b1e8035b | ||
|
|
324e985247 | ||
|
|
60faacd7d0 | ||
|
|
d4a4d7c346 | ||
|
|
189f8f1d27 | ||
|
|
ed353461da | ||
|
|
e9330d6c62 | ||
|
|
f53e7cc3f6 | ||
|
|
4f4258d532 | ||
|
|
c80910f355 | ||
|
|
12470ab178 | ||
|
|
704e091e9b | ||
|
|
f30f1e5c1f | ||
|
|
0741ce2197 | ||
|
|
fc81bae9b7 | ||
|
|
3fa68e4f96 | ||
|
|
05a767c7be | ||
|
|
8c1e8f6377 | ||
|
|
0fbae8d7da | ||
|
|
cec4b339f5 | ||
|
|
1a7656d8ee | ||
|
|
e173815810 | ||
|
|
620c768e05 | ||
|
|
7740679077 | ||
|
|
1ebc8ebff3 | ||
|
|
fa4fac70d5 | ||
|
|
8c83a18f93 | ||
|
|
a151ecfda0 | ||
|
|
dec0c0649c | ||
|
|
a356f893ac | ||
|
|
e2af4f74c3 | ||
|
|
99022b76e5 | ||
|
|
9dd6dc7523 | ||
|
|
6f588200d7 | ||
|
|
f3dbeae28e | ||
|
|
9f70361298 | ||
|
|
534353ce13 | ||
|
|
3f391a7354 | ||
|
|
0dd0752710 | ||
|
|
710fae4b0e | ||
|
|
de6c57a7ee | ||
|
|
7eee1fcd6a | ||
|
|
1fd2aedf96 | ||
|
|
ffdd7c063b | ||
|
|
a87af264eb | ||
|
|
3640cd2fdd | ||
|
|
f7cf25c4bb | ||
|
|
77dbd0d4c7 | ||
|
|
5a77c83e68 | ||
|
|
0a6763e08c | ||
|
|
b5aa206670 | ||
|
|
dbb4d9b64b | ||
|
|
6775edfd66 | ||
|
|
b06750ed65 | ||
|
|
57879c1891 | ||
|
|
65b55a76f2 | ||
|
|
8562a68306 | ||
|
|
160da46b5a | ||
|
|
ae5f70a739 | ||
|
|
4456e57466 | ||
|
|
24cd090c61 | ||
|
|
4f849f5d5f | ||
|
|
db2e86e84c | ||
|
|
14748ce2b3 | ||
|
|
527509732c | ||
|
|
aa9214477c | ||
|
|
db24ed8b55 | ||
|
|
349d1849d9 | ||
|
|
188be2fa8c | ||
|
|
5c4d7df734 | ||
|
|
37de18c670 | ||
|
|
0001e3844b | ||
|
|
65f171f701 | ||
|
|
c4308c239c | ||
|
|
2b6af3cb37 | ||
|
|
c5a16fbbc1 | ||
|
|
1db83f509b | ||
|
|
2a06304a2c | ||
|
|
7bbe684135 | ||
|
|
a996a88fc4 | ||
|
|
8cf9e7db51 | ||
|
|
ed981c67b7 | ||
|
|
6a77a0a5b9 | ||
|
|
bfb4b4431b | ||
|
|
3de44c0335 | ||
|
|
37afd1aec9 | ||
|
|
b2ac561673 | ||
|
|
c9bf7d9138 | ||
|
|
153a689bdb | ||
|
|
26b88f1a22 | ||
|
|
c66a5d028f | ||
|
|
8441c95a5c | ||
|
|
e0e32b0199 | ||
|
|
2bb76564e9 | ||
|
|
86ec25d4de | ||
|
|
ed9929963c | ||
|
|
36f50118ac | ||
|
|
72ddf27504 | ||
|
|
9bd36931ff | ||
|
|
056ef7a3d5 | ||
|
|
e483945d05 | ||
|
|
7a9c637e03 | ||
|
|
09ce543ea6 | ||
|
|
0ef24ebc3f | ||
|
|
a4419d3af6 | ||
|
|
935983c02a | ||
|
|
bd54ce5017 | ||
|
|
af6ed78b8e | ||
|
|
f72438996d | ||
|
|
9db2a55a5a | ||
|
|
950d90badb | ||
|
|
1864d180af | ||
|
|
0e82e98382 | ||
|
|
46023d35dc | ||
|
|
90d2e0a40b | ||
|
|
5f884e0796 | ||
|
|
16477052e2 | ||
|
|
66a05603f7 | ||
|
|
fe2a883386 | ||
|
|
ca7fdaa125 | ||
|
|
1283ec2008 | ||
|
|
d1f78f9048 | ||
|
|
45f43bfade | ||
|
|
4793ab4bc9 | ||
|
|
88b174dea8 | ||
|
|
06ca382bd7 | ||
|
|
b09175a8db | ||
|
|
677f68b08d | ||
|
|
8058b8dba4 | ||
|
|
4bc26f13c1 | ||
|
|
e6cf77e724 | ||
|
|
f8737c112e | ||
|
|
74b72ed9d4 | ||
|
|
4950391201 | ||
|
|
64a38d6e45 | ||
|
|
e2d8ee53f8 | ||
|
|
0204ff8dd5 | ||
|
|
4630ee6e93 | ||
|
|
c4f2abf143 | ||
|
|
e7444a0194 | ||
|
|
ffed7305ed | ||
|
|
334054bcd4 | ||
|
|
5bb98eb5b2 | ||
|
|
e842bd882a | ||
|
|
74bfb46f73 | ||
|
|
cd45bf4b70 | ||
|
|
51600bbcb0 | ||
|
|
8811a9bcaa | ||
|
|
4521f8a774 | ||
|
|
5b427783f8 | ||
|
|
9508983b15 | ||
|
|
fd09912cd7 | ||
|
|
01cc8584b6 | ||
|
|
c0b104e7c8 | ||
|
|
9a82021a82 | ||
|
|
1fb54a0b0f | ||
|
|
aa5ea61638 | ||
|
|
a59ec0258b | ||
|
|
b403f5cf71 | ||
|
|
0ac6a62b86 | ||
|
|
ed3743d3b6 | ||
|
|
3468cba000 | ||
|
|
5a4cdc5354 | ||
|
|
af4e19a57e | ||
|
|
26bb743679 | ||
|
|
96cc726e22 | ||
|
|
f4b00b01d0 | ||
|
|
127b2ca86d | ||
|
|
4b8b93e1b8 | ||
|
|
3aea412fe9 | ||
|
|
2aef96ad4f | ||
|
|
ec0a77230c | ||
|
|
b35e4bddd0 | ||
|
|
aa32beb341 | ||
|
|
efafe89b42 | ||
|
|
5ef2e49d9c | ||
|
|
1cfbf61a30 | ||
|
|
2bb5494b84 | ||
|
|
e8a41ccb47 | ||
|
|
7184f7f635 | ||
|
|
cc5727b7fa | ||
|
|
7f3d6ef6a5 | ||
|
|
44ce68097b | ||
|
|
d976cc6c19 | ||
|
|
d19d7d2a43 | ||
|
|
9bfaa02f97 | ||
|
|
2d9396b245 | ||
|
|
56e0da64ee | ||
|
|
997e9213f2 | ||
|
|
366569a23b | ||
|
|
36d5747b4f | ||
|
|
ea026c726c | ||
|
|
1e71e32c74 | ||
|
|
ed729bbd4f | ||
|
|
1a925221b7 | ||
|
|
af7b4db062 | ||
|
|
cfefe35e3f | ||
|
|
8d7845a44d | ||
|
|
3a15e6e5e9 | ||
|
|
3b8a51e855 | ||
|
|
f2cb34cc35 | ||
|
|
723ce62a34 | ||
|
|
2c9a2ea021 | ||
|
|
2be008d6d1 | ||
|
|
7fb51bdd0a | ||
|
|
abdbe3694f | ||
|
|
bd87e5012f | ||
|
|
55e18f2364 | ||
|
|
655e84072d | ||
|
|
ab5409d5b6 | ||
|
|
5a5da6486c | ||
|
|
7e99252429 | ||
|
|
10e0dcef8b | ||
|
|
80c8433aad | ||
|
|
5b0ae372f8 | ||
|
|
b5a1b529ab | ||
|
|
af821dcd9a | ||
|
|
10d3c81520 | ||
|
|
e3811b453a | ||
|
|
ca0fb05baa | ||
|
|
2b4b01bf6e | ||
|
|
3a1244bddc | ||
|
|
b8fd922b19 | ||
|
|
03f856025e | ||
|
|
45f04d24c3 | ||
|
|
2435d610c7 | ||
|
|
06572bcd14 | ||
|
|
3fab79afc0 | ||
|
|
2b7dd3fed5 | ||
|
|
d65efed032 | ||
|
|
c6896ea936 | ||
|
|
b7a41bddec | ||
|
|
6fd80c3737 | ||
|
|
0aa690b3b1 | ||
|
|
6d5bcfc6ed | ||
|
|
b60de69fa5 | ||
|
|
d991b57d08 | ||
|
|
1133d7fcba | ||
|
|
6fbf0383bb | ||
|
|
0296230219 | ||
|
|
dedfbba27a | ||
|
|
dc5441da07 | ||
|
|
585648b7f3 | ||
|
|
cd0ee1c26d | ||
|
|
d7a7be5af4 | ||
|
|
13e4267c11 | ||
|
|
15bc47e3e8 | ||
|
|
1a4380dcd7 | ||
|
|
5ace34b5cd | ||
|
|
f010c0f1fa | ||
|
|
2c721ad5dd | ||
|
|
8a7c2ce2c2 | ||
|
|
b2a7b813de | ||
|
|
e85cf04b99 | ||
|
|
ab6fa48172 | ||
|
|
c81905882f | ||
|
|
f0d31c1114 | ||
|
|
497b255216 | ||
|
|
d96286d771 | ||
|
|
2441647d75 | ||
|
|
77b780d631 | ||
|
|
cdd03bf3d4 | ||
|
|
e786c35f7d | ||
|
|
e83399acb1 | ||
|
|
ffea9e3210 | ||
|
|
f71a3a1ce3 | ||
|
|
a5ccc23604 | ||
|
|
1ed4c8a094 | ||
|
|
4a74549c1b | ||
|
|
a499cfb98f | ||
|
|
01038c8296 | ||
|
|
7407f79b3d | ||
|
|
a845dd1976 | ||
|
|
9d7549da70 | ||
|
|
f5cc61384f | ||
|
|
844778d36a | ||
|
|
871b0a274e | ||
|
|
737740a3ca | ||
|
|
ae8d600600 | ||
|
|
2f1691bfb0 | ||
|
|
a3d25f91c8 | ||
|
|
ff84b50817 | ||
|
|
0be8242284 | ||
|
|
e25a6259ea | ||
|
|
1578f82efb | ||
|
|
b5f75f0c5e | ||
|
|
601caab3b6 | ||
|
|
6d3092f440 | ||
|
|
ef27937c7a | ||
|
|
f4a7b79c4f | ||
|
|
2531776a48 | ||
|
|
ced80419aa | ||
|
|
ad2816f4e8 | ||
|
|
3528b8c674 | ||
|
|
80c83c626d | ||
|
|
6a355f2aea | ||
|
|
7d94e4a53a | ||
|
|
823c04742e | ||
|
|
043fb1abd1 | ||
|
|
f01e85b690 | ||
|
|
7eb1298847 | ||
|
|
a5480c9a96 | ||
|
|
f948600e86 | ||
|
|
420182b9bf | ||
|
|
8c430ce1a6 | ||
|
|
d3717dbe19 | ||
|
|
caa83d769f | ||
|
|
365f87828f | ||
|
|
f780887866 | ||
|
|
43627d4bb8 | ||
|
|
5d7b276cd1 | ||
|
|
23597eb997 | ||
|
|
b6948367f8 | ||
|
|
db6dd5abee | ||
|
|
702eb9e466 | ||
|
|
3c99006226 | ||
|
|
aaaf5683a5 | ||
|
|
92aa2236c0 | ||
|
|
d2592f72d6 | ||
|
|
27120d6cc9 | ||
|
|
a2669d35c3 | ||
|
|
574164081b | ||
|
|
00c2f8dcd8 | ||
|
|
0202bf60e5 | ||
|
|
16436a171b | ||
|
|
4cf4e0eabd | ||
|
|
5edb98dfb4 | ||
|
|
899bf22e15 | ||
|
|
9a33167a6c | ||
|
|
2dc068aa21 | ||
|
|
0f2aa4d2e1 | ||
|
|
a699f5c7bc | ||
|
|
ab807de3c0 | ||
|
|
68514bcb36 | ||
|
|
422bf8da59 | ||
|
|
de92dc93dd | ||
|
|
5a91b143b6 | ||
|
|
c745e85706 | ||
|
|
0f698e0c50 | ||
|
|
cd16d77bf0 | ||
|
|
fdbdd0ef4c | ||
|
|
71485f4a82 | ||
|
|
013056a6e5 | ||
|
|
263498fbdc | ||
|
|
f46ac2c007 | ||
|
|
c23841b6db | ||
|
|
18b1b5fca5 | ||
|
|
6707ac2697 | ||
|
|
d85a2fc8ce | ||
|
|
a8997e8f17 | ||
|
|
c8a22d9537 | ||
|
|
910669f786 | ||
|
|
89312c6796 | ||
|
|
a6aa96fdb0 | ||
|
|
7deb0b06af | ||
|
|
784f3771b3 | ||
|
|
80316824f7 | ||
|
|
cbaebb7b7c | ||
|
|
97d3764b6e | ||
|
|
002f8117cb | ||
|
|
0dfdb12431 | ||
|
|
d77e898929 | ||
|
|
4c937be884 | ||
|
|
c25cce4398 | ||
|
|
58c422285a | ||
|
|
f2f22f35e8 | ||
|
|
15867a7807 | ||
|
|
22e3161a9b | ||
|
|
b0c8203b24 | ||
|
|
e2c404d8bb | ||
|
|
5ee1731c92 | ||
|
|
06e8d3b660 | ||
|
|
f09709aedb | ||
|
|
598d5decac | ||
|
|
7832a6053e | ||
|
|
588748230e | ||
|
|
8668165c72 | ||
|
|
a967332f89 | ||
|
|
6719d242bd | ||
|
|
3565185808 | ||
|
|
ce51262197 | ||
|
|
992a786c2b | ||
|
|
038df95aa0 | ||
|
|
4ebd660de2 | ||
|
|
a9ea41a488 | ||
|
|
196594b490 | ||
|
|
1516d5af10 | ||
|
|
ffcd1595a9 | ||
|
|
42e5c6e111 | ||
|
|
8a9651dc50 | ||
|
|
4be2f4f2d9 | ||
|
|
369bf7a235 | ||
|
|
136545b404 | ||
|
|
73ea17ae0f | ||
|
|
23010acef4 | ||
|
|
4f74962ce2 | ||
|
|
7f978c7845 | ||
|
|
731fd46ea2 | ||
|
|
859759d691 | ||
|
|
5e9567158f | ||
|
|
51d244f3aa | ||
|
|
4c5a07edd5 | ||
|
|
8113a754a1 | ||
|
|
3cd40c6195 | ||
|
|
56cfb2ce06 | ||
|
|
72087823ac | ||
|
|
bcf7866fe2 | ||
|
|
b64772e484 | ||
|
|
088d665942 | ||
|
|
7d6c10befb | ||
|
|
f88e3d04b8 | ||
|
|
f57676a22b | ||
|
|
8618cb62e4 | ||
|
|
c31c88ed1f | ||
|
|
6022acd21f | ||
|
|
b3071b9272 | ||
|
|
ec6a6d6653 | ||
|
|
52f552b4de | ||
|
|
2643d3396b | ||
|
|
62d59a09cf | ||
|
|
a68ff8a4f0 | ||
|
|
93d4bfcd7a | ||
|
|
5fb80c1a7b | ||
|
|
04b38444ce | ||
|
|
ca3df02002 | ||
|
|
d952c33fab | ||
|
|
93bc1cd5af | ||
|
|
0e5b370ee8 | ||
|
|
f4fd4e28c9 | ||
|
|
ec8433eb45 | ||
|
|
0d4a40e9bb | ||
|
|
c3668f4179 | ||
|
|
3adb7ecb1c | ||
|
|
aa5b84a214 | ||
|
|
3d83fccbf1 | ||
|
|
4c4255be6b | ||
|
|
ed6054b082 | ||
|
|
f60f7a4dc1 | ||
|
|
998dc95cb2 | ||
|
|
12f5d7b846 | ||
|
|
906ae0a43e | ||
|
|
485b774876 | ||
|
|
3121cf5b75 | ||
|
|
ce7570dae2 | ||
|
|
b5ea0dfbc7 | ||
|
|
dd0f8f4772 | ||
|
|
9d0ab34274 | ||
|
|
e6038be6f7 | ||
|
|
bd2153a0ed | ||
|
|
e526deac20 | ||
|
|
b65f165dcf | ||
|
|
f9fbe5adc2 | ||
|
|
d1e73571a1 | ||
|
|
1047058676 | ||
|
|
29a1e77da8 | ||
|
|
290547e482 | ||
|
|
3df45ca20f | ||
|
|
cc06258484 | ||
|
|
2c262d0655 | ||
|
|
1875e1e158 | ||
|
|
1c95967b31 | ||
|
|
2ae98929de | ||
|
|
6a1dd38cbb | ||
|
|
c458a77d27 | ||
|
|
352c23f389 | ||
|
|
6c5a1dc2e1 | ||
|
|
004ff1e91d | ||
|
|
f7de4c567b | ||
|
|
b2ad1010ac | ||
|
|
660e164ff8 | ||
|
|
eb6dc80b50 | ||
|
|
a9882cc96a | ||
|
|
fb5a2ce178 | ||
|
|
d441b31dc7 | ||
|
|
d4239fe7e0 | ||
|
|
6a98999105 | ||
|
|
5caa1fe7d4 | ||
|
|
ef33bdb65e | ||
|
|
14a463382b | ||
|
|
1ad79874c8 | ||
|
|
1a61a58d28 | ||
|
|
3e3c973679 | ||
|
|
9dede84d20 | ||
|
|
e78b11cf62 | ||
|
|
341ca5a330 | ||
|
|
e5d8c02def | ||
|
|
947aa08c42 | ||
|
|
9a04d436bd | ||
|
|
be0754f568 | ||
|
|
93c4bbc0d1 | ||
|
|
b98535810b | ||
|
|
393cf8da1f | ||
|
|
ea2846973a | ||
|
|
3398c1cbfe | ||
|
|
39c79ea68e | ||
|
|
66900f4a32 | ||
|
|
df71dd14f7 | ||
|
|
8113ca3069 | ||
|
|
d468e675c2 | ||
|
|
1e84cedf82 | ||
|
|
7f4087e3de | ||
|
|
facd3c8956 | ||
|
|
3e50deecb7 | ||
|
|
628c504f84 | ||
|
|
e1ada78c1f | ||
|
|
09fa1e365c | ||
|
|
28b6b03d06 | ||
|
|
a4395b62ff | ||
|
|
4511acf273 | ||
|
|
fde53a2d83 | ||
|
|
f6cd04fdb8 | ||
|
|
a5eede6c7a | ||
|
|
2e83817c83 | ||
|
|
82aea40ae4 | ||
|
|
1b289eae24 | ||
|
|
81420b2c88 | ||
|
|
775731b745 | ||
|
|
489e7d4a67 | ||
|
|
b6508764d8 | ||
|
|
6dedd87305 | ||
|
|
89fa29b310 | ||
|
|
be5df9c22a | ||
|
|
db17739716 | ||
|
|
1439362a5e | ||
|
|
65c1b04772 | ||
|
|
83d30ebdc4 | ||
|
|
b7928a5255 | ||
|
|
a83ff9253d | ||
|
|
8cdcdaf6d9 | ||
|
|
d208edf153 | ||
|
|
8ada4765b1 | ||
|
|
9d3a808c24 | ||
|
|
828eb80266 | ||
|
|
0639f6c1d1 | ||
|
|
49b971280f | ||
|
|
3682a1010d | ||
|
|
f8b58866dc | ||
|
|
5d8d815d84 | ||
|
|
67743c7597 | ||
|
|
b33cc90447 | ||
|
|
78177f7890 | ||
|
|
3c417bfa99 | ||
|
|
f2c8d80ff8 | ||
|
|
8c1eb1e19f | ||
|
|
eb7188e81b | ||
|
|
f352fc5c67 | ||
|
|
ac313722f9 | ||
|
|
64c7f1962c | ||
|
|
c83f994b21 | ||
|
|
10f38a72f7 | ||
|
|
da35434f58 | ||
|
|
c79409e094 | ||
|
|
355ce00968 | ||
|
|
56c832f3ba | ||
|
|
40abb2720e | ||
|
|
9b6051afee | ||
|
|
dcf6f26792 | ||
|
|
85734c1146 | ||
|
|
c3fb8fcb6e | ||
|
|
013bbc1638 | ||
|
|
b0669a3b60 | ||
|
|
6f5da1ce7c | ||
|
|
3351732a2f | ||
|
|
d4d4034ecd | ||
|
|
b45e2742c3 | ||
|
|
42eba290e4 | ||
|
|
32d1e08b57 | ||
|
|
7b7d19da7c | ||
|
|
f78e400918 | ||
|
|
be4e9bf7e9 | ||
|
|
48f2079f88 | ||
|
|
36ad1ceef2 | ||
|
|
1f2e2148d5 | ||
|
|
76f3d345f9 | ||
|
|
f2c854b53a | ||
|
|
e2d336d90b | ||
|
|
c74065ff26 | ||
|
|
b6d97dc5d5 | ||
|
|
1c1c26948a | ||
|
|
d954e3ffb7 | ||
|
|
d9759a95eb | ||
|
|
d196402dd0 | ||
|
|
0f2b5531de | ||
|
|
26e4398905 | ||
|
|
06995f22fe | ||
|
|
caa2073d48 | ||
|
|
d9466fe4bc | ||
|
|
72e623a3a7 | ||
|
|
69bc06685f | ||
|
|
bf175984f3 | ||
|
|
4d4a7bfd0d | ||
|
|
044cee30a5 | ||
|
|
3c0c5aed60 | ||
|
|
e194971727 | ||
|
|
bbe85def23 | ||
|
|
9c2d53f2ae | ||
|
|
1cf36e2156 | ||
|
|
ce96e309af | ||
|
|
9ea7dc8b3c | ||
|
|
282242efd2 | ||
|
|
351ba22d64 | ||
|
|
2916625747 | ||
|
|
744dbeb7a3 | ||
|
|
fbb3c309ee | ||
|
|
473c265bac | ||
|
|
01b06c2c3c | ||
|
|
8b1f95c743 | ||
|
|
ff5dbec579 | ||
|
|
9620c5a98f | ||
|
|
7e849a2e95 | ||
|
|
229aed306e | ||
|
|
d718969cbd | ||
|
|
8593f4c3a9 | ||
|
|
d4f71d5026 | ||
|
|
a683a5f206 | ||
|
|
7917cc3eb5 | ||
|
|
6cd7ca755e | ||
|
|
fb7dfdc800 | ||
|
|
e096af320f | ||
|
|
65bff8f511 | ||
|
|
ca19d8a842 | ||
|
|
cde08c64ce | ||
|
|
3d6d7b7cb9 | ||
|
|
a56ef6f398 | ||
|
|
5a38b6614d | ||
|
|
3d5c44e8aa | ||
|
|
46a68bd5e7 | ||
|
|
c77ded51a9 | ||
|
|
59bd9fdc22 | ||
|
|
b81941394c | ||
|
|
197d5ca1f2 | ||
|
|
97eb986fa6 | ||
|
|
aba3110e31 | ||
|
|
51c27be236 | ||
|
|
592255284f | ||
|
|
82fd41dd4c | ||
|
|
4ae8df9652 | ||
|
|
759b19e444 | ||
|
|
36f705e46f | ||
|
|
1f0347682e | ||
|
|
1aee385679 | ||
|
|
5dd8191692 | ||
|
|
b6422f9b80 | ||
|
|
a6c2c9c92f | ||
|
|
247c573a6e | ||
|
|
aa435d6e94 | ||
|
|
f6858c221b | ||
|
|
437aa4510b | ||
|
|
430572fb32 | ||
|
|
9f0b755d6f | ||
|
|
66acb55a57 | ||
|
|
f936d4c36e | ||
|
|
c3ddb8df56 | ||
|
|
81ce920e69 | ||
|
|
3cb875d139 | ||
|
|
fdb217d5c6 | ||
|
|
085cae3b5f | ||
|
|
e2f99a1554 | ||
|
|
aee0c27da7 | ||
|
|
accbf4ad8b | ||
|
|
0ac1901f6b | ||
|
|
c0a0deea78 | ||
|
|
e3f099441c | ||
|
|
840dd25cd1 | ||
|
|
a493969f9b | ||
|
|
cca42a10a1 | ||
|
|
2f4aa98c3c | ||
|
|
175cb9588c | ||
|
|
a8976e7559 | ||
|
|
b663528fb0 | ||
|
|
1a2ad55677 | ||
|
|
513d2a88c0 | ||
|
|
57ef4c0396 | ||
|
|
e9599ca2f4 | ||
|
|
2fc6dbc222 | ||
|
|
225933c442 | ||
|
|
36e2f048d7 | ||
|
|
0e158bce59 | ||
|
|
202201fd31 | ||
|
|
4f8c928f44 | ||
|
|
ada062cf00 | ||
|
|
32dc12912a | ||
|
|
671d21adf4 | ||
|
|
4e3f8e4b67 | ||
|
|
586bb62073 | ||
|
|
151bf83ab1 | ||
|
|
8c618edb5a | ||
|
|
6d6b0cf8c9 | ||
|
|
c610608890 | ||
|
|
28da61daab | ||
|
|
cbfc90f60b | ||
|
|
fec92959d4 | ||
|
|
bb00d81eba | ||
|
|
bb55644c06 | ||
|
|
16215847cd | ||
|
|
8ee9aed817 | ||
|
|
515c6402b9 | ||
|
|
d1b94ec203 | ||
|
|
a037d9167e | ||
|
|
3054d33e62 | ||
|
|
6651e641e1 | ||
|
|
35f873a342 | ||
|
|
2d03ff38a1 | ||
|
|
44a3db417b | ||
|
|
f9ca702a12 | ||
|
|
4073ff38eb | ||
|
|
f0e2c9f1b6 | ||
|
|
cf040f51b5 | ||
|
|
dcf919fb36 | ||
|
|
aa19b81a68 | ||
|
|
db8d77365c | ||
|
|
280eaea84a | ||
|
|
340ef46469 | ||
|
|
a5f8651941 | ||
|
|
8a18ee548e | ||
|
|
ef791aa22a | ||
|
|
4bdf2e1cc0 | ||
|
|
ffa0ca9379 | ||
|
|
d3b3d46fc1 | ||
|
|
7e7dd8efa9 | ||
|
|
b4506f1133 | ||
|
|
7350c79113 | ||
|
|
5f7b60d3f4 | ||
|
|
266495a611 | ||
|
|
f5b4a7e77b | ||
|
|
51ed0f6487 | ||
|
|
bd70def18a | ||
|
|
93a9cf3b31 | ||
|
|
751287999f | ||
|
|
d433850cbf | ||
|
|
c93e70e2dc | ||
|
|
a129dd989a | ||
|
|
40999c3ff4 | ||
|
|
18a01d672c | ||
|
|
3648f99920 | ||
|
|
e823412f56 | ||
|
|
d090f446c9 | ||
|
|
19985d1742 | ||
|
|
ab52251116 | ||
|
|
acb6c0187c | ||
|
|
90f9c24720 | ||
|
|
415bec4646 | ||
|
|
5559f20511 | ||
|
|
6ea3399829 | ||
|
|
9e3df97737 | ||
|
|
0ac40a4d77 | ||
|
|
24bf5e8102 | ||
|
|
56eebb03c7 | ||
|
|
1cd4d6d0df | ||
|
|
22d4a8232a | ||
|
|
48076c25bf | ||
|
|
478a308e73 | ||
|
|
8ca2f0a49f | ||
|
|
4a35d7364b | ||
|
|
8b2e55a969 | ||
|
|
9b14b70687 | ||
|
|
1c01087eda | ||
|
|
5137e5a301 | ||
|
|
0fea8365b8 | ||
|
|
116a615b84 | ||
|
|
ef272dd6a8 | ||
|
|
4462ae9cae | ||
|
|
66ffad0b4e | ||
|
|
81e0fc940c | ||
|
|
3e8794db1b | ||
|
|
617622d4d8 | ||
|
|
a4240f972b | ||
|
|
42feff8882 | ||
|
|
70edd9686f | ||
|
|
d298cb2e1c | ||
|
|
3fef3f2dc3 | ||
|
|
c85f9d6ae4 | ||
|
|
85e7ac7cd7 | ||
|
|
5c7db61a7e | ||
|
|
497f9ca0b1 | ||
|
|
0c50f7a322 | ||
|
|
740bcbd12c | ||
|
|
c92be4f270 | ||
|
|
e2a7271ab2 | ||
|
|
64766713fa | ||
|
|
59bba83b1d | ||
|
|
8ac1217165 | ||
|
|
5443629ec5 | ||
|
|
8f9b41cb62 | ||
|
|
7df5eba775 | ||
|
|
e29c6ac593 | ||
|
|
4f3190bf73 | ||
|
|
f0878fccb5 | ||
|
|
b0e121a53f | ||
|
|
eda48ab0e6 | ||
|
|
6a1014d8c1 | ||
|
|
e0f87315bc | ||
|
|
f2c5dca5e7 | ||
|
|
3c435a804f | ||
|
|
7ee6775251 | ||
|
|
98adc2ecc1 | ||
|
|
45afbbdac6 | ||
|
|
a18260747b | ||
|
|
0c80e28754 | ||
|
|
719bc374ac | ||
|
|
d7a290c595 | ||
|
|
4b2bd33baa | ||
|
|
efbe4d697c | ||
|
|
a8662bdb8b | ||
|
|
d822225465 | ||
|
|
c6373ef582 | ||
|
|
5a2af6d0f9 | ||
|
|
52000d6a41 | ||
|
|
befa13eaf2 | ||
|
|
4ac68a81a3 | ||
|
|
71e472f330 | ||
|
|
ada8809ec0 | ||
|
|
25ea518266 | ||
|
|
f3720c3b94 | ||
|
|
b942a84b15 | ||
|
|
574ca90229 | ||
|
|
5610a14e49 | ||
|
|
c5bc62e6df | ||
|
|
e9f6a85cad | ||
|
|
8921011b27 | ||
|
|
90ef3f6c94 | ||
|
|
e84c75f4a8 | ||
|
|
68e20cd860 | ||
|
|
76bdcea4b1 | ||
|
|
f758cb7e6e | ||
|
|
1108477810 | ||
|
|
deb6a9e51c | ||
|
|
9660f20b87 | ||
|
|
4d26468ede | ||
|
|
125a0979d5 | ||
|
|
98bdfd3dbe | ||
|
|
ea72880e74 | ||
|
|
17fec7d6e1 | ||
|
|
d9de64604e | ||
|
|
6275ab23ff | ||
|
|
8c0271643d | ||
|
|
c3f041dc87 | ||
|
|
80e5e84341 | ||
|
|
938eee80a9 | ||
|
|
7abc2289de | ||
|
|
bb79b9ed74 | ||
|
|
1e89a8625c | ||
|
|
90b0f04b3c | ||
|
|
0f019cd9b6 | ||
|
|
1209739398 | ||
|
|
b99db2b353 | ||
|
|
f057688e7d | ||
|
|
12ae2d0c76 | ||
|
|
9d91d5a127 | ||
|
|
6015f0887a | ||
|
|
d4c473d7b3 | ||
|
|
2f3978deed | ||
|
|
34af53a15b | ||
|
|
a4eb3d7a0b | ||
|
|
5b46dd7293 | ||
|
|
4b17847ea5 | ||
|
|
acbe000f97 | ||
|
|
599071b68b | ||
|
|
2bacee919d | ||
|
|
50d35e4196 | ||
|
|
e321998b85 | ||
|
|
340c02b2af | ||
|
|
69a295fe57 | ||
|
|
64830aae9f | ||
|
|
8969b1273f | ||
|
|
475faf7943 | ||
|
|
45b1d405a6 | ||
|
|
f60c8078e4 | ||
|
|
6701e49f9a | ||
|
|
0efb3c3284 | ||
|
|
aaf3cdfdac | ||
|
|
e00c261777 | ||
|
|
1eafed755d | ||
|
|
7f6a08ae50 | ||
|
|
9901816fb9 | ||
|
|
3cbe6c1e95 | ||
|
|
679c99274e | ||
|
|
b6fa0f1ff6 | ||
|
|
9ff64b95e1 | ||
|
|
3a9885abd8 | ||
|
|
503210942c | ||
|
|
0178e015e3 | ||
|
|
7604992c35 | ||
|
|
ee9e551788 | ||
|
|
22063248ca | ||
|
|
e4ce18b35c | ||
|
|
532890674e | ||
|
|
791a3b67e6 | ||
|
|
84b560ef29 | ||
|
|
03f081f3f4 | ||
|
|
08213ae86e | ||
|
|
73abc511a8 | ||
|
|
1c943cc259 | ||
|
|
af62a89a6b | ||
|
|
af7ca5b897 | ||
|
|
27356ef747 | ||
|
|
b27f80ef87 | ||
|
|
b4aa73fc64 | ||
|
|
bbf444572b | ||
|
|
b3706574de | ||
|
|
83062f8bfb | ||
|
|
1d7fcfdaa1 | ||
|
|
58ab12b4cb | ||
|
|
28c649629f | ||
|
|
5ec190225d | ||
|
|
0e0d404997 | ||
|
|
3f16b908ca | ||
|
|
54549b261c | ||
|
|
8ce07ced9e | ||
|
|
37a5144e8f | ||
|
|
59fed7dda8 | ||
|
|
0a85a2868c | ||
|
|
0d493b3250 | ||
|
|
74530d4b1e | ||
|
|
7548c52e21 | ||
|
|
fa49844c64 | ||
|
|
3508ae1e0a | ||
|
|
f013ee64a2 | ||
|
|
af3da7ca6e | ||
|
|
cb728f65b3 | ||
|
|
af6af190cc | ||
|
|
cbcc8455ca | ||
|
|
af35fb79fe | ||
|
|
0515aaa946 | ||
|
|
1d00330e7a | ||
|
|
3fa6dcea16 | ||
|
|
51425cac4a | ||
|
|
7ce61a5d2b | ||
|
|
5e5caa979b | ||
|
|
a719518f8f | ||
|
|
d0e5aef443 | ||
|
|
b82a811c33 | ||
|
|
3356dcf8f7 | ||
|
|
8f1cc26537 | ||
|
|
27dafa83ac | ||
|
|
c1be1f329f | ||
|
|
c757cef549 | ||
|
|
da9e4c026c | ||
|
|
5821d67e69 | ||
|
|
ed14f6d13f | ||
|
|
35bdd8b4ef | ||
|
|
9cf2d30e77 | ||
|
|
a2140a3b7b | ||
|
|
900d026bcb | ||
|
|
68604ec15a | ||
|
|
f7c0ebb8d3 | ||
|
|
8be5d0c72d | ||
|
|
15c8b724e6 | ||
|
|
d6949200f9 | ||
|
|
1c2abe794a | ||
|
|
0bcf393586 | ||
|
|
bc0573918f | ||
|
|
175675ad99 | ||
|
|
b00c2afc46 | ||
|
|
c125a7fe07 | ||
|
|
a42ab32436 | ||
|
|
797a64976e | ||
|
|
ac377a8b68 | ||
|
|
842b7b1402 | ||
|
|
d449396ad5 | ||
|
|
532a87d064 | ||
|
|
e1cdc7b387 | ||
|
|
d9d917e267 | ||
|
|
8048788042 | ||
|
|
790fc88e47 | ||
|
|
7f970d489f | ||
|
|
9d85ca07f4 | ||
|
|
d75e3acdf3 | ||
|
|
694cda0e99 | ||
|
|
94f134c6a7 | ||
|
|
83f329d93c | ||
|
|
de49387fca | ||
|
|
e5567f2f46 | ||
|
|
80f545f3d5 | ||
|
|
06f0cc70b8 | ||
|
|
cf6b7544b0 | ||
|
|
d511c6334a | ||
|
|
0224fd6d54 | ||
|
|
e95ae8dbcf | ||
|
|
5fbd64da71 | ||
|
|
b282a2a621 | ||
|
|
9a7a534051 | ||
|
|
52fd030b6e | ||
|
|
dfe530a764 | ||
|
|
b079956075 | ||
|
|
e30037c4d1 | ||
|
|
eda7be627c | ||
|
|
af0a649656 | ||
|
|
8b6a3c4236 | ||
|
|
0988a92d8a | ||
|
|
9cf67764b7 | ||
|
|
6c4e1d1c41 | ||
|
|
b1cd1ea8b3 | ||
|
|
5169ed494c | ||
|
|
824211c31b | ||
|
|
be3dd83bc7 | ||
|
|
c2911c1e48 | ||
|
|
9a66c5c07d | ||
|
|
34d393b986 | ||
|
|
d4da02318d | ||
|
|
47162af6d5 | ||
|
|
8cd6a72dd3 | ||
|
|
ba0a183b6f | ||
|
|
73781c7edb | ||
|
|
6d99852c81 | ||
|
|
9325c726fd | ||
|
|
947bb8530e | ||
|
|
2b32f316ee | ||
|
|
4b1f23a189 | ||
|
|
71d220e7a4 | ||
|
|
747d0d0d17 | ||
|
|
5c72e6d335 | ||
|
|
e1ac2b0e10 | ||
|
|
0ba94f3bc9 | ||
|
|
ba0bfafcd5 | ||
|
|
81adb80b7e | ||
|
|
cb238ef170 | ||
|
|
b9b921de82 | ||
|
|
d10e31c278 | ||
|
|
fd641d77c7 | ||
|
|
2aa9710dd1 | ||
|
|
4afb2ef2b8 | ||
|
|
a54e6e7c4b | ||
|
|
7af26a356f | ||
|
|
2f66165f8c | ||
|
|
e86ce8fc38 | ||
|
|
9b48c65129 | ||
|
|
434cd133df | ||
|
|
aa91e4cdee | ||
|
|
d57c1e7ff0 | ||
|
|
13e97703e5 | ||
|
|
c597b293b8 | ||
|
|
6620d64ce7 | ||
|
|
2ae120c878 | ||
|
|
7a25035fb1 | ||
|
|
bf4052b550 | ||
|
|
5ca5ad2cee | ||
|
|
2848f1e13c | ||
|
|
f7895823cb | ||
|
|
b841c5c5e5 | ||
|
|
a7952a4633 | ||
|
|
d047d26dbf | ||
|
|
ddedc1640f | ||
|
|
021d7e5efc | ||
|
|
332e528012 | ||
|
|
a0155da06b | ||
|
|
d58d22adbe | ||
|
|
653352bff0 | ||
|
|
4f5b33d8df | ||
|
|
7e7d83ac36 | ||
|
|
0de5c043bb | ||
|
|
ec2769ea3c | ||
|
|
3f742f5f8e | ||
|
|
50849101d4 | ||
|
|
1ccf4a74c9 | ||
|
|
7cd4967963 | ||
|
|
21e2700da5 | ||
|
|
395a68ad80 | ||
|
|
b7f0132675 | ||
|
|
4ac827b9e8 | ||
|
|
d96963862e | ||
|
|
53217b061d | ||
|
|
4770daa7c6 | ||
|
|
2b709ad094 | ||
|
|
378b81b13b | ||
|
|
56ee72214f | ||
|
|
2e7c3167f5 | ||
|
|
a2fb728d3b | ||
|
|
be1c936e90 | ||
|
|
af8037ab03 | ||
|
|
3b8dc98226 | ||
|
|
b411b4e61b | ||
|
|
436eb30490 | ||
|
|
09b8087787 | ||
|
|
4ac4c69820 | ||
|
|
3ebc5c0865 | ||
|
|
7b94f8f105 | ||
|
|
ec994b3e97 | ||
|
|
86ae0182fd | ||
|
|
bfa891f0ca | ||
|
|
ef843cac63 | ||
|
|
78d68d0a4f | ||
|
|
4bceba777d | ||
|
|
2e537b1e5e | ||
|
|
df8463b625 | ||
|
|
f40371e0cf | ||
|
|
a565e7aed6 | ||
|
|
c948361090 | ||
|
|
4cf2b74a01 | ||
|
|
de9c8362ac | ||
|
|
b5bb5d35e7 | ||
|
|
d910cfa919 | ||
|
|
5857e3d5c6 | ||
|
|
25daa343c6 | ||
|
|
85224c8f0c | ||
|
|
5f8583e3eb | ||
|
|
062821d267 | ||
|
|
1fdaaf82d2 | ||
|
|
e4c1b17810 | ||
|
|
70057542c1 | ||
|
|
f2255ee707 | ||
|
|
791cc70b09 | ||
|
|
24dcb9973b | ||
|
|
adfd0d5c18 | ||
|
|
cfb128acb8 | ||
|
|
1e8e246ffb | ||
|
|
6162244730 | ||
|
|
86cbdf2442 | ||
|
|
5334626efb | ||
|
|
708d473b47 | ||
|
|
ead954ddaa | ||
|
|
294f511b9a | ||
|
|
b5ebcc3e07 | ||
|
|
7b8e7ac5c2 | ||
|
|
904d39beac | ||
|
|
8958b61fdd | ||
|
|
09293f7d9a | ||
|
|
d520c3d674 | ||
|
|
430e616328 | ||
|
|
976ad5fcee | ||
|
|
4d58ee2162 | ||
|
|
03b6abe5ec | ||
|
|
4479304f3f | ||
|
|
ed83405254 | ||
|
|
b815d945d9 | ||
|
|
2b8e024f48 | ||
|
|
2a0399b98d | ||
|
|
4e333e2d75 | ||
|
|
4f25b7bbbe | ||
|
|
3678db649b | ||
|
|
9d8b1fd99b | ||
|
|
7b62c06be3 | ||
|
|
bc978a91e3 | ||
|
|
611ba97b60 | ||
|
|
39dff1ea7c | ||
|
|
bcf7bcba1d | ||
|
|
8f00730189 | ||
|
|
1dde79ace2 | ||
|
|
5954fb91be | ||
|
|
e192383662 | ||
|
|
4a20fad4e5 | ||
|
|
a469e0864e | ||
|
|
cfce2bdbd9 | ||
|
|
529b5c0a00 | ||
|
|
7cc5787779 | ||
|
|
48eeb279e2 | ||
|
|
d67566252a | ||
|
|
8a2d79e17d | ||
|
|
17370dff54 | ||
|
|
f08a8e7634 | ||
|
|
83543bbddc | ||
|
|
b898c90f41 | ||
|
|
1ec927cf4f | ||
|
|
aef21ba9d5 | ||
|
|
0b31709aee | ||
|
|
7c3433256a | ||
|
|
c79c638f35 | ||
|
|
a148941a39 | ||
|
|
23abe26b0b | ||
|
|
8f21d6dde0 | ||
|
|
b74a6c9e03 | ||
|
|
b63f73ef93 | ||
|
|
1f0b369a15 | ||
|
|
7a43473513 | ||
|
|
07f367a2a5 | ||
|
|
c52ad68d92 | ||
|
|
85467dbd2a | ||
|
|
aa767846f0 | ||
|
|
6d3ad15d20 | ||
|
|
7b95db4d13 | ||
|
|
160b3ff655 | ||
|
|
757a39a75e | ||
|
|
4c08a527be | ||
|
|
578b7b0512 | ||
|
|
45e0259099 | ||
|
|
1ac1933ec1 | ||
|
|
43353e89bb | ||
|
|
010e4610f7 | ||
|
|
ba38853406 | ||
|
|
4944a9e51e | ||
|
|
73ad97859c | ||
|
|
e600da229c | ||
|
|
273b4f20db | ||
|
|
9843dccdf0 | ||
|
|
60ae1ec1e8 | ||
|
|
650c6670f2 | ||
|
|
d6dcba7a60 | ||
|
|
5b3849082f | ||
|
|
5de988a2e6 | ||
|
|
26220b2f54 | ||
|
|
8d8436f1f1 | ||
|
|
93057d9449 | ||
|
|
2addcc3ab5 | ||
|
|
b2419abdc8 | ||
|
|
9f654918ae | ||
|
|
cb086fe08d | ||
|
|
1482166ba3 | ||
|
|
6b8dd9bf03 | ||
|
|
a8af9da249 | ||
|
|
e410f82fdb | ||
|
|
4f70f228f1 | ||
|
|
ddc97b99fa | ||
|
|
5f42918cc9 | ||
|
|
e6dfe6fe89 | ||
|
|
fdbe693139 | ||
|
|
0633b2876c | ||
|
|
ee45ed8ec2 | ||
|
|
0c6726e3f7 | ||
|
|
2a75995189 | ||
|
|
9f074c1ab7 | ||
|
|
2917ee3b9d | ||
|
|
03f631f537 | ||
|
|
fb4171ae6f | ||
|
|
aec7d5aff7 | ||
|
|
045c225c8c | ||
|
|
c49ef49371 | ||
|
|
9fd012229b | ||
|
|
bb27099f9e | ||
|
|
411634ecda | ||
|
|
bb59f66008 | ||
|
|
b8a0f34c33 | ||
|
|
784955cdbd | ||
|
|
83764ce3d5 | ||
|
|
438cf271c0 | ||
|
|
8631eacdf3 | ||
|
|
6e7ce10585 | ||
|
|
3d4a58cb96 | ||
|
|
6fde3f8453 | ||
|
|
016d2f7ab1 | ||
|
|
a8ffc414fe | ||
|
|
b0c1c6e880 | ||
|
|
d3e6177a2b | ||
|
|
8adbdcc675 | ||
|
|
d1d7ff0d0a | ||
|
|
4b05a49b46 | ||
|
|
0309a866c8 | ||
|
|
bbc77f81ca | ||
|
|
483f6f2188 | ||
|
|
e0ef3ca39a | ||
|
|
aef29ff6c5 | ||
|
|
ec7bd8c1c8 | ||
|
|
fe0d69739d | ||
|
|
448e3a4639 | ||
|
|
2e74d6f652 | ||
|
|
237108a6d1 | ||
|
|
ed3a222ed4 | ||
|
|
8a696f6c52 | ||
|
|
184aa4fd20 | ||
|
|
ce14b9e43e | ||
|
|
1cdedb89f1 | ||
|
|
18efb098f9 | ||
|
|
fb3a27c02f | ||
|
|
6d41bcd511 | ||
|
|
e6b981fc38 | ||
|
|
74a3ea2a01 | ||
|
|
4bfb7a5d29 | ||
|
|
55ba040e3d | ||
|
|
e4e78e2083 | ||
|
|
82f25cc2e5 | ||
|
|
7b67c9aa8d | ||
|
|
854f3b5257 | ||
|
|
e37bc99c36 | ||
|
|
52121b90ff | ||
|
|
3eb9de57ef | ||
|
|
4ed501f4d4 | ||
|
|
0bb8ae540d | ||
|
|
963957f7a6 | ||
|
|
93a152068d | ||
|
|
bb318aefd2 | ||
|
|
0cafef7ab4 | ||
|
|
055e54966d | ||
|
|
c58254f951 | ||
|
|
ce710134ac | ||
|
|
fa68bdc82b | ||
|
|
cd9f2ab232 | ||
|
|
91136fe48f | ||
|
|
40cc0f721d | ||
|
|
72567c43e6 | ||
|
|
db82d7a6b3 | ||
|
|
c469a61908 | ||
|
|
1c4bcc5697 | ||
|
|
0f3b0309e8 | ||
|
|
5a393de1cb | ||
|
|
1617a61b33 | ||
|
|
ccd18c4f10 | ||
|
|
e287f5774a | ||
|
|
2dec3195ac | ||
|
|
2bea6694c7 | ||
|
|
38c468a204 | ||
|
|
6a80258163 | ||
|
|
55fe2600b7 | ||
|
|
cad4e183c5 | ||
|
|
ba447d2b00 | ||
|
|
fa5e32c46b | ||
|
|
e51b7c22a6 | ||
|
|
0473cdc5e1 | ||
|
|
36c65e8b5f | ||
|
|
d0e81b7778 | ||
|
|
7df63c87c9 | ||
|
|
0021f7b4ba | ||
|
|
540283970a | ||
|
|
5a2b9b60e6 | ||
|
|
12e09269d3 | ||
|
|
bcad5eda81 | ||
|
|
09ad56056e | ||
|
|
0c8dc63085 | ||
|
|
68e3bcba05 | ||
|
|
75a6111a9b | ||
|
|
1d9f970169 | ||
|
|
7cc328ed3b | ||
|
|
9a1c88c80d | ||
|
|
b1446438fb | ||
|
|
6ba1d71a81 | ||
|
|
a4f885a2c5 | ||
|
|
a8a3c63f64 | ||
|
|
ace7b4e2af | ||
|
|
a2885a90b7 | ||
|
|
4457487e2a | ||
|
|
830bb5f70d | ||
|
|
9106f9676c | ||
|
|
df1690515c | ||
|
|
8e287da7b5 | ||
|
|
128ebe2cb2 | ||
|
|
404964dfde | ||
|
|
7f74e79253 | ||
|
|
11b5cc83dc | ||
|
|
bda9d1f8e2 | ||
|
|
32efeee4c7 | ||
|
|
8adbd90cc3 | ||
|
|
074fcaaa73 | ||
|
|
d2209c8ead | ||
|
|
31f1079dfa | ||
|
|
2b87c83fa7 | ||
|
|
1c31c1947c | ||
|
|
50a8a83408 | ||
|
|
020fa4eefa | ||
|
|
64cf02bc45 | ||
|
|
06bd964adc | ||
|
|
2218b7b853 | ||
|
|
f6c45cd85a | ||
|
|
cf4907011b | ||
|
|
dea85add5d | ||
|
|
aa3325ef35 | ||
|
|
8059ba7306 | ||
|
|
c346c4178d | ||
|
|
f2555563a8 | ||
|
|
b948961d52 | ||
|
|
43987d844f | ||
|
|
cc3b14dbf8 | ||
|
|
3cc2d3260e | ||
|
|
be155f8c6c | ||
|
|
74952cf62d | ||
|
|
4a71df7859 | ||
|
|
7d7ea4c34b | ||
|
|
026d22f7f9 | ||
|
|
babd351151 | ||
|
|
d11894fd74 | ||
|
|
6072eab01d | ||
|
|
64e4c77428 | ||
|
|
48b70d0254 | ||
|
|
db2015f826 | ||
|
|
f893cf268b | ||
|
|
ad12d42b35 | ||
|
|
63a0c7c10b | ||
|
|
99d4d5ed9a | ||
|
|
7910c28be7 | ||
|
|
32e1ade388 | ||
|
|
a8aac7e1b4 | ||
|
|
7404aaf4d2 | ||
|
|
2f25bd476c | ||
|
|
681e4ae7fc | ||
|
|
e1ed0d2b09 | ||
|
|
32567a1c66 | ||
|
|
25753af331 | ||
|
|
6babcf9536 | ||
|
|
9a05f659f1 | ||
|
|
ca58bf661d | ||
|
|
62695d4d9a | ||
|
|
d723ef0a13 | ||
|
|
18160818d5 | ||
|
|
83bfaabd95 | ||
|
|
e23ac8496e | ||
|
|
2b7d05df6b | ||
|
|
606cb249e8 | ||
|
|
0ae739b437 | ||
|
|
f18e57b50e | ||
|
|
4c92c582d7 | ||
|
|
7b1bfeaca1 | ||
|
|
dd1d0d1cb8 | ||
|
|
995bd4953b | ||
|
|
32b61ac730 | ||
|
|
19bd54f425 | ||
|
|
76680777ff | ||
|
|
75b9a8a826 | ||
|
|
f439541844 | ||
|
|
65fb21a062 | ||
|
|
aa5df3c309 | ||
|
|
788394a1c8 | ||
|
|
7286c2f603 | ||
|
|
aff230ec7e | ||
|
|
3d538d0566 | ||
|
|
a1b287bd9d | ||
|
|
1d187f525d | ||
|
|
6c9ab0c019 | ||
|
|
e7157a8528 | ||
|
|
e2182344a2 | ||
|
|
9ebd882c3f | ||
|
|
f017b6b92f | ||
|
|
1c24c4e776 | ||
|
|
758e94a8ec | ||
|
|
9a7199bf7f | ||
|
|
1c691fa2c2 | ||
|
|
b435f49611 | ||
|
|
bcd7b2552e | ||
|
|
e76126fdd9 | ||
|
|
3fe55e7395 | ||
|
|
98e11c16fa | ||
|
|
0c5b2d302b | ||
|
|
0d526a2c4f | ||
|
|
0a8d49c233 | ||
|
|
4618d3e38a | ||
|
|
5521785b87 | ||
|
|
f12b404d58 | ||
|
|
6924e3a7d7 | ||
|
|
6fbe459903 | ||
|
|
0a245e5d65 | ||
|
|
2ed593c5e0 | ||
|
|
ce934b8998 | ||
|
|
aac4d3eefb | ||
|
|
176dc17c70 | ||
|
|
553bbda769 | ||
|
|
d4fd2d9ae0 | ||
|
|
c20138c9fc | ||
|
|
7f1ff11a4c | ||
|
|
ed9121a06f | ||
|
|
c110611d82 | ||
|
|
cc222a6f4a | ||
|
|
8293709f34 | ||
|
|
707740b7f1 | ||
|
|
98de661868 | ||
|
|
64df057e2f | ||
|
|
9d9c5cff75 | ||
|
|
3ed6f360da | ||
|
|
9f07f6441a | ||
|
|
fc0b326443 | ||
|
|
4ac0483ec6 | ||
|
|
724e78d4d5 | ||
|
|
6efa0490ea | ||
|
|
99e032eeb7 | ||
|
|
c80c212a54 | ||
|
|
95460038cf | ||
|
|
586e3b891c | ||
|
|
4402645b37 | ||
|
|
42624246bd | ||
|
|
ddd39ea0c9 | ||
|
|
a95cde372c | ||
|
|
dcbf03c355 | ||
|
|
ce44fb29c9 | ||
|
|
35f5df63e2 | ||
|
|
60c9200c06 | ||
|
|
25dacccb3b | ||
|
|
c0d08d966a | ||
|
|
cdfae2e8cb | ||
|
|
a3b0c351d0 | ||
|
|
92719d095a | ||
|
|
b2dc397fb0 | ||
|
|
55e09529c1 | ||
|
|
aed91d42bf | ||
|
|
eef58f4da0 | ||
|
|
4c427116a8 | ||
|
|
00e9624670 | ||
|
|
b52878fc97 | ||
|
|
68d9984d64 | ||
|
|
c4d293143d | ||
|
|
b08d91a218 | ||
|
|
1c586d8811 | ||
|
|
f5e13c25a7 | ||
|
|
6e267b8825 | ||
|
|
6cdfe70ac9 | ||
|
|
e8a0c693c9 | ||
|
|
ac7db87592 | ||
|
|
3d37a9eb8b | ||
|
|
8c5613b182 | ||
|
|
9e068335b6 | ||
|
|
19ed98f265 | ||
|
|
2aff36a1c0 | ||
|
|
eac7fccbb4 | ||
|
|
7562eaeb5f | ||
|
|
7b38683985 | ||
|
|
27763afbbe | ||
|
|
7a2b6f63c9 | ||
|
|
2ad8413c6d | ||
|
|
956c9000b8 | ||
|
|
838c2994a9 | ||
|
|
9974095659 | ||
|
|
85e289fd4e | ||
|
|
fa17e7019a | ||
|
|
edcb24068a | ||
|
|
38ead4909e | ||
|
|
06b2eb7662 | ||
|
|
5685f2699b | ||
|
|
c6916fd71e | ||
|
|
df64abaf9c | ||
|
|
6ed717556a | ||
|
|
e8c64074f3 | ||
|
|
4af13058f0 | ||
|
|
f3f223fa22 | ||
|
|
e244b4b263 | ||
|
|
ab09810bef | ||
|
|
fbcde465ad | ||
|
|
0f0d168976 | ||
|
|
33d15ca66d | ||
|
|
4fad97e8b1 | ||
|
|
81071771d9 | ||
|
|
4db3bafeda | ||
|
|
29aff298b8 | ||
|
|
4271289db0 | ||
|
|
9818e7c101 | ||
|
|
121a865bb8 | ||
|
|
7af340de85 | ||
|
|
cdc0dee6f5 | ||
|
|
4cbefff8b4 | ||
|
|
acc562bd7f | ||
|
|
44b87dca2a | ||
|
|
be98f786b3 | ||
|
|
6f215cbff9 | ||
|
|
96a29e91e8 | ||
|
|
e01a1a456c | ||
|
|
2561a6e00e | ||
|
|
e0bee0e0db | ||
|
|
7910d006ca | ||
|
|
778a651f7b | ||
|
|
2267dcb768 | ||
|
|
21e7c78744 | ||
|
|
54418957b6 | ||
|
|
77ce31128c | ||
|
|
79ce990644 | ||
|
|
2262d44638 | ||
|
|
972601caf6 | ||
|
|
114ebac21b | ||
|
|
57a86cd836 | ||
|
|
a741b1b9ac | ||
|
|
5d64ea48ba | ||
|
|
db6c65c7e8 | ||
|
|
b68fc6187f | ||
|
|
766a190015 | ||
|
|
2c17551d50 | ||
|
|
739bc361bd | ||
|
|
100fc20928 | ||
|
|
f42e79f5cf | ||
|
|
04da989108 | ||
|
|
11017dd8dc | ||
|
|
8d0aa65ab2 | ||
|
|
8ffb9e6f11 | ||
|
|
b0487798c2 | ||
|
|
740b89258d | ||
|
|
9026921214 | ||
|
|
df2aac3946 | ||
|
|
829e004ee5 | ||
|
|
072384eede | ||
|
|
dcc8043cf6 | ||
|
|
62c9484208 | ||
|
|
ebadccbe25 | ||
|
|
8013716158 | ||
|
|
ed7904b673 | ||
|
|
b2e3d33c80 | ||
|
|
7d1b4d916e | ||
|
|
0fb45d4584 | ||
|
|
0008919631 | ||
|
|
6bf8d19775 | ||
|
|
2e3ef87c42 | ||
|
|
abdd5cf19b | ||
|
|
8a636736b4 | ||
|
|
8fe2529306 | ||
|
|
6db217387f | ||
|
|
d33172df11 | ||
|
|
206a83c935 | ||
|
|
6a0935960b | ||
|
|
3665a792d8 | ||
|
|
90a58a1f37 | ||
|
|
f81eaf62ff | ||
|
|
8567f9b19f | ||
|
|
e33fc1a89b | ||
|
|
872ec90654 | ||
|
|
921f5524e7 | ||
|
|
a65959de3f | ||
|
|
bc095d087c | ||
|
|
3db744269e | ||
|
|
701e2f819b | ||
|
|
88bf569752 | ||
|
|
7283f48b36 | ||
|
|
f677b63c5c | ||
|
|
b2e4172c12 | ||
|
|
e0bec82a1c | ||
|
|
6cf117b3d0 | ||
|
|
6bcf47fc4b | ||
|
|
6d968bbbd8 | ||
|
|
8b803faf72 | ||
|
|
d9bcb16d9e | ||
|
|
ead9f0b3c6 | ||
|
|
6168621a36 | ||
|
|
d48c257b19 | ||
|
|
d723bbe3b7 | ||
|
|
f68e9c7681 | ||
|
|
f7a59edf19 | ||
|
|
a4741e28c5 | ||
|
|
c48ca99817 | ||
|
|
2b6965f801 | ||
|
|
caec4f80bb | ||
|
|
9aa7672b17 | ||
|
|
8e03cf9fc8 | ||
|
|
8d9a050ad6 | ||
|
|
266e08817e | ||
|
|
6c3526d47e | ||
|
|
19adfd5116 | ||
|
|
485f7fdcef | ||
|
|
856afcf3bf | ||
|
|
934b5e344f | ||
|
|
82eb8f14aa | ||
|
|
caa9cc213d | ||
|
|
403325d9dd | ||
|
|
62b3ca2fb9 | ||
|
|
e6034223f8 | ||
|
|
da02bea945 | ||
|
|
dd308d9276 | ||
|
|
fce72cad55 | ||
|
|
901b23e5ae | ||
|
|
df4d4d36bc | ||
|
|
408c56bfcf | ||
|
|
ea75ddfa85 | ||
|
|
bda5619b97 | ||
|
|
f6fb3cc766 | ||
|
|
7c3cb118a4 | ||
|
|
d03565ad95 | ||
|
|
c0f5746d1d | ||
|
|
a686bec025 | ||
|
|
c0d36bb85e | ||
|
|
e6df8dcd2e | ||
|
|
6f3fd99130 | ||
|
|
b5f7106780 | ||
|
|
e2f6dcab01 | ||
|
|
ff2f218bf0 | ||
|
|
902ed6e287 | ||
|
|
a40b49f2ed | ||
|
|
fc3b55b135 | ||
|
|
07bd5c633e | ||
|
|
f70dc14340 | ||
|
|
3d49ab2ae0 | ||
|
|
a133c39167 | ||
|
|
c9c8e20511 | ||
|
|
509ff7e833 | ||
|
|
a7a5fdb7f5 | ||
|
|
cf9ef281ce | ||
|
|
6345335be2 | ||
|
|
a008ec3729 | ||
|
|
e7a48f3909 | ||
|
|
718553373f | ||
|
|
310772d7fe | ||
|
|
213e6fab57 | ||
|
|
b7dbed5cd4 | ||
|
|
b621f2c24d | ||
|
|
21b9b090f9 | ||
|
|
709121b07f | ||
|
|
bb02a17e44 | ||
|
|
fd40c58b9c | ||
|
|
b7a6f1249d | ||
|
|
71125f07cc | ||
|
|
8500f5b437 | ||
|
|
eb25d9d7af | ||
|
|
033c85fc4b | ||
|
|
1663731489 | ||
|
|
1f65d47811 | ||
|
|
897cba3d07 | ||
|
|
f0e2247f06 | ||
|
|
1470ec60db | ||
|
|
7c04792777 | ||
|
|
6e750a2af0 | ||
|
|
7640dab380 | ||
|
|
6c10908cb7 | ||
|
|
abd235f332 | ||
|
|
4a638e82cc | ||
|
|
156d39490e | ||
|
|
a39968868f | ||
|
|
e0587ad17b | ||
|
|
06106f3831 | ||
|
|
1e332ed075 | ||
|
|
cef56cb5f1 | ||
|
|
97762712e6 | ||
|
|
4575996995 | ||
|
|
9a4594227e | ||
|
|
399bec62c2 | ||
|
|
177d015fc6 | ||
|
|
afd62ceeeb | ||
|
|
44c088c96d | ||
|
|
cf65347cd8 | ||
|
|
9fe54c5d21 | ||
|
|
0210ef2764 | ||
|
|
c66982c9bc | ||
|
|
a88be30c3f | ||
|
|
155cd31ced | ||
|
|
52dc77a00f | ||
|
|
c5de56790a | ||
|
|
d97cb4c360 | ||
|
|
8e499d164a | ||
|
|
764e1ac35f | ||
|
|
69e4fcfc76 | ||
|
|
951071e7ba | ||
|
|
5e82700871 | ||
|
|
4c706a213a | ||
|
|
d0da1f43a9 | ||
|
|
4afc439169 | ||
|
|
e47690dd52 | ||
|
|
7859aa0ce8 | ||
|
|
710c192e39 | ||
|
|
50e179f7a8 | ||
|
|
980a9d0640 | ||
|
|
fdfbf54808 | ||
|
|
fe8b0d99e5 | ||
|
|
7e817a5808 | ||
|
|
23b68bd9f9 | ||
|
|
0a8bbc7d3a | ||
|
|
0f5200aaf8 | ||
|
|
82d7b9cf05 | ||
|
|
0f5fafac8d | ||
|
|
7fcfffba6f | ||
|
|
13e9ef4c34 | ||
|
|
ea04c7d5eb | ||
|
|
8bf29528e3 | ||
|
|
c6f65debcf | ||
|
|
370b39a139 | ||
|
|
7b7e64576f | ||
|
|
d8b717a879 | ||
|
|
3967017ebb | ||
|
|
d93862043a | ||
|
|
719276b32a | ||
|
|
bf13f06e68 | ||
|
|
9f770d08f5 | ||
|
|
e62c529c1f | ||
|
|
4e1fc9b031 | ||
|
|
7d175ad38d | ||
|
|
de2c56560e | ||
|
|
0db413cb18 | ||
|
|
7c6424e05c | ||
|
|
3c328a00b5 | ||
|
|
e36f13c595 | ||
|
|
38a5cc33e8 | ||
|
|
245a3a73d8 | ||
|
|
d57d7ec4d8 | ||
|
|
09a4e4a6a5 | ||
|
|
d9beb4b660 | ||
|
|
63507a5567 | ||
|
|
e42dc259d8 | ||
|
|
f59f8d1937 | ||
|
|
0387bde81b | ||
|
|
b8fe95b945 | ||
|
|
792a1652e3 | ||
|
|
4816bfa26d | ||
|
|
95d1680ac3 | ||
|
|
5e1e6aea16 | ||
|
|
00696943d1 | ||
|
|
c1d293f9f9 | ||
|
|
d4d61d10bc | ||
|
|
c09e5e6552 | ||
|
|
ab08732ba8 | ||
|
|
584a7eb9e2 | ||
|
|
d44b04f425 | ||
|
|
eeec04b21a | ||
|
|
0642bc28bc | ||
|
|
2b12b67582 | ||
|
|
d9b575b051 | ||
|
|
c6dbb41724 | ||
|
|
ea4c1bf5e5 | ||
|
|
67afc050b8 | ||
|
|
6e90da90f1 | ||
|
|
26872c829b | ||
|
|
34f32c8ba5 | ||
|
|
b0d847dc18 | ||
|
|
adabef9706 | ||
|
|
474adf2d53 | ||
|
|
4f23ad37f5 | ||
|
|
eb6b160377 | ||
|
|
0770e00d8d | ||
|
|
ab0e95177e | ||
|
|
cfd9e4b256 | ||
|
|
bfed0ed791 | ||
|
|
3cec9f7ee0 | ||
|
|
3a5dc62908 | ||
|
|
e0cdfcf403 | ||
|
|
439866216c | ||
|
|
a1b0731665 | ||
|
|
bcc216fd2e | ||
|
|
574d848828 | ||
|
|
22acc2307c | ||
|
|
d01a3c327e | ||
|
|
7c52b6ab23 | ||
|
|
23d27aee6b | ||
|
|
9315358fa6 | ||
|
|
83aaa94cfe | ||
|
|
7744099ee5 | ||
|
|
35bb6b62d8 | ||
|
|
99844931f4 | ||
|
|
d8c34a7cdf | ||
|
|
c284edcd81 | ||
|
|
f5d4b352b3 | ||
|
|
e44b31d755 | ||
|
|
3a28068b48 | ||
|
|
03433b00d2 | ||
|
|
6f39ebf48f | ||
|
|
949ca00dbb | ||
|
|
bfdf1ee8f5 | ||
|
|
e047d4e8a6 | ||
|
|
15cf2069b6 | ||
|
|
564b0ee0c4 | ||
|
|
b454932ff5 | ||
|
|
191c07b6a1 | ||
|
|
5b0deb9fc2 | ||
|
|
297ec64bde | ||
|
|
a9b85caeb7 | ||
|
|
e30b883148 | ||
|
|
cd6be0d755 | ||
|
|
95b74c5f2f | ||
|
|
45f26bb22e | ||
|
|
09b0e09932 | ||
|
|
ea9d88009f | ||
|
|
e5013c918e | ||
|
|
4b45103a4e | ||
|
|
1ea28411da | ||
|
|
e27fce1227 | ||
|
|
cf3b8378bd | ||
|
|
9f07532140 | ||
|
|
f1aedc619e | ||
|
|
5f778e706f | ||
|
|
ee15663679 | ||
|
|
c271d4bfe4 | ||
|
|
99188f7427 | ||
|
|
88a07ecf63 | ||
|
|
f1af2e35cd | ||
|
|
352e20f01b | ||
|
|
f7d10a6cf1 | ||
|
|
6a5808077c | ||
|
|
09be11b5a8 | ||
|
|
5117c1a528 | ||
|
|
ead9857203 | ||
|
|
cd3493e040 | ||
|
|
02c22c1894 | ||
|
|
8b41a03080 | ||
|
|
306f4f119b | ||
|
|
3dc28c7291 | ||
|
|
f2f2215059 | ||
|
|
f75179b2f6 | ||
|
|
3ead35c984 | ||
|
|
efe20a1fe8 | ||
|
|
ab1e56a14e | ||
|
|
055d6bf601 | ||
|
|
683c3e96d6 | ||
|
|
d8e414a4a2 | ||
|
|
9237d68b79 | ||
|
|
12a14fbe29 | ||
|
|
22e4e4efc1 | ||
|
|
588327fd68 | ||
|
|
d35ef2d360 | ||
|
|
5ce5193430 | ||
|
|
6e74215b2e | ||
|
|
884a9835c4 | ||
|
|
233f40f3e9 | ||
|
|
9b24e61033 | ||
|
|
286928d59e | ||
|
|
47cee5f6a1 | ||
|
|
6820b748f7 | ||
|
|
42422845b0 | ||
|
|
8ed11cdc07 | ||
|
|
006cd1ee89 | ||
|
|
49f7ee6042 | ||
|
|
9612e4d4e9 | ||
|
|
4b938db0c1 | ||
|
|
b0a079dce9 | ||
|
|
52069f35c5 | ||
|
|
140535d875 | ||
|
|
611084d00c | ||
|
|
1b7c810eeb | ||
|
|
dd2a698147 | ||
|
|
e99df58bce | ||
|
|
70139221ab | ||
|
|
5d8d2b4a34 | ||
|
|
f59f322226 | ||
|
|
acca3f37b5 | ||
|
|
0de62f620d | ||
|
|
351c4a00e9 | ||
|
|
b048d81ac0 | ||
|
|
b9c9a2b75f | ||
|
|
4cea22d813 | ||
|
|
edad4e7d35 | ||
|
|
e28acd8d2d | ||
|
|
86feaff82f | ||
|
|
5f747db224 | ||
|
|
ccda41e18e | ||
|
|
2bb83c6532 | ||
|
|
ec59faba8c | ||
|
|
c5d8c44b81 | ||
|
|
7a4e4629e5 | ||
|
|
4d3dd95a60 | ||
|
|
713021359e | ||
|
|
927d2c36e8 | ||
|
|
afc6368915 | ||
|
|
3f604ad7b5 | ||
|
|
c5d4e237ad | ||
|
|
0b50b21779 | ||
|
|
cafc70f1c9 | ||
|
|
b4377c346f | ||
|
|
7c206af757 | ||
|
|
6212d0a052 | ||
|
|
8909822aea | ||
|
|
5fc0964f73 | ||
|
|
7725559a44 | ||
|
|
03414a58e9 | ||
|
|
ade0419bf6 | ||
|
|
9d646b2eed | ||
|
|
0d718a5ca2 | ||
|
|
78c8b36dfa | ||
|
|
b5ca0874fa | ||
|
|
e209becff9 | ||
|
|
652d42aa6b | ||
|
|
058a11597f | ||
|
|
dd73d6e19a | ||
|
|
3793f618a8 | ||
|
|
6621983a9c | ||
|
|
e0b0302f5a | ||
|
|
b02584bec6 | ||
|
|
c832d61409 | ||
|
|
ac701f28b5 | ||
|
|
53032140e7 | ||
|
|
3c7633564f | ||
|
|
43eae40404 | ||
|
|
e34b1b54d5 | ||
|
|
0658a7de0d | ||
|
|
9e0a4dfac8 | ||
|
|
82034ade90 | ||
|
|
b979203e12 | ||
|
|
c9a368f13a | ||
|
|
cfabf22fef | ||
|
|
61aef77916 | ||
|
|
8b6c6beceb | ||
|
|
4688e49454 | ||
|
|
76cc28b215 | ||
|
|
467e5423d2 | ||
|
|
422bf7b689 | ||
|
|
1d230bd4aa | ||
|
|
029850842b | ||
|
|
cc65555c3d | ||
|
|
3b090396a4 | ||
|
|
2e4656ae8b | ||
|
|
ebce63baa4 | ||
|
|
08f691ee0d | ||
|
|
7550a19951 | ||
|
|
daeb76df11 | ||
|
|
31315e5ba6 | ||
|
|
e4e1fa6a4c | ||
|
|
6e36312be8 | ||
|
|
9cc7ba1d82 | ||
|
|
9182001147 | ||
|
|
5d1510083e | ||
|
|
7035f4cc1f | ||
|
|
da826b2a22 | ||
|
|
a152d8664d | ||
|
|
6ad75d963a | ||
|
|
e92d34a9e3 | ||
|
|
768066db58 | ||
|
|
886b63e55b | ||
|
|
3c7ee65b7d | ||
|
|
0e060c4564 | ||
|
|
3e127dbd9c | ||
|
|
ad66d14a1b | ||
|
|
5db61b78ea | ||
|
|
85f9597f2c | ||
|
|
73aca22605 | ||
|
|
077343ca20 | ||
|
|
c76946c770 | ||
|
|
2344a49260 | ||
|
|
65f463dbbd | ||
|
|
6e100a70b9 | ||
|
|
77650ebe09 | ||
|
|
cdba2bd9e2 | ||
|
|
e84cc92fcd | ||
|
|
a2890948bb | ||
|
|
f2e1ebef45 | ||
|
|
3c33614115 | ||
|
|
fcc4b1e8ee | ||
|
|
c8ac4ec1e8 | ||
|
|
93ab8b88d4 | ||
|
|
cc12c302e3 | ||
|
|
73941ca30e | ||
|
|
f963711820 | ||
|
|
1a0a301fb6 | ||
|
|
f87a9b7aae | ||
|
|
f18f0bc762 | ||
|
|
c3b1bc6674 | ||
|
|
e9bb228528 | ||
|
|
a906957454 | ||
|
|
49d10337a1 | ||
|
|
87c6c31f1f | ||
|
|
5f3639e396 | ||
|
|
14d36ef8dc | ||
|
|
fc186f1718 | ||
|
|
70aa93fe8d | ||
|
|
901df22d39 | ||
|
|
cd0fe3d6d4 | ||
|
|
09f2c3f645 | ||
|
|
116542d8e4 | ||
|
|
ffae6e269b | ||
|
|
2194dc0463 | ||
|
|
63e3923349 | ||
|
|
da2d426137 | ||
|
|
8df7672624 | ||
|
|
b9dba61c97 | ||
|
|
1872a1a77c | ||
|
|
cf57bad7fa | ||
|
|
7e956894ee | ||
|
|
f78046d4c1 | ||
|
|
beb09cbf4a | ||
|
|
6b2a5d7777 | ||
|
|
4b0d368222 | ||
|
|
5ab2f6ea3a | ||
|
|
f845bf5b25 | ||
|
|
bb10d64d58 | ||
|
|
96e8e9736f | ||
|
|
62472b97e6 | ||
|
|
10b89ff00b | ||
|
|
2d8339343f | ||
|
|
9ddd88b5d0 | ||
|
|
614ec645c6 | ||
|
|
893ffc48dc | ||
|
|
7bddb70f11 | ||
|
|
9d4a38e560 | ||
|
|
9d20b31585 | ||
|
|
fb8838375f | ||
|
|
dead4e3f85 | ||
|
|
9c833a8a95 | ||
|
|
0b90794bca | ||
|
|
fd89757814 | ||
|
|
5eedc312c8 | ||
|
|
36c1cc5e0c | ||
|
|
0b4cdea9dd | ||
|
|
12172168f6 | ||
|
|
c79aa49252 | ||
|
|
b869308bf4 | ||
|
|
7a748bd142 | ||
|
|
d11669e613 | ||
|
|
b5108b938c | ||
|
|
049d510536 | ||
|
|
113f7b18a6 | ||
|
|
65b248c3cf | ||
|
|
1698996ac3 | ||
|
|
2ae47f2375 | ||
|
|
6733d12e81 | ||
|
|
8b5109e32f | ||
|
|
7b651d56b1 | ||
|
|
23b9b8aaf6 | ||
|
|
8d9fb43828 | ||
|
|
0d1be72fdb | ||
|
|
ce102a2af6 | ||
|
|
dda6071bbc | ||
|
|
0c4809bb16 | ||
|
|
4b87c5e80e | ||
|
|
b8fe9b9074 | ||
|
|
bc8b18cea8 | ||
|
|
20fdd3e891 | ||
|
|
ff9147487f | ||
|
|
af494f3b5e | ||
|
|
f68d26317d | ||
|
|
5b881f7c77 | ||
|
|
fe5efff255 | ||
|
|
46d18a642c | ||
|
|
c00012f28b | ||
|
|
d0050f7d59 | ||
|
|
f3a4881261 | ||
|
|
1728909964 | ||
|
|
f6e7460b24 | ||
|
|
351829888f | ||
|
|
e520c30ade | ||
|
|
99267ac2f0 | ||
|
|
6f1f7ef7fc | ||
|
|
d647e947e2 | ||
|
|
adef45c30f | ||
|
|
8b80859023 | ||
|
|
907fff89c8 | ||
|
|
ca532ffdaf | ||
|
|
64ee842dc4 | ||
|
|
9c0754a88d | ||
|
|
3dac9e3dae | ||
|
|
6963e667aa | ||
|
|
af292b291b | ||
|
|
918f75704e | ||
|
|
6e258e5d0c | ||
|
|
a8ea4ec085 | ||
|
|
2f894af028 | ||
|
|
dd400e1214 | ||
|
|
f8a183bfdf | ||
|
|
2404193b61 | ||
|
|
3ef11f5b26 | ||
|
|
30419cec12 | ||
|
|
162841feb8 | ||
|
|
f910f63f8d | ||
|
|
860cb75f8f | ||
|
|
293be93b93 | ||
|
|
976e77b31e | ||
|
|
9f28ee2982 | ||
|
|
e6c1e48b86 | ||
|
|
2c2880199d | ||
|
|
c7f8737eeb | ||
|
|
f3aea3108d | ||
|
|
e4f8ad0f05 | ||
|
|
8b80f72313 | ||
|
|
06ebce31ca | ||
|
|
59db8ba997 | ||
|
|
df37d4aa7f | ||
|
|
fd9935c800 | ||
|
|
c057430488 | ||
|
|
767fa78e22 | ||
|
|
a0e289dcd6 | ||
|
|
c62d147254 | ||
|
|
f739bee353 | ||
|
|
db4dfb69fb | ||
|
|
9729a5ef16 | ||
|
|
6f12818352 | ||
|
|
75fc318da0 | ||
|
|
acf71cc2c2 | ||
|
|
42efe10473 | ||
|
|
b77e72880b | ||
|
|
297c1524b4 | ||
|
|
6140743769 | ||
|
|
d5ca700828 | ||
|
|
a7e1fe76c3 | ||
|
|
f6c55279d1 | ||
|
|
826f4835bb | ||
|
|
119a28def1 | ||
|
|
c1ffb8bc33 | ||
|
|
60cc9d14af | ||
|
|
7cf4f8fdaa | ||
|
|
1b84e83061 | ||
|
|
b0d2ee8760 | ||
|
|
2022432d35 | ||
|
|
c0ed335d84 | ||
|
|
4964562866 | ||
|
|
f78dc443ad | ||
|
|
56b47db778 | ||
|
|
2d4bf17b28 | ||
|
|
3b1819c68d | ||
|
|
575bbdb53b | ||
|
|
2fa7482028 | ||
|
|
e5062317e2 | ||
|
|
543f4fa3c2 | ||
|
|
49cdf69815 | ||
|
|
04196a1a19 | ||
|
|
b8622b8708 | ||
|
|
a2feca0ba3 | ||
|
|
ae6c6a75b2 | ||
|
|
bd987e9531 | ||
|
|
24d69fd820 | ||
|
|
9e2fa8d7f1 | ||
|
|
74ba2f7283 | ||
|
|
7603b5c63f | ||
|
|
03566afe66 | ||
|
|
3c2e314e2d | ||
|
|
1fcf08b5cd | ||
|
|
c13db89ed4 | ||
|
|
e68ae9e8a6 | ||
|
|
fb252d6db3 | ||
|
|
df959353d6 | ||
|
|
fbc443483d | ||
|
|
00f1dbc3dd | ||
|
|
c6021ff012 | ||
|
|
7a8cfcee35 | ||
|
|
c15586a1c4 |
@@ -1,2 +0,0 @@
|
|||||||
[config]
|
|
||||||
project = Oqtane.Server/Oqtane.Server.csproj
|
|
||||||
25
.gitea/workflows/build-container.yml
Normal file
25
.gitea/workflows/build-container.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
name: build-docker-imge
|
||||||
|
on:
|
||||||
|
- push
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build the docker container
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: "Git clone"
|
||||||
|
run: git clone ${{ gitea.server_url }}/${{ gitea.repository }}.git .
|
||||||
|
- name: "Git checkout"
|
||||||
|
run: git checkout "${{ gitea.sha }}"
|
||||||
|
- uses: aevea/action-kaniko@master
|
||||||
|
name: Run Kaniko to build our api docker container.
|
||||||
|
with:
|
||||||
|
image: kocoded/oqtane.framework
|
||||||
|
tag: ${{ git.workflow_sha }}
|
||||||
|
tag_with_latest: github.ref == 'refs/heads/master'
|
||||||
|
registry: git.kocoder.xyz
|
||||||
|
username: ${{ secrets.CI_RUNNER_USER }}
|
||||||
|
password: ${{ secrets.CI_RUNNER_TOKEN }}
|
||||||
|
build_file: Dockerfile
|
||||||
|
target: deploy
|
||||||
|
|
||||||
26
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
26
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
name: Bug Report
|
||||||
|
about: Create a bug report to help us improve the product
|
||||||
|
title: "[BUG] "
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Oqtane Info
|
||||||
|
|
||||||
|
Version - #.#.#
|
||||||
|
Render Mode - Static
|
||||||
|
Interactivity - Server
|
||||||
|
Database - SQL Server
|
||||||
|
|
||||||
|
### Describe the bug
|
||||||
|
|
||||||
|
|
||||||
|
### Expected Behavior
|
||||||
|
|
||||||
|
|
||||||
|
### Steps To Reproduce
|
||||||
|
|
||||||
|
|
||||||
|
### Anything else?
|
||||||
20
.github/ISSUE_TEMPLATE/enhancement-request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/enhancement-request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Enhancement Request
|
||||||
|
about: 'Suggest a product enhancement '
|
||||||
|
title: "[ENH] "
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Oqtane Info
|
||||||
|
|
||||||
|
Version - #.#.#
|
||||||
|
Render Mode - Static
|
||||||
|
Interactivity - Server
|
||||||
|
Database - SQL Server
|
||||||
|
|
||||||
|
### Describe the enhancement
|
||||||
|
|
||||||
|
|
||||||
|
### Anything else?
|
||||||
13
.gitignore
vendored
13
.gitignore
vendored
@@ -16,16 +16,23 @@ _ReSharper.Caches
|
|||||||
Oqtane.Server/appsettings.json
|
Oqtane.Server/appsettings.json
|
||||||
Oqtane.Server/Data
|
Oqtane.Server/Data
|
||||||
|
|
||||||
/Oqtane.Server/Properties/PublishProfiles/FolderProfile.pubxml
|
Oqtane.Server/Properties/PublishProfiles/FolderProfile.pubxml
|
||||||
Oqtane.Server/Content
|
Oqtane.Server/Content
|
||||||
Oqtane.Server/Packages
|
Oqtane.Server/Packages
|
||||||
Oqtane.Server/wwwroot/Content
|
Oqtane.Server/wwwroot/Content
|
||||||
Oqtane.Server/wwwroot/Packages/*.log
|
Oqtane.Server/wwwroot/Packages/*.log
|
||||||
|
|
||||||
Oqtane.Server/wwwroot/Modules
|
Oqtane.Server/wwwroot/_content/*
|
||||||
|
!Oqtane.Server/wwwroot/_content/Placeholder.txt
|
||||||
|
|
||||||
|
Oqtane.Server/wwwroot/Modules/*
|
||||||
!Oqtane.Server/wwwroot/Modules/Oqtane.Modules.*
|
!Oqtane.Server/wwwroot/Modules/Oqtane.Modules.*
|
||||||
!Oqtane.Server/wwwroot/Modules/Templates
|
!Oqtane.Server/wwwroot/Modules/Templates
|
||||||
|
Oqtane.Server/wwwroot/Modules/Templates/*
|
||||||
|
!Oqtane.Server/wwwroot/Modules/Templates/External
|
||||||
|
|
||||||
Oqtane.Server/wwwroot/Themes
|
Oqtane.Server/wwwroot/Themes/*
|
||||||
!Oqtane.Server/wwwroot/Themes/Oqtane.Themes.*
|
!Oqtane.Server/wwwroot/Themes/Oqtane.Themes.*
|
||||||
!Oqtane.Server/wwwroot/Themes/Templates
|
!Oqtane.Server/wwwroot/Themes/Templates
|
||||||
|
Oqtane.Server/wwwroot/Themes/Templates/*
|
||||||
|
Oqtane.Server/wwwroot/Themes/Templates/External
|
||||||
|
|||||||
24
CONTRIBUTING.md
Normal file
24
CONTRIBUTING.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Contributing to Oqtane
|
||||||
|
|
||||||
|
## How to Contribute
|
||||||
|
|
||||||
|
We track all of our issues on Github. If you want to contribute, everything starts with an issue. If you don't have an issue yet, you can add one. Then a core contributor will tag it as either an enhancement [ENH] or a bug [BUG]. Tagged issues are open for contribution.
|
||||||
|
|
||||||
|
## Use GitHub-flow process
|
||||||
|
- Make a comment on the issue that you intend to work on it and read all the comments to gain a full understanding.
|
||||||
|
- Fork the repository
|
||||||
|
- Create a new branch and update your comment on the issue with a llink to the branch
|
||||||
|
- Make your changes and commit them
|
||||||
|
- Push to the branch
|
||||||
|
- Create a pull request
|
||||||
|
|
||||||
|
## Reporting Bugs
|
||||||
|
|
||||||
|
- Check if the issue has already been reported.
|
||||||
|
- Open a new issue if it hasn’t been reported.
|
||||||
|
|
||||||
|
## Requesting Features
|
||||||
|
|
||||||
|
- Use the feature request template in the Issues tab.
|
||||||
|
|
||||||
|
Thank you for contributing!
|
||||||
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-2023 .NET Foundation
|
Copyright (c) 2018-2025 .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
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
@using Microsoft.AspNetCore.Http
|
|
||||||
@inject IInstallationService InstallationService
|
|
||||||
@inject IJSRuntime JSRuntime
|
|
||||||
@inject SiteState SiteState
|
|
||||||
@inject IServiceProvider ServiceProvider
|
|
||||||
|
|
||||||
@if (_initialized)
|
|
||||||
{
|
|
||||||
@if (!_installation.Success)
|
|
||||||
{
|
|
||||||
<Installer />
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
@if (string.IsNullOrEmpty(_installation.Message))
|
|
||||||
{
|
|
||||||
<div style="@_display">
|
|
||||||
<CascadingAuthenticationState>
|
|
||||||
<CascadingValue Value="@PageState">
|
|
||||||
<SiteRouter Runtime="@Runtime" RenderMode="@RenderMode" VisitorId="@VisitorId" OnStateChange="@ChangeState" />
|
|
||||||
</CascadingValue>
|
|
||||||
</CascadingAuthenticationState>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<div class="app-alert">
|
|
||||||
@_installation.Message
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@code {
|
|
||||||
[Parameter]
|
|
||||||
public string AntiForgeryToken { get; set; }
|
|
||||||
|
|
||||||
[Parameter]
|
|
||||||
public string Runtime { get; set; }
|
|
||||||
|
|
||||||
[Parameter]
|
|
||||||
public string RenderMode { get; set; }
|
|
||||||
|
|
||||||
[Parameter]
|
|
||||||
public int VisitorId { get; set; }
|
|
||||||
|
|
||||||
[Parameter]
|
|
||||||
public string RemoteIPAddress { get; set; }
|
|
||||||
|
|
||||||
[Parameter]
|
|
||||||
public string AuthorizationToken { get; set; }
|
|
||||||
|
|
||||||
private bool _initialized = false;
|
|
||||||
private string _display = "display: none;";
|
|
||||||
private Installation _installation = new Installation { Success = false, Message = "" };
|
|
||||||
|
|
||||||
private PageState PageState { get; set; }
|
|
||||||
|
|
||||||
private IHttpContextAccessor accessor;
|
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
|
||||||
{
|
|
||||||
SiteState.RemoteIPAddress = RemoteIPAddress;
|
|
||||||
SiteState.AntiForgeryToken = AntiForgeryToken;
|
|
||||||
SiteState.AuthorizationToken = AuthorizationToken;
|
|
||||||
|
|
||||||
accessor = (IHttpContextAccessor)ServiceProvider.GetService(typeof(IHttpContextAccessor));
|
|
||||||
if (accessor != null)
|
|
||||||
{
|
|
||||||
SiteState.IsPrerendering = !accessor.HttpContext.Response.HasStarted;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SiteState.IsPrerendering = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
_installation = await InstallationService.IsInstalled();
|
|
||||||
if (_installation.Alias != null)
|
|
||||||
{
|
|
||||||
SiteState.Alias = _installation.Alias;
|
|
||||||
}
|
|
||||||
_initialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnAfterRender(bool firstRender)
|
|
||||||
{
|
|
||||||
if (firstRender)
|
|
||||||
{
|
|
||||||
// prevents flash on initial page load
|
|
||||||
_display = "";
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ChangeState(PageState pageState)
|
|
||||||
{
|
|
||||||
PageState = pageState;
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using Microsoft.AspNetCore.Components.Authorization;
|
using Microsoft.AspNetCore.Components.Authorization;
|
||||||
|
using Oqtane.Interfaces;
|
||||||
using Oqtane.Providers;
|
using Oqtane.Providers;
|
||||||
using Oqtane.Services;
|
using Oqtane.Services;
|
||||||
using Oqtane.Shared;
|
using Oqtane.Shared;
|
||||||
@@ -7,16 +8,17 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||||||
{
|
{
|
||||||
public static class OqtaneServiceCollectionExtensions
|
public static class OqtaneServiceCollectionExtensions
|
||||||
{
|
{
|
||||||
public static IServiceCollection AddOqtaneAuthorization(this IServiceCollection services)
|
public static IServiceCollection AddOqtaneAuthentication(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddAuthorizationCore();
|
services.AddAuthorizationCore();
|
||||||
|
services.AddCascadingAuthenticationState();
|
||||||
services.AddScoped<IdentityAuthenticationStateProvider>();
|
services.AddScoped<IdentityAuthenticationStateProvider>();
|
||||||
services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<IdentityAuthenticationStateProvider>());
|
services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<IdentityAuthenticationStateProvider>());
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IServiceCollection AddOqtaneScopedServices(this IServiceCollection services)
|
public static IServiceCollection AddOqtaneClientScopedServices(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddScoped<SiteState>();
|
services.AddScoped<SiteState>();
|
||||||
services.AddScoped<IInstallationService, InstallationService>();
|
services.AddScoped<IInstallationService, InstallationService>();
|
||||||
@@ -49,6 +51,14 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||||||
services.AddScoped<IUrlMappingService, UrlMappingService>();
|
services.AddScoped<IUrlMappingService, UrlMappingService>();
|
||||||
services.AddScoped<IVisitorService, VisitorService>();
|
services.AddScoped<IVisitorService, VisitorService>();
|
||||||
services.AddScoped<ISyncService, SyncService>();
|
services.AddScoped<ISyncService, SyncService>();
|
||||||
|
services.AddScoped<ILocalizationCookieService, LocalizationCookieService>();
|
||||||
|
services.AddScoped<ICookieConsentService, CookieConsentService>();
|
||||||
|
services.AddScoped<IOutputCacheService, OutputCacheService>();
|
||||||
|
services.AddScoped<ITimeZoneService, TimeZoneService>();
|
||||||
|
|
||||||
|
// providers
|
||||||
|
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.QuillJSTextEditor>();
|
||||||
|
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.TextAreaTextEditor>();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|||||||
119
Oqtane.Client/Installer/Controls/AzureSqlConfig.razor
Normal file
119
Oqtane.Client/Installer/Controls/AzureSqlConfig.razor
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
@namespace Oqtane.Installer.Controls
|
||||||
|
@implements Oqtane.Interfaces.IDatabaseConfigControl
|
||||||
|
@inject IStringLocalizer<SqlServerConfig> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="server" HelpText="Enter the database server name. This might include a port number as well if you are using a cloud service." ResourceKey="Server">Server:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="server" type="text" class="form-control" @bind="@_server" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="database" HelpText="Enter the name of the database" ResourceKey="Database">Database:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="database" type="text" class="form-control" @bind="@_database" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="security" HelpText="Select your security method" ResourceKey="Security">Security:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="security" class="form-select custom-select" @bind="@_security">
|
||||||
|
<option value="integrated" selected>@Localizer["Integrated"]</option>
|
||||||
|
<option value="custom">@Localizer["Custom"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (_security == "custom")
|
||||||
|
{
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="uid" HelpText="Enter the username to use for the database" ResourceKey="Uid">User Id:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="uid" type="text" class="form-control" @bind="@_uid" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="pwd" HelpText="Enter the password to use for the database" ResourceKey="Pwd">Password:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<input id="pwd" type="@_passwordType" class="form-control" @bind="@_pwd" autocomplete="new-password" />
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglePassword</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="encryption" HelpText="Specify if you are using an encrypted database connection. It is highly recommended to use encryption in a production environment." ResourceKey="Encryption">Encryption:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="encryption" class="form-select custom-select" @bind="@_encryption">
|
||||||
|
<option value="true">@SharedLocalizer["True"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["False"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (_encryption == "true")
|
||||||
|
{
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="trustservercertificate" HelpText="Specify the type of certificate you are using for encryption. Verifiable is equivalent to False. Self Signed is equivalent to True." ResourceKey="TrustServerCertificate">Certificate:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="encryption" class="form-select custom-select" @bind="@_trustservercertificate">
|
||||||
|
<option value="true">@Localizer["Self Signed"]</option>
|
||||||
|
<option value="false">@Localizer["Verifiable"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private string _server = "tcp:{SQL Server Name}.database.windows.net,1433";
|
||||||
|
private string _database = "{SQL Database Name}";
|
||||||
|
private string _security = "custom";
|
||||||
|
private string _uid = "{SQL Administrator Login}";
|
||||||
|
private string _pwd = String.Empty;
|
||||||
|
private string _passwordType = "password";
|
||||||
|
private string _togglePassword = string.Empty;
|
||||||
|
private string _encryption = "true";
|
||||||
|
private string _trustservercertificate = "false";
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
_togglePassword = SharedLocalizer["ShowPassword"];
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetConnectionString()
|
||||||
|
{
|
||||||
|
var connectionString = String.Empty;
|
||||||
|
|
||||||
|
if (!String.IsNullOrEmpty(_server) && !String.IsNullOrEmpty(_database))
|
||||||
|
{
|
||||||
|
connectionString = $"Data Source={_server};Initial Catalog={_database};";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_security == "integrated")
|
||||||
|
{
|
||||||
|
connectionString += "Integrated Security=SSPI;";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
connectionString += $"User ID={_uid};Password={_pwd};";
|
||||||
|
}
|
||||||
|
connectionString += $"Encrypt={_encryption};";
|
||||||
|
connectionString += $"TrustServerCertificate={_trustservercertificate};";
|
||||||
|
|
||||||
|
return connectionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TogglePassword()
|
||||||
|
{
|
||||||
|
if (_passwordType == "password")
|
||||||
|
{
|
||||||
|
_passwordType = "text";
|
||||||
|
_togglePassword = SharedLocalizer["HidePassword"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_passwordType = "password";
|
||||||
|
_togglePassword = SharedLocalizer["ShowPassword"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="server" HelpText="Enter the database server name. This might include a port number as well if you are using a cloud service (ie. servername.database.windows.net,1433) " ResourceKey="Server">Server:</Label>
|
<Label Class="col-sm-3" For="server" HelpText="Enter the database server name. This might include a port number as well if you are using a cloud service." ResourceKey="Server">Server:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="server" type="text" class="form-control" @bind="@_server" />
|
<input id="server" type="text" class="form-control" @bind="@_server" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="mx-auto text-center">
|
<div class="mx-auto text-center">
|
||||||
<img src="oqtane-black.png" />
|
<img src="oqtane-black.png" />
|
||||||
<div style="font-weight: bold">@SharedLocalizer["Version"] @Constants.Version (.NET 7)</div>
|
<div style="font-weight: bold">@SharedLocalizer["Version"] @Constants.Version (.NET @Environment.Version.Major)</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr class="app-rule" />
|
<hr class="app-rule" />
|
||||||
@@ -69,16 +69,16 @@
|
|||||||
<h2>@Localizer["ApplicationAdmin"]</h2><br />
|
<h2>@Localizer["ApplicationAdmin"]</h2><br />
|
||||||
<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="Provide a username for the primary user accountt" ResourceKey="Username">Username:</Label>
|
<Label Class="col-sm-3" For="username" HelpText="Provide a username for the primary user account" ResourceKey="Username">Username:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="username" type="text" class="form-control" @bind="@_hostUsername" />
|
<input id="username" type="text" class="form-control" maxlength="256" @bind="@_hostUsername" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="password" HelpText="Provide a password for the primary user account" ResourceKey="Password">Password:</Label>
|
<Label Class="col-sm-3" For="password" HelpText="Provide a password for the primary user account" ResourceKey="Password">Password:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="password" type="@_passwordType" class="form-control" @bind="@_hostPassword" autocomplete="new-password" />
|
<input id="password" type="@_passwordType" class="form-control" maxlength="256" @bind="@_hostPassword" autocomplete="new-password" />
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglePassword</button>
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglePassword</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
<Label Class="col-sm-3" For="confirm" HelpText="Please confirm the password entered above by entering it again" ResourceKey="Confirm">Confirm:</Label>
|
<Label Class="col-sm-3" For="confirm" HelpText="Please confirm the password entered above by entering it again" ResourceKey="Confirm">Confirm:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="confirm" type="@_confirmPasswordType" class="form-control" @bind="@_confirmPassword" autocomplete="new-password" />
|
<input id="confirm" type="@_confirmPasswordType" class="form-control" maxlength="256" @bind="@_confirmPassword" autocomplete="new-password" />
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@ToggleConfirmPassword" tabindex="-1">@_toggleConfirmPassword</button>
|
<button type="button" class="btn btn-secondary" @onclick="@ToggleConfirmPassword" tabindex="-1">@_toggleConfirmPassword</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -95,7 +95,13 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="email" HelpText="Provide the email address for the host user account" ResourceKey="Email">Email:</Label>
|
<Label Class="col-sm-3" For="email" HelpText="Provide the email address for the host user account" ResourceKey="Email">Email:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input type="text" class="form-control" @bind="@_hostEmail" />
|
<input type="text" class="form-control" maxlength="256" @bind="@_hostEmail" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="name" HelpText="Provide the full name of the host user" ResourceKey="Name">Full Name:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" class="form-control" maxlength="50" @bind="@_hostName" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -153,6 +159,7 @@
|
|||||||
private string _toggleConfirmPassword = string.Empty;
|
private string _toggleConfirmPassword = string.Empty;
|
||||||
private string _confirmPassword = string.Empty;
|
private string _confirmPassword = string.Empty;
|
||||||
private string _hostEmail = string.Empty;
|
private string _hostEmail = string.Empty;
|
||||||
|
private string _hostName = string.Empty;
|
||||||
private List<SiteTemplate> _templates;
|
private List<SiteTemplate> _templates;
|
||||||
private string _template = Constants.DefaultSiteTemplate;
|
private string _template = Constants.DefaultSiteTemplate;
|
||||||
private bool _register = true;
|
private bool _register = true;
|
||||||
@@ -162,7 +169,7 @@
|
|||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
// include CSS
|
// include CSS
|
||||||
var content = "<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css\" integrity=\"sha512-t4GWSVZO1eC8BM339Xd7Uphw5s17a86tIZIj8qRxhnKub6WoyhnrxeCIMeAqBPgdZGlCcG2PrZjMc+Wr78+5Xg==\" crossorigin=\"anonymous\" type=\"text/css\"/>";
|
var content = $"<link rel=\"stylesheet\" href=\"{Constants.BootstrapStylesheetUrl}\" integrity=\"{Constants.BootstrapStylesheetIntegrity}\" crossorigin=\"anonymous\" type=\"text/css\"/>";
|
||||||
SiteState.AppendHeadContent(content);
|
SiteState.AppendHeadContent(content);
|
||||||
|
|
||||||
_togglePassword = SharedLocalizer["ShowPassword"];
|
_togglePassword = SharedLocalizer["ShowPassword"];
|
||||||
@@ -217,7 +224,7 @@
|
|||||||
{
|
{
|
||||||
// include JavaScript
|
// include JavaScript
|
||||||
var interop = new Interop(JSRuntime);
|
var interop = new Interop(JSRuntime);
|
||||||
await interop.IncludeScript("", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/js/bootstrap.bundle.min.js", "sha512-VK2zcvntEufaimc+efOYi622VN5ZacdnufnmX7zIhCPmjhKnOi9ZDMtg1/ug5l183f19gG1/cBstPO4D8N/Img==", "anonymous", "", "head");
|
await interop.IncludeScript("", Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous", "", "head");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,9 +243,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connectionString != "" && !string.IsNullOrEmpty(_hostUsername) && !string.IsNullOrEmpty(_hostPassword) && _hostPassword == _confirmPassword && !string.IsNullOrEmpty(_hostEmail) && _hostEmail.Contains("@"))
|
if (connectionString != "" && !string.IsNullOrEmpty(_hostUsername) && !string.IsNullOrEmpty(_hostPassword) && _hostPassword == _confirmPassword && !string.IsNullOrEmpty(_hostEmail) && _hostEmail.Contains("@") && !string.IsNullOrEmpty(_hostName))
|
||||||
{
|
{
|
||||||
if (await UserService.ValidatePasswordAsync(_hostPassword))
|
var result = await UserService.ValidateUserAsync(_hostUsername, _hostEmail, _hostPassword);
|
||||||
|
if (result.Succeeded)
|
||||||
{
|
{
|
||||||
_loadingDisplay = "";
|
_loadingDisplay = "";
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
@@ -255,12 +263,14 @@
|
|||||||
HostUsername = _hostUsername,
|
HostUsername = _hostUsername,
|
||||||
HostPassword = _hostPassword,
|
HostPassword = _hostPassword,
|
||||||
HostEmail = _hostEmail,
|
HostEmail = _hostEmail,
|
||||||
HostName = _hostUsername,
|
HostName = _hostName,
|
||||||
TenantName = TenantNames.Master,
|
TenantName = TenantNames.Master,
|
||||||
IsNewTenant = true,
|
IsNewTenant = true,
|
||||||
SiteName = Constants.DefaultSite,
|
SiteName = Constants.DefaultSite,
|
||||||
Register = _register,
|
Register = _register,
|
||||||
SiteTemplate = _template
|
SiteTemplate = _template,
|
||||||
|
RenderMode = RenderModes.Static,
|
||||||
|
Runtime = Runtimes.Server
|
||||||
};
|
};
|
||||||
|
|
||||||
var installation = await InstallationService.Install(config);
|
var installation = await InstallationService.Install(config);
|
||||||
@@ -276,7 +286,7 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_message = Localizer["Message.Password.Invalid"];
|
_message = string.Join("<br />", result.Errors.Select(i => !string.IsNullOrEmpty(i.Value) ? i.Value : Localizer[i.Key]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -12,9 +12,10 @@
|
|||||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
||||||
{
|
{
|
||||||
string url = NavigateUrl(p.Path);
|
string url = NavigateUrl(p.Path);
|
||||||
<div class="col-md-2 mx-auto text-center mb-3">
|
<div class="col-md-2 mx-auto text-center my-3">
|
||||||
<NavLink class="nav-link text-primary" href="@url" Match="NavLinkMatch.All">
|
<NavLink class="nav-link text-body" href="@url" Match="NavLinkMatch.All">
|
||||||
<h2><span class="@p.Icon" aria-hidden="true"></span></h2>@SharedLocalizer[p.Name]
|
<h2><span class="@p.Icon" aria-hidden="true"></span></h2>
|
||||||
|
<div class="lead">@((MarkupString)SharedLocalizer[p.Name].ToString().Replace(" ", "<br />"))</div>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -26,6 +27,7 @@
|
|||||||
private List<Page> _pages;
|
private List<Page> _pages;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
||||||
|
public override string RenderMode => RenderModes.Static;
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IFileService FileService
|
@inject IFileService FileService
|
||||||
@inject IFolderService FolderService
|
@inject IFolderService FolderService
|
||||||
|
@inject ISettingService SettingService
|
||||||
@inject IStringLocalizer<Add> Localizer
|
@inject IStringLocalizer<Add> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@@ -80,6 +81,7 @@
|
|||||||
{
|
{
|
||||||
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 (_url == string.Empty || _folderId == -1)
|
if (_url == string.Empty || _folderId == -1)
|
||||||
@@ -93,7 +95,7 @@
|
|||||||
_name = _url.Substring(_url.LastIndexOf("/", StringComparison.Ordinal) + 1);
|
_name = _url.Substring(_url.LastIndexOf("/", StringComparison.Ordinal) + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Constants.UploadableFiles.Split(',').Contains(Path.GetExtension(_name).ToLower().Replace(".", "")))
|
if (!PageState.Site.UploadableFiles.Split(',').Contains(Path.GetExtension(_name).ToLower().Replace(".", "")))
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Message.Download.InvalidExtension"], MessageType.Warning);
|
AddModuleMessage(Localizer["Message.Download.InvalidExtension"], MessageType.Warning);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -40,10 +40,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveFile">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveFile">@SharedLocalizer["Save"]</button>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
<br />
|
@if (_name.ToLower().EndsWith(".zip"))
|
||||||
<br />
|
{
|
||||||
|
<button type="button" class="btn btn-primary mx-1" @onclick="UnzipFile">Unzip</button>
|
||||||
|
}
|
||||||
|
<br /><br />
|
||||||
<AuditInfo CreatedBy="@_createdBy" CreatedOn="@_createdOn" ModifiedBy="@_modifiedBy" ModifiedOn="@_modifiedOn"></AuditInfo>
|
<AuditInfo CreatedBy="@_createdBy" CreatedOn="@_createdOn" ModifiedBy="@_modifiedBy" ModifiedOn="@_modifiedOn"></AuditInfo>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
@@ -126,4 +130,18 @@
|
|||||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task UnzipFile()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await FileService.UnzipFileAsync(_fileId);
|
||||||
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Unzipping File {FileId} {Error}", _fileId, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.File.Unzip"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -8,27 +8,41 @@
|
|||||||
|
|
||||||
@if (_folders != null)
|
@if (_folders != null)
|
||||||
{
|
{
|
||||||
|
<TabStrip>
|
||||||
|
<TabPanel Name="Settings" ResourceKey="Settings" Heading="Settings">
|
||||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="parent" HelpText="Select the parent folder" ResourceKey="Parent">Parent: </Label>
|
<Label Class="col-sm-3" For="parent" HelpText="Select the parent folder" ResourceKey="Parent">Parent: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="parent" class="form-select" @bind="@_parentId" required>
|
@if (_parentId == -1)
|
||||||
@if (PageState.QueryString.ContainsKey("id"))
|
|
||||||
{
|
{
|
||||||
|
<select id="parent" class="form-select" @bind="@_parentId" required>
|
||||||
<option value="-1"><@Localizer["NoParent"]></option>
|
<option value="-1"><@Localizer["NoParent"]></option>
|
||||||
|
</select>
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<select id="parent" class="form-select" @bind="@_parentId" required>
|
||||||
@foreach (Folder folder in _folders)
|
@foreach (Folder folder in _folders)
|
||||||
{
|
{
|
||||||
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
|
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
|
}
|
||||||
</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="name" HelpText="Enter the folder name" ResourceKey="Name">Name: </Label>
|
<Label Class="col-sm-3" For="name" HelpText="Enter the folder name" ResourceKey="Name">Name: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
|
@if (_isSystem)
|
||||||
|
{
|
||||||
|
<input id="name" class="form-control" @bind="@_name" readonly />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
<input id="name" class="form-control" @bind="@_name" maxlength="256" required />
|
<input id="name" class="form-control" @bind="@_name" maxlength="256" required />
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -47,12 +61,6 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="imagesizes" HelpText="Enter a list of image sizes which can be generated dynamically from uploaded images (ie. 200x200,400x400). Use * to indicate the folder supports all image sizes." ResourceKey="ImageSizes">Image Sizes: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input id="imagesizes" class="form-control" @bind="@_imagesizes" maxlength="512" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="capacity" HelpText="Enter the maximum folder capacity (in megabytes). Specify zero if the capacity is unlimited." ResourceKey="Capacity">Capacity: </Label>
|
<Label Class="col-sm-3" For="capacity" HelpText="Enter the maximum folder capacity (in megabytes). Specify zero if the capacity is unlimited." ResourceKey="Capacity">Capacity: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -60,14 +68,35 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<div class="col-sm-12">
|
<Label Class="col-sm-3" For="cachecontrol" HelpText="Optionally provide a Cache-Control directive for this folder. For example 'public, max-age=60' indicates that files in this folder should be cached for 60 seconds. Please note that when caching is enabled, changes to files will not be immediately reflected in the UI." ResourceKey="CacheControl">Caching: </Label>
|
||||||
<Label Class="col-sm-3" For="permissions" HelpText="Select the permissions you want for the folder" ResourceKey="Permissions">Permissions: </Label>
|
<div class="col-sm-9">
|
||||||
|
<input id="cachecontrol" class="form-control" @bind="@_cachecontrol" maxlength="50" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="imagesizes" HelpText="Optionally enter a list of image sizes which can be generated dynamically from uploaded images (ie. 200x200,400x400). Use * to indicate the folder supports all image sizes (not recommended)." ResourceKey="ImageSizes">Image Sizes: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="imagesizes" class="form-control" @bind="@_imagesizes" maxlength="512" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (PageState.QueryString.ContainsKey("id"))
|
||||||
|
{
|
||||||
|
<br />
|
||||||
|
<AuditInfo CreatedBy="@_createdBy" CreatedOn="@_createdOn" ModifiedBy="@_modifiedBy" ModifiedOn="@_modifiedOn"></AuditInfo>
|
||||||
|
}
|
||||||
|
</form>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel Name="Permissions" ResourceKey="Permissions" Heading="Permissions">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
<PermissionGrid EntityName="@EntityNames.Folder" PermissionNames="@(PermissionNames.Browse + "," + PermissionNames.View + "," + PermissionNames.Edit)" PermissionList="@_permissions" @ref="_permissionGrid" />
|
<PermissionGrid EntityName="@EntityNames.Folder" PermissionNames="@(PermissionNames.Browse + "," + PermissionNames.View + "," + PermissionNames.Edit)" PermissionList="@_permissions" @ref="_permissionGrid" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</TabPanel>
|
||||||
</form>
|
</TabStrip>
|
||||||
|
|
||||||
|
<br />
|
||||||
@if (!_isSystem)
|
@if (!_isSystem)
|
||||||
{
|
{
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveFolder">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveFolder">@SharedLocalizer["Save"]</button>
|
||||||
@@ -79,12 +108,6 @@
|
|||||||
@((MarkupString)" ")
|
@((MarkupString)" ")
|
||||||
<ActionDialog Header="Delete Folder" Message="Are You Sure You Wish To Delete This Folder?" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteFolder())" ResourceKey="DeleteFolder" />
|
<ActionDialog Header="Delete Folder" Message="Are You Sure You Wish To Delete This Folder?" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteFolder())" ResourceKey="DeleteFolder" />
|
||||||
}
|
}
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
@if (PageState.QueryString.ContainsKey("id"))
|
|
||||||
{
|
|
||||||
<AuditInfo CreatedBy="@_createdBy" CreatedOn="@_createdOn" ModifiedBy="@_modifiedBy" ModifiedOn="@_modifiedOn"></AuditInfo>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@@ -95,8 +118,9 @@
|
|||||||
private int _parentId = -1;
|
private int _parentId = -1;
|
||||||
private string _name;
|
private string _name;
|
||||||
private string _type = FolderTypes.Private;
|
private string _type = FolderTypes.Private;
|
||||||
private string _imagesizes = string.Empty;
|
|
||||||
private string _capacity = "0";
|
private string _capacity = "0";
|
||||||
|
private string _cachecontrol = string.Empty;
|
||||||
|
private string _imagesizes = string.Empty;
|
||||||
private bool _isSystem;
|
private bool _isSystem;
|
||||||
private List<Permission> _permissions = null;
|
private List<Permission> _permissions = null;
|
||||||
private string _createdBy;
|
private string _createdBy;
|
||||||
@@ -127,8 +151,9 @@
|
|||||||
_parentId = folder.ParentId ?? -1;
|
_parentId = folder.ParentId ?? -1;
|
||||||
_name = folder.Name;
|
_name = folder.Name;
|
||||||
_type = folder.Type;
|
_type = folder.Type;
|
||||||
_imagesizes = folder.ImageSizes;
|
|
||||||
_capacity = folder.Capacity.ToString();
|
_capacity = folder.Capacity.ToString();
|
||||||
|
_cachecontrol = folder.CacheControl;
|
||||||
|
_imagesizes = folder.ImageSizes;
|
||||||
_isSystem = folder.IsSystem;
|
_isSystem = folder.IsSystem;
|
||||||
_permissions = folder.PermissionList;
|
_permissions = folder.PermissionList;
|
||||||
_createdBy = folder.CreatedBy;
|
_createdBy = folder.CreatedBy;
|
||||||
@@ -170,6 +195,7 @@
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Folder folder;
|
Folder folder;
|
||||||
|
|
||||||
if (_folderId != -1)
|
if (_folderId != -1)
|
||||||
{
|
{
|
||||||
folder = await FolderService.GetFolderAsync(_folderId);
|
folder = await FolderService.GetFolderAsync(_folderId);
|
||||||
@@ -179,8 +205,6 @@
|
|||||||
folder = new Folder();
|
folder = new Folder();
|
||||||
}
|
}
|
||||||
|
|
||||||
folder.SiteId = PageState.Site.SiteId;
|
|
||||||
|
|
||||||
if (_parentId == -1)
|
if (_parentId == -1)
|
||||||
{
|
{
|
||||||
folder.ParentId = null;
|
folder.ParentId = null;
|
||||||
@@ -190,10 +214,19 @@
|
|||||||
folder.ParentId = _parentId;
|
folder.ParentId = _parentId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check for duplicate folder names
|
||||||
|
if (_folders.Any(item => item.ParentId == folder.ParentId && item.Name == _name && item.FolderId != _folderId))
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.Folder.Duplicate"], MessageType.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
folder.SiteId = PageState.Site.SiteId;
|
||||||
folder.Name = _name;
|
folder.Name = _name;
|
||||||
folder.Type = _type;
|
folder.Type = _type;
|
||||||
folder.ImageSizes = _imagesizes;
|
|
||||||
folder.Capacity = int.Parse(_capacity);
|
folder.Capacity = int.Parse(_capacity);
|
||||||
|
folder.CacheControl = _cachecontrol;
|
||||||
|
folder.ImageSizes = _imagesizes;
|
||||||
folder.IsSystem = _isSystem;
|
folder.IsSystem = _isSystem;
|
||||||
folder.PermissionList = _permissionGrid.GetPermissionList();
|
folder.PermissionList = _permissionGrid.GetPermissionList();
|
||||||
|
|
||||||
@@ -208,7 +241,6 @@
|
|||||||
|
|
||||||
if (folder != null)
|
if (folder != null)
|
||||||
{
|
{
|
||||||
await FolderService.UpdateFolderOrderAsync(folder.SiteId, folder.FolderId, folder.ParentId);
|
|
||||||
await logger.LogInformation("Folder Saved {Folder}", folder);
|
await logger.LogInformation("Folder Saved {Folder}", folder);
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
}
|
}
|
||||||
@@ -243,20 +275,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!isparent)
|
if (!isparent)
|
||||||
{
|
|
||||||
var files = await FileService.GetFilesAsync(_folderId);
|
|
||||||
if (files.Count == 0)
|
|
||||||
{
|
{
|
||||||
await FolderService.DeleteFolderAsync(_folderId);
|
await FolderService.DeleteFolderAsync(_folderId);
|
||||||
await logger.LogInformation("Folder Deleted {Folder}", _folderId);
|
await logger.LogInformation("Folder Deleted {Folder}", _folderId);
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
|
||||||
AddModuleMessage(Localizer["Message.Folder.Files.InvalidDelete"], MessageType.Warning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Message.Folder.Subfolders.InvalidDelete"], MessageType.Warning);
|
AddModuleMessage(Localizer["Message.Folder.Subfolders.InvalidDelete"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,32 +3,44 @@
|
|||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IFolderService FolderService
|
@inject IFolderService FolderService
|
||||||
@inject IFileService FileService
|
@inject IFileService FileService
|
||||||
|
@inject ISettingService SettingService
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@if (_files != null)
|
@if (_files == null)
|
||||||
{
|
{
|
||||||
<div class="container">
|
<p>
|
||||||
<div class="row mb-1 align-items-center">
|
<em>@SharedLocalizer["Loading"]</em>
|
||||||
<div class="col-sm-2">
|
</p>
|
||||||
<label class="control-label">@Localizer["Folder"] </label>
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<TabStrip>
|
||||||
|
<TabPanel Name="Files" Heading="Files" ResourceKey="Files">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md mb-1">
|
||||||
|
<ActionLink Action="Edit" Text="Add Folder" Class="btn btn-secondary" ResourceKey="AddFolder" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-md-8 mb-1">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">@Localizer["Folder"]:</span>
|
||||||
<select class="form-select" @onchange="(e => FolderChanged(e))">
|
<select class="form-select" @onchange="(e => FolderChanged(e))">
|
||||||
@foreach (Folder folder in _folders)
|
@foreach (Folder folder in _folders)
|
||||||
{
|
{
|
||||||
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
|
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
|
||||||
<div class="col-sm-4">
|
|
||||||
<ActionLink Action="Edit" Text="Edit Folder" Class="btn btn-secondary" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="EditFolder" />
|
<ActionLink Action="Edit" Text="Edit Folder" Class="btn btn-secondary" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="EditFolder" />
|
||||||
<ActionLink Action="Edit" Text="Add Folder" Class="btn btn-secondary" ResourceKey="AddFolder" />
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md mb-1 text-end">
|
||||||
<ActionLink Action="Add" Text="Upload Files" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="UploadFiles" />
|
<ActionLink Action="Add" Text="Upload Files" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="UploadFiles" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<Pager Items="@_files">
|
@if (_files.Count != 0)
|
||||||
|
{
|
||||||
|
<Pager Items="@_files" SearchProperties="Name">
|
||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
@@ -41,15 +53,42 @@
|
|||||||
<td><ActionLink Action="Details" Text="Edit" Parameters="@($"id=" + context.FileId.ToString())" ResourceKey="Details" /></td>
|
<td><ActionLink Action="Details" Text="Edit" Parameters="@($"id=" + context.FileId.ToString())" ResourceKey="Details" /></td>
|
||||||
<td><ActionDialog Header="Delete File" Message="@string.Format(Localizer["Confirm.File.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteFile(context))" ResourceKey="DeleteFile" /></td>
|
<td><ActionDialog Header="Delete File" Message="@string.Format(Localizer["Confirm.File.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteFile(context))" ResourceKey="DeleteFile" /></td>
|
||||||
<td><a href="@context.Url" target="_new">@context.Name</a></td>
|
<td><a href="@context.Url" target="_new">@context.Name</a></td>
|
||||||
<td>@context.ModifiedOn</td>
|
<td>@UtcToLocal(context.ModifiedOn)</td>
|
||||||
<td>@context.Extension.ToUpper() @SharedLocalizer["File"]</td>
|
<td>@context.Extension.ToUpper() @SharedLocalizer["File"]</td>
|
||||||
<td>@string.Format("{0:0.00}", ((decimal)context.Size / 1000)) KB</td>
|
<td>@string.Format("{0:0.00}", ((decimal)context.Size / 1000)) KB</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
@if (_files.Count == 0)
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
<div class="text-center">@Localizer["NoFiles"]</div>
|
<div class="text-center">@Localizer["NoFiles"]</div>
|
||||||
}
|
}
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings" Security="SecurityAccessLevel.Admin">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="imageExt" HelpText="Enter a comma separated list of image file extensions" ResourceKey="ImageExtensions">Image Extensions: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="imageExt" spellcheck="false" class="form-control" @bind="@_imageFiles" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="uploadableFileExt" HelpText="Enter a comma separated list of uploadable file extensions" ResourceKey="UploadableFileExtensions">Uploadable File Extensions: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="uploadableFileExt" spellcheck="false" class="form-control" @bind="@_uploadableFiles" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="maxChunkSize" HelpText="Files are split into chunks to streamline the upload process. Specify the maximum chunk size in MB (note that higher chunk sizes should only be used on faster networks)." ResourceKey="MaxChunkSize">Max Upload Chunk Size (MB): </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="maxChunkSize" type="number" min="1" max="10" step="1" class="form-control" @bind="@_maxChunkSize" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||||
|
</TabPanel>
|
||||||
|
</TabStrip>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@@ -57,6 +96,10 @@
|
|||||||
private int _folderId = -1;
|
private int _folderId = -1;
|
||||||
private List<File> _files;
|
private List<File> _files;
|
||||||
|
|
||||||
|
private string _imageFiles = string.Empty;
|
||||||
|
private string _uploadableFiles = string.Empty;
|
||||||
|
private int _maxChunkSize = 1;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
@@ -70,6 +113,13 @@
|
|||||||
_folderId = _folders[0].FolderId;
|
_folderId = _folders[0].FolderId;
|
||||||
await GetFiles();
|
await GetFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
_imageFiles = SettingService.GetSetting(settings, "ImageFiles", Constants.ImageFiles);
|
||||||
|
_imageFiles = (string.IsNullOrEmpty(_imageFiles)) ? Constants.ImageFiles : _imageFiles;
|
||||||
|
_uploadableFiles = SettingService.GetSetting(settings, "UploadableFiles", Constants.UploadableFiles);
|
||||||
|
_uploadableFiles = (string.IsNullOrEmpty(_uploadableFiles)) ? Constants.UploadableFiles : _uploadableFiles;
|
||||||
|
_maxChunkSize = int.Parse(SettingService.GetSetting(settings, "MaxChunkSize", "1"));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -114,4 +164,23 @@
|
|||||||
AddModuleMessage(string.Format(Localizer["Error.File.Delete"], file.Name), MessageType.Error);
|
AddModuleMessage(string.Format(Localizer["Error.File.Delete"], file.Name), MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task SaveSiteSettings()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
settings = SettingService.SetSetting(settings, "ImageFiles", (_imageFiles != Constants.ImageFiles) ? _imageFiles.Replace(" ", "") : "", false);
|
||||||
|
settings = SettingService.SetSetting(settings, "UploadableFiles", (_uploadableFiles != Constants.UploadableFiles) ? _uploadableFiles.Replace(" ", "") : "", false);
|
||||||
|
settings = SettingService.SetSetting(settings, "MaxChunkSize", _maxChunkSize.ToString(), false);
|
||||||
|
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||||
|
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
19
Oqtane.Client/Modules/Admin/Files/ModuleInfo.cs
Normal file
19
Oqtane.Client/Modules/Admin/Files/ModuleInfo.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using Oqtane.Documentation;
|
||||||
|
using Oqtane.Models;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
|
||||||
|
namespace Oqtane.Modules.Admin.Files
|
||||||
|
{
|
||||||
|
[PrivateApi("Mark this as private, since it's not very useful in the public docs")]
|
||||||
|
public class ModuleInfo : IModule
|
||||||
|
{
|
||||||
|
public ModuleDefinition ModuleDefinition => new ModuleDefinition
|
||||||
|
{
|
||||||
|
Name = "File Management",
|
||||||
|
Description = "File Management",
|
||||||
|
Version = Constants.Version,
|
||||||
|
Categories = "Admin",
|
||||||
|
ServerManagerType = "Oqtane.Modules.Admin.Files.Manager.FileManager, Oqtane.Server"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
<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" @bind="@_interval" maxlength="4" required />
|
<input id="runs-every" class="form-control mb-1" @bind="@_interval" maxlength="4" required />
|
||||||
<select id="runs-every" class="form-select" @bind="@_frequency" required>
|
<select id="runs-every" class="form-select" @bind="@_frequency" required>
|
||||||
<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>
|
||||||
@@ -45,7 +45,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" class="form-control" @bind="@_retentionHistory" maxlength="4" required />
|
<input id="retention" type="number" min="0" step ="1" class="form-control" @bind="@_retentionHistory" maxlength="3" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -132,13 +132,13 @@
|
|||||||
_isEnabled = job.IsEnabled.ToString();
|
_isEnabled = job.IsEnabled.ToString();
|
||||||
_interval = job.Interval.ToString();
|
_interval = job.Interval.ToString();
|
||||||
_frequency = job.Frequency;
|
_frequency = job.Frequency;
|
||||||
_startDate = Utilities.UtcAsLocalDate(job.StartDate);
|
_startDate = UtcToLocal(job.StartDate);
|
||||||
_startTime = Utilities.UtcAsLocalDateTime(job.StartDate);
|
_startTime = UtcToLocal(job.StartDate);
|
||||||
_endDate = Utilities.UtcAsLocalDate(job.EndDate);
|
_endDate = UtcToLocal(job.EndDate);
|
||||||
_endTime = Utilities.UtcAsLocalDateTime(job.EndDate);
|
_endTime = UtcToLocal(job.EndDate);
|
||||||
_retentionHistory = job.RetentionHistory.ToString();
|
_retentionHistory = job.RetentionHistory.ToString();
|
||||||
_nextDate = Utilities.UtcAsLocalDate(job.NextExecution);
|
_nextDate = UtcToLocal(job.NextExecution);
|
||||||
_nextTime = Utilities.UtcAsLocalDateTime(job.NextExecution);
|
_nextTime = UtcToLocal(job.NextExecution);
|
||||||
createdby = job.CreatedBy;
|
createdby = job.CreatedBy;
|
||||||
createdon = job.CreatedOn;
|
createdon = job.CreatedOn;
|
||||||
modifiedby = job.ModifiedBy;
|
modifiedby = job.ModifiedBy;
|
||||||
@@ -154,6 +154,11 @@
|
|||||||
|
|
||||||
private async Task SaveJob()
|
private async Task SaveJob()
|
||||||
{
|
{
|
||||||
|
if (!Utilities.ValidateEffectiveExpiryDates(_startDate, _endDate))
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.StartEndDateError"], MessageType.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
validated = true;
|
validated = true;
|
||||||
var interop = new Interop(JSRuntime);
|
var interop = new Interop(JSRuntime);
|
||||||
if (await interop.FormValid(form))
|
if (await interop.FormValid(form))
|
||||||
@@ -171,10 +176,10 @@
|
|||||||
{
|
{
|
||||||
job.Interval = int.Parse(_interval);
|
job.Interval = int.Parse(_interval);
|
||||||
}
|
}
|
||||||
job.StartDate = Utilities.LocalDateAndTimeAsUtc(_startDate, _startTime);
|
job.StartDate = LocalToUtc(_startDate.Value.Date.Add(_startTime.Value.TimeOfDay));
|
||||||
job.EndDate = Utilities.LocalDateAndTimeAsUtc(_endDate, _endTime);
|
job.EndDate = LocalToUtc(_endDate.Value.Date.Add(_endTime.Value.TimeOfDay));
|
||||||
job.RetentionHistory = int.Parse(_retentionHistory);
|
job.RetentionHistory = int.Parse(_retentionHistory);
|
||||||
job.NextExecution = Utilities.LocalDateAndTimeAsUtc(_nextDate, _nextTime);
|
job.NextExecution = LocalToUtc(_nextDate.Value.Date.Add(_nextTime.Value.TimeOfDay));
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,14 +10,11 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<ActionLink Action="Log" Class="btn btn-secondary" Text="View Logs" ResourceKey="ViewJobs" />
|
<button type="button" class="btn btn-secondary" @onclick="Refresh">@Localizer["Refresh.Text"]</button>
|
||||||
<button type="button" class="btn btn-secondary" @onclick="(async () => await Refresh())">@Localizer["Refresh.Text"]</button>
|
|
||||||
<br />
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<Pager Items="@_jobs">
|
<Pager Items="@_jobs" SearchProperties="Name">
|
||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th>@SharedLocalizer["Name"]</th>
|
<th>@SharedLocalizer["Name"]</th>
|
||||||
@@ -27,13 +24,12 @@ else
|
|||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.JobId.ToString())" ResourceKey="EditJob" /></td>
|
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.JobId.ToString())" ResourceKey="EditJob" /></td>
|
||||||
<td><ActionDialog Header="Delete Job" Message="Are You Sure You Wish To Delete This Job?" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteJob(context))" ResourceKey="DeleteJob" /></td>
|
<td><ActionLink Action="Log" Text="Log" Class="btn btn-secondary" Parameters="@($"id=" + context.JobId.ToString())" ResourceKey="JobLog" /></td>
|
||||||
<td><ActionLink Action="Log" Class="btn btn-secondary" Parameters="@($"id=" + context.JobId.ToString())" ResourceKey="JobLog" /></td>
|
|
||||||
<td>@context.Name</td>
|
<td>@context.Name</td>
|
||||||
<td>@DisplayStatus(context.IsEnabled, context.IsExecuting)</td>
|
<td>@DisplayStatus(context.IsEnabled, context.IsExecuting)</td>
|
||||||
<td>@DisplayFrequency(context.Interval, context.Frequency)</td>
|
<td>@DisplayFrequency(context.Interval, context.Frequency)</td>
|
||||||
<td>@context.NextExecution?.ToLocalTime()</td>
|
<td>@UtcToLocal(context.NextExecution)</td>
|
||||||
<td>
|
<td>
|
||||||
@if (context.IsStarted)
|
@if (context.IsStarted)
|
||||||
{
|
{
|
||||||
@@ -46,6 +42,9 @@ else
|
|||||||
</td>
|
</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<ActionLink Action="Log" Class="btn btn-secondary" Text="View All Logs" ResourceKey="ViewLogs" />
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@@ -53,13 +52,14 @@ else
|
|||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.Host; } }
|
public override SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.Host; } }
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await GetJobs();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GetJobs()
|
||||||
{
|
{
|
||||||
_jobs = await JobService.GetJobsAsync();
|
_jobs = await JobService.GetJobsAsync();
|
||||||
if (_jobs.Count == 0)
|
|
||||||
{
|
|
||||||
AddModuleMessage(string.Format(Localizer["Message.NoJobs"], NavigateUrl("admin/system")), MessageType.Warning);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string DisplayStatus(bool isEnabled, bool isExecuting)
|
private string DisplayStatus(bool isEnabled, bool isExecuting)
|
||||||
@@ -112,22 +112,6 @@ else
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DeleteJob(Job job)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await JobService.DeleteJobAsync(job.JobId);
|
|
||||||
await logger.LogInformation("Job Deleted {Job}", job);
|
|
||||||
_jobs = await JobService.GetJobsAsync();
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error Deleting Job {Job} {Error}", job, ex.Message);
|
|
||||||
AddModuleMessage(Localizer["Error.Job.Delete"], MessageType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task StartJob(int jobId)
|
private async Task StartJob(int jobId)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -164,7 +148,8 @@ else
|
|||||||
|
|
||||||
private async Task Refresh()
|
private async Task Refresh()
|
||||||
{
|
{
|
||||||
_jobs = await JobService.GetJobsAsync();
|
ShowProgressIndicator();
|
||||||
StateHasChanged();
|
await GetJobs();
|
||||||
|
HideProgressIndicator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,9 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="Refresh">@Localizer["Refresh"]</button>
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
<Pager Items="@_jobLogs">
|
<Pager Items="@_jobLogs">
|
||||||
<Header>
|
<Header>
|
||||||
<th>@SharedLocalizer["Name"]</th>
|
<th>@SharedLocalizer["Name"]</th>
|
||||||
@@ -20,8 +23,8 @@ else
|
|||||||
<Row>
|
<Row>
|
||||||
<td>@context.Job.Name</td>
|
<td>@context.Job.Name</td>
|
||||||
<td>@DisplayStatus(context.Job.IsExecuting, context.Succeeded)</td>
|
<td>@DisplayStatus(context.Job.IsExecuting, context.Succeeded)</td>
|
||||||
<td>@context.StartDate</td>
|
<td>@UtcToLocal(context.StartDate)</td>
|
||||||
<td>@context.FinishDate</td>
|
<td>@UtcToLocal(context.FinishDate)</td>
|
||||||
</Row>
|
</Row>
|
||||||
<Detail>
|
<Detail>
|
||||||
<td colspan="4">@((MarkupString)context.Notes)</td>
|
<td colspan="4">@((MarkupString)context.Notes)</td>
|
||||||
@@ -36,14 +39,17 @@ else
|
|||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
{
|
{
|
||||||
_jobLogs = await JobLogService.GetJobLogsAsync();
|
await GetJobLogs();
|
||||||
|
|
||||||
if (PageState.QueryString.ContainsKey("id"))
|
|
||||||
{
|
|
||||||
_jobLogs = _jobLogs.Where(item => item.JobId == Int32.Parse(PageState.QueryString["id"])).ToList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_jobLogs = _jobLogs.OrderByDescending(item => item.JobLogId).ToList();
|
private async Task GetJobLogs()
|
||||||
|
{
|
||||||
|
var jobId = -1;
|
||||||
|
if (PageState.QueryString.ContainsKey("id"))
|
||||||
|
{
|
||||||
|
jobId = int.Parse(PageState.QueryString["id"]);
|
||||||
|
}
|
||||||
|
_jobLogs = await JobLogService.GetJobLogsAsync(jobId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string DisplayStatus(bool isExecuting, bool? succeeded)
|
private string DisplayStatus(bool isExecuting, bool? succeeded)
|
||||||
@@ -67,4 +73,11 @@ else
|
|||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task Refresh()
|
||||||
|
{
|
||||||
|
ShowProgressIndicator();
|
||||||
|
await GetJobLogs();
|
||||||
|
HideProgressIndicator();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
@namespace Oqtane.Modules.Admin.Languages
|
@namespace Oqtane.Modules.Admin.Languages
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@using System.Globalization
|
@using System.Globalization
|
||||||
@using Microsoft.AspNetCore.Localization
|
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject ILocalizationService LocalizationService
|
@inject ILocalizationService LocalizationService
|
||||||
@inject ILanguageService LanguageService
|
@inject ILanguageService LanguageService
|
||||||
@@ -94,7 +93,6 @@ else
|
|||||||
var language = new Language
|
var language = new Language
|
||||||
{
|
{
|
||||||
SiteId = PageState.Page.SiteId,
|
SiteId = PageState.Page.SiteId,
|
||||||
Name = CultureInfo.GetCultureInfo(_code).DisplayName,
|
|
||||||
Code = _code,
|
Code = _code,
|
||||||
IsDefault = (_default == null ? false : Boolean.Parse(_default))
|
IsDefault = (_default == null ? false : Boolean.Parse(_default))
|
||||||
};
|
};
|
||||||
@@ -130,9 +128,7 @@ else
|
|||||||
{
|
{
|
||||||
var interop = new Interop(JSRuntime);
|
var interop = new Interop(JSRuntime);
|
||||||
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
|
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
|
||||||
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360);
|
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360, true, "Lax");
|
||||||
|
|
||||||
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
109
Oqtane.Client/Modules/Admin/Languages/Edit.razor
Normal file
109
Oqtane.Client/Modules/Admin/Languages/Edit.razor
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
@namespace Oqtane.Modules.Admin.Languages
|
||||||
|
@inherits ModuleBase
|
||||||
|
@using System.Globalization
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject ILocalizationService LocalizationService
|
||||||
|
@inject ILanguageService LanguageService
|
||||||
|
@inject IPackageService PackageService
|
||||||
|
@inject IStringLocalizer<Edit> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
@if (_code == null)
|
||||||
|
{
|
||||||
|
<p><em>@SharedLocalizer["Loading"]</em></p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<TabStrip>
|
||||||
|
<TabPanel Name="Manage" ResourceKey="Manage" Heading="Manage">
|
||||||
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="name" HelpText="Name Of The Language" ResourceKey="Name">Name:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="code" class="form-control" @bind="@_code" readonly/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="default" HelpText="Indicates Whether Or Not This Language Is The Default For The Site" ResourceKey="IsDefault">Default?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="default" class="form-select" @bind="@_default" required>
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SaveLanguage">@SharedLocalizer["Save"]</button>
|
||||||
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
</form>
|
||||||
|
</TabPanel>
|
||||||
|
</TabStrip>
|
||||||
|
}
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private ElementReference form;
|
||||||
|
private bool validated = false;
|
||||||
|
private int _languageId = -1;
|
||||||
|
private string _code = string.Empty;
|
||||||
|
private string _cultureName = string.Empty;
|
||||||
|
private string _default = "False";
|
||||||
|
private List<Language> _languages;
|
||||||
|
|
||||||
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
_languageId = Int32.Parse(PageState.QueryString["id"]);
|
||||||
|
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId, Constants.ClientId);
|
||||||
|
Language language = _languages.Where(x => x.LanguageId == _languageId).FirstOrDefault();
|
||||||
|
if (language != null)
|
||||||
|
{
|
||||||
|
_code = language.Code;
|
||||||
|
_cultureName = language.Name;
|
||||||
|
_default = language.IsDefault.ToString();
|
||||||
|
if (language.SiteId == null)
|
||||||
|
{
|
||||||
|
language.SiteId = PageState.Site.SiteId;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveLanguage()
|
||||||
|
{
|
||||||
|
Language language = _languages.Where(x => x.LanguageId == _languageId).FirstOrDefault();
|
||||||
|
if (language != null)
|
||||||
|
{
|
||||||
|
language.IsDefault = Boolean.Parse(_default);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await LanguageService.EditLanguageAsync(language);
|
||||||
|
|
||||||
|
if (language.IsDefault)
|
||||||
|
{
|
||||||
|
await SetCultureAsync(language.Code);
|
||||||
|
}
|
||||||
|
|
||||||
|
await logger.LogInformation("Language Edited {Language}", language);
|
||||||
|
|
||||||
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Editing Language {Language} {Error}", language, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.Language.Edit"], MessageType.Error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SetCultureAsync(string culture)
|
||||||
|
{
|
||||||
|
if (culture != CultureInfo.CurrentUICulture.Name)
|
||||||
|
{
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
|
||||||
|
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360, true, "Lax");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -14,8 +14,9 @@ else
|
|||||||
{
|
{
|
||||||
<ActionLink Action="Add" Text="Add Language" ResourceKey="AddLanguage" />
|
<ActionLink Action="Add" Text="Add Language" ResourceKey="AddLanguage" />
|
||||||
|
|
||||||
<Pager Items="@_languages">
|
<Pager Items="@_languages" SearchProperties="Name,Code">
|
||||||
<Header>
|
<Header>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th>@SharedLocalizer["Name"]</th>
|
<th>@SharedLocalizer["Name"]</th>
|
||||||
<th>@Localizer["Code"]</th>
|
<th>@Localizer["Code"]</th>
|
||||||
@@ -27,6 +28,12 @@ else
|
|||||||
}
|
}
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
|
<td>
|
||||||
|
@if (!context.IsDefault)
|
||||||
|
{
|
||||||
|
<ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.LanguageId.ToString())" ResourceKey="EditLanguage" />
|
||||||
|
}
|
||||||
|
</td>
|
||||||
<td><ActionDialog Header="Delete Language" Message="@string.Format(Localizer["Confirm.Language.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteLanguage(context))" Disabled="@((context.IsDefault && _languages.Count > 2) || context.Code == Constants.DefaultCulture)" ResourceKey="DeleteLanguage" /></td>
|
<td><ActionDialog Header="Delete Language" Message="@string.Format(Localizer["Confirm.Language.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteLanguage(context))" Disabled="@((context.IsDefault && _languages.Count > 2) || context.Code == Constants.DefaultCulture)" ResourceKey="DeleteLanguage" /></td>
|
||||||
<td>@context.Name</td>
|
<td>@context.Name</td>
|
||||||
<td>@context.Code</td>
|
<td>@context.Code</td>
|
||||||
|
|||||||
@@ -8,11 +8,12 @@
|
|||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<AuthorizeView Roles="@RoleNames.Registered">
|
@if (PageState.User != null)
|
||||||
<Authorizing>
|
{
|
||||||
<text>...</text>
|
<ModuleMessage Message="@Localizer["Info.SignedIn"]" Type="MessageType.Info" />
|
||||||
</Authorizing>
|
}
|
||||||
<NotAuthorized>
|
else
|
||||||
|
{
|
||||||
@if (!twofactor)
|
@if (!twofactor)
|
||||||
{
|
{
|
||||||
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
@@ -20,31 +21,45 @@
|
|||||||
@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" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
|
||||||
<br /><br />
|
<br />
|
||||||
|
|
||||||
|
<br />
|
||||||
}
|
}
|
||||||
@if (_allowsitelogin)
|
@if (_allowsitelogin)
|
||||||
{
|
{
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<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" 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 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" required />
|
<input id="password" type="@_passwordtype" name="Password" class="form-control" placeholder="@Localizer["Password.Placeholder"]" @bind="@_password" @bind:event="oninput" required />
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group mt-2">
|
<div class="form-group mt-2">
|
||||||
|
@if (!_alwaysremember)
|
||||||
|
{
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<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">Remember Me?</Label>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<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="Cancel">@SharedLocalizer["Cancel"]</button>
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
<br /><br />
|
<br />
|
||||||
|
|
||||||
|
<br />
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["ForgotPassword"]</button>
|
<button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["ForgotPassword"]</button>
|
||||||
|
@if (PageState.Site.AllowRegistration)
|
||||||
|
{
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<NavLink href="@NavigateUrl("register")">@Localizer["Register"]</NavLink>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -63,8 +78,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
</NotAuthorized>
|
}
|
||||||
</AuthorizeView>
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private bool _allowsitelogin = true;
|
private bool _allowsitelogin = true;
|
||||||
@@ -78,11 +92,11 @@
|
|||||||
private string _passwordtype = "password";
|
private string _passwordtype = "password";
|
||||||
private string _togglepassword = string.Empty;
|
private string _togglepassword = string.Empty;
|
||||||
private bool _remember = false;
|
private bool _remember = false;
|
||||||
|
private bool _alwaysremember = false;
|
||||||
private string _code = string.Empty;
|
private string _code = string.Empty;
|
||||||
|
|
||||||
private string _returnUrl = 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>()
|
||||||
{
|
{
|
||||||
@@ -93,20 +107,9 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (PageState.QueryString.ContainsKey("returnurl"))
|
|
||||||
{
|
|
||||||
_returnUrl = PageState.QueryString["returnurl"];
|
|
||||||
}
|
|
||||||
|
|
||||||
_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"));
|
||||||
|
_alwaysremember = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AlwaysRemember", "false"));
|
||||||
if (_allowexternallogin && !_allowsitelogin)
|
|
||||||
{
|
|
||||||
// redirect to external login
|
|
||||||
NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + _returnUrl), true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
|
|
||||||
@@ -141,7 +144,7 @@
|
|||||||
user = await UserService.VerifyEmailAsync(user, PageState.QueryString["token"]);
|
user = await UserService.VerifyEmailAsync(user, PageState.QueryString["token"]);
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
await logger.LogInformation(LogFunction.Security, "Email Verified For For Username {Username}", _username);
|
await logger.LogInformation(LogFunction.Security, "Email Verified For Username {Username}", _username);
|
||||||
AddModuleMessage(Localizer["Success.Account.Verified"], MessageType.Info);
|
AddModuleMessage(Localizer["Success.Account.Verified"], MessageType.Info);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -169,12 +172,15 @@
|
|||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
{
|
{
|
||||||
if (firstRender && PageState.User == null && _allowsitelogin)
|
if (firstRender && PageState.User == null && _allowsitelogin)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(username.Id)) // ensure username is visible in UI
|
||||||
{
|
{
|
||||||
await username.FocusAsync();
|
await username.FocusAsync();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// redirect logged in user to specified page
|
// redirect logged in user to specified page
|
||||||
if (PageState.User != null)
|
if (PageState.User != null && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||||
}
|
}
|
||||||
@@ -193,6 +199,7 @@
|
|||||||
|
|
||||||
if (!twofactor)
|
if (!twofactor)
|
||||||
{
|
{
|
||||||
|
_remember = _alwaysremember || _remember;
|
||||||
user = await UserService.LoginUserAsync(user, hybrid, _remember);
|
user = await UserService.LoginUserAsync(user, hybrid, _remember);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -200,28 +207,31 @@
|
|||||||
user = await UserService.VerifyTwoFactorAsync(user, _code);
|
user = await UserService.VerifyTwoFactorAsync(user, _code);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.IsAuthenticated)
|
if (user != null && user.IsAuthenticated)
|
||||||
{
|
{
|
||||||
await logger.LogInformation(LogFunction.Security, "Login Successful For Username {Username}", _username);
|
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(WebUtility.UrlDecode(_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 = _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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "required" || user.TwoFactorRequired)
|
if (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "required" || (user != null && user.TwoFactorRequired))
|
||||||
{
|
{
|
||||||
twofactor = true;
|
twofactor = true;
|
||||||
validated = false;
|
validated = false;
|
||||||
@@ -256,7 +266,7 @@
|
|||||||
|
|
||||||
private void Cancel()
|
private void Cancel()
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(_returnUrl);
|
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Forgot()
|
private async Task Forgot()
|
||||||
@@ -324,7 +334,7 @@
|
|||||||
|
|
||||||
private void ExternalLogin()
|
private void ExternalLogin()
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + _returnUrl), true);
|
NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + WebUtility.UrlEncode(PageState.ReturnUrl)), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,7 +141,7 @@
|
|||||||
var log = await LogService.GetLogAsync(_logId);
|
var log = await LogService.GetLogAsync(_logId);
|
||||||
if (log != null)
|
if (log != null)
|
||||||
{
|
{
|
||||||
_logDate = log.LogDate.ToString(CultureInfo.CurrentCulture);
|
_logDate = UtcToLocal(log.LogDate).Value.ToString(CultureInfo.CurrentCulture);
|
||||||
_level = log.Level;
|
_level = log.Level;
|
||||||
_feature = log.Feature;
|
_feature = log.Feature;
|
||||||
_function = log.Function;
|
_function = log.Function;
|
||||||
|
|||||||
@@ -63,8 +63,8 @@ else
|
|||||||
<th>@Localizer["Function"]</th>
|
<th>@Localizer["Function"]</th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td class="@GetClass(context.Function)"><ActionLink Action="Detail" Parameters="@($"/{context.LogId}")" ReturnUrl="@(NavigateUrl(PageState.Page.Path, AddUrlParameters(_level, _function, _rows, _page)))" ResourceKey="LogDetails" /></td>
|
<td class="@GetClass(context.Function)"><ActionLink Action="Detail" Text="Details" Parameters="@($"/{context.LogId}")" ReturnUrl="@(NavigateUrl(PageState.Page.Path, AddUrlParameters(_level, _function, _rows, _page)))" ResourceKey="LogDetails" /></td>
|
||||||
<td class="@GetClass(context.Function)">@context.LogDate</td>
|
<td class="@GetClass(context.Function)">@UtcToLocal(context.LogDate)</td>
|
||||||
<td class="@GetClass(context.Function)">@context.Level</td>
|
<td class="@GetClass(context.Function)">@context.Level</td>
|
||||||
<td class="@GetClass(context.Function)">@context.Feature</td>
|
<td class="@GetClass(context.Function)">@context.Feature</td>
|
||||||
<td class="@GetClass(context.Function)">@context.Function</td>
|
<td class="@GetClass(context.Function)">@context.Function</td>
|
||||||
@@ -81,12 +81,13 @@ else
|
|||||||
<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 days of events to retain" ResourceKey="Retention">Retention (Days): </Label>
|
<Label Class="col-sm-3" For="retention" HelpText="Number of days of events to retain" ResourceKey="Retention">Retention (Days): </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="retention" class="form-control" @bind="@_retention" />
|
<input id="retention" type="number" min="0" step="1" class="form-control" @bind="@_retention" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
|
<ActionDialog Header="Clear Events" Message="Are You Sure You Wish To Remove All Log Events?" Action="DeleteLogs" Class="btn btn-danger" OnClick="@(async () => await DeleteLogs())" ResourceKey="DeleteLogs" />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabStrip>
|
</TabStrip>
|
||||||
}
|
}
|
||||||
@@ -97,7 +98,7 @@ else
|
|||||||
private string _rows = "10";
|
private string _rows = "10";
|
||||||
private int _page = 1;
|
private int _page = 1;
|
||||||
private List<Log> _logs;
|
private List<Log> _logs;
|
||||||
private string _retention = "";
|
private int _retention = 30;
|
||||||
|
|
||||||
public override string UrlParametersTemplate => "/{level}/{function}/{rows}/{page}";
|
public override string UrlParametersTemplate => "/{level}/{function}/{rows}/{page}";
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
@@ -126,7 +127,7 @@ else
|
|||||||
await GetLogs();
|
await GetLogs();
|
||||||
|
|
||||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
_retention = SettingService.GetSetting(settings, "LogRetention", "30");
|
_retention = int.Parse( SettingService.GetSetting(settings, "LogRetention", "30"));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -218,7 +219,7 @@ else
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
settings = SettingService.SetSetting(settings, "LogRetention", _retention, true);
|
settings = SettingService.SetSetting(settings, "LogRetention", _retention.ToString(), true);
|
||||||
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||||
|
|
||||||
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
||||||
@@ -230,6 +231,21 @@ else
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task DeleteLogs()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await LogService.DeleteLogsAsync(PageState.Site.SiteId);
|
||||||
|
await GetLogs();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Deleting Logs {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.DeleteLogs"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OnPageChange(int page)
|
private void OnPageChange(int page)
|
||||||
{
|
{
|
||||||
_page = page;
|
_page = page;
|
||||||
|
|||||||
@@ -66,6 +66,7 @@
|
|||||||
<div class="container-fluid px-0">
|
<div class="container-fluid px-0">
|
||||||
<div class="row g-0 mb-2">
|
<div class="row g-0 mb-2">
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
|
<a href="@context.ProductUrl" target="_blank">
|
||||||
@if (context.LogoUrl != null)
|
@if (context.LogoUrl != null)
|
||||||
{
|
{
|
||||||
<img src="@context.LogoUrl" class="img-fluid" alt="@context.Name" />
|
<img src="@context.LogoUrl" class="img-fluid" alt="@context.Name" />
|
||||||
@@ -74,6 +75,7 @@
|
|||||||
{
|
{
|
||||||
<img src="/package.png" class="img-fluid" alt="@context.Name" />
|
<img src="/package.png" class="img-fluid" alt="@context.Name" />
|
||||||
}
|
}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-8 text-end">
|
<div class="col-8 text-end">
|
||||||
<small>@SharedLocalizer["Search.Version"]:</small> <strong>@context.Version</strong>
|
<small>@SharedLocalizer["Search.Version"]:</small> <strong>@context.Version</strong>
|
||||||
@@ -123,7 +125,7 @@
|
|||||||
<TabPanel Name="Upload" ResourceKey="Upload" Heading="Upload">
|
<TabPanel Name="Upload" ResourceKey="Upload" Heading="Upload">
|
||||||
<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" HelpText="Upload one or more module packages. Once they are uploaded click Install to complete the installation." ResourceKey="Module">Module: </Label>
|
<Label Class="col-sm-3" HelpText="Upload one or more module packages." ResourceKey="Module">Module: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" OnUpload="OnUpload" />
|
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" OnUpload="OnUpload" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,13 +13,13 @@
|
|||||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="owner" HelpText="Enter the name of the organization who is developing this module. It should not contain spaces or punctuation." ResourceKey="OwnerName">Owner Name: </Label>
|
<Label Class="col-sm-3" For="owner" HelpText="Enter the name of the organization who is developing this module. It should not contain spaces or punctuation or the word oqtane." ResourceKey="OwnerName">Owner Name: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="owner" class="form-control" @bind="@_owner" required />
|
<input id="owner" class="form-control" @bind="@_owner" required />
|
||||||
</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="module" HelpText="Enter a name for this module. It should not contain spaces or punctuation." ResourceKey="ModuleName">Module Name: </Label>
|
<Label Class="col-sm-3" For="module" HelpText="Enter a name for this module. It should not contain spaces or punctuation or the word oqtane." ResourceKey="ModuleName">Module Name: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="module" class="form-control" @bind="@_module" required />
|
<input id="module" class="form-control" @bind="@_module" required />
|
||||||
</div>
|
</div>
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="description" HelpText="Enter a short description for the module" ResourceKey="Description">Description: </Label>
|
<Label Class="col-sm-3" For="description" HelpText="Enter a short description for the module" ResourceKey="Description">Description: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<textarea id="description" class="form-control" @bind="@_description" rows="3" maxlength="2000" required></textarea>
|
<textarea id="description" class="form-control" @bind="@_description" rows="3" maxlength="2000"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -111,6 +111,8 @@
|
|||||||
private async Task CreateModule()
|
private async Task CreateModule()
|
||||||
{
|
{
|
||||||
validated = true;
|
validated = true;
|
||||||
|
_owner = _owner.Trim();
|
||||||
|
_module = _module.Trim();
|
||||||
var interop = new Interop(JSRuntime);
|
var interop = new Interop(JSRuntime);
|
||||||
if (await interop.FormValid(form))
|
if (await interop.FormValid(form))
|
||||||
{
|
{
|
||||||
@@ -118,6 +120,7 @@
|
|||||||
{
|
{
|
||||||
if (IsValid(_owner) && IsValid(_module) && _owner != _module && _template != "-")
|
if (IsValid(_owner) && IsValid(_module) && _owner != _module && _template != "-")
|
||||||
{
|
{
|
||||||
|
if (string.IsNullOrEmpty(_description)) _description = _module;
|
||||||
if (IsValidXML(_description))
|
if (IsValidXML(_description))
|
||||||
{
|
{
|
||||||
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
||||||
@@ -156,7 +159,7 @@
|
|||||||
private bool IsValidXML(string description)
|
private bool IsValidXML(string description)
|
||||||
{
|
{
|
||||||
// must contain letters, digits, or spaces
|
// must contain letters, digits, or spaces
|
||||||
return Regex.IsMatch(description, "^[A-Za-z0-9 ]+$");
|
return Regex.IsMatch(description, "^[A-Za-z0-9 .,!?]+$");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TemplateChanged(ChangeEventArgs e)
|
private void TemplateChanged(ChangeEventArgs e)
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
@namespace Oqtane.Modules.Admin.ModuleDefinitions
|
@namespace Oqtane.Modules.Admin.ModuleDefinitions
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@using System.Globalization
|
@using System.Globalization
|
||||||
@using Microsoft.AspNetCore.Localization
|
|
||||||
@inject IModuleDefinitionService ModuleDefinitionService
|
@inject IModuleDefinitionService ModuleDefinitionService
|
||||||
@inject IPackageService PackageService
|
@inject IPackageService PackageService
|
||||||
@inject ILanguageService LanguageService
|
@inject ILanguageService LanguageService
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IStringLocalizer<Edit> Localizer
|
@inject IStringLocalizer<Edit> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
@inject IPageModuleService PageModuleService
|
||||||
|
@inject IModuleService ModuleService
|
||||||
|
@inject IPageService PageService
|
||||||
|
|
||||||
@if (_initialized)
|
@if (_initialized)
|
||||||
{
|
{
|
||||||
@@ -30,7 +32,7 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="categories" HelpText="Comma delimited list of module categories" ResourceKey="Categories">Categories: </Label>
|
<Label Class="col-sm-3" For="categories" HelpText="Comma delimited list of module categories" ResourceKey="Categories">Categories: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="categories" class="form-control" @bind="@_categories" maxlength="200" required />
|
<input id="categories" class="form-control" @bind="@_categories" maxlength="200" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -61,24 +63,7 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this module was installed. This value must be specified within the module's IModule interface specification." ResourceKey="PackageName">Package Name: </Label>
|
<Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this module was installed. This value must be specified within the module's IModule interface specification." ResourceKey="PackageName">Package Name: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@if (!string.IsNullOrEmpty(_packagename))
|
|
||||||
{
|
|
||||||
<div class="input-group">
|
|
||||||
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
|
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
|
||||||
@if (string.IsNullOrEmpty(_packageurl))
|
|
||||||
{
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="ValidatePackage">@Localizer["Validate"]</button>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<a href="@_packageurl" target="_blank" class="btn btn-primary">@SharedLocalizer["Download"]</a>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -88,7 +73,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="url" HelpText="The reference url of the module" ResourceKey="ReferenceUrl">Reference Url: </Label>
|
<Label Class="col-sm-3" For="url" HelpText="The url of the module" ResourceKey="Url">Url: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="url" class="form-control" @bind="@_url" disabled />
|
<input id="url" class="form-control" @bind="@_url" disabled />
|
||||||
</div>
|
</div>
|
||||||
@@ -131,6 +116,18 @@
|
|||||||
<button type="button" class="btn btn-success" @onclick="SaveModuleDefinition">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveModuleDefinition">@SharedLocalizer["Save"]</button>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
<TabPanel Name="Pages" ResourceKey="Pages" Heading="Pages">
|
||||||
|
<Pager Items="@_pagesWithModules" RowClass="align-middle">
|
||||||
|
<Header>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
|
<th>@SharedLocalizer["Name"]</th>
|
||||||
|
</Header>
|
||||||
|
<Row>
|
||||||
|
<td><button type="button" class="btn btn-secondary" @onclick="@(async () => NavigationManager.NavigateTo(Browse(context)))">@Localizer["Browse"]</button></td>
|
||||||
|
<td>@(string.IsNullOrEmpty(context.Title) ? @context.Name : @context.Title )</td>
|
||||||
|
</Row>
|
||||||
|
</Pager>
|
||||||
|
</TabPanel>
|
||||||
<TabPanel Name="Translations" ResourceKey="Translations" Heading="Translations">
|
<TabPanel Name="Translations" ResourceKey="Translations" Heading="Translations">
|
||||||
@if (_languages != null && _languages.Count > 0)
|
@if (_languages != null && _languages.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -230,7 +227,6 @@
|
|||||||
private string _moduledefinitionname = "";
|
private string _moduledefinitionname = "";
|
||||||
private string _version;
|
private string _version;
|
||||||
private string _packagename = "";
|
private string _packagename = "";
|
||||||
private string _packageurl = "";
|
|
||||||
private string _owner = "";
|
private string _owner = "";
|
||||||
private string _url = "";
|
private string _url = "";
|
||||||
private string _contact = "";
|
private string _contact = "";
|
||||||
@@ -240,6 +236,7 @@
|
|||||||
private DateTime _createdon;
|
private DateTime _createdon;
|
||||||
private string _modifiedby;
|
private string _modifiedby;
|
||||||
private DateTime _modifiedon;
|
private DateTime _modifiedon;
|
||||||
|
private List<Page> _pagesWithModules;
|
||||||
|
|
||||||
#pragma warning disable 649
|
#pragma warning disable 649
|
||||||
private PermissionGrid _permissionGrid;
|
private PermissionGrid _permissionGrid;
|
||||||
@@ -291,6 +288,19 @@
|
|||||||
_languages = _languages.OrderBy(item => item.Name).ToList();
|
_languages = _languages.OrderBy(item => item.Name).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get distinct pages where module exists
|
||||||
|
var modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
|
||||||
|
var distinctPageIds = modules
|
||||||
|
.Where(md => md.ModuleDefinition?.ModuleDefinitionId == _moduleDefinitionId && md.IsDeleted == false)
|
||||||
|
.Select(md => md.PageId)
|
||||||
|
.Distinct();
|
||||||
|
|
||||||
|
// retrieve the pages which contain the module
|
||||||
|
var pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||||
|
_pagesWithModules = pages
|
||||||
|
.Where(pg => distinctPageIds.Contains(pg.PageId) && pg.IsDeleted == false)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -417,27 +427,5 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ValidatePackage()
|
private string Browse(Page page) => string.IsNullOrEmpty(page.Url) ? NavigateUrl(page.Path) : page.Url;
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var package = await PackageService.GetPackageAsync(_packagename, _version, true);
|
|
||||||
if (package == null || string.IsNullOrEmpty(package.PackageUrl))
|
|
||||||
{
|
|
||||||
AddModuleMessage(Localizer["Message.Validate"], MessageType.Warning);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_packageurl = package.PackageUrl;
|
|
||||||
AddModuleMessage(Localizer["Message.Download"], MessageType.Info);
|
|
||||||
}
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packagename, _version);
|
|
||||||
AddModuleMessage(Localizer["Error.Validate"], MessageType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
@namespace Oqtane.Modules.Admin.ModuleDefinitions
|
@namespace Oqtane.Modules.Admin.ModuleDefinitions
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject IModuleService ModuleService
|
||||||
@inject IModuleDefinitionService ModuleDefinitionService
|
@inject IModuleDefinitionService ModuleDefinitionService
|
||||||
@inject IPackageService PackageService
|
@inject IPackageService PackageService
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@@ -16,8 +17,8 @@ else
|
|||||||
<div class="row mb-3 align-items-center">
|
<div class="row mb-3 align-items-center">
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<ActionLink Action="Add" Text="Install Module" ResourceKey="InstallModule" />
|
<ActionLink Action="Add" Text="Install Module" ResourceKey="InstallModule" />
|
||||||
@((MarkupString)" ")
|
<ActionLink Action="Create" Text="Create Module" ResourceKey="CreateModule" Class="btn btn-secondary ps-2" />
|
||||||
<ActionLink Action="Create" Text="Create Module" ResourceKey="CreateModule" Class="btn btn-secondary" />
|
<button type="button" class="btn btn-secondary pw-2" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<select class="form-select" @onchange="(e => CategoryChanged(e))">
|
<select class="form-select" @onchange="(e => CategoryChanged(e))">
|
||||||
@@ -50,7 +51,7 @@ else
|
|||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ModuleDefinitionId.ToString())" ResourceKey="EditModule" /></td>
|
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.ModuleDefinitionId.ToString())" ResourceKey="EditModule" /></td>
|
||||||
<td>
|
<td>
|
||||||
@if (context.AssemblyName != Constants.ClientId)
|
@if (context.AssemblyName != Constants.ClientId)
|
||||||
{
|
{
|
||||||
@@ -70,7 +71,7 @@ else
|
|||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@if (context.AssemblyName == Constants.ClientId || PageState.Modules.Where(m => m.ModuleDefinition?.ModuleDefinitionId == context.ModuleDefinitionId).FirstOrDefault() != null)
|
@if (context.AssemblyName == Constants.ClientId || _modules.Where(m => m.ModuleDefinition?.ModuleDefinitionId == context.ModuleDefinitionId).FirstOrDefault() != null)
|
||||||
{
|
{
|
||||||
<span>@SharedLocalizer["Yes"]</span>
|
<span>@SharedLocalizer["Yes"]</span>
|
||||||
}
|
}
|
||||||
@@ -99,6 +100,7 @@ else
|
|||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
private List<Module> _modules;
|
||||||
private List<ModuleDefinition> _allModuleDefinitions;
|
private List<ModuleDefinition> _allModuleDefinitions;
|
||||||
private List<ModuleDefinition> _moduleDefinitions;
|
private List<ModuleDefinition> _moduleDefinitions;
|
||||||
private List<Package> _packages;
|
private List<Package> _packages;
|
||||||
@@ -111,6 +113,7 @@ else
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
_modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
|
||||||
_allModuleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
|
_allModuleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
|
||||||
_categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',')).Distinct().ToList();
|
_categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',')).Distinct().ToList();
|
||||||
await LoadModuleDefinitions();
|
await LoadModuleDefinitions();
|
||||||
@@ -217,4 +220,27 @@ else
|
|||||||
_category = (string)e.Value;
|
_category = (string)e.Value;
|
||||||
await LoadModuleDefinitions();
|
await LoadModuleDefinitions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task Synchronize()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ShowProgressIndicator();
|
||||||
|
foreach (var moduleDefinition in _moduleDefinitions)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(moduleDefinition.PackageName) && !_packages.Any(item => item.PackageId == moduleDefinition.PackageName))
|
||||||
|
{
|
||||||
|
var package = await PackageService.GetPackageAsync(moduleDefinition.PackageName, moduleDefinition.Version, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HideProgressIndicator();
|
||||||
|
AddModuleMessage(Localizer["Success.Module.Synchronize"], MessageType.Success);
|
||||||
|
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Synchronizing Modules {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.Module.Synchronize"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,24 +5,57 @@
|
|||||||
@inject IStringLocalizer<Export> Localizer
|
@inject IStringLocalizer<Export> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<div class="container">
|
<TabStrip>
|
||||||
|
<TabPanel Name="Content" Heading="Content" ResourceKey="Content">
|
||||||
|
<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="content" HelpText="The Exported Module Content" ResourceKey="Content">Content: </Label>
|
<Label Class="col-sm-3" For="content" HelpText="Select the Export option and you will be able to view the module content" ResourceKey="Content">Content: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<textarea id="content" class="form-control" @bind="@_content" rows="5" readonly></textarea>
|
<textarea id="content" class="form-control" @bind="@_content" rows="5" readonly></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="ExportText">@Localizer["Export"]</button>
|
||||||
|
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel Name="File" Heading="File" ResourceKey="File">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="folder" HelpText="Select a folder where you wish to save the exported content" ResourceKey="Folder">Folder: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<FileManager ShowFiles="false" ShowUpload="false" @ref="_filemanager" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="filename" HelpText="Specify a name for the file (without an extension)" ResourceKey="Filename">Filename: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="content" type="text" class="form-control" @bind="@_filename" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="ExportFile">@Localizer["Export"]</button>
|
||||||
|
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
</TabPanel>
|
||||||
|
</TabStrip>
|
||||||
|
|
||||||
|
|
||||||
<button type="button" class="btn btn-success" @onclick="ExportModule">@Localizer["Export"]</button>
|
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private string _content = string.Empty;
|
private string _content = string.Empty;
|
||||||
|
private FileManager _filemanager;
|
||||||
|
private string _filename = string.Empty;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||||
public override string Title => "Export Content";
|
public override string Title => "Export Content";
|
||||||
|
|
||||||
private async Task ExportModule()
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
_filename = Utilities.GetFriendlyUrl(ModuleState.Title);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ExportText()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -35,4 +68,34 @@
|
|||||||
AddModuleMessage(Localizer["Error.Module.Export"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.Module.Export"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ExportFile()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var folderid = _filemanager.GetFolderId();
|
||||||
|
if (folderid != -1 && !string.IsNullOrEmpty(_filename))
|
||||||
|
{
|
||||||
|
var fileid = await ModuleService.ExportModuleAsync(ModuleState.ModuleId, PageState.Page.PageId, folderid, _filename);
|
||||||
|
if (fileid != -1)
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Success.Content.Export"], MessageType.Success);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Error.Module.Export"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.Content.Export"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Exporting Module {ModuleId} {Error}", ModuleState.ModuleId, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.Module.Export"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,22 +2,29 @@
|
|||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IModuleService ModuleService
|
@inject IModuleService ModuleService
|
||||||
|
@inject IFileService FileService
|
||||||
@inject IStringLocalizer<Import> Localizer
|
@inject IStringLocalizer<Import> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="content" HelpText="Enter The Module Content To Import" ResourceKey="Content">Content: </Label>
|
<Label Class="col-sm-3" For="file" HelpText="Optionally upload or select a file to import for this module" ResourceKey="File">File: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<FileManager Filter="json" OnSelectFile="OnSelectFile" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="content" HelpText="Provide the module content to import" ResourceKey="Content">Content: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<textarea id="content" class="form-control" @bind="@_content" rows="5" required></textarea>
|
<textarea id="content" class="form-control" @bind="@_content" rows="5" required></textarea>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="ImportModule">@Localizer["Import"]</button>
|
<button type="button" class="btn btn-success" @onclick="ImportModule">@Localizer["Import"]</button>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@@ -28,6 +35,12 @@
|
|||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||||
public override string Title => "Import Content";
|
public override string Title => "Import Content";
|
||||||
|
|
||||||
|
private async Task OnSelectFile(int fileId)
|
||||||
|
{
|
||||||
|
var bytes = await FileService.DownloadFileAsync(fileId);
|
||||||
|
_content = System.Text.Encoding.UTF8.GetString(bytes, 0, bytes.Length);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ImportModule()
|
private async Task ImportModule()
|
||||||
{
|
{
|
||||||
validated = true;
|
validated = true;
|
||||||
|
|||||||
@@ -3,13 +3,16 @@
|
|||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IThemeService ThemeService
|
@inject IThemeService ThemeService
|
||||||
|
@inject IPageService PageService
|
||||||
@inject IModuleService ModuleService
|
@inject IModuleService ModuleService
|
||||||
@inject IPageModuleService PageModuleService
|
@inject IPageModuleService PageModuleService
|
||||||
@inject IStringLocalizer<Settings> Localizer
|
@inject IStringLocalizer<Settings> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
@if (_initialized)
|
||||||
<TabStrip>
|
{
|
||||||
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
|
<TabStrip ActiveTab="@_activetab">
|
||||||
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
|
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
|
||||||
@if (_containers != null)
|
@if (_containers != null)
|
||||||
{
|
{
|
||||||
@@ -26,6 +29,17 @@
|
|||||||
<input id="title" type="text" class="form-control" @bind="@_title" required />
|
<input id="title" type="text" class="form-control" @bind="@_title" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="pane" HelpText="The pane where the module will be displayed" ResourceKey="Pane">Pane: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select class="form-select" @bind="@_pane">
|
||||||
|
@foreach (string pane in PageState.Page.Panes)
|
||||||
|
{
|
||||||
|
<option value="@pane">@pane Pane</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="container" HelpText="Select the module's container" ResourceKey="Container">Container: </Label>
|
<Label Class="col-sm-3" For="container" HelpText="Select the module's container" ResourceKey="Container">Container: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -37,6 +51,18 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this module 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 module 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="allpages" HelpText="Indicate if this module should be displayed on all pages" ResourceKey="DisplayOnAllPages">Display On All Pages? </Label>
|
<Label Class="col-sm-3" For="allpages" HelpText="Indicate if this module should be displayed on all pages" ResourceKey="DisplayOnAllPages">Display On All Pages? </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -56,22 +82,41 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
foreach (Page p in PageState.Pages)
|
if (_pages != null)
|
||||||
|
{
|
||||||
|
foreach (Page p in _pages)
|
||||||
{
|
{
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, p.PermissionList))
|
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, p.PermissionList))
|
||||||
{
|
{
|
||||||
<option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option>
|
<option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<br />
|
||||||
|
<Section Name="ModuleContent" Heading="Content" ResourceKey="ModuleContent">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="moduleheader" HelpText="Optionally provide content to be injected above the module instance" ResourceKey="Header">Header: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea id="moduleheader" class="form-control" @bind="@_header" rows="3" maxlength="4000"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="modulefooter" HelpText="Optionally provide content to be injected below the module instance" ResourceKey="Footer">Footer: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea id="modulefooter" class="form-control" @bind="@_footer" rows="3" maxlength="4000"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
}
|
}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Permissions" ResourceKey="Permissions">
|
<TabPanel Name="Permissions" Heading="Permissions" ResourceKey="Permissions">
|
||||||
@if (_permissions != null)
|
@if (_permissions != null)
|
||||||
{
|
{
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@@ -97,30 +142,34 @@
|
|||||||
</TabStrip>
|
</TabStrip>
|
||||||
<br />
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveModule">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveModule">@SharedLocalizer["Save"]</button>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@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 {
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||||
public override string Title => "Module Settings";
|
|
||||||
|
|
||||||
|
private bool _initialized = false;
|
||||||
private ElementReference form;
|
private ElementReference form;
|
||||||
private bool validated = false;
|
private bool validated = false;
|
||||||
private List<ThemeControl> _containers = new List<ThemeControl>();
|
private List<ThemeControl> _containers = new List<ThemeControl>();
|
||||||
private string _module;
|
private string _module;
|
||||||
private string _title;
|
private string _title;
|
||||||
|
private string _pane;
|
||||||
private string _containerType;
|
private string _containerType;
|
||||||
private string _allPages = "false";
|
private string _allPages = "false";
|
||||||
|
private string _header = "";
|
||||||
|
private string _footer = "";
|
||||||
private string _permissionNames = "";
|
private string _permissionNames = "";
|
||||||
private List<Permission> _permissions = null;
|
private List<Permission> _permissions = null;
|
||||||
private string _pageId;
|
private string _pageId;
|
||||||
private PermissionGrid _permissionGrid;
|
private PermissionGrid _permissionGrid;
|
||||||
private Type _moduleSettingsType;
|
private Type _moduleSettingsType;
|
||||||
private object _moduleSettings;
|
private object _moduleSettings;
|
||||||
private string _moduleSettingsTitle = "Module Settings";
|
private string _moduleSettingsTitle;
|
||||||
private RenderFragment ModuleSettingsComponent { get; set; }
|
private RenderFragment ModuleSettingsComponent { get; set; }
|
||||||
private Type _containerSettingsType;
|
private Type _containerSettingsType;
|
||||||
private object _containerSettings;
|
private object _containerSettings;
|
||||||
@@ -129,34 +178,55 @@
|
|||||||
private DateTime createdon;
|
private DateTime createdon;
|
||||||
private string modifiedby;
|
private string modifiedby;
|
||||||
private DateTime modifiedon;
|
private DateTime modifiedon;
|
||||||
|
private DateTime? _effectivedate = null;
|
||||||
|
private DateTime? _expirydate = null;
|
||||||
|
private List<Page> _pages;
|
||||||
|
private string _activetab = "";
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
_module = ModuleState.ModuleDefinition.Name;
|
SetModuleTitle(Localizer["ModuleSettings.Title"]);
|
||||||
_title = ModuleState.Title;
|
_moduleSettingsTitle = Localizer["ModuleSettings.Heading"];
|
||||||
|
|
||||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, PageState.Page.ThemeType);
|
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, PageState.Page.ThemeType);
|
||||||
_containerType = ModuleState.ContainerType;
|
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||||
_allPages = ModuleState.AllPages.ToString();
|
|
||||||
_permissions = ModuleState.PermissionList;
|
|
||||||
_pageId = ModuleState.PageId.ToString();
|
|
||||||
createdby = ModuleState.CreatedBy;
|
|
||||||
createdon = ModuleState.CreatedOn;
|
|
||||||
modifiedby = ModuleState.ModifiedBy;
|
|
||||||
modifiedon = ModuleState.ModifiedOn;
|
|
||||||
|
|
||||||
if (ModuleState.ModuleDefinition != null)
|
var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
|
||||||
|
|
||||||
|
_pageId = pagemodule.PageId.ToString();
|
||||||
|
_title = pagemodule.Title;
|
||||||
|
_pane = pagemodule.Pane;
|
||||||
|
_containerType = pagemodule.ContainerType;
|
||||||
|
if (string.IsNullOrEmpty(_containerType))
|
||||||
{
|
{
|
||||||
_permissionNames = ModuleState.ModuleDefinition?.PermissionNames;
|
_containerType = (!string.IsNullOrEmpty(PageState.Page.DefaultContainerType)) ? PageState.Page.DefaultContainerType : PageState.Site.DefaultContainerType;
|
||||||
|
}
|
||||||
|
_header = pagemodule.Header;
|
||||||
|
_footer = pagemodule.Footer;
|
||||||
|
_effectivedate = Utilities.UtcAsLocalDate(pagemodule.EffectiveDate);
|
||||||
|
_expirydate = Utilities.UtcAsLocalDate(pagemodule.ExpiryDate);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(ModuleState.ModuleDefinition.SettingsType))
|
_allPages = pagemodule.Module.AllPages.ToString();
|
||||||
|
createdby = pagemodule.Module.CreatedBy;
|
||||||
|
createdon = pagemodule.Module.CreatedOn;
|
||||||
|
modifiedby = pagemodule.Module.ModifiedBy;
|
||||||
|
modifiedon = pagemodule.Module.ModifiedOn;
|
||||||
|
_permissions = pagemodule.Module.PermissionList;
|
||||||
|
|
||||||
|
if (pagemodule.Module.ModuleDefinition != null)
|
||||||
|
{
|
||||||
|
_module = pagemodule.Module.ModuleDefinition.Name;
|
||||||
|
_permissionNames = pagemodule.Module.ModuleDefinition?.PermissionNames;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(pagemodule.Module.ModuleDefinition.SettingsType))
|
||||||
{
|
{
|
||||||
// module settings type explicitly declared in IModule interface
|
// module settings type explicitly declared in IModule interface
|
||||||
_moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.SettingsType);
|
_moduleSettingsType = Type.GetType(pagemodule.Module.ModuleDefinition.SettingsType);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// legacy support - module settings type determined by convention ( ie. existence of a "Settings.razor" component in module )
|
// legacy support - module settings type determined by convention ( ie. existence of a "Settings.razor" component in module )
|
||||||
_moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, PageState.Action), false, true);
|
_moduleSettingsType = Type.GetType(pagemodule.Module.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, PageState.Action), false, true);
|
||||||
}
|
}
|
||||||
if (_moduleSettingsType != null)
|
if (_moduleSettingsType != null)
|
||||||
{
|
{
|
||||||
@@ -169,14 +239,15 @@
|
|||||||
ModuleSettingsComponent = builder =>
|
ModuleSettingsComponent = builder =>
|
||||||
{
|
{
|
||||||
builder.OpenComponent(0, _moduleSettingsType);
|
builder.OpenComponent(0, _moduleSettingsType);
|
||||||
builder.AddComponentReferenceCapture(1, inst => { _moduleSettings = Convert.ChangeType(inst, _moduleSettingsType); });
|
builder.AddAttribute(1, "RenderModeBoundary", RenderModeBoundary);
|
||||||
|
builder.AddComponentReferenceCapture(2, inst => { _moduleSettings = Convert.ChangeType(inst, _moduleSettingsType); });
|
||||||
builder.CloseComponent();
|
builder.CloseComponent();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AddModuleMessage(string.Format(Localizer["Error.Module.Load"], ModuleState.ModuleDefinitionName), MessageType.Error);
|
AddModuleMessage(string.Format(Localizer["Error.Module.Load"], pagemodule.Module.ModuleDefinitionName), MessageType.Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
var theme = PageState.Site.Themes.FirstOrDefault(item => item.Containers.Any(themecontrol => themecontrol.TypeName.Equals(_containerType)));
|
var theme = PageState.Site.Themes.FirstOrDefault(item => item.Containers.Any(themecontrol => themecontrol.TypeName.Equals(_containerType)));
|
||||||
@@ -188,24 +259,37 @@
|
|||||||
ContainerSettingsComponent = builder =>
|
ContainerSettingsComponent = builder =>
|
||||||
{
|
{
|
||||||
builder.OpenComponent(0, _containerSettingsType);
|
builder.OpenComponent(0, _containerSettingsType);
|
||||||
builder.AddComponentReferenceCapture(1, inst => { _containerSettings = Convert.ChangeType(inst, _containerSettingsType); });
|
builder.AddAttribute(1, "RenderModeBoundary", RenderModeBoundary);
|
||||||
|
builder.AddComponentReferenceCapture(2, inst => { _containerSettings = Convert.ChangeType(inst, _containerSettingsType); });
|
||||||
builder.CloseComponent();
|
builder.CloseComponent();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SaveModule()
|
private async Task SaveModule()
|
||||||
{
|
{
|
||||||
|
|
||||||
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 (!string.IsNullOrEmpty(_title))
|
if (!string.IsNullOrEmpty(_title))
|
||||||
{
|
{
|
||||||
|
if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate))
|
||||||
|
{
|
||||||
|
AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
|
var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
|
||||||
pagemodule.PageId = int.Parse(_pageId);
|
pagemodule.PageId = int.Parse(_pageId);
|
||||||
pagemodule.Title = _title;
|
pagemodule.Title = _title;
|
||||||
|
pagemodule.Pane = _pane;
|
||||||
|
pagemodule.EffectiveDate = Utilities.LocalDateAndTimeAsUtc(_effectivedate);
|
||||||
|
pagemodule.ExpiryDate = Utilities.LocalDateAndTimeAsUtc(_expirydate);
|
||||||
pagemodule.ContainerType = (_containerType != "-") ? _containerType : string.Empty;
|
pagemodule.ContainerType = (_containerType != "-") ? _containerType : string.Empty;
|
||||||
if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Page.DefaultContainerType)
|
if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Page.DefaultContainerType)
|
||||||
{
|
{
|
||||||
@@ -215,10 +299,12 @@
|
|||||||
{
|
{
|
||||||
pagemodule.ContainerType = string.Empty;
|
pagemodule.ContainerType = string.Empty;
|
||||||
}
|
}
|
||||||
|
pagemodule.Header = _header;
|
||||||
|
pagemodule.Footer = _footer;
|
||||||
await PageModuleService.UpdatePageModuleAsync(pagemodule);
|
await PageModuleService.UpdatePageModuleAsync(pagemodule);
|
||||||
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
|
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
|
||||||
|
|
||||||
var module = ModuleState;
|
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();
|
||||||
@@ -243,17 +329,18 @@
|
|||||||
await containerSettingsControl.UpdateSettings();
|
await containerSettingsControl.UpdateSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
_activetab = "Settings";
|
||||||
AddModuleMessage(Localizer["Message.Required.Title"], MessageType.Warning);
|
AddModuleMessage(Localizer["Message.Required.Title"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
_activetab = "Settings";
|
||||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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" required />
|
<input id="name" class="form-control" @bind="@_name" maxlength="50" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="parent" class="form-select" value="@_parentid" @onchange="(e => ParentChanged(e))" required>
|
<select id="parent" class="form-select" value="@_parentid" @onchange="(e => ParentChanged(e))" required>
|
||||||
<option value="-1"><@Localizer["SiteRoot"]></option>
|
<option value="-1"><@Localizer["SiteRoot"]></option>
|
||||||
@foreach (Page page in PageState.Pages)
|
@foreach (Page page in _pages)
|
||||||
{
|
{
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, page.PermissionList))
|
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, page.PermissionList))
|
||||||
{
|
{
|
||||||
@@ -101,13 +101,13 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="path" HelpText="Optionally enter a url path for this page (ie. home ). If you do not provide a url path, the page name will be used. If the page is intended to be the root path specify '/'." ResourceKey="UrlPath">Url Path: </Label>
|
<Label Class="col-sm-3" For="path" HelpText="Optionally enter a url path for this page (ie. home ). If you do not provide a url path, the page name will be used. If the page is intended to be the root path specify '/'." ResourceKey="UrlPath">Url Path: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="path" class="form-control" @bind="@_path" />
|
<input id="path" class="form-control" @bind="@_path" maxlength="256" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="url" HelpText="Optionally enter a url which this page should redirect to when a user navigates to it" ResourceKey="Redirect">Redirect: </Label>
|
<Label Class="col-sm-3" For="url" HelpText="Optionally enter a url which this page should redirect to when a user navigates to it" ResourceKey="Redirect">Redirect: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="url" class="form-control" @bind="@_url" />
|
<input id="url" class="form-control" @bind="@_url" maxlength="500" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -119,6 +119,18 @@
|
|||||||
<i class="@_icon"></i>
|
<i class="@_icon"></i>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
||||||
@@ -135,7 +147,7 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<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>
|
<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">
|
||||||
<input id="title" class="form-control" @bind="@_title" />
|
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -143,9 +155,16 @@
|
|||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="theme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
|
<select id="theme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
|
||||||
@foreach (var theme in _themes)
|
@foreach (var theme in _themes)
|
||||||
|
{
|
||||||
|
@if (theme.TypeName == PageState.Site.DefaultThemeType)
|
||||||
|
{
|
||||||
|
<option value="@theme.TypeName">*@theme.Name*</option>
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
<option value="@theme.TypeName">@theme.Name</option>
|
<option value="@theme.TypeName">@theme.Name</option>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -167,13 +186,13 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="headcontent" HelpText="Optionally enter content to be included in the page head (ie. meta, link, or script tags)" ResourceKey="HeadContent">Head Content: </Label>
|
<Label Class="col-sm-3" For="headcontent" HelpText="Optionally enter content to be included in the page head (ie. meta, link, or script tags)" ResourceKey="HeadContent">Head Content: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<textarea id="headcontent" class="form-control" @bind="@_headcontent" rows="3"></textarea>
|
<textarea id="headcontent" class="form-control" @bind="@_headcontent" rows="3" maxlength="4000"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="bodycontent" HelpText="Optionally enter content to be included in the page body (ie. script tags)" ResourceKey="BodyContent">Body Content: </Label>
|
<Label Class="col-sm-3" For="bodycontent" HelpText="Optionally enter content to be included in the page body (ie. script tags)" ResourceKey="BodyContent">Body Content: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<textarea id="bodycontent" class="form-control" @bind="@_bodycontent" rows="3"></textarea>
|
<textarea id="bodycontent" class="form-control" @bind="@_bodycontent" rows="3" maxlength="4000"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -186,12 +205,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
@if (_themeSettingsType != null)
|
|
||||||
{
|
|
||||||
<TabPanel Name="ThemeSettings" Heading=@Localizer["Theme.Heading"] ResourceKey="ThemeSettings">
|
|
||||||
@ThemeSettingsComponent
|
|
||||||
</TabPanel>
|
|
||||||
}
|
|
||||||
</TabStrip>
|
</TabStrip>
|
||||||
<br />
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
|
||||||
@@ -207,6 +220,7 @@
|
|||||||
private bool validated = false;
|
private bool validated = false;
|
||||||
private List<ThemeControl> _themes = new List<ThemeControl>();
|
private List<ThemeControl> _themes = new List<ThemeControl>();
|
||||||
private List<ThemeControl> _containers = new List<ThemeControl>();
|
private List<ThemeControl> _containers = new List<ThemeControl>();
|
||||||
|
private List<Page> _pages;
|
||||||
private int _pageId;
|
private int _pageId;
|
||||||
private string _name;
|
private string _name;
|
||||||
private string _parentid = "-1";
|
private string _parentid = "-1";
|
||||||
@@ -226,18 +240,19 @@
|
|||||||
private string _bodycontent;
|
private string _bodycontent;
|
||||||
private string _permissions = null;
|
private string _permissions = null;
|
||||||
private PermissionGrid _permissionGrid;
|
private PermissionGrid _permissionGrid;
|
||||||
private Type _themeSettingsType;
|
|
||||||
private object _themeSettings;
|
|
||||||
private RenderFragment ThemeSettingsComponent { get; set; }
|
|
||||||
private bool _refresh = false;
|
private bool _refresh = false;
|
||||||
protected Page _parent = null;
|
protected Page _parent = null;
|
||||||
protected Dictionary<string, string> _icons;
|
protected Dictionary<string, string> _icons;
|
||||||
private string _iconresources = "";
|
private string _iconresources = "";
|
||||||
|
private DateTime? _effectivedate = null;
|
||||||
|
private DateTime? _expirydate = null;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||||
|
|
||||||
if (PageState.QueryString.ContainsKey("id"))
|
if (PageState.QueryString.ContainsKey("id"))
|
||||||
{
|
{
|
||||||
_pageId = Int32.Parse(PageState.QueryString["id"]);
|
_pageId = Int32.Parse(PageState.QueryString["id"]);
|
||||||
@@ -258,14 +273,15 @@
|
|||||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
||||||
_containertype = PageState.Site.DefaultContainerType;
|
_containertype = PageState.Site.DefaultContainerType;
|
||||||
_children = new List<Page>();
|
_children = new List<Page>();
|
||||||
foreach (Page p in PageState.Pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
|
foreach (Page p in _pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
|
||||||
{
|
{
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
||||||
{
|
{
|
||||||
_children.Add(p);
|
_children.Add(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ThemeSettings();
|
_effectivedate = Utilities.UtcAsLocalDate(PageState.Page.EffectiveDate);
|
||||||
|
_expirydate = Utilities.UtcAsLocalDate(PageState.Page.ExpiryDate);
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -287,7 +303,7 @@
|
|||||||
{
|
{
|
||||||
_parentid = (string)e.Value;
|
_parentid = (string)e.Value;
|
||||||
_children = new List<Page>();
|
_children = new List<Page>();
|
||||||
foreach (Page p in PageState.Pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
|
foreach (Page p in _pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
|
||||||
{
|
{
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
||||||
{
|
{
|
||||||
@@ -300,39 +316,22 @@
|
|||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Loading Child Pages For Parent {PageId} {Error}", _parentid, ex.Message);
|
await logger.LogError(ex, "Error Loading Child Pages For Parent {PageId} {Error}", _parentid, ex.Message);
|
||||||
AddModuleMessage(Localizer["Error.ChildPage.Load"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.ChildPage.Load"], MessageType.Error);
|
||||||
|
await ScrollToPageTop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThemeChanged(ChangeEventArgs e)
|
private async Task ThemeChanged(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
_themetype = (string)e.Value;
|
_themetype = (string)e.Value;
|
||||||
|
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
||||||
|
_containertype = _containers.First().TypeName;
|
||||||
|
StateHasChanged();
|
||||||
|
|
||||||
|
// if theme chosen is different than default site theme, display warning message to user
|
||||||
if (ThemeService.GetTheme(PageState.Site.Themes, _themetype)?.ThemeName != ThemeService.GetTheme(PageState.Site.Themes, PageState.Site.DefaultThemeType)?.ThemeName)
|
if (ThemeService.GetTheme(PageState.Site.Themes, _themetype)?.ThemeName != ThemeService.GetTheme(PageState.Site.Themes, PageState.Site.DefaultThemeType)?.ThemeName)
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["ThemeChanged.Message"], MessageType.Warning);
|
AddModuleMessage(Localizer["ThemeChanged.Message"], MessageType.Warning);
|
||||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
await ScrollToPageTop();
|
||||||
_containertype = _containers.First().TypeName;
|
|
||||||
ThemeSettings();
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ThemeSettings()
|
|
||||||
{
|
|
||||||
_themeSettingsType = null;
|
|
||||||
var theme = PageState.Site.Themes.FirstOrDefault(item => item.Themes.Any(themecontrol => themecontrol.TypeName.Equals(_themetype)));
|
|
||||||
if (theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType))
|
|
||||||
{
|
|
||||||
_themeSettingsType = Type.GetType(theme.ThemeSettingsType);
|
|
||||||
if (_themeSettingsType != null)
|
|
||||||
{
|
|
||||||
ThemeSettingsComponent = builder =>
|
|
||||||
{
|
|
||||||
builder.OpenComponent(0, _themeSettingsType);
|
|
||||||
builder.AddComponentReferenceCapture(1, inst => { _themeSettings = Convert.ChangeType(inst, _themeSettingsType); });
|
|
||||||
builder.CloseComponent();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
_refresh = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,16 +344,32 @@
|
|||||||
Page page = null;
|
Page page = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate))
|
||||||
|
{
|
||||||
|
AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning);
|
||||||
|
await ScrollToPageTop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!string.IsNullOrEmpty(_themetype) && !string.IsNullOrEmpty(_containertype))
|
if (!string.IsNullOrEmpty(_themetype) && !string.IsNullOrEmpty(_containertype))
|
||||||
{
|
{
|
||||||
page = new Page();
|
page = new Page();
|
||||||
page.SiteId = PageState.Page.SiteId;
|
page.SiteId = PageState.Page.SiteId;
|
||||||
page.Name = _name;
|
page.Name = _name;
|
||||||
|
|
||||||
|
if (_parentid == "-1")
|
||||||
|
{
|
||||||
|
page.ParentId = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
page.ParentId = Int32.Parse(_parentid);
|
||||||
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(_path))
|
if (string.IsNullOrEmpty(_path))
|
||||||
{
|
{
|
||||||
_path = _name;
|
_path = _name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_path.Contains("/"))
|
if (_path.Contains("/"))
|
||||||
{
|
{
|
||||||
if (_path.EndsWith("/") && _path != "/")
|
if (_path.EndsWith("/") && _path != "/")
|
||||||
@@ -363,16 +378,13 @@
|
|||||||
}
|
}
|
||||||
_path = _path.Substring(_path.LastIndexOf("/") + 1);
|
_path = _path.Substring(_path.LastIndexOf("/") + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_parentid == "-1")
|
if (_parentid == "-1")
|
||||||
{
|
{
|
||||||
page.ParentId = null;
|
|
||||||
page.Path = Utilities.GetFriendlyUrl(_path);
|
page.Path = Utilities.GetFriendlyUrl(_path);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
page.ParentId = Int32.Parse(_parentid);
|
Page parent = _pages.FirstOrDefault(item => item.PageId == page.ParentId);
|
||||||
var parent = PageState.Pages.Where(item => item.PageId == page.ParentId).FirstOrDefault();
|
|
||||||
if (parent.Path == string.Empty)
|
if (parent.Path == string.Empty)
|
||||||
{
|
{
|
||||||
page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
|
page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
|
||||||
@@ -383,16 +395,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var _pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
|
||||||
if (_pages.Any(item => item.Path == page.Path))
|
if (_pages.Any(item => item.Path == page.Path))
|
||||||
{
|
{
|
||||||
AddModuleMessage(string.Format(Localizer["Message.Page.Exists"], _path), MessageType.Warning);
|
AddModuleMessage(string.Format(Localizer["Message.Page.Exists"], _path), MessageType.Warning);
|
||||||
|
await ScrollToPageTop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (page.ParentId == null && Constants.ReservedRoutes.Contains(page.Name.ToLower()))
|
if (page.ParentId == null && Constants.ReservedRoutes.Contains(page.Name.ToLower()))
|
||||||
{
|
{
|
||||||
AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], page.Name), MessageType.Warning);
|
AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], page.Name), MessageType.Warning);
|
||||||
|
await ScrollToPageTop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,11 +416,11 @@
|
|||||||
page.Order = 0;
|
page.Order = 0;
|
||||||
break;
|
break;
|
||||||
case "<":
|
case "<":
|
||||||
child = PageState.Pages.Where(item => item.PageId == _childid).FirstOrDefault();
|
child = _pages.Where(item => item.PageId == _childid).FirstOrDefault();
|
||||||
page.Order = child.Order - 1;
|
page.Order = child.Order - 1;
|
||||||
break;
|
break;
|
||||||
case ">":
|
case ">":
|
||||||
child = PageState.Pages.Where(item => item.PageId == _childid).FirstOrDefault();
|
child = _pages.Where(item => item.PageId == _childid).FirstOrDefault();
|
||||||
page.Order = child.Order + 1;
|
page.Order = child.Order + 1;
|
||||||
break;
|
break;
|
||||||
case ">>":
|
case ">>":
|
||||||
@@ -419,6 +432,8 @@
|
|||||||
page.IsClickable = (_isclickable == null ? true : Boolean.Parse(_isclickable));
|
page.IsClickable = (_isclickable == null ? true : Boolean.Parse(_isclickable));
|
||||||
page.Url = _url;
|
page.Url = _url;
|
||||||
page.IsPersonalizable = (_ispersonalizable == null ? false : Boolean.Parse(_ispersonalizable));
|
page.IsPersonalizable = (_ispersonalizable == null ? false : Boolean.Parse(_ispersonalizable));
|
||||||
|
page.EffectiveDate = Utilities.LocalDateAndTimeAsUtc(_effectivedate);
|
||||||
|
page.ExpiryDate = Utilities.LocalDateAndTimeAsUtc(_expirydate);
|
||||||
page.UserId = null;
|
page.UserId = null;
|
||||||
|
|
||||||
// appearance
|
// appearance
|
||||||
@@ -448,7 +463,7 @@
|
|||||||
await logger.LogInformation("Page Added {Page}", page);
|
await logger.LogInformation("Page Added {Page}", page);
|
||||||
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
|
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(page.Path); // redirect to new page
|
NavigationManager.NavigateTo(NavigateUrl(page.Path), true); // redirect to page added and reload
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -458,6 +473,7 @@
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Message.Required.PageInfo"], MessageType.Warning);
|
AddModuleMessage(Localizer["Message.Required.PageInfo"], MessageType.Warning);
|
||||||
|
await ScrollToPageTop();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -465,11 +481,13 @@
|
|||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Saving Page {Page} {Error}", page, ex.Message);
|
await logger.LogError(ex, "Error Saving Page {Page} {Error}", page, ex.Message);
|
||||||
AddModuleMessage(Localizer["Error.Page.Save"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.Page.Save"], MessageType.Error);
|
||||||
|
await ScrollToPageTop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
|
await ScrollToPageTop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
@namespace Oqtane.Modules.Admin.Pages
|
@namespace Oqtane.Modules.Admin.Pages
|
||||||
@using Oqtane.Interfaces
|
@using Oqtane.Interfaces
|
||||||
|
@using System.Globalization
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IPageService PageService
|
@inject IPageService PageService
|
||||||
@inject IPageModuleService PageModuleService
|
@inject IPageModuleService PageModuleService
|
||||||
|
@inject IModuleService ModuleService
|
||||||
@inject IThemeService ThemeService
|
@inject IThemeService ThemeService
|
||||||
@inject ISystemService SystemService
|
@inject ISystemService SystemService
|
||||||
@inject IStringLocalizer<Edit> Localizer
|
@inject IStringLocalizer<Edit> Localizer
|
||||||
@@ -30,7 +32,7 @@
|
|||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="parent" class="form-select" value="@_parentid" @onchange="(e => ParentChanged(e))" required>
|
<select id="parent" class="form-select" value="@_parentid" @onchange="(e => ParentChanged(e))" required>
|
||||||
<option value="-1"><@Localizer["SiteRoot"]></option>
|
<option value="-1"><@Localizer["SiteRoot"]></option>
|
||||||
@foreach (Page page in PageState.Pages)
|
@foreach (Page page in _pages)
|
||||||
{
|
{
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, page.PermissionList) && page.PageId != _pageId)
|
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, page.PermissionList) && page.PageId != _pageId)
|
||||||
{
|
{
|
||||||
@@ -114,7 +116,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="path" HelpText="Optionally enter a url path for this page (ie. home ). If you do not provide a url path, the page name will be used. If the page is intended to be the root path specify '/'." ResourceKey="UrlPath">Url Path: </Label>
|
<Label Class="col-sm-3" For="path" HelpText="Optionally enter a url path for this page (ie. home ). If you do not provide a url path, the page name will be used. Please note that spaces and punctuation will be replaced by a dash. If the page is intended to be the root path specify '/'." ResourceKey="UrlPath">Url Path: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="path" class="form-control" @bind="@_path" maxlength="256" />
|
<input id="path" class="form-control" @bind="@_path" maxlength="256" />
|
||||||
</div>
|
</div>
|
||||||
@@ -134,6 +136,18 @@
|
|||||||
<i class="@_icon"></i>
|
<i class="@_icon"></i>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
||||||
@@ -157,9 +171,16 @@
|
|||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="theme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
|
<select id="theme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
|
||||||
@foreach (var theme in _themes)
|
@foreach (var theme in _themes)
|
||||||
|
{
|
||||||
|
@if (theme.TypeName == PageState.Site.DefaultThemeType)
|
||||||
|
{
|
||||||
|
<option value="@theme.TypeName">*@theme.Name*</option>
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
<option value="@theme.TypeName">@theme.Name</option>
|
<option value="@theme.TypeName">@theme.Name</option>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -167,10 +188,13 @@
|
|||||||
<Label Class="col-sm-3" For="container" HelpText="Select the default container for the page" ResourceKey="DefaultContainer">Default Container: </Label>
|
<Label Class="col-sm-3" For="container" HelpText="Select the default container for the page" ResourceKey="DefaultContainer">Default Container: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="container" class="form-select" @bind="@_containertype" required>
|
<select id="container" class="form-select" @bind="@_containertype" required>
|
||||||
@foreach (var container in _containers)
|
@if (_containers != null)
|
||||||
|
{
|
||||||
|
foreach (var container in _containers)
|
||||||
{
|
{
|
||||||
<option value="@container.TypeName">@container.Name</option>
|
<option value="@container.TypeName">@container.Name</option>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -181,18 +205,21 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="headcontent" HelpText="Optionally enter content to be included in the page head (ie. meta, link, or script tags)" ResourceKey="HeadContent">Head Content: </Label>
|
<Label Class="col-sm-3" For="headcontent" HelpText="Optionally enter content to be included in the page head (ie. meta, link, or script tags)" ResourceKey="HeadContent">Head Content: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<textarea id="headcontent" class="form-control" @bind="@_headcontent" rows="3"></textarea>
|
<textarea id="headcontent" class="form-control" @bind="@_headcontent" rows="3" maxlength="4000"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="bodycontent" HelpText="Optionally enter content to be included in the page body (ie. script tags)" ResourceKey="BodyContent">Body Content: </Label>
|
<Label Class="col-sm-3" For="bodycontent" HelpText="Optionally enter content to be included in the page body (ie. script tags)" ResourceKey="BodyContent">Body Content: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<textarea id="bodycontent" class="form-control" @bind="@_bodycontent" rows="3"></textarea>
|
<textarea id="bodycontent" class="form-control" @bind="@_bodycontent" rows="3" maxlength="4000"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
<br />
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
|
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
@@ -201,6 +228,19 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<PermissionGrid EntityName="@EntityNames.Page" PermissionList="@_permissions" @ref="_permissionGrid" />
|
<PermissionGrid EntityName="@EntityNames.Page" PermissionList="@_permissions" @ref="_permissionGrid" />
|
||||||
</div>
|
</div>
|
||||||
|
<br /><br />
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="updatemodulepermissions" HelpText="Specify if changes made to page permissions should be propagated to the modules on this page" ResourceKey="UpdateModulePermissions">Update Module Permissions? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="updatemodulepermissions" class="form-select" @bind="@_updatemodulepermissions" required>
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
</div>
|
</div>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="PageModules" Heading="Modules" ResourceKey="PageModules">
|
<TabPanel Name="PageModules" Heading="Modules" ResourceKey="PageModules">
|
||||||
@@ -222,9 +262,11 @@
|
|||||||
@if (_themeSettingsType != null)
|
@if (_themeSettingsType != null)
|
||||||
{
|
{
|
||||||
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
|
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
|
||||||
@ThemeSettingsComponent
|
@_themeSettingsComponent
|
||||||
</TabPanel>
|
|
||||||
<br />
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
|
</TabPanel>
|
||||||
}
|
}
|
||||||
</TabStrip>
|
</TabStrip>
|
||||||
}
|
}
|
||||||
@@ -239,14 +281,27 @@
|
|||||||
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="path" HelpText="Provide a url path for your personalized page. Please note that spaces and punctuation will be replaced by a dash." ResourceKey="PersonalizedUrlPath">Url Path: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="path" class="form-control" @bind="@_path" maxlength="256" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<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">
|
||||||
<select id="theme" class="form-select" @bind="@_themetype" required>
|
<select id="theme" class="form-select" @bind="@_themetype" required>
|
||||||
@foreach (var theme in _themes)
|
@foreach (var theme in _themes)
|
||||||
|
{
|
||||||
|
@if (theme.TypeName == PageState.Site.DefaultThemeType)
|
||||||
|
{
|
||||||
|
<option value="@theme.TypeName">*@theme.Name*</option>
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
<option value="@theme.TypeName">@theme.Name</option>
|
<option value="@theme.TypeName">@theme.Name</option>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -262,19 +317,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
@if (_themeSettingsType != null)
|
@if (_themeSettingsType != null)
|
||||||
{
|
{
|
||||||
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
|
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
|
||||||
@ThemeSettingsComponent
|
@_themeSettingsComponent
|
||||||
</TabPanel>
|
|
||||||
<br />
|
|
||||||
}
|
|
||||||
</TabStrip>
|
|
||||||
}
|
|
||||||
<br />
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SavePage">@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>
|
||||||
|
}
|
||||||
|
</TabStrip>
|
||||||
|
}
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,6 +343,7 @@
|
|||||||
private bool validated = false;
|
private bool validated = false;
|
||||||
private List<ThemeControl> _themes = new List<ThemeControl>();
|
private List<ThemeControl> _themes = new List<ThemeControl>();
|
||||||
private List<ThemeControl> _containers = new List<ThemeControl>();
|
private List<ThemeControl> _containers = new List<ThemeControl>();
|
||||||
|
private List<Page> _pages;
|
||||||
private int _pageId;
|
private int _pageId;
|
||||||
private string _name;
|
private string _name;
|
||||||
private string _currentparentid;
|
private string _currentparentid;
|
||||||
@@ -305,11 +363,12 @@
|
|||||||
private string _containertype = "-";
|
private string _containertype = "-";
|
||||||
private Type _themeSettingsType;
|
private Type _themeSettingsType;
|
||||||
private object _themeSettings;
|
private object _themeSettings;
|
||||||
private RenderFragment ThemeSettingsComponent { get; set; }
|
private RenderFragment _themeSettingsComponent { get; set; }
|
||||||
private string _headcontent;
|
private string _headcontent;
|
||||||
private string _bodycontent;
|
private string _bodycontent;
|
||||||
private List<Permission> _permissions = null;
|
private List<Permission> _permissions = null;
|
||||||
private PermissionGrid _permissionGrid;
|
private PermissionGrid _permissionGrid;
|
||||||
|
private string _updatemodulepermissions;
|
||||||
private List<Module> _pageModules;
|
private List<Module> _pageModules;
|
||||||
private string _createdby;
|
private string _createdby;
|
||||||
private DateTime _createdon;
|
private DateTime _createdon;
|
||||||
@@ -322,11 +381,14 @@
|
|||||||
protected Page _parent = null;
|
protected Page _parent = null;
|
||||||
protected Dictionary<string, string> _icons;
|
protected Dictionary<string, string> _icons;
|
||||||
private string _iconresources = "";
|
private string _iconresources = "";
|
||||||
|
private DateTime? _effectivedate = null;
|
||||||
|
private DateTime? _expirydate = null;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||||
_pageId = Int32.Parse(PageState.QueryString["id"]);
|
_pageId = Int32.Parse(PageState.QueryString["id"]);
|
||||||
_page = await PageService.GetPageAsync(_pageId);
|
_page = await PageService.GetPageAsync(_pageId);
|
||||||
_icons = await SystemService.GetIconsAsync();
|
_icons = await SystemService.GetIconsAsync();
|
||||||
@@ -342,10 +404,10 @@
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
_parentid = _page.ParentId.ToString();
|
_parentid = _page.ParentId.ToString();
|
||||||
_parent = PageState.Pages.FirstOrDefault(item => item.PageId == _page.ParentId);
|
_parent = _pages.FirstOrDefault(item => item.PageId == _page.ParentId);
|
||||||
}
|
}
|
||||||
_children = new List<Page>();
|
_children = new List<Page>();
|
||||||
foreach (Page p in PageState.Pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
|
foreach (Page p in _pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid, CultureInfo.InvariantCulture))))
|
||||||
{
|
{
|
||||||
if (p.PageId != _pageId && UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
if (p.PageId != _pageId && UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
||||||
{
|
{
|
||||||
@@ -370,11 +432,17 @@
|
|||||||
}
|
}
|
||||||
_url = _page.Url;
|
_url = _page.Url;
|
||||||
_icon = _page.Icon;
|
_icon = _page.Icon;
|
||||||
|
_effectivedate = Utilities.UtcAsLocalDate(_page.EffectiveDate);
|
||||||
|
_expirydate = Utilities.UtcAsLocalDate(_page.ExpiryDate);
|
||||||
_ispersonalizable = _page.IsPersonalizable.ToString();
|
_ispersonalizable = _page.IsPersonalizable.ToString();
|
||||||
|
|
||||||
// appearance
|
// appearance
|
||||||
_title = _page.Title;
|
_title = _page.Title;
|
||||||
_themetype = _page.ThemeType;
|
_themetype = _page.ThemeType;
|
||||||
|
if (string.IsNullOrEmpty(_themetype))
|
||||||
|
{
|
||||||
|
_themetype = PageState.Site.DefaultThemeType;
|
||||||
|
}
|
||||||
_themes = ThemeService.GetThemeControls(PageState.Site.Themes);
|
_themes = ThemeService.GetThemeControls(PageState.Site.Themes);
|
||||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
||||||
_containertype = _page.DefaultContainerType;
|
_containertype = _page.DefaultContainerType;
|
||||||
@@ -389,9 +457,11 @@
|
|||||||
|
|
||||||
// permissions
|
// permissions
|
||||||
_permissions = _page.PermissionList;
|
_permissions = _page.PermissionList;
|
||||||
|
_updatemodulepermissions = "True";
|
||||||
|
|
||||||
// page modules
|
// page modules
|
||||||
_pageModules = PageState.Modules.Where(m => m.PageId == _page.PageId).ToList();
|
var modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
|
||||||
|
_pageModules = modules.Where(item => item.PageId == _page.PageId && !item.IsDeleted).ToList();
|
||||||
|
|
||||||
// audit
|
// audit
|
||||||
_createdby = _page.CreatedBy;
|
_createdby = _page.CreatedBy;
|
||||||
@@ -423,7 +493,7 @@
|
|||||||
{
|
{
|
||||||
_parentid = (string)e.Value;
|
_parentid = (string)e.Value;
|
||||||
_children = new List<Page>();
|
_children = new List<Page>();
|
||||||
foreach (Page p in PageState.Pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
|
foreach (Page p in _pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
|
||||||
{
|
{
|
||||||
if (p.PageId != _pageId && UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
if (p.PageId != _pageId && UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
||||||
{
|
{
|
||||||
@@ -437,35 +507,42 @@
|
|||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Loading Child Pages For Parent {PageId} {Error}", _parentid, ex.Message);
|
await logger.LogError(ex, "Error Loading Child Pages For Parent {PageId} {Error}", _parentid, ex.Message);
|
||||||
AddModuleMessage(Localizer["Error.ChildPage.Load"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.ChildPage.Load"], MessageType.Error);
|
||||||
|
await ScrollToPageTop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThemeChanged(ChangeEventArgs e)
|
private async Task ThemeChanged(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
_themetype = (string)e.Value;
|
_themetype = (string)e.Value;
|
||||||
if (ThemeService.GetTheme(PageState.Site.Themes, _themetype)?.ThemeName != ThemeService.GetTheme(PageState.Site.Themes, PageState.Site.DefaultThemeType)?.ThemeName)
|
|
||||||
{
|
|
||||||
AddModuleMessage(Localizer["ThemeChanged.Message"], MessageType.Warning);
|
|
||||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
||||||
_containertype = _containers.First().TypeName;
|
_containertype = _containers.First().TypeName;
|
||||||
ThemeSettings();
|
ThemeSettings();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
|
|
||||||
|
// if theme chosen is different than default site theme, display warning message to user
|
||||||
|
if (ThemeService.GetTheme(PageState.Site.Themes, _themetype)?.ThemeName != ThemeService.GetTheme(PageState.Site.Themes, PageState.Site.DefaultThemeType)?.ThemeName)
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["ThemeChanged.Message"], MessageType.Warning);
|
||||||
|
await ScrollToPageTop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThemeSettings()
|
private void ThemeSettings()
|
||||||
{
|
{
|
||||||
_themeSettingsType = null;
|
_themeSettingsType = null;
|
||||||
|
_themeSettingsComponent = null;
|
||||||
var theme = PageState.Site.Themes.FirstOrDefault(item => item.Themes.Any(themecontrol => themecontrol.TypeName.Equals(_themetype)));
|
var theme = PageState.Site.Themes.FirstOrDefault(item => item.Themes.Any(themecontrol => themecontrol.TypeName.Equals(_themetype)));
|
||||||
if (theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType))
|
if (theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType))
|
||||||
{
|
{
|
||||||
_themeSettingsType = Type.GetType(theme.ThemeSettingsType);
|
_themeSettingsType = Type.GetType(theme.ThemeSettingsType);
|
||||||
if (_themeSettingsType != null)
|
if (_themeSettingsType != null)
|
||||||
{
|
{
|
||||||
ThemeSettingsComponent = builder =>
|
_themeSettingsComponent = builder =>
|
||||||
{
|
{
|
||||||
builder.OpenComponent(0, _themeSettingsType);
|
builder.OpenComponent(0, _themeSettingsType);
|
||||||
builder.AddComponentReferenceCapture(1, inst => { _themeSettings = Convert.ChangeType(inst, _themeSettingsType); });
|
builder.AddAttribute(1, "RenderModeBoundary", RenderModeBoundary);
|
||||||
|
builder.AddComponentReferenceCapture(2, inst => { _themeSettings = Convert.ChangeType(inst, _themeSettingsType); });
|
||||||
|
|
||||||
builder.CloseComponent();
|
builder.CloseComponent();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -481,16 +558,32 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate))
|
||||||
|
{
|
||||||
|
AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning);
|
||||||
|
await ScrollToPageTop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!string.IsNullOrEmpty(_themetype) && _containertype != "-")
|
if (!string.IsNullOrEmpty(_themetype) && _containertype != "-")
|
||||||
{
|
{
|
||||||
string currentPath = _page.Path;
|
string currentPath = _page.Path;
|
||||||
|
|
||||||
_page.Name = _name;
|
_page.Name = _name;
|
||||||
|
|
||||||
|
if (_parentid == "-1")
|
||||||
|
{
|
||||||
|
_page.ParentId = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_page.ParentId = Int32.Parse(_parentid);
|
||||||
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(_path))
|
if (string.IsNullOrEmpty(_path))
|
||||||
{
|
{
|
||||||
_path = _name;
|
_path = _name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_path.Contains("/"))
|
if (_path.Contains("/"))
|
||||||
{
|
{
|
||||||
if (_path.EndsWith("/") && _path != "/")
|
if (_path.EndsWith("/") && _path != "/")
|
||||||
@@ -499,16 +592,13 @@
|
|||||||
}
|
}
|
||||||
_path = _path.Substring(_path.LastIndexOf("/") + 1);
|
_path = _path.Substring(_path.LastIndexOf("/") + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_parentid == "-1")
|
if (_parentid == "-1")
|
||||||
{
|
{
|
||||||
_page.ParentId = null;
|
|
||||||
_page.Path = Utilities.GetFriendlyUrl(_path);
|
_page.Path = Utilities.GetFriendlyUrl(_path);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_page.ParentId = Int32.Parse(_parentid);
|
Page parent = _pages.FirstOrDefault(item => item.PageId == _page.ParentId);
|
||||||
Page parent = PageState.Pages.FirstOrDefault(item => item.PageId == _page.ParentId);
|
|
||||||
if (parent.Path == string.Empty)
|
if (parent.Path == string.Empty)
|
||||||
{
|
{
|
||||||
_page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
|
_page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
|
||||||
@@ -519,16 +609,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var _pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
|
||||||
if (_pages.Any(item => item.Path == _page.Path && item.PageId != _page.PageId))
|
if (_pages.Any(item => item.Path == _page.Path && item.PageId != _page.PageId))
|
||||||
{
|
{
|
||||||
AddModuleMessage(string.Format(Localizer["Mesage.Page.PathExists"], _path), MessageType.Warning);
|
AddModuleMessage(string.Format(Localizer["Mesage.Page.PathExists"], _path), MessageType.Warning);
|
||||||
|
await ScrollToPageTop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_page.ParentId == null && Constants.ReservedRoutes.Contains(_page.Name.ToLower()))
|
if (_page.ParentId == null && Constants.ReservedRoutes.Contains(_page.Name.ToLower()))
|
||||||
{
|
{
|
||||||
AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], _page.Name), MessageType.Warning);
|
AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], _page.Name), MessageType.Warning);
|
||||||
|
await ScrollToPageTop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -541,11 +632,11 @@
|
|||||||
_page.Order = 0;
|
_page.Order = 0;
|
||||||
break;
|
break;
|
||||||
case "<":
|
case "<":
|
||||||
child = PageState.Pages.FirstOrDefault(item => item.PageId == _childid);
|
child = _pages.FirstOrDefault(item => item.PageId == _childid);
|
||||||
if (child != null) _page.Order = child.Order - 1;
|
if (child != null) _page.Order = child.Order - 1;
|
||||||
break;
|
break;
|
||||||
case ">":
|
case ">":
|
||||||
child = PageState.Pages.FirstOrDefault(item => item.PageId == _childid);
|
child = _pages.FirstOrDefault(item => item.PageId == _childid);
|
||||||
if (child != null) _page.Order = child.Order + 1;
|
if (child != null) _page.Order = child.Order + 1;
|
||||||
break;
|
break;
|
||||||
case ">>":
|
case ">>":
|
||||||
@@ -557,6 +648,8 @@
|
|||||||
_page.IsClickable = (_isclickable == null ? true : Boolean.Parse(_isclickable));
|
_page.IsClickable = (_isclickable == null ? true : Boolean.Parse(_isclickable));
|
||||||
_page.Url = _url;
|
_page.Url = _url;
|
||||||
_page.Icon = _icon ?? string.Empty;
|
_page.Icon = _icon ?? string.Empty;
|
||||||
|
_page.EffectiveDate = Utilities.LocalDateAndTimeAsUtc(_effectivedate);
|
||||||
|
_page.ExpiryDate = Utilities.LocalDateAndTimeAsUtc(_expirydate);
|
||||||
_page.IsPersonalizable = (_ispersonalizable != null && Boolean.Parse(_ispersonalizable));
|
_page.IsPersonalizable = (_ispersonalizable != null && Boolean.Parse(_ispersonalizable));
|
||||||
|
|
||||||
// appearance
|
// appearance
|
||||||
@@ -580,6 +673,7 @@
|
|||||||
if (_page.UserId == null)
|
if (_page.UserId == null)
|
||||||
{
|
{
|
||||||
_page.PermissionList = _permissionGrid.GetPermissionList();
|
_page.PermissionList = _permissionGrid.GetPermissionList();
|
||||||
|
_page.UpdateModulePermissions = bool.Parse(_updatemodulepermissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
_page = await PageService.UpdatePageAsync(_page);
|
_page = await PageService.UpdatePageAsync(_page);
|
||||||
@@ -601,27 +695,30 @@
|
|||||||
await logger.LogInformation("Page Saved {Page}", _page);
|
await logger.LogInformation("Page Saved {Page}", _page);
|
||||||
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
|
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
NavigationManager.NavigateTo(PageState.ReturnUrl, true); // redirect to page being edited and reload
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
NavigationManager.NavigateTo(NavigateUrl()); // redirect to page management
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Message.Required.PageInfo"], MessageType.Warning);
|
AddModuleMessage(Localizer["Message.Required.PageInfo"], MessageType.Warning);
|
||||||
|
await ScrollToPageTop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Saving Page {Page} {Error}", _page, ex.Message);
|
await logger.LogError(ex, "Error Saving Page {Page} {Error}", _page, ex.Message);
|
||||||
AddModuleMessage(Localizer["Error.Page.Save"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.Page.Save"], MessageType.Error);
|
||||||
|
await ScrollToPageTop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
|
await ScrollToPageTop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@if (PageState.Pages != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
@if (_pages != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
{
|
{
|
||||||
<ActionLink Action="Add" Text="Add Page" ResourceKey="AddPage" />
|
<ActionLink Action="Add" Text="Add Page" ResourceKey="AddPage" />
|
||||||
|
|
||||||
<Pager Items="@PageState.Pages.Where(item => !item.IsDeleted)">
|
<Pager Items="@_pages.Where(item => !item.IsDeleted)" SearchProperties="Name">
|
||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
<th>@SharedLocalizer["Name"]</th>
|
<th>@SharedLocalizer["Name"]</th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.PageId.ToString())" ResourceKey="EditPage" /></td>
|
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.PageId.ToString())" ResourceKey="EditPage" /></td>
|
||||||
<td><ActionDialog Header="Delete Page" Message="@string.Format(Localizer["Confirm.Page.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
|
<td><ActionDialog Header="Delete Page" Message="@string.Format(Localizer["Confirm.Page.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
|
||||||
<td><button type="button" class="btn btn-secondary" @onclick="@(async () => NavigationManager.NavigateTo(Browse(context)))">@Localizer["Browse"]</button></td>
|
<td><button type="button" class="btn btn-secondary" @onclick="@(async () => NavigationManager.NavigateTo(Browse(context)))">@Localizer["Browse"]</button></td>
|
||||||
<td>@(new string('-', context.Level * 2))@(context.Name)</td>
|
<td>@(new string('-', context.Level * 2))@(context.Name)</td>
|
||||||
@@ -28,6 +28,21 @@
|
|||||||
@code {
|
@code {
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
|
||||||
|
private List<Page> _pages;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Loading Pages {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.Page.Load"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task DeletePage(Page page)
|
private async Task DeletePage(Page page)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -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="description" HelpText="The help text displayed to the user for this profile item" ResourceKey="Description">Description: </Label>
|
<Label Class="col-sm-3" For="description" HelpText="The help text displayed to the user for this profile item" ResourceKey="Description">Description: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<textarea id="description" class="form-control" @bind="@_description" rows="5" maxlength="256" required ></textarea>
|
<textarea id="description" class="form-control" @bind="@_description" rows="3" maxlength="256" required></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -34,19 +34,25 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="order" HelpText="The index order of where this profile item should be displayed" ResourceKey="Order">Order: </Label>
|
<Label Class="col-sm-3" For="order" HelpText="The index order of where this profile item should be displayed" ResourceKey="Order">Order: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="order" class="form-control" @bind="@_vieworder" maxlength="4" required />
|
<input id="order" class="form-control" @bind="@_vieworder" min="0" max="9999" type="number" required />
|
||||||
</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="length" HelpText="The max number of characters this profile item should accept (enter zero for unlimited)" ResourceKey="Length">Length: </Label>
|
<Label Class="col-sm-3" For="length" HelpText="The max number of characters this profile item should accept (enter zero for unlimited)" ResourceKey="Length">Length: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="length" class="form-control" @bind="@_maxlength" maxlength="4" required />
|
<input id="length" class="form-control" @bind="@_maxlength" min="0" max="524288" type="number" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="rows" HelpText="The number of rows for text entry (one is the default)" ResourceKey="Rows">Rows: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="rows" class="form-control" @bind="@_rows" min="1" max="10" type="number" required />
|
||||||
</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="defaultVal" HelpText="The default value for this profile item" ResourceKey="DefaultValue">Default Value: </Label>
|
<Label Class="col-sm-3" For="defaultVal" HelpText="The default value for this profile item" ResourceKey="DefaultValue">Default Value: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="defaultVal" class="form-control" @bind="@_defaultvalue" maxlength="2000"/>
|
<input id="defaultVal" class="form-control" @bind="@_defaultvalue" maxlength="2000" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -70,6 +76,12 @@
|
|||||||
<input id="validation" class="form-control" @bind="@_validation" maxlength="200" />
|
<input id="validation" class="form-control" @bind="@_validation" maxlength="200" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="autocomplete" HelpText="The HTML autocomplete attribute allows you to specify browser behavior for automated user assistance in filling out form field values. Allowable values are blank (default), 'on', 'off', or any value from the standardized taxonomy defined for this attribute." ResourceKey="Autocomplete">Autocomplete: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="autocomplete" class="form-control" @bind="@_autocomplete" maxlength="30" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="private" HelpText="Should this profile item be visible to all users?" ResourceKey="Private">Private? </Label>
|
<Label Class="col-sm-3" For="private" HelpText="Should this profile item be visible to all users?" ResourceKey="Private">Private? </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -101,9 +113,11 @@
|
|||||||
private string _category = string.Empty;
|
private string _category = string.Empty;
|
||||||
private string _vieworder = "0";
|
private string _vieworder = "0";
|
||||||
private string _maxlength = "0";
|
private string _maxlength = "0";
|
||||||
|
private string _rows = "1";
|
||||||
private string _defaultvalue = string.Empty;
|
private string _defaultvalue = string.Empty;
|
||||||
private string _options = string.Empty;
|
private string _options = string.Empty;
|
||||||
private string _validation = string.Empty;
|
private string _validation = string.Empty;
|
||||||
|
private string _autocomplete = string.Empty;
|
||||||
private string _isrequired = "False";
|
private string _isrequired = "False";
|
||||||
private string _isprivate = "False";
|
private string _isprivate = "False";
|
||||||
private string createdby;
|
private string createdby;
|
||||||
@@ -131,9 +145,11 @@
|
|||||||
_category = profile.Category;
|
_category = profile.Category;
|
||||||
_vieworder = profile.ViewOrder.ToString();
|
_vieworder = profile.ViewOrder.ToString();
|
||||||
_maxlength = profile.MaxLength.ToString();
|
_maxlength = profile.MaxLength.ToString();
|
||||||
|
_rows = profile.Rows.ToString();
|
||||||
_defaultvalue = profile.DefaultValue;
|
_defaultvalue = profile.DefaultValue;
|
||||||
_options = profile.Options;
|
_options = profile.Options;
|
||||||
_validation = profile.Validation;
|
_validation = profile.Validation;
|
||||||
|
_autocomplete = profile.Autocomplete;
|
||||||
_isrequired = profile.IsRequired.ToString();
|
_isrequired = profile.IsRequired.ToString();
|
||||||
_isprivate = profile.IsPrivate.ToString();
|
_isprivate = profile.IsPrivate.ToString();
|
||||||
createdby = profile.CreatedBy;
|
createdby = profile.CreatedBy;
|
||||||
@@ -175,11 +191,14 @@
|
|||||||
profile.Category = _category;
|
profile.Category = _category;
|
||||||
profile.ViewOrder = int.Parse(_vieworder);
|
profile.ViewOrder = int.Parse(_vieworder);
|
||||||
profile.MaxLength = int.Parse(_maxlength);
|
profile.MaxLength = int.Parse(_maxlength);
|
||||||
|
profile.Rows = int.Parse(_rows);
|
||||||
profile.DefaultValue = _defaultvalue;
|
profile.DefaultValue = _defaultvalue;
|
||||||
profile.Options = _options;
|
profile.Options = _options;
|
||||||
profile.Validation = _validation;
|
profile.Validation = _validation;
|
||||||
|
profile.Autocomplete = _autocomplete;
|
||||||
profile.IsRequired = (_isrequired == null ? false : Boolean.Parse(_isrequired));
|
profile.IsRequired = (_isrequired == null ? false : Boolean.Parse(_isrequired));
|
||||||
profile.IsPrivate = (_isprivate == null ? false : Boolean.Parse(_isprivate));
|
profile.IsPrivate = (_isprivate == null ? false : Boolean.Parse(_isprivate));
|
||||||
|
|
||||||
if (_profileid != -1)
|
if (_profileid != -1)
|
||||||
{
|
{
|
||||||
profile = await ProfileService.UpdateProfileAsync(profile);
|
profile = await ProfileService.UpdateProfileAsync(profile);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ else
|
|||||||
{
|
{
|
||||||
<ActionLink Action="Add" Text="Add Profile" Security="SecurityAccessLevel.Edit" ResourceKey="AddProfile" />
|
<ActionLink Action="Add" Text="Add Profile" Security="SecurityAccessLevel.Edit" ResourceKey="AddProfile" />
|
||||||
|
|
||||||
<Pager Items="@_profiles">
|
<Pager Items="@_profiles" SearchProperties="Title,Category">
|
||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
@@ -22,7 +22,7 @@ else
|
|||||||
<th>@Localizer["Order"]</th>
|
<th>@Localizer["Order"]</th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ProfileId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="EditProfile" /></td>
|
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.ProfileId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="EditProfile" /></td>
|
||||||
<td><ActionDialog Header="Delete Profile" Message="@string.Format(Localizer["Confirm.Profile.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteProfile(context.ProfileId))" ResourceKey="DeleteProfile" /></td>
|
<td><ActionDialog Header="Delete Profile" Message="@string.Format(Localizer["Confirm.Profile.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteProfile(context.ProfileId))" ResourceKey="DeleteProfile" /></td>
|
||||||
<td>@context.Name</td>
|
<td>@context.Name</td>
|
||||||
<td>@context.Title</td>
|
<td>@context.Title</td>
|
||||||
|
|||||||
@@ -22,18 +22,18 @@ else
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<Pager Items="@_pages.Where(item => item.IsDeleted)" CurrentPage="@_pagePage.ToString()" OnPageChange="OnPageChangePage">
|
<Pager Items="@_pages.Where(item => item.IsDeleted).OrderByDescending(item => item.DeletedOn)" CurrentPage="@_pagePage.ToString()" OnPageChange="OnPageChangePage">
|
||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th>@SharedLocalizer["Name"]</th>
|
<th>@SharedLocalizer["Path"]</th>
|
||||||
<th>@Localizer["DeletedBy"]</th>
|
<th>@Localizer["DeletedBy"]</th>
|
||||||
<th>@Localizer["DeletedOn"]</th>
|
<th>@Localizer["DeletedOn"]</th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><button type="button" @onclick="@(() => RestorePage(context))" class="btn btn-success" title="Restore">@Localizer["Restore"]</button></td>
|
<td><button type="button" @onclick="@(() => RestorePage(context))" class="btn btn-success" title="Restore">@Localizer["Restore"]</button></td>
|
||||||
<td><ActionDialog Header="Delete Page" Message="@string.Format(Localizer["Confirm.Page.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
|
<td><ActionDialog Header="Delete Page" Message="@string.Format(Localizer["Confirm.Page.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
|
||||||
<td>@context.Name</td>
|
<td>@context.Path</td>
|
||||||
<td>@context.DeletedBy</td>
|
<td>@context.DeletedBy</td>
|
||||||
<td>@context.DeletedOn</td>
|
<td>@context.DeletedOn</td>
|
||||||
</Row>
|
</Row>
|
||||||
@@ -50,7 +50,7 @@ else
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<Pager Items="@_modules.Where(item => item.IsDeleted)" CurrentPage="@_pageModule.ToString()" OnPageChange="OnPageChangeModule">
|
<Pager Items="@_modules.Where(item => item.IsDeleted).OrderByDescending(item => item.DeletedOn)" CurrentPage="@_pageModule.ToString()" OnPageChange="OnPageChangeModule">
|
||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
@@ -104,13 +104,26 @@ else
|
|||||||
private async Task RestorePage(Page page)
|
private async Task RestorePage(Page page)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
var validated = true;
|
||||||
|
if (page.ParentId != null)
|
||||||
|
{
|
||||||
|
var parent = _pages.Find(item => item.PageId == page.ParentId);
|
||||||
|
validated = !parent.IsDeleted;
|
||||||
|
}
|
||||||
|
if (validated)
|
||||||
{
|
{
|
||||||
page.IsDeleted = false;
|
page.IsDeleted = false;
|
||||||
await PageService.UpdatePageAsync(page);
|
await PageService.UpdatePageAsync(page);
|
||||||
await logger.LogInformation("Page Restored {Page}", page);
|
await logger.LogInformation("Page Restored {Page}", page);
|
||||||
|
AddModuleMessage(Localizer["Success.Page.Restore"], MessageType.Success);
|
||||||
await Load();
|
await Load();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.Page.Restore"], MessageType.Warning);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -125,9 +138,9 @@ else
|
|||||||
{
|
{
|
||||||
await PageService.DeletePageAsync(page.PageId);
|
await PageService.DeletePageAsync(page.PageId);
|
||||||
await logger.LogInformation("Page Permanently Deleted {Page}", page);
|
await logger.LogInformation("Page Permanently Deleted {Page}", page);
|
||||||
|
AddModuleMessage(Localizer["Success.Page.Delete"], MessageType.Success);
|
||||||
await Load();
|
await Load();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -140,7 +153,7 @@ else
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ModuleInstance.ShowProgressIndicator();
|
ShowProgressIndicator();
|
||||||
foreach (Page page in _pages.Where(item => item.IsDeleted))
|
foreach (Page page in _pages.Where(item => item.IsDeleted))
|
||||||
{
|
{
|
||||||
await PageService.DeletePageAsync(page.PageId);
|
await PageService.DeletePageAsync(page.PageId);
|
||||||
@@ -148,16 +161,16 @@ else
|
|||||||
}
|
}
|
||||||
|
|
||||||
await logger.LogInformation("Pages Permanently Deleted");
|
await logger.LogInformation("Pages Permanently Deleted");
|
||||||
|
AddModuleMessage(Localizer["Success.Pages.Delete"], MessageType.Success);
|
||||||
await Load();
|
await Load();
|
||||||
ModuleInstance.HideProgressIndicator();
|
HideProgressIndicator();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Permanently Deleting Pages {Error}", ex.Message);
|
await logger.LogError(ex, "Error Permanently Deleting Pages {Error}", ex.Message);
|
||||||
AddModuleMessage(ex.Message, MessageType.Error);
|
AddModuleMessage(ex.Message, MessageType.Error);
|
||||||
ModuleInstance.HideProgressIndicator();
|
HideProgressIndicator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,6 +182,7 @@ else
|
|||||||
pagemodule.IsDeleted = false;
|
pagemodule.IsDeleted = false;
|
||||||
await PageModuleService.UpdatePageModuleAsync(pagemodule);
|
await PageModuleService.UpdatePageModuleAsync(pagemodule);
|
||||||
await logger.LogInformation("Module Restored {Module}", module);
|
await logger.LogInformation("Module Restored {Module}", module);
|
||||||
|
AddModuleMessage(Localizer["Success.Module.Restore"], MessageType.Success);
|
||||||
await Load();
|
await Load();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
@@ -185,6 +199,7 @@ else
|
|||||||
{
|
{
|
||||||
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
|
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
|
||||||
await logger.LogInformation("Module Permanently Deleted {Module}", module);
|
await logger.LogInformation("Module Permanently Deleted {Module}", module);
|
||||||
|
AddModuleMessage(Localizer["Success.Module.Delete"], MessageType.Success);
|
||||||
await Load();
|
await Load();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
@@ -199,21 +214,22 @@ else
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ModuleInstance.ShowProgressIndicator();
|
ShowProgressIndicator();
|
||||||
foreach (Module module in _modules.Where(item => item.IsDeleted).ToList())
|
foreach (Module module in _modules.Where(item => item.IsDeleted).ToList())
|
||||||
{
|
{
|
||||||
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
|
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
|
||||||
}
|
}
|
||||||
await logger.LogInformation("Modules Permanently Deleted");
|
await logger.LogInformation("Modules Permanently Deleted");
|
||||||
|
AddModuleMessage(Localizer["Success.Modules.Delete"], MessageType.Success);
|
||||||
await Load();
|
await Load();
|
||||||
ModuleInstance.HideProgressIndicator();
|
HideProgressIndicator();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Permanently Deleting Modules {Error}", ex.Message);
|
await logger.LogError(ex, "Error Permanently Deleting Modules {Error}", ex.Message);
|
||||||
AddModuleMessage(Localizer["Error.Modules.Delete"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.Modules.Delete"], MessageType.Error);
|
||||||
ModuleInstance.HideProgressIndicator();
|
HideProgressIndicator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private void OnPageChangePage(int page)
|
private void OnPageChangePage(int page)
|
||||||
|
|||||||
@@ -1,21 +1,25 @@
|
|||||||
@namespace Oqtane.Modules.Admin.Register
|
@namespace Oqtane.Modules.Admin.Register
|
||||||
|
@using System.Net
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IUserService UserService
|
@inject IUserService UserService
|
||||||
|
@inject ITimeZoneService TimeZoneService
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
@inject ISettingService SettingService
|
@inject ISettingService SettingService
|
||||||
|
|
||||||
@if (PageState.Site.AllowRegistration)
|
@if (_initialized)
|
||||||
{
|
{
|
||||||
<AuthorizeView Roles="@RoleNames.Registered">
|
@if (PageState.Site.AllowRegistration)
|
||||||
<Authorizing>
|
{
|
||||||
<text>...</text>
|
if (!_userCreated)
|
||||||
</Authorizing>
|
{
|
||||||
<Authorized>
|
if (PageState.User != null)
|
||||||
|
{
|
||||||
<ModuleMessage Message="@Localizer["Info.Registration.Exists"]" Type="MessageType.Info" />
|
<ModuleMessage Message="@Localizer["Info.Registration.Exists"]" Type="MessageType.Info" />
|
||||||
</Authorized>
|
}
|
||||||
<NotAuthorized>
|
else
|
||||||
|
{
|
||||||
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@@ -55,20 +59,42 @@
|
|||||||
<input id="displayname" class="form-control" @bind="@_displayname" maxlength="50" />
|
<input id="displayname" class="form-control" @bind="@_displayname" maxlength="50" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="timezone" HelpText="Your time zone" ResourceKey="TimeZone">Time Zone:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="timezone" class="form-select" @bind="@_timezoneid">
|
||||||
|
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||||
|
@foreach (var timezone in _timezones)
|
||||||
|
{
|
||||||
|
<option value="@timezone.Id">@timezone.DisplayName</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<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>
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
|
@if (_allowsitelogin)
|
||||||
|
{
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<NavLink href="@NavigateUrl("login")">@Localizer["Login"]</NavLink>
|
||||||
|
}
|
||||||
</form>
|
</form>
|
||||||
</NotAuthorized>
|
}
|
||||||
</AuthorizeView>
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<ModuleMessage Message="@Localizer["Info.Registration.Disabled"]" Type="MessageType.Info" />
|
<ModuleMessage Message="@Localizer["Info.Registration.Disabled"]" Type="MessageType.Info" />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
private bool _initialized = false;
|
||||||
|
private List<Models.TimeZone> _timezones;
|
||||||
private string _passwordrequirements;
|
private string _passwordrequirements;
|
||||||
private string _username = string.Empty;
|
private string _username = string.Empty;
|
||||||
private ElementReference form;
|
private ElementReference form;
|
||||||
@@ -79,12 +105,19 @@ else
|
|||||||
private string _confirm = string.Empty;
|
private string _confirm = string.Empty;
|
||||||
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 bool _userCreated = false;
|
||||||
|
private bool _allowsitelogin = true;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
_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"));
|
||||||
|
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
||||||
|
_timezoneid = PageState.Site.TimeZoneId;
|
||||||
|
_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
protected override void OnParametersSet()
|
||||||
@@ -113,6 +146,7 @@ else
|
|||||||
Password = _password,
|
Password = _password,
|
||||||
Email = _email,
|
Email = _email,
|
||||||
DisplayName = (_displayname == string.Empty ? _username : _displayname),
|
DisplayName = (_displayname == string.Empty ? _username : _displayname),
|
||||||
|
TimeZoneId = _timezoneid,
|
||||||
PhotoFileId = null
|
PhotoFileId = null
|
||||||
};
|
};
|
||||||
user = await UserService.AddUserAsync(user);
|
user = await UserService.AddUserAsync(user);
|
||||||
@@ -120,6 +154,7 @@ else
|
|||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
await logger.LogInformation("User Created {Username} {Email}", _username, _email);
|
await logger.LogInformation("User Created {Username} {Email}", _username, _email);
|
||||||
|
_userCreated = true;
|
||||||
AddModuleMessage(Localizer["Info.User.AccountCreate"], MessageType.Info);
|
AddModuleMessage(Localizer["Info.User.AccountCreate"], MessageType.Info);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -152,7 +187,7 @@ else
|
|||||||
|
|
||||||
private void Cancel()
|
private void Cancel()
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(NavigateUrl(string.Empty));
|
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TogglePassword()
|
private void TogglePassword()
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ else
|
|||||||
{
|
{
|
||||||
<ActionLink Action="Add" Text="Add Role" Security="SecurityAccessLevel.Edit" ResourceKey="AddRole" />
|
<ActionLink Action="Add" Text="Add Role" Security="SecurityAccessLevel.Edit" ResourceKey="AddRole" />
|
||||||
|
|
||||||
<Pager Items="@_roles">
|
<Pager Items="@_roles" SearchProperties="Name">
|
||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
@@ -20,9 +20,9 @@ else
|
|||||||
<th>@SharedLocalizer["Name"]</th>
|
<th>@SharedLocalizer["Name"]</th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.RoleId.ToString())" Security="SecurityAccessLevel.Edit" Disabled="@(context.IsSystem)" ResourceKey="Edit" /></td>
|
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.RoleId.ToString())" Security="SecurityAccessLevel.Edit" Disabled="@(context.IsSystem)" ResourceKey="Edit" /></td>
|
||||||
<td><ActionDialog Header="Delete Role" Message="@string.Format(Localizer["Confirm.DeleteUser"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteRole(context))" Disabled="@(context.IsSystem)" ResourceKey="DeleteRole" /></td>
|
<td><ActionDialog Header="Delete Role" Message="@string.Format(Localizer["Confirm.DeleteUser"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteRole(context))" Disabled="@(context.IsSystem)" ResourceKey="DeleteRole" /></td>
|
||||||
<td><ActionLink Action="Users" Parameters="@($"id=" + context.RoleId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="Users" /></td>
|
<td><ActionLink Action="Users" Text="Users" Parameters="@($"id=" + context.RoleId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="Users" /></td>
|
||||||
<td>@context.Name</td>
|
<td>@context.Name</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ else
|
|||||||
<td>@context.EffectiveDate</td>
|
<td>@context.EffectiveDate</td>
|
||||||
<td>@context.ExpiryDate</td>
|
<td>@context.ExpiryDate</td>
|
||||||
<td>
|
<td>
|
||||||
<ActionDialog Header="Remove User" Message="@string.Format(Localizer["Confirm.User.DeleteRole"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.IsAutoAssigned || context.User.Username == UserNames.Host || context.User.UserId == PageState.User.UserId)" ResourceKey="DeleteUserRole" />
|
<ActionDialog Header="Remove User" Message="@string.Format(Localizer["Confirm.User.DeleteRole"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.User.Username == UserNames.Host || context.User.UserId == PageState.User.UserId)" ResourceKey="DeleteUserRole" />
|
||||||
</td>
|
</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
@@ -179,15 +179,21 @@ else
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task DeleteUserRole(int UserRoleId)
|
private async Task DeleteUserRole(int UserRoleId)
|
||||||
{
|
|
||||||
validated = true;
|
|
||||||
var interop = new Interop(JSRuntime);
|
|
||||||
if (await interop.FormValid(form))
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
var userrole = await UserRoleService.GetUserRoleAsync(UserRoleId);
|
||||||
|
if (userrole.Role.Name == RoleNames.Registered)
|
||||||
|
{
|
||||||
|
userrole.ExpiryDate = DateTime.UtcNow;
|
||||||
|
await UserRoleService.UpdateUserRoleAsync(userrole);
|
||||||
|
await logger.LogInformation("User {Username} Expired From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
await UserRoleService.DeleteUserRoleAsync(UserRoleId);
|
await UserRoleService.DeleteUserRoleAsync(UserRoleId);
|
||||||
await logger.LogInformation("User Removed From Role {UserRoleId}", UserRoleId);
|
await logger.LogInformation("User {Username} Removed From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
||||||
|
}
|
||||||
AddModuleMessage(Localizer["Confirm.User.RoleRemoved"], MessageType.Success);
|
AddModuleMessage(Localizer["Confirm.User.RoleRemoved"], MessageType.Success);
|
||||||
await GetUserRoles();
|
await GetUserRoles();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
@@ -198,9 +204,4 @@ else
|
|||||||
AddModuleMessage(Localizer["Error.User.RemoveRole"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.User.RemoveRole"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
119
Oqtane.Client/Modules/Admin/Search/Index.razor
Normal file
119
Oqtane.Client/Modules/Admin/Search/Index.razor
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
@namespace Oqtane.Modules.Admin.Search
|
||||||
|
@inherits ModuleBase
|
||||||
|
@inject ISettingService SettingService
|
||||||
|
@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="searchprovider" HelpText="Specify the search provider for this site" ResourceKey="SearchProvider">Search Provider: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="searchprovider" class="form-control" @bind="@_searchProvider" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="enabled" HelpText="Specify if search indexing is enabled" ResourceKey="Enabled">Indexing Enabled? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="enabled" class="form-select" @bind="@_enabled">
|
||||||
|
<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="lastindexedon" HelpText="The date/time which the site was last indexed on" ResourceKey="LastIndexedOn">Last Indexed: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="lastindexedon" class="form-control" @bind="@_lastIndexedOn" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="ignorepages" HelpText="Comma delimited list of pages which should be ignored (based on their path)" ResourceKey="IgnorePages">Ignore Pages: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea id="ignorepages" class="form-control" @bind="@_ignorePages" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="ignoreentities" HelpText="Comma delimited list of entities which should be ignored" ResourceKey="IgnoreEntities">Ignore Entities: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea id="ignoreentities" class="form-control" @bind="@_ignoreEntities" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="minimumwordlength" HelpText="Minimum length of a word to be indexed" ResourceKey="MinimumWordLength">Word Length: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="minimumwordlength" class="form-control" type="number" min="0" step="1" @bind="@_minimumWordLength" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="ignorewords" HelpText="Comma delimited list of words which should be ignored" ResourceKey="IgnoreWords">Ignore Words: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea id="ignorewords" class="form-control" @bind="@_ignoreWords" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br /><br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
|
||||||
|
<ActionDialog Header="Reindex" Message="Are You Sure You Wish To Reindex Search Content?" Action="Reindex" Class="btn btn-danger" OnClick="@(async () => await Reindex())" ResourceKey="Reindex" />
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
@code {
|
||||||
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
|
||||||
|
private string _searchProvider;
|
||||||
|
private string _enabled = "True";
|
||||||
|
private string _lastIndexedOn = "";
|
||||||
|
private string _ignorePages = "";
|
||||||
|
private string _ignoreEntities = "File";
|
||||||
|
private string _minimumWordLength = "3";
|
||||||
|
private string _ignoreWords = "the,be,to,of,and,a,i,in,that,have,it,for,not,on,with,he,as,you,do,at,this,but,his,by,from,they,we,say,her,she,or,an,will,my,one,all,would,there,their,what,so,up,out,if,about,who,get,which,go,me,when,make,can,like,time,no,just,him,know,take,people,into,year,your,good,some,could,them,see,other,than,then,now,look,only,come,its,over,think,also,back,after,use,two,how,our,work,first,well,way,even,new,want,because,any,these,give,day,most,us";
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
_searchProvider = SettingService.GetSetting(settings, "Search_SearchProvider", Constants.DefaultSearchProviderName);
|
||||||
|
_enabled = SettingService.GetSetting(settings, "Search_Enabled", _enabled);
|
||||||
|
_lastIndexedOn = SettingService.GetSetting(settings, "Search_LastIndexedOn", _lastIndexedOn);
|
||||||
|
_ignorePages = SettingService.GetSetting(settings, "Search_IgnorePages", _ignorePages);
|
||||||
|
_ignoreEntities = SettingService.GetSetting(settings, "Search_IgnoreEntities", _ignoreEntities);
|
||||||
|
_minimumWordLength = SettingService.GetSetting(settings, "Search_MininumWordLength", _minimumWordLength);
|
||||||
|
_ignoreWords = SettingService.GetSetting(settings, "Search_IgnoreWords", _ignoreWords);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Save()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
settings = SettingService.SetSetting(settings, "Search_SearchProvider", _searchProvider);
|
||||||
|
settings = SettingService.SetSetting(settings, "Search_Enabled", _enabled);
|
||||||
|
settings = SettingService.SetSetting(settings, "Search_LastIndexedOn", _lastIndexedOn, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "Search_IgnorePages", _ignorePages, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "Search_IgnoreEntities", _ignoreEntities, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "Search_MininumWordLength", _minimumWordLength, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "Search_IgnoreWords", _ignoreWords, true);
|
||||||
|
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||||
|
AddModuleMessage(Localizer["Success.Save"], MessageType.Success);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Saving Search Settings {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.Save"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Reindex()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_lastIndexedOn = DateTime.MinValue.ToString();
|
||||||
|
await Save();
|
||||||
|
AddModuleMessage(Localizer["Message.Reindex"], MessageType.Success);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Saving Search Settings {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.Save"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
142
Oqtane.Client/Modules/Admin/SearchResults/Index.razor
Normal file
142
Oqtane.Client/Modules/Admin/SearchResults/Index.razor
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
@using Oqtane.Services
|
||||||
|
@using System.Net
|
||||||
|
@namespace Oqtane.Modules.Admin.SearchResults
|
||||||
|
@inherits ModuleBase
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject ISearchResultsService SearchResultsService
|
||||||
|
@inject ISettingService SettingService
|
||||||
|
@inject IStringLocalizer<Index> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
@attribute [StreamRendering] // attribute allows the progress indicator to be displayed
|
||||||
|
|
||||||
|
<div class="search-result-container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<form method="post" @formname="SearchResultsForm" @onsubmit="Search" data-enhance>
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<span class="input-group-text">@Localizer["SearchLabel"]</span>
|
||||||
|
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||||
|
<input type="text" name="keywords" class="form-control shadow-none" maxlength="50"
|
||||||
|
aria-label="Keywords"
|
||||||
|
placeholder="@Localizer["SearchPlaceholder"]"
|
||||||
|
@bind="@_keywords">
|
||||||
|
<button class="btn btn-primary" type="submit">@SharedLocalizer["Search"]</button>
|
||||||
|
<a class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Reset"]</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12 mb-3">
|
||||||
|
@if (_loading)
|
||||||
|
{
|
||||||
|
<div class="app-progress-indicator"></div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@if (_searchResults != null && _searchResults.Results != null)
|
||||||
|
{
|
||||||
|
if (_searchResults.Results.Any())
|
||||||
|
{
|
||||||
|
<Pager Items="@_searchResults?.Results"
|
||||||
|
Format="Grid"
|
||||||
|
Columns="1"
|
||||||
|
Toolbar="Bottom"
|
||||||
|
Parameters="@($"q={_keywords}")">
|
||||||
|
<Row>
|
||||||
|
<div class="search-item mb-2">
|
||||||
|
<h4 class="mb-1"><a href="@context.Url">@context.Title</a></h4>
|
||||||
|
<p class="mb-0 text-muted">@((MarkupString)context.Snippet)</p>
|
||||||
|
</div>
|
||||||
|
</Row>
|
||||||
|
</Pager>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="alert alert-info show mt-3" role="alert">
|
||||||
|
@Localizer["NoResult"]
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
public override string RenderMode => RenderModes.Static;
|
||||||
|
|
||||||
|
private string _includeEntities;
|
||||||
|
private string _excludeEntities;
|
||||||
|
private string _fromDate;
|
||||||
|
private string _toDate;
|
||||||
|
private string _pageSize;
|
||||||
|
private string _sortField;
|
||||||
|
private string _sortOrder;
|
||||||
|
private string _bodyLength;
|
||||||
|
|
||||||
|
private string _keywords;
|
||||||
|
private SearchResults _searchResults;
|
||||||
|
private bool _loading;
|
||||||
|
|
||||||
|
[SupplyParameterFromForm(FormName = "SearchResultsForm")]
|
||||||
|
public string KeyWords { get => ""; set => _keywords = value; }
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
_includeEntities = SettingService.GetSetting(ModuleState.Settings, "SearchResults_IncludeEntities", "");
|
||||||
|
_excludeEntities = SettingService.GetSetting(ModuleState.Settings, "SearchResults_ExcludeEntities", "");
|
||||||
|
_fromDate = SettingService.GetSetting(ModuleState.Settings, "SearchResults_FromDate", DateTime.MinValue.ToString());
|
||||||
|
_toDate = SettingService.GetSetting(ModuleState.Settings, "SearchResults_ToDate", DateTime.MaxValue.ToString());
|
||||||
|
_pageSize = SettingService.GetSetting(ModuleState.Settings, "SearchResults_PageSize", int.MaxValue.ToString());
|
||||||
|
_sortField = SettingService.GetSetting(ModuleState.Settings, "SearchResults_SortField", "Relevance");
|
||||||
|
_sortOrder = SettingService.GetSetting(ModuleState.Settings, "SearchResults_SortOrder", "Descending");
|
||||||
|
_bodyLength = SettingService.GetSetting(ModuleState.Settings, "SearchResults_BodyLength", "255");
|
||||||
|
|
||||||
|
if (_keywords == null && PageState.QueryString.ContainsKey("q"))
|
||||||
|
{
|
||||||
|
_keywords = WebUtility.UrlDecode(PageState.QueryString["q"]);
|
||||||
|
await PerformSearch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Search()
|
||||||
|
{
|
||||||
|
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, $"page=1&q={WebUtility.UrlEncode(_keywords)}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task PerformSearch()
|
||||||
|
{
|
||||||
|
_loading = true;
|
||||||
|
StateHasChanged();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(_keywords))
|
||||||
|
{
|
||||||
|
var searchQuery = new SearchQuery
|
||||||
|
{
|
||||||
|
SiteId = PageState.Site.SiteId,
|
||||||
|
Alias = PageState.Alias,
|
||||||
|
Keywords = _keywords,
|
||||||
|
IncludeEntities = _includeEntities,
|
||||||
|
ExcludeEntities = _excludeEntities,
|
||||||
|
FromDate = (!string.IsNullOrEmpty(_fromDate)) ? DateTime.Parse(_fromDate) : DateTime.MinValue,
|
||||||
|
ToDate = (!string.IsNullOrEmpty(_toDate)) ? DateTime.Parse(_toDate) : DateTime.MaxValue,
|
||||||
|
PageSize = (!string.IsNullOrEmpty(_pageSize)) ? int.Parse(_pageSize) : int.MaxValue,
|
||||||
|
PageIndex = 0,
|
||||||
|
SortField = (!string.IsNullOrEmpty(_sortField)) ? (SearchSortField)Enum.Parse(typeof(SearchSortField), _sortField) : SearchSortField.Relevance,
|
||||||
|
SortOrder = (!string.IsNullOrEmpty(_sortOrder)) ? (SearchSortOrder)Enum.Parse(typeof(SearchSortOrder), _sortOrder) : SearchSortOrder.Descending,
|
||||||
|
BodyLength = (!string.IsNullOrEmpty(_bodyLength)) ? int.Parse(_bodyLength) : 255
|
||||||
|
};
|
||||||
|
|
||||||
|
_searchResults = await SearchResultsService.GetSearchResultsAsync(searchQuery);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["NoCriteria"], MessageType.Info, "bottom");
|
||||||
|
}
|
||||||
|
|
||||||
|
_loading = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
19
Oqtane.Client/Modules/Admin/SearchResults/ModuleInfo.cs
Normal file
19
Oqtane.Client/Modules/Admin/SearchResults/ModuleInfo.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using Oqtane.Documentation;
|
||||||
|
using Oqtane.Models;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
|
||||||
|
namespace Oqtane.Modules.Admin.SearchResults
|
||||||
|
{
|
||||||
|
[PrivateApi("Mark this as private, since it's not very useful in the public docs")]
|
||||||
|
public class ModuleInfo : IModule
|
||||||
|
{
|
||||||
|
public ModuleDefinition ModuleDefinition => new ModuleDefinition
|
||||||
|
{
|
||||||
|
Name = "Search Results",
|
||||||
|
Description = "Search Results",
|
||||||
|
Categories = "Admin",
|
||||||
|
Version = Constants.Version,
|
||||||
|
SettingsType = "Oqtane.Modules.Admin.SearchResults.Settings, Oqtane.Client"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
123
Oqtane.Client/Modules/Admin/SearchResults/Settings.razor
Normal file
123
Oqtane.Client/Modules/Admin/SearchResults/Settings.razor
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
@namespace Oqtane.Modules.Admin.SearchResults
|
||||||
|
@inherits ModuleBase
|
||||||
|
@inject ISettingService SettingService
|
||||||
|
@implements Oqtane.Interfaces.ISettingsControl
|
||||||
|
@inject IStringLocalizer<Settings> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="includeentities" ResourceKey="IncludeEntities" ResourceType="@resourceType" HelpText="Comma delimited list of entities to include in the search results. By default all entities will be included.">Include Entities: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="includeentities" type="text" class="form-control" @bind="@_includeEntities" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="excludeentities" ResourceKey="ExcludeEntities" ResourceType="@resourceType" HelpText="Comma delimited list of entities to exclude from search results. By default no entities will be excluded.">Exclude Entities: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="excludeentities" class="form-control" @bind="@_excludeEntities" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="daterange" ResourceKey="DateRange" ResourceType="@resourceType" HelpText="Enter the date range for search results. The default includes all content.">Date Range: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="date" class="form-control" @bind="@_fromDate" />
|
||||||
|
<span class="input-group-text">@Localizer["To"]</span>
|
||||||
|
<input type="date" class="form-control" @bind="@_toDate" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="pagesize" ResourceKey="PageSize" ResourceType="@resourceType" HelpText="The maximum number of search results to retrieve. The default is unlimited.">Page Size: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="pagesize" type="text" class="form-control" @bind="@_pageSize" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="sortfield" ResourceKey="SortField" ResourceType="@resourceType" HelpText="Specify the default sort field">Sort By: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="softfield" class="form-select" @bind="@_sortField">
|
||||||
|
<option value="Relevance">@Localizer["Relevance"]</option>
|
||||||
|
<option value="Title">@Localizer["Title"]</option>
|
||||||
|
<option value="LastModified">@Localizer["LastModified"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="sortorder" ResourceKey="SortOrder" ResourceType="@resourceType" HelpText="Specify the default sort order">Sort Order: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="softorder" class="form-select" @bind="@_sortOrder">
|
||||||
|
<option value="Ascending">@Localizer["Ascending"]</option>
|
||||||
|
<option value="Descending">@Localizer["Descending"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="bodylength" ResourceKey="BodyLength" ResourceType="@resourceType" HelpText="The number of characters displayed for each search result summary. The default is 255 characters.">Body Size: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="bodylength" type="text" class="form-control" @bind="@_bodyLength" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private string resourceType = "Oqtane.Modules.Admin.SearchResults.Settings, Oqtane.Client"; // for localization
|
||||||
|
|
||||||
|
private ElementReference form;
|
||||||
|
private bool validated = false;
|
||||||
|
|
||||||
|
private string _includeEntities;
|
||||||
|
private string _excludeEntities;
|
||||||
|
private DateTime? _fromDate = null;
|
||||||
|
private DateTime? _toDate = null;
|
||||||
|
private string _pageSize;
|
||||||
|
private string _sortField;
|
||||||
|
private string _sortOrder;
|
||||||
|
private string _bodyLength;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_includeEntities = SettingService.GetSetting(ModuleState.Settings, "SearchResults_IncludeEntities", "");
|
||||||
|
_excludeEntities = SettingService.GetSetting(ModuleState.Settings, "SearchResults_ExcludeEntities", "");
|
||||||
|
var fromDate = SettingService.GetSetting(ModuleState.Settings, "SearchResults_FromDate", "");
|
||||||
|
_fromDate = (string.IsNullOrEmpty(fromDate)) ? null : DateTime.Parse(fromDate);
|
||||||
|
var toDate = SettingService.GetSetting(ModuleState.Settings, "SearchResults_ToDate", "");
|
||||||
|
_toDate = (string.IsNullOrEmpty(toDate)) ? null : DateTime.Parse(toDate);
|
||||||
|
_pageSize = SettingService.GetSetting(ModuleState.Settings, "SearchResults_PageSize", "");
|
||||||
|
_sortField = SettingService.GetSetting(ModuleState.Settings, "SearchResults_SortField", "Relevance");
|
||||||
|
_sortOrder = SettingService.GetSetting(ModuleState.Settings, "SearchResults_SortOrder", "Descending");
|
||||||
|
_bodyLength = SettingService.GetSetting(ModuleState.Settings, "SearchResults_BodyLength", "255");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AddModuleMessage(ex.Message, MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateSettings()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
|
||||||
|
settings = SettingService.SetSetting(settings, "SearchResults_IncludeEntities", _includeEntities);
|
||||||
|
settings = SettingService.SetSetting(settings, "SearchResults_ExcludeEntities", _excludeEntities);
|
||||||
|
settings = SettingService.SetSetting(settings, "SearchResults_From", _fromDate.ToString());
|
||||||
|
settings = SettingService.SetSetting(settings, "SearchResults_To", _toDate.ToString());
|
||||||
|
settings = SettingService.SetSetting(settings, "SearchResults_PageSize", _pageSize);
|
||||||
|
settings = SettingService.SetSetting(settings, "SearchResults_SortField", _sortField);
|
||||||
|
settings = SettingService.SetSetting(settings, "SearchResults_SortOrder", _sortOrder);
|
||||||
|
settings = SettingService.SetSetting(settings, "SearchResults_BodyLength", _bodyLength);
|
||||||
|
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AddModuleMessage(ex.Message, MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,21 @@
|
|||||||
@namespace Oqtane.Modules.Admin.Site
|
@namespace Oqtane.Modules.Admin.Site
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@using System.Text.RegularExpressions
|
@using System.Text.RegularExpressions
|
||||||
|
@using Microsoft.Extensions.DependencyInjection
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject ISiteService SiteService
|
@inject ISiteService SiteService
|
||||||
|
@inject IPageService PageService
|
||||||
@inject ITenantService TenantService
|
@inject ITenantService TenantService
|
||||||
@inject IDatabaseService DatabaseService
|
@inject IDatabaseService DatabaseService
|
||||||
@inject IAliasService AliasService
|
@inject IAliasService AliasService
|
||||||
@inject IThemeService ThemeService
|
@inject IThemeService ThemeService
|
||||||
@inject ISettingService SettingService
|
@inject ISettingService SettingService
|
||||||
|
@inject ITimeZoneService TimeZoneService
|
||||||
|
@inject IServiceProvider ServiceProvider
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject INotificationService NotificationService
|
@inject INotificationService NotificationService
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
@inject IOutputCacheService CacheService
|
||||||
|
|
||||||
@if (_initialized)
|
@if (_initialized)
|
||||||
{
|
{
|
||||||
@@ -27,7 +32,7 @@
|
|||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="homepage" class="form-select" @bind="@_homepageid" required>
|
<select id="homepage" class="form-select" @bind="@_homepageid" required>
|
||||||
<option value="-"><@SharedLocalizer["Not Specified"]></option>
|
<option value="-"><@SharedLocalizer["Not Specified"]></option>
|
||||||
@foreach (Page page in PageState.Pages)
|
@foreach (Page page in _pages)
|
||||||
{
|
{
|
||||||
if (UserSecurity.ContainsRole(page.PermissionList, PermissionNames.View, RoleNames.Everyone))
|
if (UserSecurity.ContainsRole(page.PermissionList, PermissionNames.View, RoleNames.Everyone))
|
||||||
{
|
{
|
||||||
@@ -37,6 +42,18 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="timezone" HelpText="Your time zone" ResourceKey="TimeZone">Time Zone:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="timezone" class="form-select" @bind="@_timezoneid">
|
||||||
|
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||||
|
@foreach (var timezone in _timezones)
|
||||||
|
{
|
||||||
|
<option value="@timezone.Id">@timezone.DisplayName</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="isDeleted" HelpText="Is this site deleted?" ResourceKey="IsDeleted">Deleted? </Label>
|
<Label Class="col-sm-3" For="isDeleted" HelpText="Is this site deleted?" ResourceKey="IsDeleted">Deleted? </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -47,11 +64,12 @@
|
|||||||
</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="sitemap" HelpText="The site map url for this site which can be submitted to search engines for indexing" ResourceKey="SiteMap">Site Map: </Label>
|
<Label Class="col-sm-3" For="sitemap" HelpText="The site map url for this site which can be submitted to search engines for indexing. The sitemap is cached for 5 minutes and the cache can be manually cleared." ResourceKey="SiteMap">Site Map: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="sitemap" class="form-control" @bind="@_sitemap" disabled />
|
<input id="sitemap" class="form-control" @bind="@_sitemap" disabled />
|
||||||
<a href="@_sitemap" class="btn btn-secondary" target="_new">@Localizer["Browse"]</a>
|
<a href="@_sitemap" class="btn btn-secondary" target="_new">@Localizer["Browse"]</a>
|
||||||
|
<button type="button" class="btn btn-danger" @onclick="EvictSitemapOutputCache">@Localizer["SiteMap.EvictCache"]</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -69,20 +87,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<Section Name="Appearance" Heading="Appearance" ResourceKey="Appearance">
|
<Section Name="Theme" Heading="Theme" ResourceKey="Theme">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="logo" HelpText="Specify a logo for the site" ResourceKey="Logo">Logo: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<FileManager FileId="@_logofileid" Filter="@Constants.ImageFiles" @ref="_logofilemanager" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="favicon" HelpText="Specify a Favicon" ResourceKey="FavoriteIcon">Favicon: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<FileManager FileId="@_faviconfileid" Filter="ico,png,gif" @ref="_faviconfilemanager" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="defaultTheme" HelpText="Select the sites default theme" ResourceKey="DefaultTheme">Default Theme: </Label>
|
<Label Class="col-sm-3" For="defaultTheme" HelpText="Select the sites default theme" ResourceKey="DefaultTheme">Default Theme: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -98,10 +104,13 @@
|
|||||||
<Label Class="col-sm-3" For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label>
|
<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">
|
<div class="col-sm-9">
|
||||||
<select id="defaultContainer" class="form-select" @bind="@_containertype" required>
|
<select id="defaultContainer" class="form-select" @bind="@_containertype" required>
|
||||||
|
@if (_containers != null)
|
||||||
|
{
|
||||||
@foreach (var container in _containers)
|
@foreach (var container in _containers)
|
||||||
{
|
{
|
||||||
<option value="@container.TypeName">@container.Name</option>
|
<option value="@container.TypeName">@container.Name</option>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -110,10 +119,57 @@
|
|||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="defaultAdminContainer" class="form-select" @bind="@_admincontainertype" required>
|
<select id="defaultAdminContainer" class="form-select" @bind="@_admincontainertype" required>
|
||||||
<option value="@Constants.DefaultAdminContainer"><@Localizer["DefaultAdminContainer"]></option>
|
<option value="@Constants.DefaultAdminContainer"><@Localizer["DefaultAdminContainer"]></option>
|
||||||
|
@if (_containers != null)
|
||||||
|
{
|
||||||
@foreach (var container in _containers)
|
@foreach (var container in _containers)
|
||||||
{
|
{
|
||||||
<option value="@container.TypeName">@container.Name</option>
|
<option value="@container.TypeName">@container.Name</option>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="cookieconsent" HelpText="Specify if cookie consent is enabled on this site. Please note this option must be used in conjunction with a Theme which supports cookie consent." ResourceKey="CookieConsent">Cookie Consent: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="cookieconsent" class="form-select" @bind="@_cookieconsent">
|
||||||
|
<option value="">@SharedLocalizer["Disabled"]</option>
|
||||||
|
<option value="optin">@Localizer["OptIn"]</option>
|
||||||
|
<option value="optout">@Localizer["OptOut"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
<Section Name="Appearance" Heading="Appearance" ResourceKey="Appearance">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="logo" HelpText="Specify a logo for the site" ResourceKey="Logo">Logo: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<FileManager FileId="@_logofileid" Filter="@_imagefiles" @ref="_logofilemanager" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="favicon" HelpText="Specify a Favicon" ResourceKey="FavoriteIcon">Favicon: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<FileManager FileId="@_faviconfileid" Filter="ico,png,gif" @ref="_faviconfilemanager" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
<Section Name="Functionality" Heading="Functionality" ResourceKey="Functionality">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="textEditor" HelpText="Select the text editor for the site" ResourceKey="TextEditor">Text Editor: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="textEditor" class="form-select" @bind="@_textEditor" required>
|
||||||
|
@if (_textEditors != null)
|
||||||
|
{
|
||||||
|
@foreach (var textEditor in _textEditors)
|
||||||
|
{
|
||||||
|
<option value="@textEditor.Value">@textEditor.Key</option>
|
||||||
|
}
|
||||||
|
}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -168,14 +224,14 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="username" HelpText="Enter the username for your SMTP account" ResourceKey="SmtpUsername">Username: </Label>
|
<Label Class="col-sm-3" For="username" HelpText="Enter the username for your SMTP account" ResourceKey="SmtpUsername">Username: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="username" class="form-control" @bind="@_smtpusername" />
|
<input id="username" class="form-control" @bind="@_smtpusername" autocomplete="off"/>
|
||||||
</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="password" HelpText="Enter the password for your SMTP account" ResourceKey="SmtpPassword">Password: </Label>
|
<Label Class="col-sm-3" For="password" HelpText="Enter the password for your SMTP account" ResourceKey="SmtpPassword">Password: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="password" type="@_smtppasswordtype" class="form-control" @bind="@_smtppassword" />
|
<input id="password" type="@_smtppasswordtype" class="form-control" @bind="@_smtppassword" autocomplete="off"/>
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@ToggleSMTPPassword" tabindex="-1">@_togglesmtppassword</button>
|
<button type="button" class="btn btn-secondary" @onclick="@ToggleSMTPPassword" tabindex="-1">@_togglesmtppassword</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -207,7 +263,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 days of notifications to retain" ResourceKey="Retention">Retention (Days): </Label>
|
<Label Class="col-sm-3" For="retention" HelpText="Number of days of notifications to retain" ResourceKey="Retention">Retention (Days): </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="retention" class="form-control" @bind="@_retention" />
|
<input id="retention" class="form-control" type="number" min="0" step="1" @bind="@_retention" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-secondary" @onclick="SendEmail">@Localizer["Smtp.TestConfig"]</button>
|
<button type="button" class="btn btn-secondary" @onclick="SendEmail">@Localizer["Smtp.TestConfig"]</button>
|
||||||
@@ -270,7 +326,7 @@
|
|||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>@context.Name</td>
|
<td>@context.Name</td>
|
||||||
<td>@context.IsDefault</td>
|
<td>@((context.IsDefault) ? SharedLocalizer["Yes"] : SharedLocalizer["No"])</td>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -295,21 +351,40 @@
|
|||||||
<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">
|
||||||
<Label Class="col-sm-3" For="runtime" HelpText="The Blazor runtime hosting model" ResourceKey="Runtime">Runtime: </Label>
|
<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">
|
<div class="col-sm-9">
|
||||||
<select id="runtime" class="form-select" @bind="@_runtime" required>
|
<select id="rendermode" class="form-select" value="@_rendermode" @onchange="(e => RenderModeChanged(e))" required>
|
||||||
<option value="Server">@SharedLocalizer["BlazorServer"]</option>
|
<option value="@RenderModes.Interactive">@(SharedLocalizer["RenderMode" + @RenderModes.Interactive])</option>
|
||||||
<option value="WebAssembly">@SharedLocalizer["BlazorWebAssembly"]</option>
|
<option value="@RenderModes.Static">@(SharedLocalizer["RenderMode" + @RenderModes.Static])</option>
|
||||||
<option value="Hybrid">@SharedLocalizer["BlazorHybrid"]</option>
|
<option value="@RenderModes.Headless">@(SharedLocalizer["RenderMode" + @RenderModes.Headless])</option>
|
||||||
</select>
|
</select>
|
||||||
</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="prerender" HelpText="Specifies if the site should be prerendered (for search crawlers, etc...)" ResourceKey="Prerender">Prerender? </Label>
|
<Label Class="col-sm-3" For="runtime" HelpText="The render mode for UI components which require interactivity" ResourceKey="Runtime">Interactivity: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="runtime" class="form-select" @bind="@_runtime" required>
|
||||||
|
<option value="@Runtimes.Server">@(SharedLocalizer["Runtime" + @Runtimes.Server])</option>
|
||||||
|
<option value="@Runtimes.WebAssembly">@(SharedLocalizer["Runtime" + @Runtimes.WebAssembly])</option>
|
||||||
|
<option value="@Runtimes.Auto">@(SharedLocalizer["Runtime" + @Runtimes.Auto])</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="prerender" HelpText="Specifies if interactive components should prerender their output on the server" ResourceKey="Prerender">Prerender: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="prerender" class="form-select" @bind="@_prerender" required>
|
<select id="prerender" class="form-select" @bind="@_prerender" required>
|
||||||
<option value="Prerendered">@SharedLocalizer["Yes"]</option>
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
<option value="">@SharedLocalizer["No"]</option>
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="hybrid" HelpText="Specifies if the site can be integrated with an external .NET MAUI hybrid application" ResourceKey="Hybrid">Hybrid? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="hybrid" class="form-select" @bind="@_hybrid" required>
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -318,7 +393,7 @@
|
|||||||
<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">
|
||||||
<Label Class="col-sm-3" For="tenant" HelpText="The name of the database used for the site" ResourceKey="Tenant">Database: </Label>
|
<Label Class="col-sm-3" For="tenant" HelpText="The name of the database used for the site. Note that this is not the physical database name but rather the tenant name which is used within the framework to identify a database." ResourceKey="Tenant">Database: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="tenant" class="form-control" @bind="@_tenant" readonly />
|
<input id="tenant" class="form-control" @bind="@_tenant" readonly />
|
||||||
</div>
|
</div>
|
||||||
@@ -330,7 +405,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="connectionstring" HelpText="The connection information for the database" ResourceKey="ConnectionString">Connection: </Label>
|
<Label Class="col-sm-3" For="connectionstring" HelpText="The name of the connection string in appsettings.json which will be used to connect to the database" ResourceKey="ConnectionString">Connection: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="connectionstring" class="form-control" @bind="@_connectionstring" readonly />
|
<input id="connectionstring" class="form-control" @bind="@_connectionstring" readonly />
|
||||||
</div>
|
</div>
|
||||||
@@ -353,12 +428,17 @@
|
|||||||
private bool _initialized = false;
|
private bool _initialized = false;
|
||||||
private List<ThemeControl> _themes = new List<ThemeControl>();
|
private List<ThemeControl> _themes = new List<ThemeControl>();
|
||||||
private List<ThemeControl> _containers = new List<ThemeControl>();
|
private List<ThemeControl> _containers = new List<ThemeControl>();
|
||||||
|
private List<Page> _pages;
|
||||||
|
private List<Models.TimeZone> _timezones;
|
||||||
|
|
||||||
private string _name = string.Empty;
|
private string _name = string.Empty;
|
||||||
private string _homepageid = "-";
|
private string _homepageid = "-";
|
||||||
|
private string _timezoneid = string.Empty;
|
||||||
private string _isdeleted;
|
private string _isdeleted;
|
||||||
private string _sitemap = "";
|
private string _sitemap = "";
|
||||||
private string _siteguid = "";
|
private string _siteguid = "";
|
||||||
private string _version = "";
|
private string _version = "";
|
||||||
|
|
||||||
private int _logofileid = -1;
|
private int _logofileid = -1;
|
||||||
private FileManager _logofilemanager;
|
private FileManager _logofilemanager;
|
||||||
private int _faviconfileid = -1;
|
private int _faviconfileid = -1;
|
||||||
@@ -366,8 +446,15 @@
|
|||||||
private string _themetype = "";
|
private string _themetype = "";
|
||||||
private string _containertype = "";
|
private string _containertype = "";
|
||||||
private string _admincontainertype = "";
|
private string _admincontainertype = "";
|
||||||
|
private string _cookieconsent = "";
|
||||||
|
|
||||||
|
private Dictionary<string, string> _textEditors = new Dictionary<string, string>();
|
||||||
|
private string _textEditor = "";
|
||||||
|
private string _imagefiles = string.Empty;
|
||||||
|
|
||||||
private string _headcontent = string.Empty;
|
private string _headcontent = string.Empty;
|
||||||
private string _bodycontent = string.Empty;
|
private string _bodycontent = string.Empty;
|
||||||
|
|
||||||
private string _smtphost = string.Empty;
|
private string _smtphost = string.Empty;
|
||||||
private string _smtpport = string.Empty;
|
private string _smtpport = string.Empty;
|
||||||
private string _smtpssl = "False";
|
private string _smtpssl = "False";
|
||||||
@@ -378,21 +465,28 @@
|
|||||||
private string _smtpsender = string.Empty;
|
private string _smtpsender = string.Empty;
|
||||||
private string _smtprelay = "False";
|
private string _smtprelay = "False";
|
||||||
private string _smtpenabled = "True";
|
private string _smtpenabled = "True";
|
||||||
private string _retention = string.Empty;
|
private int _retention = 30;
|
||||||
|
|
||||||
private string _pwaisenabled;
|
private string _pwaisenabled;
|
||||||
private int _pwaappiconfileid = -1;
|
private int _pwaappiconfileid = -1;
|
||||||
private FileManager _pwaappiconfilemanager;
|
private FileManager _pwaappiconfilemanager;
|
||||||
private int _pwasplashiconfileid = -1;
|
private int _pwasplashiconfileid = -1;
|
||||||
private FileManager _pwasplashiconfilemanager;
|
private FileManager _pwasplashiconfilemanager;
|
||||||
|
|
||||||
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 string _runtime = "";
|
|
||||||
private string _prerender = "";
|
private string _rendermode = RenderModes.Interactive;
|
||||||
|
private string _runtime = Runtimes.Server;
|
||||||
|
private string _prerender = "True";
|
||||||
|
private string _hybrid = "False";
|
||||||
|
|
||||||
private string _tenant = string.Empty;
|
private string _tenant = string.Empty;
|
||||||
private string _database = string.Empty;
|
private string _database = string.Empty;
|
||||||
private string _connectionstring = string.Empty;
|
private string _connectionstring = string.Empty;
|
||||||
|
|
||||||
private string _createdby;
|
private string _createdby;
|
||||||
private DateTime _createdon;
|
private DateTime _createdon;
|
||||||
private string _modifiedby;
|
private string _modifiedby;
|
||||||
@@ -406,16 +500,27 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (PageState.QueryString.ContainsKey("updated"))
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Success.Settings.SaveSite"], MessageType.Success);
|
||||||
|
}
|
||||||
|
|
||||||
Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
|
Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
|
||||||
if (site != null)
|
if (site != null)
|
||||||
{
|
{
|
||||||
|
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
||||||
|
|
||||||
|
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||||
|
|
||||||
_name = site.Name;
|
_name = site.Name;
|
||||||
|
_timezoneid = site.TimeZoneId;
|
||||||
if (site.HomePageId != null)
|
if (site.HomePageId != null)
|
||||||
{
|
{
|
||||||
_homepageid = site.HomePageId.Value.ToString();
|
_homepageid = site.HomePageId.Value.ToString();
|
||||||
}
|
}
|
||||||
_isdeleted = site.IsDeleted.ToString();
|
_isdeleted = site.IsDeleted.ToString();
|
||||||
_sitemap = PageState.Alias.Protocol + PageState.Alias.Name + "/pages/sitemap.xml";
|
_sitemap = PageState.Alias.Protocol + PageState.Alias.Name + "/sitemap.xml";
|
||||||
_siteguid = site.SiteGuid;
|
_siteguid = site.SiteGuid;
|
||||||
_version = site.Version;
|
_version = site.Version;
|
||||||
|
|
||||||
@@ -434,11 +539,34 @@
|
|||||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
||||||
_containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer;
|
_containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer;
|
||||||
_admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer;
|
_admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer;
|
||||||
|
_cookieconsent = SettingService.GetSetting(settings, "CookieConsent", string.Empty);
|
||||||
|
|
||||||
|
// functionality
|
||||||
|
var textEditors = ServiceProvider.GetServices<ITextEditor>();
|
||||||
|
foreach (var textEditor in textEditors)
|
||||||
|
{
|
||||||
|
_textEditors.Add(textEditor.Name, Utilities.GetFullTypeName(textEditor.GetType().AssemblyQualifiedName));
|
||||||
|
}
|
||||||
|
_textEditor = SettingService.GetSetting(settings, "TextEditor", Constants.DefaultTextEditor);
|
||||||
|
_imagefiles = SettingService.GetSetting(settings, "ImageFiles", Constants.ImageFiles);
|
||||||
|
_imagefiles = (string.IsNullOrEmpty(_imagefiles)) ? Constants.ImageFiles : _imagefiles;
|
||||||
|
|
||||||
// page content
|
// page content
|
||||||
_headcontent = site.HeadContent;
|
_headcontent = site.HeadContent;
|
||||||
_bodycontent = site.BodyContent;
|
_bodycontent = site.BodyContent;
|
||||||
|
|
||||||
|
// SMTP
|
||||||
|
_smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty);
|
||||||
|
_smtpport = SettingService.GetSetting(settings, "SMTPPort", string.Empty);
|
||||||
|
_smtpssl = SettingService.GetSetting(settings, "SMTPSSL", "False");
|
||||||
|
_smtpusername = SettingService.GetSetting(settings, "SMTPUsername", string.Empty);
|
||||||
|
_smtppassword = SettingService.GetSetting(settings, "SMTPPassword", string.Empty);
|
||||||
|
_togglesmtppassword = SharedLocalizer["ShowPassword"];
|
||||||
|
_smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty);
|
||||||
|
_smtprelay = SettingService.GetSetting(settings, "SMTPRelay", "False");
|
||||||
|
_smtpenabled = SettingService.GetSetting(settings, "SMTPEnabled", "True");
|
||||||
|
_retention = int.Parse(SettingService.GetSetting(settings, "NotificationRetention", "30"));
|
||||||
|
|
||||||
// PWA
|
// PWA
|
||||||
_pwaisenabled = site.PwaIsEnabled.ToString();
|
_pwaisenabled = site.PwaIsEnabled.ToString();
|
||||||
if (site.PwaAppIconFileId != null)
|
if (site.PwaAppIconFileId != null)
|
||||||
@@ -450,25 +578,14 @@
|
|||||||
_pwasplashiconfileid = site.PwaSplashIconFileId.Value;
|
_pwasplashiconfileid = site.PwaSplashIconFileId.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SMTP
|
|
||||||
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
|
||||||
_smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty);
|
|
||||||
_smtpport = SettingService.GetSetting(settings, "SMTPPort", string.Empty);
|
|
||||||
_smtpssl = SettingService.GetSetting(settings, "SMTPSSL", "False");
|
|
||||||
_smtpusername = SettingService.GetSetting(settings, "SMTPUsername", string.Empty);
|
|
||||||
_smtppassword = SettingService.GetSetting(settings, "SMTPPassword", string.Empty);
|
|
||||||
_togglesmtppassword = SharedLocalizer["ShowPassword"];
|
|
||||||
_smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty);
|
|
||||||
_smtprelay = SettingService.GetSetting(settings, "SMTPRelay", "False");
|
|
||||||
_smtpenabled = SettingService.GetSetting(settings, "SMTPEnabled", "True");
|
|
||||||
_retention = SettingService.GetSetting(settings, "NotificationRetention", "30");
|
|
||||||
|
|
||||||
// aliases
|
// aliases
|
||||||
await GetAliases();
|
await GetAliases();
|
||||||
|
|
||||||
// hosting model
|
// hosting model
|
||||||
|
_rendermode = site.RenderMode;
|
||||||
_runtime = site.Runtime;
|
_runtime = site.Runtime;
|
||||||
_prerender = site.RenderMode.Replace(_runtime, "");
|
_prerender = site.Prerender.ToString();
|
||||||
|
_hybrid = site.Hybrid.ToString();
|
||||||
|
|
||||||
// database
|
// database
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
@@ -479,7 +596,7 @@
|
|||||||
if (tenant != null)
|
if (tenant != null)
|
||||||
{
|
{
|
||||||
_tenant = tenant.Name;
|
_tenant = tenant.Name;
|
||||||
_database = _databases.Find(item => item.DBType == tenant.DBType)?.Name;
|
_database = _databases.Find(item => item.DBType == tenant.DBType && item.Name != "LocalDB")?.Name;
|
||||||
_connectionstring = tenant.DBConnectionString;
|
_connectionstring = tenant.DBConnectionString;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -519,6 +636,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RenderModeChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
_rendermode = (string)e.Value;
|
||||||
|
switch (_rendermode)
|
||||||
|
{
|
||||||
|
case RenderModes.Interactive:
|
||||||
|
_prerender = "True";
|
||||||
|
break;
|
||||||
|
case RenderModes.Static:
|
||||||
|
_prerender = "False";
|
||||||
|
break;
|
||||||
|
case RenderModes.Headless:
|
||||||
|
_prerender = "False";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task SaveSite()
|
private async Task SaveSite()
|
||||||
{
|
{
|
||||||
validated = true;
|
validated = true;
|
||||||
@@ -532,10 +666,8 @@
|
|||||||
var site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
|
var site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
|
||||||
if (site != null)
|
if (site != null)
|
||||||
{
|
{
|
||||||
bool refresh = false;
|
|
||||||
bool reload = false;
|
|
||||||
|
|
||||||
site.Name = _name;
|
site.Name = _name;
|
||||||
|
site.TimeZoneId = _timezoneid;
|
||||||
site.HomePageId = (_homepageid != "-" ? int.Parse(_homepageid) : null);
|
site.HomePageId = (_homepageid != "-" ? int.Parse(_homepageid) : null);
|
||||||
site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
|
site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
|
||||||
|
|
||||||
@@ -545,23 +677,24 @@
|
|||||||
if (logofileid != -1)
|
if (logofileid != -1)
|
||||||
{
|
{
|
||||||
site.LogoFileId = logofileid;
|
site.LogoFileId = logofileid;
|
||||||
|
if (logofileid != _logofileid)
|
||||||
|
{
|
||||||
|
_logofileid = logofileid;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
int? faviconFieldId = _faviconfilemanager.GetFileId();
|
int? faviconFieldId = _faviconfilemanager.GetFileId();
|
||||||
if (faviconFieldId == -1) faviconFieldId = null;
|
if (faviconFieldId == -1) faviconFieldId = null;
|
||||||
if (site.FaviconFileId != faviconFieldId)
|
if (site.FaviconFileId != faviconFieldId)
|
||||||
{
|
{
|
||||||
site.FaviconFileId = faviconFieldId;
|
site.FaviconFileId = faviconFieldId;
|
||||||
reload = true; // needs to be reloaded on server
|
|
||||||
}
|
}
|
||||||
if (site.DefaultThemeType != _themetype)
|
if (site.DefaultThemeType != _themetype)
|
||||||
{
|
{
|
||||||
site.DefaultThemeType = _themetype;
|
site.DefaultThemeType = _themetype;
|
||||||
refresh = true; // needs to be refreshed on client
|
|
||||||
}
|
}
|
||||||
if (site.DefaultContainerType != _containertype)
|
if (site.DefaultContainerType != _containertype)
|
||||||
{
|
{
|
||||||
site.DefaultContainerType = _containertype;
|
site.DefaultContainerType = _containertype;
|
||||||
refresh = true; // needs to be refreshed on client
|
|
||||||
}
|
}
|
||||||
site.AdminContainerType = _admincontainertype;
|
site.AdminContainerType = _admincontainertype;
|
||||||
|
|
||||||
@@ -569,43 +702,39 @@
|
|||||||
if (site.HeadContent != _headcontent)
|
if (site.HeadContent != _headcontent)
|
||||||
{
|
{
|
||||||
site.HeadContent = _headcontent;
|
site.HeadContent = _headcontent;
|
||||||
reload = true;
|
|
||||||
}
|
}
|
||||||
if (site.BodyContent != _bodycontent)
|
if (site.BodyContent != _bodycontent)
|
||||||
{
|
{
|
||||||
site.BodyContent = _bodycontent;
|
site.BodyContent = _bodycontent;
|
||||||
reload = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PWA
|
// PWA
|
||||||
if (site.PwaIsEnabled.ToString() != _pwaisenabled)
|
if (site.PwaIsEnabled.ToString() != _pwaisenabled)
|
||||||
{
|
{
|
||||||
site.PwaIsEnabled = Boolean.Parse(_pwaisenabled);
|
site.PwaIsEnabled = Boolean.Parse(_pwaisenabled);
|
||||||
reload = true; // needs to be reloaded on server
|
|
||||||
}
|
}
|
||||||
int? pwaappiconfileid = _pwaappiconfilemanager.GetFileId();
|
int? pwaappiconfileid = _pwaappiconfilemanager.GetFileId();
|
||||||
if (pwaappiconfileid == -1) pwaappiconfileid = null;
|
if (pwaappiconfileid == -1) pwaappiconfileid = null;
|
||||||
if (site.PwaAppIconFileId != pwaappiconfileid)
|
if (site.PwaAppIconFileId != pwaappiconfileid)
|
||||||
{
|
{
|
||||||
site.PwaAppIconFileId = pwaappiconfileid;
|
site.PwaAppIconFileId = pwaappiconfileid;
|
||||||
reload = true; // needs to be reloaded on server
|
|
||||||
}
|
}
|
||||||
int? pwasplashiconfileid = _pwasplashiconfilemanager.GetFileId();
|
int? pwasplashiconfileid = _pwasplashiconfilemanager.GetFileId();
|
||||||
if (pwasplashiconfileid == -1) pwasplashiconfileid = null;
|
if (pwasplashiconfileid == -1) pwasplashiconfileid = null;
|
||||||
if (site.PwaSplashIconFileId != pwasplashiconfileid)
|
if (site.PwaSplashIconFileId != pwasplashiconfileid)
|
||||||
{
|
{
|
||||||
site.PwaSplashIconFileId = pwasplashiconfileid;
|
site.PwaSplashIconFileId = pwasplashiconfileid;
|
||||||
reload = true; // needs to be reloaded on server
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// hosting model
|
// hosting model
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
if (site.Runtime != _runtime || site.RenderMode != _runtime + _prerender)
|
if (site.RenderMode != _rendermode || site.Runtime != _runtime || site.Prerender != bool.Parse(_prerender) || site.Hybrid != bool.Parse(_hybrid))
|
||||||
{
|
{
|
||||||
|
site.RenderMode = _rendermode;
|
||||||
site.Runtime = _runtime;
|
site.Runtime = _runtime;
|
||||||
site.RenderMode = _runtime + _prerender;
|
site.Prerender = bool.Parse(_prerender);
|
||||||
reload = true; // needs to be reloaded on server
|
site.Hybrid = bool.Parse(_hybrid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -622,20 +751,19 @@
|
|||||||
settings = SettingService.SetSetting(settings, "SMTPRelay", _smtprelay, true);
|
settings = SettingService.SetSetting(settings, "SMTPRelay", _smtprelay, true);
|
||||||
settings = SettingService.SetSetting(settings, "SMTPEnabled", _smtpenabled, true);
|
settings = SettingService.SetSetting(settings, "SMTPEnabled", _smtpenabled, true);
|
||||||
settings = SettingService.SetSetting(settings, "SiteGuid", _siteguid, true);
|
settings = SettingService.SetSetting(settings, "SiteGuid", _siteguid, true);
|
||||||
settings = SettingService.SetSetting(settings, "NotificationRetention", _retention, true);
|
settings = SettingService.SetSetting(settings, "NotificationRetention", _retention.ToString(), true);
|
||||||
|
|
||||||
|
//cookie consent
|
||||||
|
settings = SettingService.SetSetting(settings, "CookieConsent", _cookieconsent);
|
||||||
|
|
||||||
|
// functionality
|
||||||
|
settings = SettingService.SetSetting(settings, "TextEditor", _textEditor);
|
||||||
|
|
||||||
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
|
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
|
||||||
|
|
||||||
await logger.LogInformation("Site Settings Saved {Site}", site);
|
await logger.LogInformation("Site Settings Saved {Site}", site);
|
||||||
|
|
||||||
if (refresh || reload)
|
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, "updated=true"), true); // reload
|
||||||
{
|
|
||||||
NavigationManager.NavigateTo(NavigateUrl(true), reload); // refresh/reload
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AddModuleMessage(Localizer["Success.Settings.SaveSite"], MessageType.Success);
|
|
||||||
await ScrollToPageTop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -774,8 +902,17 @@
|
|||||||
if (!string.IsNullOrEmpty(_aliasname))
|
if (!string.IsNullOrEmpty(_aliasname))
|
||||||
{
|
{
|
||||||
var aliases = await AliasService.GetAliasesAsync();
|
var aliases = await AliasService.GetAliasesAsync();
|
||||||
var alias = aliases.Where(item => item.Name == _aliasname).FirstOrDefault();
|
|
||||||
|
int protocolIndex = _aliasname.IndexOf("://", StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (protocolIndex != -1)
|
||||||
|
{
|
||||||
|
_aliasname = _aliasname.Substring(protocolIndex + 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
var alias = aliases.FirstOrDefault(item => item.Name == _aliasname);
|
||||||
|
|
||||||
bool unique = (alias == null || alias.AliasId == _aliasid);
|
bool unique = (alias == null || alias.AliasId == _aliasid);
|
||||||
|
|
||||||
if (unique)
|
if (unique)
|
||||||
{
|
{
|
||||||
if (_aliasid == 0)
|
if (_aliasid == 0)
|
||||||
@@ -785,22 +922,27 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
alias = _aliases.Single(item => item.AliasId == _aliasid);
|
alias = _aliases.SingleOrDefault(item => item.AliasId == _aliasid);
|
||||||
|
if (alias != null)
|
||||||
|
{
|
||||||
alias.Name = _aliasname;
|
alias.Name = _aliasname;
|
||||||
alias.IsDefault = bool.Parse(_defaultalias);
|
alias.IsDefault = bool.Parse(_defaultalias);
|
||||||
await AliasService.UpdateAliasAsync(alias);
|
await AliasService.UpdateAliasAsync(alias);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else // duplicate alias
|
|
||||||
{
|
|
||||||
AddModuleMessage(Localizer["Message.Aliases.Taken"], MessageType.Warning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await GetAliases();
|
await GetAliases();
|
||||||
_aliasid = -1;
|
_aliasid = -1;
|
||||||
_aliasname = "";
|
_aliasname = "";
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
else // Duplicate alias
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.Aliases.Taken"], MessageType.Warning);
|
||||||
|
await ScrollToPageTop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CancelAlias()
|
private async Task CancelAlias()
|
||||||
@@ -810,4 +952,9 @@
|
|||||||
_aliasname = "";
|
_aliasname = "";
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task EvictSitemapOutputCache() {
|
||||||
|
await CacheService.EvictByTag(Constants.SitemapOutputCacheTag);
|
||||||
|
AddModuleMessage(Localizer["Success.SiteMap.CacheEvicted"], MessageType.Success);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ else
|
|||||||
<div class="row mb-1 align-items-center">
|
<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>
|
<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">
|
<div class="col-sm-9">
|
||||||
<select id="defaultTheme" class="form-select" @onchange="(e => ThemeChanged(e))" required>
|
<select id="defaultTheme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
|
||||||
<option value="-"><@Localizer["Theme.Select"]></option>
|
<option value="-"><@Localizer["Theme.Select"]></option>
|
||||||
@foreach (var theme in _themes)
|
@foreach (var theme in _themes)
|
||||||
{
|
{
|
||||||
@@ -58,19 +58,6 @@ else
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="adminContainer" HelpText="Select the admin container for the site" ResourceKey="AdminContainer">Admin Container: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<select id="adminContainer" class="form-select" @bind="@_admincontainertype" required>
|
|
||||||
<option value="-"><@Localizer["Container.Select"]></option>
|
|
||||||
<option value=""><@Localizer["DefaultContainer.Admin"]></option>
|
|
||||||
@foreach (var container in _containers)
|
|
||||||
{
|
|
||||||
<option value="@container.TypeName">@container.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="siteTemplate" HelpText="Select the site template" ResourceKey="SiteTemplate">Site Template: </Label>
|
<Label Class="col-sm-3" For="siteTemplate" HelpText="Select the site template" ResourceKey="SiteTemplate">Site Template: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -84,28 +71,29 @@ 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="runtime" HelpText="The runtime hosting model" ResourceKey="Runtime">Runtime: </Label>
|
<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">
|
<div class="col-sm-9">
|
||||||
<select id="runtime" class="form-select" @bind="@_runtime" required>
|
<select id="rendermode" class="form-select" @bind="@_rendermode" required>
|
||||||
<option value="Server">@SharedLocalizer["BlazorServer"]</option>
|
<option value="@RenderModes.Interactive">@(SharedLocalizer["RenderMode" + @RenderModes.Interactive])</option>
|
||||||
<option value="WebAssembly">@SharedLocalizer["BlazorWebAssembly"]</option>
|
<option value="@RenderModes.Static">@(SharedLocalizer["RenderMode" + @RenderModes.Static])</option>
|
||||||
<option value="Hybrid">@SharedLocalizer["BlazorHybrid"]</option>
|
<option value="@RenderModes.Headless">@(SharedLocalizer["RenderMode" + @RenderModes.Headless])</option>
|
||||||
</select>
|
</select>
|
||||||
</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="prerender" HelpText="Specifies if the site should be prerendered (for search crawlers, etc...)" ResourceKey="Prerender">Prerender? </Label>
|
<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">
|
<div class="col-sm-9">
|
||||||
<select id="prerender" class="form-select" @bind="@_prerender" required>
|
<select id="runtime" class="form-select" @bind="@_runtime" required>
|
||||||
<option value="Prerendered">@SharedLocalizer["Yes"]</option>
|
<option value="@Runtimes.Server">@(SharedLocalizer["Runtime" + @Runtimes.Server])</option>
|
||||||
<option value="">@SharedLocalizer["No"]</option>
|
<option value="@Runtimes.WebAssembly">@(SharedLocalizer["Runtime" + @Runtimes.WebAssembly])</option>
|
||||||
|
<option value="@Runtimes.Auto">@(SharedLocalizer["Runtime" + @Runtimes.Auto])</option>
|
||||||
</select>
|
</select>
|
||||||
</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="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">
|
||||||
<select id="tenant" class="form-select" @onchange="(e => TenantChanged(e))" required>
|
<select id="tenant" class="form-select" value="@_tenantid" @onchange="(e => TenantChanged(e))" required>
|
||||||
<option value="-"><@Localizer["Tenant.Select"]></option>
|
<option value="-"><@Localizer["Tenant.Select"]></option>
|
||||||
<option value="+"><@Localizer["Tenant.Add"]></option>
|
<option value="+"><@Localizer["Tenant.Add"]></option>
|
||||||
@foreach (Tenant tenant in _tenants)
|
@foreach (Tenant tenant in _tenants)
|
||||||
@@ -121,7 +109,7 @@ else
|
|||||||
<hr class="app-rule" />
|
<hr class="app-rule" />
|
||||||
</div>
|
</div>
|
||||||
<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 name for the database" ResourceKey="TenantName">Name: </Label>
|
<Label Class="col-sm-3" For="name" HelpText="Enter the name for the database. Note that this will be the tenant name which is used within the framework to identify the database." ResourceKey="TenantName">Name: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="name" class="form-control" @bind="@_tenantName" maxlength="100" required />
|
<input id="name" class="form-control" @bind="@_tenantName" maxlength="100" required />
|
||||||
</div>
|
</div>
|
||||||
@@ -214,20 +202,33 @@ else
|
|||||||
private string _urls = string.Empty;
|
private string _urls = string.Empty;
|
||||||
private string _themetype = "-";
|
private string _themetype = "-";
|
||||||
private string _containertype = "-";
|
private string _containertype = "-";
|
||||||
private string _admincontainertype = "";
|
|
||||||
private string _sitetemplatetype = "-";
|
private string _sitetemplatetype = "-";
|
||||||
private string _runtime = "Server";
|
private string _rendermode = RenderModes.Static;
|
||||||
private string _prerender = "Prerendered";
|
private string _runtime = Runtimes.Server;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
_tenants = await TenantService.GetTenantsAsync();
|
_tenants = await TenantService.GetTenantsAsync();
|
||||||
|
if (_tenants.Any(item => item.Name == TenantNames.Master))
|
||||||
|
{
|
||||||
|
_tenantid = _tenants.First(item => item.Name == TenantNames.Master).TenantId.ToString();
|
||||||
|
}
|
||||||
_urls = PageState.Alias.Name;
|
_urls = PageState.Alias.Name;
|
||||||
_themeList = await ThemeService.GetThemesAsync();
|
_themeList = await ThemeService.GetThemesAsync();
|
||||||
_themes = ThemeService.GetThemeControls(_themeList);
|
_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))
|
||||||
|
{
|
||||||
|
_sitetemplatetype = Constants.DefaultSiteTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
_databases = await DatabaseService.GetDatabasesAsync();
|
_databases = await DatabaseService.GetDatabasesAsync();
|
||||||
if (_databases.Exists(item => item.IsDefault))
|
if (_databases.Exists(item => item.IsDefault))
|
||||||
@@ -295,7 +296,6 @@ else
|
|||||||
_containers = new List<ThemeControl>();
|
_containers = new List<ThemeControl>();
|
||||||
_containertype = "-";
|
_containertype = "-";
|
||||||
}
|
}
|
||||||
_admincontainertype = "";
|
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -399,10 +399,10 @@ else
|
|||||||
config.Aliases = _urls;
|
config.Aliases = _urls;
|
||||||
config.DefaultTheme = _themetype;
|
config.DefaultTheme = _themetype;
|
||||||
config.DefaultContainer = _containertype;
|
config.DefaultContainer = _containertype;
|
||||||
config.DefaultAdminContainer = _admincontainertype;
|
config.DefaultAdminContainer = "";
|
||||||
config.SiteTemplate = _sitetemplatetype;
|
config.SiteTemplate = _sitetemplatetype;
|
||||||
|
config.RenderMode = _rendermode;
|
||||||
config.Runtime = _runtime;
|
config.Runtime = _runtime;
|
||||||
config.RenderMode = _runtime + _prerender;
|
|
||||||
|
|
||||||
ShowProgressIndicator();
|
ShowProgressIndicator();
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ else
|
|||||||
{
|
{
|
||||||
<ActionLink Action="Add" Text="Add Site" ResourceKey="AddSite" />
|
<ActionLink Action="Add" Text="Add Site" ResourceKey="AddSite" />
|
||||||
|
|
||||||
<Pager Items="@_sites">
|
<Pager Items="@_sites" SearchProperties="Name">
|
||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
@@ -47,12 +47,26 @@ else
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void Edit(string name)
|
private void Edit(string name)
|
||||||
|
{
|
||||||
|
if (PageState.Alias.Name == name)
|
||||||
|
{
|
||||||
|
NavigationManager.NavigateTo("/admin/site");
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(PageState.Uri.Scheme + "://" + name + "/admin/site", true);
|
NavigationManager.NavigateTo(PageState.Uri.Scheme + "://" + name + "/admin/site", true);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void Browse(string name)
|
private void Browse(string name)
|
||||||
|
{
|
||||||
|
if (PageState.Alias.Name == name)
|
||||||
|
{
|
||||||
|
NavigationManager.NavigateTo(PageState.Alias.Path + "/");
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(PageState.Uri.Scheme + "://" + name, true);
|
NavigationManager.NavigateTo(PageState.Uri.Scheme + "://" + name, true);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,23 +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">
|
||||||
@if (_databases != null)
|
<input id="databasetype" class="form-control" @bind="@_databasetype" readonly />
|
||||||
{
|
|
||||||
<select id="databasetype" class="form-select" @bind="@_databasetype" required>
|
|
||||||
<option value="-"><@Localizer["Type.Select"]></option>
|
|
||||||
@foreach (var database in _databases)
|
|
||||||
{
|
|
||||||
<option value="@database.Name">@Localizer[@database.Name]</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
}
|
|
||||||
</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">
|
||||||
@@ -204,12 +195,12 @@ else
|
|||||||
{
|
{
|
||||||
_connectionstring = _connections[_connection].ToString();
|
_connectionstring = _connections[_connection].ToString();
|
||||||
_tenant = "";
|
_tenant = "";
|
||||||
_databasetype = "-";
|
_databasetype = "";
|
||||||
var tenant = _tenants.FirstOrDefault(item => item.DBConnectionString == _connection);
|
var tenant = _tenants.FirstOrDefault(item => item.DBConnectionString == _connection);
|
||||||
if (tenant != null)
|
if (tenant != null)
|
||||||
{
|
{
|
||||||
_tenant = tenant.Name;
|
_tenant = tenant.Name;
|
||||||
_databasetype = _databases.FirstOrDefault(item => item.DBType == tenant.DBType).Name;
|
_databasetype = _databases.FirstOrDefault(item => item.DBType == tenant.DBType && item.Name != "LocalDB").Name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -26,6 +26,12 @@
|
|||||||
<input id="osversion" class="form-control" @bind="@_osversion" readonly />
|
<input id="osversion" class="form-control" @bind="@_osversion" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="process" HelpText="Indicates if the current process is 32 bit or 64 bit" ResourceKey="Process">Process: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="process" class="form-control" @bind="@_process" readonly />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="machinename" HelpText="Machine Name" ResourceKey="MachineName">Machine Name: </Label>
|
<Label Class="col-sm-3" For="machinename" HelpText="Machine Name" ResourceKey="MachineName">Machine Name: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -62,12 +68,6 @@
|
|||||||
<input id="servertime" class="form-control" @bind="@_servertime" readonly />
|
<input id="servertime" class="form-control" @bind="@_servertime" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="tickcount" HelpText="Amount Of Time The Service Has Been Available And Operational" ResourceKey="TickCount">Service Uptime: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input id="tickcount" class="form-control" @bind="@_tickcount" readonly />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="workingset" HelpText="Memory Allocation Of Service (in MB)" ResourceKey="WorkingSet">Memory Allocation: </Label>
|
<Label Class="col-sm-3" For="workingset" HelpText="Memory Allocation Of Service (in MB)" ResourceKey="WorkingSet">Memory Allocation: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -133,16 +133,30 @@
|
|||||||
</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="packageregistryurl" HelpText="Specify The Package Manager Service For Installing Modules, Themes, And Translations. If This Field Is Blank It Means The Package Manager Service Is Disabled For This Installation." ResourceKey="PackageManager">Package Manager: </Label>
|
<Label Class="col-sm-3" For="cachecontrol" HelpText="Provide a Cache-Control directive for static assets. For example 'public, max-age=60' indicates that static assets should be cached for 60 seconds. A blank value indicates caching is not enabled." ResourceKey="CacheControl">Static Asset Caching: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="cachecontrol" class="form-control" @bind="@_cachecontrol" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="packageregistryurl" HelpText="Specify The Url Of The Package Manager Service For Installing Modules, Themes, And Translations. If This Field Is Blank It Means The Package Manager Service Is Disabled For This Installation." ResourceKey="PackageManager">Package Manager Url: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="packageregistryurl" class="form-control" @bind="@_packageregistryurl" />
|
<input id="packageregistryurl" class="form-control" @bind="@_packageregistryurl" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="packageregistryemail" HelpText="Specify The Email Address Of The User Account Used For Interacting With The Package Manager Service. This Account Is Used For Managing Packages Across Multiple Installations." ResourceKey="PackageManagerEmail">Package Manager Email: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="packageregistryemail" class="form-control" @bind="@_packageregistryemail" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br /><br />
|
<br /><br />
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveConfig">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveConfig">@SharedLocalizer["Save"]</button>
|
||||||
<a class="btn btn-primary" href="swagger/index.html" target="_new">@Localizer["Access.ApiFramework"]</a>
|
|
||||||
<ActionDialog Header="Restart Application" Message="Are You Sure You Wish To Restart The Application?" Action="Restart Application" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await RestartApplication())" ResourceKey="RestartApplication" />
|
<ActionDialog Header="Restart Application" Message="Are You Sure You Wish To Restart The Application?" Action="Restart Application" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await RestartApplication())" ResourceKey="RestartApplication" />
|
||||||
|
<br /><br />
|
||||||
|
<a class="btn btn-primary" href="swagger/index.html" target="_new">@Localizer["Swagger"]</a>
|
||||||
|
<a class="btn btn-secondary" href="api/endpoint" target="_new">@Localizer["Endpoints"]</a>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Log" Heading="Log" ResourceKey="Log">
|
<TabPanel Name="Log" Heading="Log" ResourceKey="Log">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@@ -165,13 +179,13 @@
|
|||||||
private string _version = string.Empty;
|
private string _version = string.Empty;
|
||||||
private string _clrversion = string.Empty;
|
private string _clrversion = string.Empty;
|
||||||
private string _osversion = string.Empty;
|
private string _osversion = string.Empty;
|
||||||
|
private string _process = string.Empty;
|
||||||
private string _machinename = string.Empty;
|
private string _machinename = string.Empty;
|
||||||
private string _ipaddress = string.Empty;
|
private string _ipaddress = string.Empty;
|
||||||
private string _environment = string.Empty;
|
private string _environment = string.Empty;
|
||||||
private string _contentrootpath = string.Empty;
|
private string _contentrootpath = string.Empty;
|
||||||
private string _webrootpath = string.Empty;
|
private string _webrootpath = string.Empty;
|
||||||
private string _servertime = string.Empty;
|
private string _servertime = string.Empty;
|
||||||
private string _tickcount = string.Empty;
|
|
||||||
private string _workingset = string.Empty;
|
private string _workingset = string.Empty;
|
||||||
private string _installationid = string.Empty;
|
private string _installationid = string.Empty;
|
||||||
|
|
||||||
@@ -179,7 +193,9 @@
|
|||||||
private string _logginglevel = string.Empty;
|
private string _logginglevel = string.Empty;
|
||||||
private string _notificationlevel = string.Empty;
|
private string _notificationlevel = string.Empty;
|
||||||
private string _swagger = string.Empty;
|
private string _swagger = string.Empty;
|
||||||
|
private string _cachecontrol = string.Empty;
|
||||||
private string _packageregistryurl = string.Empty;
|
private string _packageregistryurl = string.Empty;
|
||||||
|
private string _packageregistryemail = string.Empty;
|
||||||
|
|
||||||
private string _log = string.Empty;
|
private string _log = string.Empty;
|
||||||
|
|
||||||
@@ -192,13 +208,13 @@
|
|||||||
{
|
{
|
||||||
_clrversion = systeminfo["CLRVersion"].ToString();
|
_clrversion = systeminfo["CLRVersion"].ToString();
|
||||||
_osversion = systeminfo["OSVersion"].ToString();
|
_osversion = systeminfo["OSVersion"].ToString();
|
||||||
|
_process = systeminfo["Process"].ToString();
|
||||||
_machinename = systeminfo["MachineName"].ToString();
|
_machinename = systeminfo["MachineName"].ToString();
|
||||||
_ipaddress = systeminfo["IPAddress"].ToString();
|
_ipaddress = systeminfo["IPAddress"].ToString();
|
||||||
_environment = systeminfo["Environment"].ToString();
|
_environment = systeminfo["Environment"].ToString();
|
||||||
_contentrootpath = systeminfo["ContentRootPath"].ToString();
|
_contentrootpath = systeminfo["ContentRootPath"].ToString();
|
||||||
_webrootpath = systeminfo["WebRootPath"].ToString();
|
_webrootpath = systeminfo["WebRootPath"].ToString();
|
||||||
_servertime = systeminfo["ServerTime"].ToString() + " UTC";
|
_servertime = systeminfo["ServerTime"].ToString() + " UTC";
|
||||||
_tickcount = TimeSpan.FromMilliseconds(Convert.ToInt64(systeminfo["TickCount"].ToString())).ToString();
|
|
||||||
_workingset = (Convert.ToInt64(systeminfo["WorkingSet"].ToString()) / 1000000).ToString() + " MB";
|
_workingset = (Convert.ToInt64(systeminfo["WorkingSet"].ToString()) / 1000000).ToString() + " MB";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +226,9 @@
|
|||||||
_logginglevel = systeminfo["Logging:LogLevel:Default"].ToString();
|
_logginglevel = systeminfo["Logging:LogLevel:Default"].ToString();
|
||||||
_notificationlevel = systeminfo["Logging:LogLevel:Notify"].ToString();
|
_notificationlevel = systeminfo["Logging:LogLevel:Notify"].ToString();
|
||||||
_swagger = systeminfo["UseSwagger"].ToString();
|
_swagger = systeminfo["UseSwagger"].ToString();
|
||||||
|
_cachecontrol = systeminfo["CacheControl"].ToString();
|
||||||
_packageregistryurl = systeminfo["PackageRegistryUrl"].ToString();
|
_packageregistryurl = systeminfo["PackageRegistryUrl"].ToString();
|
||||||
|
_packageregistryemail = systeminfo["PackageRegistryEmail"].ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
systeminfo = await SystemService.GetSystemInfoAsync("log");
|
systeminfo = await SystemService.GetSystemInfoAsync("log");
|
||||||
@@ -229,7 +247,9 @@
|
|||||||
settings.Add("Logging:LogLevel:Default", _logginglevel);
|
settings.Add("Logging:LogLevel:Default", _logginglevel);
|
||||||
settings.Add("Logging:LogLevel:Notify", _notificationlevel);
|
settings.Add("Logging:LogLevel:Notify", _notificationlevel);
|
||||||
settings.Add("UseSwagger", _swagger);
|
settings.Add("UseSwagger", _swagger);
|
||||||
|
settings.Add("CacheControl", _cachecontrol);
|
||||||
settings.Add("PackageRegistryUrl", _packageregistryurl);
|
settings.Add("PackageRegistryUrl", _packageregistryurl);
|
||||||
|
settings.Add("PackageRegistryEmail", _packageregistryemail);
|
||||||
await SystemService.UpdateSystemInfoAsync(settings);
|
await SystemService.UpdateSystemInfoAsync(settings);
|
||||||
AddModuleMessage(Localizer["Success.UpdateConfig.Restart"], MessageType.Success);
|
AddModuleMessage(Localizer["Success.UpdateConfig.Restart"], MessageType.Success);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,7 @@
|
|||||||
<div class="container-fluid px-0">
|
<div class="container-fluid px-0">
|
||||||
<div class="row g-0 mb-2">
|
<div class="row g-0 mb-2">
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
|
<a href="@context.ProductUrl" target="_blank">
|
||||||
@if (context.LogoUrl != null)
|
@if (context.LogoUrl != null)
|
||||||
{
|
{
|
||||||
<img src="@context.LogoUrl" class="img-fluid" alt="@context.Name" />
|
<img src="@context.LogoUrl" class="img-fluid" alt="@context.Name" />
|
||||||
@@ -74,6 +75,7 @@
|
|||||||
{
|
{
|
||||||
<img src="/package.png" class="img-fluid" alt="@context.Name" />
|
<img src="/package.png" class="img-fluid" alt="@context.Name" />
|
||||||
}
|
}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-8 text-end">
|
<div class="col-8 text-end">
|
||||||
<small>@SharedLocalizer["Search.Version"]:</small> <strong>@context.Version</strong>
|
<small>@SharedLocalizer["Search.Version"]:</small> <strong>@context.Version</strong>
|
||||||
@@ -123,7 +125,7 @@
|
|||||||
<TabPanel Name="Upload" ResourceKey="Upload">
|
<TabPanel Name="Upload" ResourceKey="Upload">
|
||||||
<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" HelpText="Upload one or more theme packages. Once they are uploaded click Install to complete the installation." ResourceKey="Theme">Theme: </Label>
|
<Label Class="col-sm-3" HelpText="Upload one or more theme packages." ResourceKey="Theme">Theme: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" OnUpload="OnUpload" />
|
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" OnUpload="OnUpload" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -45,24 +45,7 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this theme was installed. This value must be specified within the theme's ITheme interface specification." ResourceKey="PackageName">Package Name: </Label>
|
<Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this theme was installed. This value must be specified within the theme's ITheme interface specification." ResourceKey="PackageName">Package Name: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@if (!string.IsNullOrEmpty(_packagename))
|
|
||||||
{
|
|
||||||
<div class="input-group">
|
|
||||||
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
|
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
|
||||||
@if (string.IsNullOrEmpty(_packageurl))
|
|
||||||
{
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="ValidatePackage">@Localizer["Validate"]</button>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<a href="@_packageurl" target="_blank" class="btn btn-primary">@SharedLocalizer["Download"]</a>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -72,7 +55,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="url" HelpText="The reference url of the theme" ResourceKey="ReferenceUrl">Reference Url: </Label>
|
<Label Class="col-sm-3" For="url" HelpText="The url of the theme" ResourceKey="Url">Url: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="url" class="form-control" @bind="@_url" disabled />
|
<input id="url" class="form-control" @bind="@_url" disabled />
|
||||||
</div>
|
</div>
|
||||||
@@ -116,7 +99,6 @@
|
|||||||
private string _name;
|
private string _name;
|
||||||
private string _version;
|
private string _version;
|
||||||
private string _packagename = "";
|
private string _packagename = "";
|
||||||
private string _packageurl = "";
|
|
||||||
private string _owner = "";
|
private string _owner = "";
|
||||||
private string _url = "";
|
private string _url = "";
|
||||||
private string _contact = "";
|
private string _contact = "";
|
||||||
@@ -185,27 +167,4 @@
|
|||||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ValidatePackage()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var package = await PackageService.GetPackageAsync(_packagename, _version, true);
|
|
||||||
if (package == null || string.IsNullOrEmpty(package.PackageUrl))
|
|
||||||
{
|
|
||||||
AddModuleMessage(Localizer["Message.Validate"], MessageType.Warning);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_packageurl = package.PackageUrl;
|
|
||||||
AddModuleMessage(Localizer["Message.Download"], MessageType.Info);
|
|
||||||
}
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packagename, _version);
|
|
||||||
AddModuleMessage(Localizer["Error.Validate"], MessageType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IThemeService ThemeService
|
@inject IThemeService ThemeService
|
||||||
@inject IPackageService PackageService
|
@inject IPackageService PackageService
|
||||||
|
@inject ISiteService SiteService
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@@ -14,11 +15,12 @@
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
<ActionLink Action="Add" Text="Install Theme" ResourceKey="InstallTheme" />
|
<ActionLink Action="Add" Text="Install Theme" ResourceKey="InstallTheme" />
|
||||||
@((MarkupString)" ")
|
<ActionLink Action="Create" Text="Create Theme" ResourceKey="CreateTheme" Class="btn btn-secondary ps-2" />
|
||||||
<ActionLink Action="Create" Text="Create Theme" ResourceKey="CreateTheme" Class="btn btn-secondary" />
|
<button type="button" class="btn btn-secondary pw-2" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
|
||||||
|
|
||||||
<Pager Items="@_themes">
|
<Pager Items="@_themes">
|
||||||
<Header>
|
<Header>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th>@SharedLocalizer["Name"]</th>
|
<th>@SharedLocalizer["Name"]</th>
|
||||||
@@ -29,13 +31,14 @@ else
|
|||||||
<th> </th>
|
<th> </th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ThemeId.ToString())" ResourceKey="EditTheme" /></td>
|
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.ThemeId.ToString())" ResourceKey="EditTheme" /></td>
|
||||||
<td>
|
<td>
|
||||||
@if (context.AssemblyName != Constants.ClientId)
|
@if (context.AssemblyName != Constants.ClientId)
|
||||||
{
|
{
|
||||||
<ActionDialog Header="Delete Theme" Message="@string.Format(Localizer["Confirm.Theme.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteTheme(context))" ResourceKey="DeleteTheme" />
|
<ActionDialog Header="Delete Theme" Message="@string.Format(Localizer["Confirm.Theme.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteTheme(context))" ResourceKey="DeleteTheme" />
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
|
<td><NavLink class="btn btn-secondary" href="@NavigateUrl("admin/site")">@Localizer["Assign"]</NavLink></td>
|
||||||
<td>@context.Name</td>
|
<td>@context.Name</td>
|
||||||
<td>@context.Version</td>
|
<td>@context.Version</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -63,7 +66,6 @@ else
|
|||||||
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadTheme(context.PackageName, version))>@SharedLocalizer["Upgrade"]</button>
|
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadTheme(context.PackageName, version))>@SharedLocalizer["Upgrade"]</button>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td></td>
|
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
}
|
}
|
||||||
@@ -171,4 +173,27 @@ else
|
|||||||
AddModuleMessage(Localizer["Error.Theme.Delete"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.Theme.Delete"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task Synchronize()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ShowProgressIndicator();
|
||||||
|
foreach (var theme in _themes)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(theme.PackageName) && !_packages.Any(item => item.PackageId == theme.PackageName))
|
||||||
|
{
|
||||||
|
await PackageService.GetPackageAsync(theme.PackageName, theme.Version, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HideProgressIndicator();
|
||||||
|
AddModuleMessage(Localizer["Success.Theme.Synchronize"], MessageType.Success);
|
||||||
|
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Synchronizing Themes {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.Theme.Synchronize"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -13,17 +13,33 @@
|
|||||||
<TabPanel Name="Download" ResourceKey="Download">
|
<TabPanel Name="Download" ResourceKey="Download">
|
||||||
@if (_package != null && _upgradeavailable)
|
@if (_package != null && _upgradeavailable)
|
||||||
{
|
{
|
||||||
<ModuleMessage Type="MessageType.Info" Message="Select The Download Button To Download The Framework Upgrade Package And Then Select Upgrade"></ModuleMessage>
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" HelpText="Specify if you want to backup files during the upgrade process. Disabling this option will reduce the time required for the upgrade." ResourceKey="Backup">Backup Files? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="backup" class="form-select" @bind="@_backup">
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
@if (!_downloaded)
|
||||||
|
{
|
||||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await Download(Constants.PackageId, @_package.Version))>@SharedLocalizer["Download"] @_package.Version</button>
|
<button type="button" class="btn btn-primary" @onclick=@(async () => await Download(Constants.PackageId, @_package.Version))>@SharedLocalizer["Download"] @_package.Version</button>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
<button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button>
|
<button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<ModuleMessage Type="MessageType.Info" Message=@Localizer["Message.Text"]></ModuleMessage>
|
<ModuleMessage Type="MessageType.Info" Message=@Localizer["Message.Text"]></ModuleMessage>
|
||||||
}
|
}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Upload" ResourceKey="Upload">
|
<TabPanel Name="Upload" ResourceKey="Upload">
|
||||||
<ModuleMessage Type="MessageType.Info" Message=@Localizer["MessageUpgrade.Text"]></ModuleMessage>
|
|
||||||
<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" HelpText="Upload A Framework Package And Then Select Upgrade" ResourceKey="Framework">Framework: </Label>
|
<Label Class="col-sm-3" HelpText="Upload A Framework Package And Then Select Upgrade" ResourceKey="Framework">Framework: </Label>
|
||||||
@@ -31,7 +47,17 @@
|
|||||||
<FileManager Folder="@Constants.PackagesFolder" />
|
<FileManager Folder="@Constants.PackagesFolder" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" HelpText="Specify if you want to backup files during the upgrade process. Disabling this option will reduce the time required for the upgrade." ResourceKey="Backup">Backup Files? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="backup" class="form-select" @bind="@_backup">
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button>
|
<button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabStrip>
|
</TabStrip>
|
||||||
@@ -39,8 +65,10 @@
|
|||||||
|
|
||||||
@code {
|
@code {
|
||||||
private bool _initialized = false;
|
private bool _initialized = false;
|
||||||
|
private bool _downloaded = false;
|
||||||
private Package _package;
|
private Package _package;
|
||||||
private bool _upgradeavailable = false;
|
private bool _upgradeavailable = false;
|
||||||
|
private string _backup = "True";
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
@@ -54,6 +82,8 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
AddModuleMessage(Localizer["Disclaimer.Text"], MessageType.Warning);
|
||||||
|
|
||||||
List<Package> packages = await PackageService.GetPackagesAsync("framework", "", "", "");
|
List<Package> packages = await PackageService.GetPackagesAsync("framework", "", "", "");
|
||||||
if (packages != null)
|
if (packages != null)
|
||||||
{
|
{
|
||||||
@@ -84,7 +114,7 @@
|
|||||||
ShowProgressIndicator();
|
ShowProgressIndicator();
|
||||||
var interop = new Interop(JSRuntime);
|
var interop = new Interop(JSRuntime);
|
||||||
await interop.RedirectBrowser(NavigateUrl(), 10);
|
await interop.RedirectBrowser(NavigateUrl(), 10);
|
||||||
await InstallationService.Upgrade();
|
await InstallationService.Upgrade(bool.Parse(_backup));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -97,13 +127,17 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
ShowProgressIndicator();
|
||||||
await PackageService.DownloadPackageAsync(packageid, version);
|
await PackageService.DownloadPackageAsync(packageid, version);
|
||||||
await PackageService.DownloadPackageAsync(Constants.UpdaterPackageId, version);
|
await PackageService.DownloadPackageAsync(Constants.UpdaterPackageId, version);
|
||||||
|
_downloaded = true;
|
||||||
|
HideProgressIndicator();
|
||||||
AddModuleMessage(Localizer["Success.Framework.Download"], MessageType.Success);
|
AddModuleMessage(Localizer["Success.Framework.Download"], MessageType.Success);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Downloading Framework Package {Error}", ex.Message);
|
await logger.LogError(ex, "Error Downloading Framework Package {Error}", ex.Message);
|
||||||
|
HideProgressIndicator();
|
||||||
AddModuleMessage(Localizer["Error.Framework.Download"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.Framework.Download"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,13 +8,16 @@
|
|||||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="url" HelpText="The fully qualified Url for this site" ResourceKey="Url">Url:</Label>
|
<Label Class="col-sm-3" For="url" HelpText="A Url identifying a path to a specific page in the site (absolute or relative)" ResourceKey="Url">Url:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
<input id="url" class="form-control" @bind="@_url" maxlength="500" required />
|
<input id="url" class="form-control" @bind="@_url" maxlength="500" required />
|
||||||
|
<button type="button" class="btn btn-primary" @onclick="GenerateUrl">@Localizer["Generate"]</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="mappedurl" HelpText="A fully qualified Url where the user will be redirected" ResourceKey="MappedUrl">Redirect To:</Label>
|
<Label Class="col-sm-3" For="mappedurl" HelpText="A Url where the user will be redirected (absolute or relative). Use '/' for site root path." ResourceKey="MappedUrl">Redirect To:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="mappedurl" class="form-control" @bind="@_mappedurl" maxlength="500" required />
|
<input id="mappedurl" class="form-control" @bind="@_mappedurl" maxlength="500" required />
|
||||||
</div>
|
</div>
|
||||||
@@ -48,13 +51,15 @@
|
|||||||
_url = (_url.StartsWith("/")) ? _url.Substring(1) : _url;
|
_url = (_url.StartsWith("/")) ? _url.Substring(1) : _url;
|
||||||
_url = (!_url.StartsWith("http")) ? url + _url : _url;
|
_url = (!_url.StartsWith("http")) ? url + _url : _url;
|
||||||
|
|
||||||
|
_mappedurl = _mappedurl.Replace(url, "");
|
||||||
|
_mappedurl = (_mappedurl.StartsWith("/") && _mappedurl != "/") ? _mappedurl.Substring(1) : _mappedurl;
|
||||||
|
|
||||||
if (_url.StartsWith(url))
|
if (_url.StartsWith(url))
|
||||||
{
|
{
|
||||||
var urlmapping = new UrlMapping();
|
var urlmapping = new UrlMapping();
|
||||||
urlmapping.SiteId = PageState.Site.SiteId;
|
urlmapping.SiteId = PageState.Site.SiteId;
|
||||||
var route = new Route(_url, PageState.Alias.Path);
|
urlmapping.Url = new Route(_url, PageState.Alias.Path).PagePath;
|
||||||
urlmapping.Url = route.PagePath;
|
urlmapping.MappedUrl = _mappedurl;
|
||||||
urlmapping.MappedUrl = _mappedurl.Replace(url, "");
|
|
||||||
urlmapping.Requests = 0;
|
urlmapping.Requests = 0;
|
||||||
urlmapping.CreatedOn = DateTime.UtcNow;
|
urlmapping.CreatedOn = DateTime.UtcNow;
|
||||||
urlmapping.RequestedOn = DateTime.UtcNow;
|
urlmapping.RequestedOn = DateTime.UtcNow;
|
||||||
@@ -86,4 +91,18 @@
|
|||||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void GenerateUrl()
|
||||||
|
{
|
||||||
|
var url = PageState.Uri.Scheme + "://" + PageState.Uri.Authority + "/";
|
||||||
|
url = url + (!string.IsNullOrEmpty(PageState.Alias.Path) ? PageState.Alias.Path + "/" : "");
|
||||||
|
|
||||||
|
var chars = "abcdefghijklmnopqrstuvwxyz";
|
||||||
|
Random rnd = new Random();
|
||||||
|
for (int i = 0; i < 5; i++)
|
||||||
|
{
|
||||||
|
url += chars.Substring(rnd.Next(0, chars.Length - 1), 1);
|
||||||
|
}
|
||||||
|
_url = url;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,13 +8,13 @@
|
|||||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="url" HelpText="A fully qualified Url for this site" ResourceKey="Url">Url:</Label>
|
<Label Class="col-sm-3" For="url" HelpText="A Url identifying a path to a specific page in the site (absolute or relative)" ResourceKey="Url">Url:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="url" class="form-control" @bind="@_url" maxlength="500" readonly />
|
<input id="url" class="form-control" @bind="@_url" maxlength="500" readonly />
|
||||||
</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="mappedurl" HelpText="A fully qualified Url where the user will be redirected" ResourceKey="MappedUrl">Redirect To:</Label>
|
<Label Class="col-sm-3" For="mappedurl" HelpText="A Url where the user will be redirected (absolute or relative). Use '/' for site root path." ResourceKey="MappedUrl">Redirect To:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="mappedurl" class="form-control" @bind="@_mappedurl" maxlength="500" required />
|
<input id="mappedurl" class="form-control" @bind="@_mappedurl" maxlength="500" required />
|
||||||
</div>
|
</div>
|
||||||
@@ -67,8 +67,11 @@
|
|||||||
var url = PageState.Uri.Scheme + "://" + PageState.Uri.Authority + "/";
|
var url = PageState.Uri.Scheme + "://" + PageState.Uri.Authority + "/";
|
||||||
url = url + (!string.IsNullOrEmpty(PageState.Alias.Path) ? PageState.Alias.Path + "/" : "");
|
url = url + (!string.IsNullOrEmpty(PageState.Alias.Path) ? PageState.Alias.Path + "/" : "");
|
||||||
|
|
||||||
|
_mappedurl = _mappedurl.Replace(url, "");
|
||||||
|
_mappedurl = (_mappedurl.StartsWith("/") && _mappedurl != "/") ? _mappedurl.Substring(1) : _mappedurl;
|
||||||
|
|
||||||
var urlmapping = await UrlMappingService.GetUrlMappingAsync(_urlmappingid);
|
var urlmapping = await UrlMappingService.GetUrlMappingAsync(_urlmappingid);
|
||||||
urlmapping.MappedUrl = _mappedurl.Replace(url, "");
|
urlmapping.MappedUrl = _mappedurl;
|
||||||
urlmapping = await UrlMappingService.UpdateUrlMappingAsync(urlmapping);
|
urlmapping = await UrlMappingService.UpdateUrlMappingAsync(urlmapping);
|
||||||
await logger.LogInformation("UrlMapping Saved {UrlMapping}", urlmapping);
|
await logger.LogInformation("UrlMapping Saved {UrlMapping}", urlmapping);
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IUrlMappingService UrlMappingService
|
@inject IUrlMappingService UrlMappingService
|
||||||
@inject ISiteService SiteService
|
@inject ISiteService SiteService
|
||||||
|
@inject ISettingService SettingService
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ else
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
<Pager Items="@_urlMappings">
|
<Pager Items="@_urlMappings" SearchProperties="Url">
|
||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
@@ -37,7 +38,7 @@ else
|
|||||||
<th>@Localizer["Requested"]</th>
|
<th>@Localizer["Requested"]</th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.UrlMappingId.ToString())" ResourceKey="Edit" /></td>
|
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.UrlMappingId.ToString())" ResourceKey="Edit" /></td>
|
||||||
<td><ActionDialog Header="Delete Url Mapping" Message="@string.Format(Localizer["Confirm.DeleteUrlMapping"], context.Url)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteUrlMapping(context))" ResourceKey="DeleteUrlMapping" /></td>
|
<td><ActionDialog Header="Delete Url Mapping" Message="@string.Format(Localizer["Confirm.DeleteUrlMapping"], context.Url)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteUrlMapping(context))" ResourceKey="DeleteUrlMapping" /></td>
|
||||||
<td>
|
<td>
|
||||||
<a href="@Utilities.TenantUrl(PageState.Alias, context.Url)">@context.Url</a>
|
<a href="@Utilities.TenantUrl(PageState.Alias, context.Url)">@context.Url</a>
|
||||||
@@ -47,7 +48,7 @@ else
|
|||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>@context.Requests</td>
|
<td>@context.Requests</td>
|
||||||
<td>@context.RequestedOn</td>
|
<td>@UtcToLocal(context.RequestedOn)</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
@@ -62,6 +63,12 @@ else
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="retention" HelpText="Number of days of broken urls to retain" ResourceKey="Retention">Retention (Days): </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="retention" class="form-control" type="number" min="0" step="1" @bind="@_retention" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</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>
|
||||||
@@ -73,6 +80,7 @@ else
|
|||||||
private bool _mapped = true;
|
private bool _mapped = true;
|
||||||
private List<UrlMapping> _urlMappings;
|
private List<UrlMapping> _urlMappings;
|
||||||
private string _capturebrokenurls;
|
private string _capturebrokenurls;
|
||||||
|
private int _retention = 30;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
|
||||||
@@ -80,6 +88,9 @@ else
|
|||||||
{
|
{
|
||||||
await GetUrlMappings();
|
await GetUrlMappings();
|
||||||
_capturebrokenurls = PageState.Site.CaptureBrokenUrls.ToString();
|
_capturebrokenurls = PageState.Site.CaptureBrokenUrls.ToString();
|
||||||
|
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
_retention = int.Parse(SettingService.GetSetting(settings, "UrlMappingRetention", "30"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void MappedChanged(ChangeEventArgs e)
|
private async void MappedChanged(ChangeEventArgs e)
|
||||||
@@ -124,6 +135,11 @@ else
|
|||||||
var site = PageState.Site;
|
var site = PageState.Site;
|
||||||
site.CaptureBrokenUrls = bool.Parse(_capturebrokenurls);
|
site.CaptureBrokenUrls = bool.Parse(_capturebrokenurls);
|
||||||
await SiteService.UpdateSiteAsync(site);
|
await SiteService.UpdateSiteAsync(site);
|
||||||
|
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
||||||
|
settings = SettingService.SetSetting(settings, "UrlMappingRetention", _retention.ToString(), true);
|
||||||
|
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
|
||||||
|
|
||||||
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IUserService UserService
|
@inject IUserService UserService
|
||||||
|
@inject IUserRoleService UserRoleService
|
||||||
@inject INotificationService NotificationService
|
@inject INotificationService NotificationService
|
||||||
@inject IStringLocalizer<Add> Localizer
|
@inject IStringLocalizer<Add> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
@@ -10,9 +11,9 @@
|
|||||||
{
|
{
|
||||||
<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="to" HelpText="Enter the username you wish to send a message to" ResourceKey="To">To: </Label>
|
<Label Class="col-sm-3" For="to" HelpText="Enter the user you wish to send a message to" ResourceKey="To">To: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="to" class="form-control" @bind="@username" />
|
<AutoComplete OnSearch="GetUsers" Placeholder="@Localizer["Username.Enter"]" @ref="username" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -30,11 +31,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
<button type="button" class="btn btn-primary" @onclick="Send">@SharedLocalizer["Send"]</button>
|
<button type="button" class="btn btn-primary" @onclick="Send">@SharedLocalizer["Send"]</button>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private string username = "";
|
private AutoComplete username;
|
||||||
private string subject = "";
|
private string subject = "";
|
||||||
private string body = "";
|
private string body = "";
|
||||||
|
|
||||||
@@ -42,23 +43,37 @@
|
|||||||
|
|
||||||
public override string Title => "Send Notification";
|
public override string Title => "Send Notification";
|
||||||
|
|
||||||
|
private async Task<Dictionary<string, string>> GetUsers(string filter)
|
||||||
|
{
|
||||||
|
var users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
|
||||||
|
return users.Where(item => item.User.Username.Contains(filter, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.ToDictionary(item => item.UserId.ToString(), item => item.User.Username);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task Send()
|
private async Task Send()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var user = await UserService.GetUserAsync(username, PageState.Site.SiteId);
|
if (!string.IsNullOrEmpty(username.Key) && !string.IsNullOrEmpty(subject))
|
||||||
|
{
|
||||||
|
var user = await UserService.GetUserAsync(int.Parse(username.Key), ModuleState.SiteId);
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
var notification = new Notification(PageState.Site.SiteId, PageState.User, user, subject, body);
|
var notification = new Notification(PageState.Site.SiteId, PageState.User, user, subject, body);
|
||||||
notification = await NotificationService.AddNotificationAsync(notification);
|
notification = await NotificationService.AddNotificationAsync(notification);
|
||||||
await logger.LogInformation("Notification Created {NotificationId}", notification.NotificationId);
|
await logger.LogInformation("Notification Created {NotificationId}", notification.NotificationId);
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Message.User.Invalid"], MessageType.Warning);
|
AddModuleMessage(Localizer["Message.User.Invalid"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.Required"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Adding Notification {Error}", ex.Message);
|
await logger.LogError(ex, "Error Adding Notification {Error}", ex.Message);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
@namespace Oqtane.Modules.Admin.UserProfile
|
@namespace Oqtane.Modules.Admin.UserProfile
|
||||||
|
@using System.Net
|
||||||
@using System.Text.RegularExpressions;
|
@using System.Text.RegularExpressions;
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@@ -8,27 +9,30 @@
|
|||||||
@inject INotificationService NotificationService
|
@inject INotificationService NotificationService
|
||||||
@inject IFileService FileService
|
@inject IFileService FileService
|
||||||
@inject IFolderService FolderService
|
@inject IFolderService FolderService
|
||||||
|
@inject ITimeZoneService TimeZoneService
|
||||||
|
@inject IJSRuntime jsRuntime
|
||||||
|
@inject IServiceProvider ServiceProvider
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@if (PageState.User != null && photo != null)
|
@if (_initialized)
|
||||||
{
|
{
|
||||||
<img src="@ImageUrl(photofileid, 400, 400)" alt="@displayname" style="max-width: 400px" class="rounded-circle mx-auto d-block">
|
@if (PageState.User != null && _photo != null)
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<br />
|
|
||||||
}
|
|
||||||
<TabStrip>
|
|
||||||
<TabPanel Name="Identity" ResourceKey="Identity">
|
|
||||||
@if (profiles != null && settings != null)
|
|
||||||
{
|
{
|
||||||
|
<img src="@ImageUrl(_photofileid, 400, 400)" alt="@_displayname" style="max-width: 400px" class="rounded-circle mx-auto d-block">
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<br />
|
||||||
|
}
|
||||||
|
<TabStrip>
|
||||||
|
<TabPanel Name="Identity" ResourceKey="Identity">
|
||||||
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
<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>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="username" class="form-control" @bind="@username" readonly />
|
<input id="username" class="form-control" @bind="@_username" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -44,17 +48,17 @@ else
|
|||||||
<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>
|
<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="col-sm-9">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@confirm" autocomplete="new-password" />
|
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" />
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (allowtwofactor)
|
@if (_allowtwofactor)
|
||||||
{
|
{
|
||||||
<div class="row mb-1 align-items-center">
|
<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>
|
<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">
|
<div class="col-sm-9">
|
||||||
<select id="twofactor" class="form-select" @bind="@twofactor" required>
|
<select id="twofactor" class="form-select" @bind="@_twofactor" required>
|
||||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
<option value="False">@SharedLocalizer["No"]</option>
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
</select>
|
</select>
|
||||||
@@ -64,50 +68,62 @@ else
|
|||||||
<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">
|
||||||
<input id="email" class="form-control" @bind="@email" />
|
<input id="email" class="form-control" @bind="@_email" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="displayname" HelpText="Your full name" ResourceKey="DisplayName"></Label>
|
<Label Class="col-sm-3" For="displayname" HelpText="Your full name" ResourceKey="DisplayName"></Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="displayname" class="form-control" @bind="@displayname" />
|
<input id="displayname" class="form-control" @bind="@_displayname" />
|
||||||
</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="@photofileid.ToString()" HelpText="A photo of yourself" ResourceKey="Photo"></Label>
|
<Label Class="col-sm-3" For="timezone" HelpText="Your time zone" ResourceKey="TimeZone">Time Zone:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<FileManager FileId="@photofileid" Filter="@Constants.ImageFiles" ShowFolders="false" ShowFiles="true" UploadMultiple="false" FolderId="@folderid" @ref="filemanager" />
|
<select id="timezone" class="form-select" @bind="@_timezoneid">
|
||||||
|
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||||
|
@foreach (var timezone in _timezones)
|
||||||
|
{
|
||||||
|
<option value="@timezone.Id">@timezone.DisplayName</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="@_photofileid.ToString()" HelpText="A photo of yourself" ResourceKey="Photo"></Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<FileManager FileId="@_photofileid" Filter="@PageState.Site.ImageFiles" ShowFolders="false" ShowFiles="true" UploadMultiple="false" FolderId="@_folderid" @ref="_filemanager" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<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="Profile" ResourceKey="Profile">
|
||||||
@if (profiles != null && settings != null)
|
|
||||||
{
|
|
||||||
<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)
|
||||||
{
|
{
|
||||||
var p = profile;
|
var p = profile;
|
||||||
if (!p.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
if (!p.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
{
|
{
|
||||||
if (p.Category != category)
|
if (p.Category != _category)
|
||||||
{
|
{
|
||||||
<div class="col text-center pb-2">
|
<div class="col text-center pb-2">
|
||||||
@p.Category
|
@p.Category
|
||||||
</div>
|
</div>
|
||||||
category = p.Category;
|
_category = p.Category;
|
||||||
}
|
}
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
|
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@if (!string.IsNullOrEmpty(p.Options))
|
@if (!string.IsNullOrEmpty(p.Options))
|
||||||
{
|
{
|
||||||
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
|
@if (!string.IsNullOrEmpty(p.Autocomplete))
|
||||||
|
{
|
||||||
|
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete">
|
||||||
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||||
{
|
{
|
||||||
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
|
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
|
||||||
@@ -123,13 +139,80 @@ else
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@if (p.IsRequired)
|
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
|
||||||
|
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||||
{
|
{
|
||||||
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" />
|
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
|
||||||
|
{
|
||||||
|
<option value="@option" selected>@option</option>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
|
<option value="@option">@option</option>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@if (p.Rows == 1)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(p.Autocomplete))
|
||||||
|
{
|
||||||
|
@if (p.IsRequired)
|
||||||
|
{
|
||||||
|
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"
|
||||||
|
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"
|
||||||
|
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@if (p.IsRequired)
|
||||||
|
{
|
||||||
|
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))"
|
||||||
|
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"
|
||||||
|
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(p.Autocomplete))
|
||||||
|
{
|
||||||
|
@if (p.IsRequired)
|
||||||
|
{
|
||||||
|
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"
|
||||||
|
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"
|
||||||
|
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@if (p.IsRequired)
|
||||||
|
{
|
||||||
|
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))"
|
||||||
|
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"
|
||||||
|
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -140,21 +223,21 @@ else
|
|||||||
</div>
|
</div>
|
||||||
<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" ResourceKey="Notifications">
|
||||||
@if (notifications != null)
|
<ActionLink Action="Add" Text="Send Notification" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="SendNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" />
|
||||||
{
|
<br />
|
||||||
|
<br />
|
||||||
<select class="form-select" @onchange="(e => FilterChanged(e))">
|
<select class="form-select" @onchange="(e => FilterChanged(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>
|
||||||
<br />
|
<br />
|
||||||
<ActionLink Action="Add" Text="Send Notification" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="SendNotification" />
|
@if (_filter == "to")
|
||||||
<br /><br />
|
|
||||||
@if (filter == "to")
|
|
||||||
{
|
{
|
||||||
<Pager Items="@notifications">
|
@if (_notifications.Any())
|
||||||
|
{
|
||||||
|
<Pager Items="@_notifications">
|
||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
@@ -163,18 +246,18 @@ else
|
|||||||
<th>@Localizer["Received"]</th>
|
<th>@Localizer["Received"]</th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" /></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 Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
|
||||||
|
|
||||||
@if (context.IsRead)
|
@if (context.IsRead)
|
||||||
{
|
{
|
||||||
<td>@context.FromDisplayName</td>
|
<td>@(string.IsNullOrEmpty(context.FromDisplayName) ? SharedLocalizer["System"] : context.FromDisplayName)</td>
|
||||||
<td>@context.Subject</td>
|
<td>@context.Subject</td>
|
||||||
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</td>
|
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</td>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<td><b>@context.FromDisplayName</b></td>
|
<td><b>@(string.IsNullOrEmpty(context.FromDisplayName) ? SharedLocalizer["System"] : context.FromDisplayName)</b></td>
|
||||||
<td><b>@context.Subject</b></td>
|
<td><b>@context.Subject</b></td>
|
||||||
<td><b>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</b></td>
|
<td><b>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</b></td>
|
||||||
}
|
}
|
||||||
@@ -190,32 +273,43 @@ else
|
|||||||
context.Body = context.Body.Replace("\n", "");
|
context.Body = context.Body.Replace("\n", "");
|
||||||
context.Body = context.Body.Replace("\r", "");
|
context.Body = context.Body.Replace("\r", "");
|
||||||
}
|
}
|
||||||
notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body;
|
_notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body;
|
||||||
}
|
}
|
||||||
@if (context.IsRead)
|
@if (context.IsRead)
|
||||||
{
|
{
|
||||||
@notificationSummary
|
@_notificationSummary
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<b>@notificationSummary</b>
|
<b>@_notificationSummary</b>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
</Detail>
|
</Detail>
|
||||||
</Pager>
|
</Pager>
|
||||||
|
<br />
|
||||||
|
<ActionDialog Header="Clear Notifications" Message="Are You Sure You Wish To Permanently Delete All Notifications ?" Action="Delete All Notifications" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllNotifications())" ResourceKey="DeleteAllNotifications" />
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<Pager Items="@notifications">
|
<div class="no-notifications-text">
|
||||||
|
@Localizer["NoNotificationsReceived.Text"]
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@if (_notifications.Any())
|
||||||
|
{
|
||||||
|
<Pager Items="@_notifications">
|
||||||
<Header>
|
<Header>
|
||||||
<th> </th>
|
<th style="width: 1px;"></th>
|
||||||
<th> </th>
|
<th style="width: 1px;"></th>
|
||||||
<th>@Localizer["To"]</th>
|
<th>@Localizer["To"]</th>
|
||||||
<th>@Localizer["Subject"]</th>
|
<th>@Localizer["Subject"]</th>
|
||||||
<th>@Localizer["Sent"]</th>
|
<th>@Localizer["Sent"]</th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" /></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 Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
|
||||||
|
|
||||||
@if (context.IsRead)
|
@if (context.IsRead)
|
||||||
@@ -243,93 +337,114 @@ else
|
|||||||
context.Body = context.Body.Replace("\n", "");
|
context.Body = context.Body.Replace("\n", "");
|
||||||
context.Body = context.Body.Replace("\r", "");
|
context.Body = context.Body.Replace("\r", "");
|
||||||
}
|
}
|
||||||
notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body;
|
_notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body;
|
||||||
}
|
}
|
||||||
@if (context.IsRead)
|
@if (context.IsRead)
|
||||||
{
|
{
|
||||||
@notificationSummary
|
@_notificationSummary
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<b>@notificationSummary</b>
|
<b>@_notificationSummary</b>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
</Detail>
|
</Detail>
|
||||||
</Pager>
|
</Pager>
|
||||||
}
|
|
||||||
@if (notifications.Any())
|
|
||||||
{
|
|
||||||
<br />
|
<br />
|
||||||
<ActionDialog Header="Clear Notifications" Message="Are You Sure You Wish To Permanently Delete All Notifications ?" Action="Delete All Notifications" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllNotifications())" ResourceKey="DeleteAllNotifications" />
|
<ActionDialog Header="Clear Notifications" Message="Are You Sure You Wish To Permanently Delete All Notifications ?" Action="Delete All Notifications" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllNotifications())" ResourceKey="DeleteAllNotifications" />
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="no-notifications-text">
|
||||||
|
@Localizer["NoNotificationsSent.Text"]
|
||||||
|
</div>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabStrip>
|
</TabStrip>
|
||||||
<br /><br />
|
<br />
|
||||||
|
<br />
|
||||||
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
private List<Models.TimeZone> _timezones;
|
||||||
|
private bool _initialized = false;
|
||||||
private string _passwordrequirements;
|
private string _passwordrequirements;
|
||||||
private string username = string.Empty;
|
private string _username = string.Empty;
|
||||||
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 string confirm = string.Empty;
|
private string _confirm = string.Empty;
|
||||||
private bool allowtwofactor = false;
|
private bool _allowtwofactor = false;
|
||||||
private string twofactor = "False";
|
private string _twofactor = "False";
|
||||||
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 int photofileid = -1;
|
private string _timezoneid = string.Empty;
|
||||||
private File photo = null;
|
private int _photofileid = -1;
|
||||||
private List<Profile> profiles;
|
private File _photo = null;
|
||||||
private Dictionary<string, string> settings;
|
private string _imagefiles = string.Empty;
|
||||||
private string category = string.Empty;
|
|
||||||
private string filter = "to";
|
private List<Profile> _profiles;
|
||||||
private List<Notification> notifications;
|
private Dictionary<string, string> _userSettings;
|
||||||
private string notificationSummary = string.Empty;
|
private string _category = string.Empty;
|
||||||
|
|
||||||
|
private string _filter = "to";
|
||||||
|
private List<Notification> _notifications;
|
||||||
|
private string _notificationSummary = string.Empty;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||||
|
|
||||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
_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);
|
||||||
|
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
||||||
|
|
||||||
if (PageState.User != null)
|
if (PageState.User != null)
|
||||||
{
|
{
|
||||||
username = PageState.User.Username;
|
_username = PageState.User.Username;
|
||||||
twofactor = PageState.User.TwoFactorRequired.ToString();
|
_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;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(_email))
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.User.NoEmail"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
|
||||||
// get user folder
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PageState.User.PhotoFileId != null)
|
if (PageState.User.PhotoFileId != null)
|
||||||
{
|
{
|
||||||
photofileid = PageState.User.PhotoFileId.Value;
|
_photofileid = PageState.User.PhotoFileId.Value;
|
||||||
photo = await FileService.GetFileAsync(photofileid);
|
_photo = await FileService.GetFileAsync(_photofileid);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
photofileid = -1;
|
_photofileid = -1;
|
||||||
photo = null;
|
_photo = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
_userSettings = PageState.User.Settings;
|
||||||
settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
|
var sitesettings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
_imagefiles = SettingService.GetSetting(_userSettings, "ImageFiles", Constants.ImageFiles);
|
||||||
|
_imagefiles = (string.IsNullOrEmpty(_imagefiles)) ? Constants.ImageFiles : _imagefiles;
|
||||||
|
|
||||||
await LoadNotificationsAsync();
|
await LoadNotificationsAsync();
|
||||||
|
|
||||||
|
_initialized = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -345,13 +460,13 @@ else
|
|||||||
|
|
||||||
private async Task LoadNotificationsAsync()
|
private async Task LoadNotificationsAsync()
|
||||||
{
|
{
|
||||||
notifications = await NotificationService.GetNotificationsAsync(PageState.Site.SiteId, filter, PageState.User.UserId);
|
_notifications = await NotificationService.GetNotificationsAsync(PageState.Site.SiteId, _filter, PageState.User.UserId);
|
||||||
notifications = notifications.Where(item => item.DeletedBy != PageState.User.Username).ToList();
|
_notifications = _notifications.Where(item => item.DeletedBy != PageState.User.Username).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetProfileValue(string SettingName, string DefaultValue)
|
private string GetProfileValue(string SettingName, string DefaultValue)
|
||||||
{
|
{
|
||||||
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
|
string value = SettingService.GetSetting(_userSettings, SettingName, DefaultValue);
|
||||||
if (value.Contains("]"))
|
if (value.Contains("]"))
|
||||||
{
|
{
|
||||||
value = value.Substring(value.IndexOf("]") + 1);
|
value = value.Substring(value.IndexOf("]") + 1);
|
||||||
@@ -363,46 +478,57 @@ else
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (username != string.Empty && email != string.Empty && ValidateProfiles())
|
if (_username != string.Empty && _email != string.Empty)
|
||||||
{
|
{
|
||||||
if (_password == confirm)
|
if (_password == _confirm)
|
||||||
|
{
|
||||||
|
if (ValidateProfiles())
|
||||||
{
|
{
|
||||||
var user = PageState.User;
|
var user = PageState.User;
|
||||||
user.Username = username;
|
user.Username = _username;
|
||||||
user.Password = _password;
|
user.Password = _password;
|
||||||
user.TwoFactorRequired = bool.Parse(twofactor);
|
user.TwoFactorRequired = bool.Parse(_twofactor);
|
||||||
user.Email = email;
|
user.Email = _email;
|
||||||
user.DisplayName = (displayname == string.Empty ? username : displayname);
|
user.DisplayName = (_displayname == string.Empty ? _username : _displayname);
|
||||||
user.PhotoFileId = filemanager.GetFileId();
|
user.TimeZoneId = _timezoneid;
|
||||||
|
user.PhotoFileId = _filemanager.GetFileId();
|
||||||
if (user.PhotoFileId == -1)
|
if (user.PhotoFileId == -1)
|
||||||
{
|
{
|
||||||
user.PhotoFileId = null;
|
user.PhotoFileId = null;
|
||||||
}
|
}
|
||||||
if (user.PhotoFileId != null)
|
if (user.PhotoFileId != null)
|
||||||
{
|
{
|
||||||
photofileid = user.PhotoFileId.Value;
|
_photofileid = user.PhotoFileId.Value;
|
||||||
photo = await FileService.GetFileAsync(photofileid);
|
_photo = await FileService.GetFileAsync(_photofileid);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
photofileid = -1;
|
_photofileid = -1;
|
||||||
photo = null;
|
_photo = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
user = await UserService.UpdateUserAsync(user);
|
user = await UserService.UpdateUserAsync(user);
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
|
await SettingService.UpdateUserSettingsAsync(_userSettings, PageState.User.UserId);
|
||||||
await logger.LogInformation("User Profile Saved");
|
await logger.LogInformation("User Profile Saved");
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
|
||||||
|
{
|
||||||
|
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||||
|
}
|
||||||
|
else // legacy behavior
|
||||||
|
{
|
||||||
AddModuleMessage(Localizer["Success.Profile.Update"], MessageType.Success);
|
AddModuleMessage(Localizer["Success.Profile.Update"], MessageType.Success);
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error);
|
AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Message.Password.Invalid"], MessageType.Warning);
|
AddModuleMessage(Localizer["Message.Password.Invalid"], MessageType.Warning);
|
||||||
@@ -422,40 +548,72 @@ else
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task Logout()
|
||||||
|
{
|
||||||
|
await logger.LogInformation("User Logout Everywhere For Username {Username}", PageState.User?.Username);
|
||||||
|
|
||||||
|
var url = NavigateUrl(""); // home page
|
||||||
|
|
||||||
|
if (PageState.Runtime == Shared.Runtime.Hybrid)
|
||||||
|
{
|
||||||
|
if (PageState.User != null)
|
||||||
|
{
|
||||||
|
// hybrid apps utilize an interactive logout
|
||||||
|
await UserService.LogoutUserEverywhereAsync(PageState.User);
|
||||||
|
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
|
||||||
|
authstateprovider.NotifyAuthenticationChanged();
|
||||||
|
NavigationManager.NavigateTo(url, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// post to the Logout page to complete the logout process
|
||||||
|
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, returnurl = url, everywhere = true };
|
||||||
|
var interop = new Interop(jsRuntime);
|
||||||
|
await interop.SubmitForm(Utilities.TenantUrl(PageState.Alias, "/pages/logout/"), fields);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private bool ValidateProfiles()
|
private bool ValidateProfiles()
|
||||||
{
|
{
|
||||||
bool valid = true;
|
foreach (Profile profile in _profiles)
|
||||||
foreach (Profile profile in profiles)
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)) && !string.IsNullOrEmpty(profile.DefaultValue))
|
var value = GetProfileValue(profile.Name, string.Empty);
|
||||||
|
if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue))
|
||||||
{
|
{
|
||||||
settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue);
|
_userSettings = SettingService.SetSetting(_userSettings, profile.Name, profile.DefaultValue);
|
||||||
}
|
}
|
||||||
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
{
|
{
|
||||||
if (valid == true && profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)))
|
if (profile.IsRequired && string.IsNullOrEmpty(value))
|
||||||
{
|
{
|
||||||
valid = false;
|
AddModuleMessage(string.Format(SharedLocalizer["ProfileRequired"], profile.Title), MessageType.Warning);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
if (valid == true && !string.IsNullOrEmpty(profile.Validation))
|
if (!string.IsNullOrEmpty(profile.Validation))
|
||||||
{
|
{
|
||||||
Regex regex = new Regex(profile.Validation);
|
Regex regex = new Regex(profile.Validation);
|
||||||
valid = regex.Match(SettingService.GetSetting(settings, profile.Name, string.Empty)).Success;
|
bool valid = regex.Match(value).Success;
|
||||||
|
if (!valid)
|
||||||
|
{
|
||||||
|
AddModuleMessage(string.Format(SharedLocalizer["ProfileInvalid"], profile.Title), MessageType.Warning);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return valid;
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Cancel()
|
private void Cancel()
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(NavigateUrl(string.Empty));
|
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProfileChanged(ChangeEventArgs e, string SettingName)
|
private void ProfileChanged(ChangeEventArgs e, string SettingName)
|
||||||
{
|
{
|
||||||
var value = (string)e.Value;
|
var value = (string)e.Value;
|
||||||
settings = SettingService.SetSetting(settings, SettingName, value);
|
_userSettings = SettingService.SetSetting(_userSettings, SettingName, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Delete(Notification Notification)
|
private async Task Delete(Notification Notification)
|
||||||
@@ -485,8 +643,7 @@ else
|
|||||||
|
|
||||||
private async void FilterChanged(ChangeEventArgs e)
|
private async void FilterChanged(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
filter = (string)e.Value;
|
_filter = (string)e.Value;
|
||||||
|
|
||||||
await LoadNotificationsAsync();
|
await LoadNotificationsAsync();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
@@ -495,8 +652,8 @@ else
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ModuleInstance.ShowProgressIndicator();
|
ShowProgressIndicator();
|
||||||
foreach(var Notification in notifications)
|
foreach(var Notification in _notifications)
|
||||||
{
|
{
|
||||||
if (!Notification.IsDeleted)
|
if (!Notification.IsDeleted)
|
||||||
{
|
{
|
||||||
@@ -511,7 +668,7 @@ else
|
|||||||
}
|
}
|
||||||
await logger.LogInformation("Notifications Permanently Deleted");
|
await logger.LogInformation("Notifications Permanently Deleted");
|
||||||
await LoadNotificationsAsync();
|
await LoadNotificationsAsync();
|
||||||
ModuleInstance.HideProgressIndicator();
|
HideProgressIndicator();
|
||||||
|
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
@@ -519,9 +676,8 @@ else
|
|||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Deleting Notifications {Error}", ex.Message);
|
await logger.LogError(ex, "Error Deleting Notifications {Error}", ex.Message);
|
||||||
AddModuleMessage(ex.Message, MessageType.Error);
|
AddModuleMessage(ex.Message, MessageType.Error);
|
||||||
ModuleInstance.HideProgressIndicator();
|
HideProgressIndicator();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TogglePassword()
|
private void TogglePassword()
|
||||||
@@ -537,5 +693,4 @@ else
|
|||||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,94 +8,71 @@
|
|||||||
|
|
||||||
@if (PageState.User != null)
|
@if (PageState.User != null)
|
||||||
{
|
{
|
||||||
|
@if (title == "From")
|
||||||
|
{
|
||||||
<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">@Localizer["Title"] </label>
|
<Label Class="col-sm-3" For="username" HelpText="The user who sent the message" ResourceKey="From">From:</Label>
|
||||||
@if (title == "From")
|
|
||||||
{
|
|
||||||
<div class="col-sm-3">
|
|
||||||
<input class="form-control" @bind="@username" readonly />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (title == "To")
|
|
||||||
{
|
|
||||||
<div class="col-sm-3">
|
|
||||||
<input class="form-control" @bind="@username" />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<label Class="col-sm-3">@Localizer["Subject"] </label>
|
|
||||||
@if (title == "From")
|
|
||||||
{
|
|
||||||
<div class="col-sm-3">
|
|
||||||
<input class="form-control" @bind="@subject" readonly />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (title == "To")
|
|
||||||
{
|
|
||||||
<div class="col-sm-3">
|
|
||||||
<input class="form-control" @bind="@subject" />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="container">
|
|
||||||
@if (title == "From")
|
|
||||||
{
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<label class="col-sm-3">@Localizer["Date"] </label>
|
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input class="form-control" @bind="@createdon" readonly />
|
<input id="username" class="form-control" @bind="@username" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
@if (title == "From")
|
|
||||||
{
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<label class="col-sm-3">@Localizer["Message"] </label>
|
<Label Class="col-sm-3" For="subject" HelpText="The subject of the message" ResourceKey="Subject">Subject:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<textarea id="txtFrom" class="form-control" @bind="@body" rows="5" readonly />
|
<input id="subject" class="form-control" @bind="@subject" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
}
|
|
||||||
@if (title == "To")
|
|
||||||
{
|
|
||||||
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<label class="col-sm-3">@Localizer["Message"] </label>
|
<Label class="col-sm-3" For="date" HelpText="The date the message was sent" ResourceKey="Date">Sent:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<textarea id="txtTo" class="form-control" @bind="@body" rows="5" />
|
<input id="date" class="form-control" @bind="@createdon" readonly />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label class="col-sm-3" For="message" HelpText="The contents of the message" ResourceKey="Message">Message:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea id="message" class="form-control" @bind="@body" rows="5" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@if (reply != string.Empty)
|
|
||||||
{
|
|
||||||
<button type="button" class="btn btn-primary" @onclick="Send">@SharedLocalizer["Send"]</button>
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (title == "From")
|
<div class="container">
|
||||||
{
|
<div class="row mb-1 align-items-center">
|
||||||
<button type="button" class="btn btn-primary" @onclick="Reply">@Localizer["Reply"]</button>
|
<Label Class="col-sm-3" For="username" HelpText="The user who will be the recipient of the message" ResourceKey="To">To:</Label>
|
||||||
}
|
<div class="col-sm-9">
|
||||||
}
|
<input id="username" class="form-control" @bind="@username" readonly />
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
</div>
|
||||||
<br />
|
</div>
|
||||||
<br />
|
<div class="row mb-1 align-items-center">
|
||||||
@if (title == "To")
|
<Label Class="col-sm-3" For="subject" HelpText="The subject of the message" ResourceKey="Subject">Subject:</Label>
|
||||||
{
|
<div class="col-sm-9">
|
||||||
<div class="control-group">
|
<input id="subject" class="form-control" @bind="@subject" readonly="@(!reply)" />
|
||||||
<label class="control-label">@Localizer["OriginalMessage"] </label>
|
</div>
|
||||||
<textarea id="txtReply" class="form-control" @bind="@reply" rows="5" readonly />
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label class="col-sm-3" For="message" HelpText="The content of the message" ResourceKey="Message">Message:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea id="message" class="form-control" @bind="@body" rows="5" readonly="@(!reply)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@if (reply)
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-primary me-2" @onclick="Send">@SharedLocalizer["Send"]</button>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (title == "From" && username != Localizer["System"])
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-primary me-2" @onclick="Reply">@Localizer["Reply"]</button>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@@ -105,7 +82,7 @@
|
|||||||
private string subject = string.Empty;
|
private string subject = string.Empty;
|
||||||
private string createdon = string.Empty;
|
private string createdon = string.Empty;
|
||||||
private string body = string.Empty;
|
private string body = string.Empty;
|
||||||
private string reply = string.Empty;
|
private bool reply = false;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
||||||
public override string Title => "View Notification";
|
public override string Title => "View Notification";
|
||||||
@@ -118,9 +95,6 @@
|
|||||||
Notification notification = await NotificationService.GetNotificationAsync(notificationid);
|
Notification notification = await NotificationService.GetNotificationAsync(notificationid);
|
||||||
if (notification != null)
|
if (notification != null)
|
||||||
{
|
{
|
||||||
notification.IsRead = true;
|
|
||||||
notification = await NotificationService.UpdateNotificationAsync(notification);
|
|
||||||
|
|
||||||
int userid = -1;
|
int userid = -1;
|
||||||
if (notification.ToUserId == PageState.User.UserId)
|
if (notification.ToUserId == PageState.User.UserId)
|
||||||
{
|
{
|
||||||
@@ -148,11 +122,17 @@
|
|||||||
}
|
}
|
||||||
if (username == "")
|
if (username == "")
|
||||||
{
|
{
|
||||||
username = "System";
|
username = Localizer["System"];
|
||||||
}
|
}
|
||||||
subject = notification.Subject;
|
subject = notification.Subject;
|
||||||
createdon = notification.CreatedOn.ToString();
|
createdon = notification.CreatedOn.ToString();
|
||||||
body = notification.Body;
|
body = notification.Body;
|
||||||
|
|
||||||
|
if (title == "From" && !notification.IsRead)
|
||||||
|
{
|
||||||
|
notification.IsRead = true;
|
||||||
|
notification = await NotificationService.UpdateNotificationAsync(notification);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -165,12 +145,16 @@
|
|||||||
private void Reply()
|
private void Reply()
|
||||||
{
|
{
|
||||||
title = "To";
|
title = "To";
|
||||||
if (!subject.Contains("RE:"))
|
if (!subject.Contains(Localizer["RE:"]))
|
||||||
{
|
{
|
||||||
subject = "RE: " + subject;
|
subject = Localizer["RE"] + " " + subject;
|
||||||
}
|
}
|
||||||
reply = body;
|
body = $"\n\n____________________________________________\n" +
|
||||||
body = "\n\n____________________________________________\nSent: " + createdon + "\nSubject: " + subject + "\n\n" + body;
|
$"{Localizer["From.Text"]} {username}\n" +
|
||||||
|
$"{Localizer["Date.Text"]} {createdon}\n" +
|
||||||
|
$"{Localizer["Subject.Text"]} {subject}\n\n" +
|
||||||
|
body;
|
||||||
|
reply = true;
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,7 +168,7 @@
|
|||||||
var notification = new Notification(PageState.Site.SiteId, PageState.User, user, subject, body, notificationid);
|
var notification = new Notification(PageState.Site.SiteId, PageState.User, user, subject, body, notificationid);
|
||||||
notification = await NotificationService.AddNotificationAsync(notification);
|
notification = await NotificationService.AddNotificationAsync(notification);
|
||||||
await logger.LogInformation("Notification Created {NotificationId}", notification.NotificationId);
|
await logger.LogInformation("Notification Created {NotificationId}", notification.NotificationId);
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,14 +5,16 @@
|
|||||||
@inject IUserService UserService
|
@inject IUserService UserService
|
||||||
@inject IProfileService ProfileService
|
@inject IProfileService ProfileService
|
||||||
@inject ISettingService SettingService
|
@inject ISettingService SettingService
|
||||||
|
@inject ITimeZoneService TimeZoneService
|
||||||
@inject IStringLocalizer<Add> Localizer
|
@inject IStringLocalizer<Add> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<TabStrip>
|
@if (_initialized)
|
||||||
|
{
|
||||||
|
<TabStrip>
|
||||||
<TabPanel Name="Identity" ResourceKey="Identity">
|
<TabPanel Name="Identity" ResourceKey="Identity">
|
||||||
@if (profiles != null)
|
@if (_profiles != null)
|
||||||
{
|
{
|
||||||
<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="A unique username for a user. Note that this field can not be modified once it is saved." ResourceKey="Username"></Label>
|
<Label Class="col-sm-3" For="username" HelpText="A unique username for a user. Note that this field can not be modified once it is saved." ResourceKey="Username"></Label>
|
||||||
@@ -20,24 +22,6 @@
|
|||||||
<input id="username" class="form-control" @bind="@_username" />
|
<input id="username" class="form-control" @bind="@_username" />
|
||||||
</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"></Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<div class="input-group">
|
|
||||||
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required />
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm"></Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<div class="input-group">
|
|
||||||
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" required />
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email"></Label>
|
<Label Class="col-sm-3" For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email"></Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -50,6 +34,18 @@
|
|||||||
<input id="displayname" class="form-control" @bind="@_displayname" />
|
<input id="displayname" class="form-control" @bind="@_displayname" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="timezone" HelpText="Your time zone" ResourceKey="TimeZone">Time Zone:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="timezone" class="form-select" @bind="@_timezoneid">
|
||||||
|
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||||
|
@foreach (var timezone in _timezones)
|
||||||
|
{
|
||||||
|
<option value="@timezone.Id">@timezone.DisplayName</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<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">
|
||||||
@@ -63,58 +59,73 @@
|
|||||||
}
|
}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Profile" ResourceKey="Profile">
|
<TabPanel Name="Profile" ResourceKey="Profile">
|
||||||
@if (profiles != null)
|
|
||||||
{
|
|
||||||
<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)
|
||||||
{
|
{
|
||||||
var p = profile;
|
var p = profile;
|
||||||
if (p.Category != category)
|
if (p.Category != _category)
|
||||||
{
|
{
|
||||||
<div class="col text-center pb-2">
|
<div class="col text-center pb-2">
|
||||||
<strong>@p.Category</strong>
|
<strong>@p.Category</strong>
|
||||||
</div>
|
</div>
|
||||||
category = p.Category;
|
_category = p.Category;
|
||||||
}
|
}
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
|
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@if (p.IsRequired)
|
@if (!string.IsNullOrEmpty(p.Options))
|
||||||
{
|
{
|
||||||
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" />
|
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
|
||||||
|
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||||
|
{
|
||||||
|
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
|
||||||
|
{
|
||||||
|
<option value="@option" selected>@option</option>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
|
<option value="@option">@option</option>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@if (p.Rows == 1)
|
||||||
|
{
|
||||||
|
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" @attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" @attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabStrip>
|
</TabStrip>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private string _passwordrequirements;
|
private List<Models.TimeZone> _timezones;
|
||||||
|
private bool _initialized = false;
|
||||||
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 _displayname = string.Empty;
|
private string _displayname = string.Empty;
|
||||||
|
private string _timezoneid = 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;
|
||||||
private string category = string.Empty;
|
private string _category = string.Empty;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||||
|
|
||||||
@@ -122,10 +133,11 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
||||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
_profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
||||||
profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
_settings = new Dictionary<string, string>();
|
||||||
settings = new Dictionary<string, string>();
|
_timezoneid = PageState.Site.TimeZoneId;
|
||||||
|
_initialized = true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -136,7 +148,7 @@
|
|||||||
|
|
||||||
private string GetProfileValue(string SettingName, string DefaultValue)
|
private string GetProfileValue(string SettingName, string DefaultValue)
|
||||||
{
|
{
|
||||||
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
|
string value = SettingService.GetSetting(_settings, SettingName, DefaultValue);
|
||||||
if (value.Contains("]"))
|
if (value.Contains("]"))
|
||||||
{
|
{
|
||||||
value = value.Substring(value.IndexOf("]") + 1);
|
value = value.Substring(value.IndexOf("]") + 1);
|
||||||
@@ -148,16 +160,17 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_username != string.Empty && _password != string.Empty && _confirm != string.Empty && _email != string.Empty && ValidateProfiles())
|
if (_username != string.Empty && _email != string.Empty)
|
||||||
{
|
{
|
||||||
if (_password == _confirm)
|
if (ValidateProfiles())
|
||||||
{
|
{
|
||||||
var user = new User();
|
var user = new User();
|
||||||
user.SiteId = PageState.Site.SiteId;
|
user.SiteId = PageState.Site.SiteId;
|
||||||
user.Username = _username;
|
user.Username = _username;
|
||||||
user.Password = _password;
|
user.Password = ""; // will be auto generated
|
||||||
user.Email = _email;
|
user.Email = _email;
|
||||||
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
||||||
|
user.TimeZoneId = _timezoneid;
|
||||||
user.PhotoFileId = null;
|
user.PhotoFileId = null;
|
||||||
user.SuppressNotification = !bool.Parse(_notify);
|
user.SuppressNotification = !bool.Parse(_notify);
|
||||||
|
|
||||||
@@ -165,7 +178,7 @@
|
|||||||
|
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
|
await SettingService.UpdateUserSettingsAsync(_settings, user.UserId);
|
||||||
await logger.LogInformation("User Created {User}", user);
|
await logger.LogInformation("User Created {User}", user);
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
}
|
}
|
||||||
@@ -175,10 +188,6 @@
|
|||||||
AddModuleMessage(Localizer["Error.User.AddCheckPass"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.User.AddCheckPass"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
AddModuleMessage(Localizer["Message.Password.NoMatch"], MessageType.Warning);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -194,46 +203,38 @@
|
|||||||
|
|
||||||
private bool ValidateProfiles()
|
private bool ValidateProfiles()
|
||||||
{
|
{
|
||||||
bool valid = true;
|
foreach (Profile profile in _profiles)
|
||||||
foreach (Profile profile in profiles)
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)) && !string.IsNullOrEmpty(profile.DefaultValue))
|
var value = GetProfileValue(profile.Name, string.Empty);
|
||||||
|
if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue))
|
||||||
{
|
{
|
||||||
settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue);
|
_settings = SettingService.SetSetting(_settings, profile.Name, profile.DefaultValue);
|
||||||
}
|
}
|
||||||
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
{
|
{
|
||||||
if (valid == true && profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)))
|
if (profile.IsRequired && string.IsNullOrEmpty(value))
|
||||||
{
|
{
|
||||||
valid = false;
|
AddModuleMessage(string.Format(SharedLocalizer["ProfileRequired"], profile.Title), MessageType.Warning);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
if (valid == true && !string.IsNullOrEmpty(profile.Validation))
|
if (!string.IsNullOrEmpty(profile.Validation))
|
||||||
{
|
{
|
||||||
Regex regex = new Regex(profile.Validation);
|
Regex regex = new Regex(profile.Validation);
|
||||||
valid = regex.Match(SettingService.GetSetting(settings, profile.Name, string.Empty)).Success;
|
bool valid = regex.Match(value).Success;
|
||||||
|
if (!valid)
|
||||||
|
{
|
||||||
|
AddModuleMessage(string.Format(SharedLocalizer["ProfileInvalid"], profile.Title), MessageType.Warning);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return valid;
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProfileChanged(ChangeEventArgs e, string SettingName)
|
private void ProfileChanged(ChangeEventArgs e, string SettingName)
|
||||||
{
|
{
|
||||||
var value = (string)e.Value;
|
var value = (string)e.Value;
|
||||||
settings = SettingService.SetSetting(settings, SettingName, value);
|
_settings = SettingService.SetSetting(_settings, SettingName, value);
|
||||||
}
|
|
||||||
|
|
||||||
private void TogglePassword()
|
|
||||||
{
|
|
||||||
if (_passwordtype == "password")
|
|
||||||
{
|
|
||||||
_passwordtype = "text";
|
|
||||||
_togglepassword = SharedLocalizer["HidePassword"];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_passwordtype = "password";
|
|
||||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,31 +6,25 @@
|
|||||||
@inject IProfileService ProfileService
|
@inject IProfileService ProfileService
|
||||||
@inject ISettingService SettingService
|
@inject ISettingService SettingService
|
||||||
@inject IFileService FileService
|
@inject IFileService FileService
|
||||||
|
@inject ITimeZoneService TimeZoneService
|
||||||
|
@inject IServiceProvider ServiceProvider
|
||||||
@inject IStringLocalizer<Edit> Localizer
|
@inject IStringLocalizer<Edit> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@if (PageState.User != null && photo != null)
|
@if (_initialized)
|
||||||
{
|
{
|
||||||
<img src="@photo.Url" alt="@displayname" style="max-width: 400px" class="rounded-circle mx-auto d-block">
|
<TabStrip>
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<br />
|
|
||||||
}
|
|
||||||
<TabStrip>
|
|
||||||
<TabPanel Name="Identity" ResourceKey="Identity">
|
<TabPanel Name="Identity" ResourceKey="Identity">
|
||||||
@if (profiles != null)
|
|
||||||
{
|
|
||||||
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
<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"></Label>
|
<Label Class="col-sm-3" For="username" HelpText="The unique username for a user. Note that this field can not be modified." ResourceKey="Username">Username:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="username" class="form-control" @bind="@username" readonly />
|
<input id="username" class="form-control" @bind="@_username" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password"></Label>
|
<Label Class="col-sm-3" For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password">Password:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
|
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
|
||||||
@@ -39,70 +33,85 @@ 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="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm"></Label>
|
<Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm">Confirm Password:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@confirm" autocomplete="new-password" />
|
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" />
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||||
</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="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email"></Label>
|
<Label Class="col-sm-3" For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email">Email:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="email" class="form-control" @bind="@email" />
|
<input id="email" class="form-control" @bind="@_email" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="displayname" HelpText="The full name of the user" ResourceKey="DisplayName"></Label>
|
<Label Class="col-sm-3" For="confirmed" HelpText="Indicates if the user's email is verified" ResourceKey="Confirmed">Confirmed?</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="displayname" class="form-control" @bind="@displayname" />
|
<select id="confirmed" class="form-select" @bind="@_confirmed">
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="@photofileid.ToString()" HelpText="A photo of the user" ResourceKey="Photo"></Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<FileManager FileId="@photofileid" @ref="filemanager" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="isdeleted" HelpText="Indicate if the user is active" ResourceKey="IsDeleted"></Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<select id="isdeleted" class="form-select" @bind="@isdeleted">
|
|
||||||
<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>
|
||||||
</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="lastlogin" HelpText="The date and time when the user last signed in" ResourceKey="LastLogin"></Label>
|
<Label Class="col-sm-3" For="displayname" HelpText="The full name of the user" ResourceKey="DisplayName">Full Name:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="lastlogin" class="form-control" @bind="@lastlogin" readonly />
|
<input id="displayname" class="form-control" @bind="@_displayname" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="lastipaddress" HelpText="The IP Address of the user recorded during their last login" ResourceKey="LastIPAddress"></Label>
|
<Label Class="col-sm-3" For="timezone" HelpText="Your time zone" ResourceKey="TimeZone">Time Zone:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="lastipaddress" class="form-control" @bind="@lastipaddress" readonly />
|
<select id="timezone" class="form-select" @bind="@_timezoneid">
|
||||||
|
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||||
|
@foreach (var timezone in _timezones)
|
||||||
|
{
|
||||||
|
<option value="@timezone.Id">@timezone.DisplayName</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
|
{
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="isdeleted" HelpText="Indicate if the user is active" ResourceKey="IsDeleted">Deleted?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="isdeleted" class="form-select" @bind="@_isdeleted">
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="lastlogin" HelpText="The date and time when the user last signed in" ResourceKey="LastLogin">Last Login:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="lastlogin" class="form-control" @bind="@_lastlogin" readonly />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="lastipaddress" HelpText="The IP Address of the user recorded during their last login" ResourceKey="LastIPAddress">Last IP Address:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="lastipaddress" class="form-control" @bind="@_lastipaddress" readonly />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Profile" ResourceKey="Profile">
|
<TabPanel Name="Profile" ResourceKey="Profile">
|
||||||
@if (profiles != null)
|
|
||||||
{
|
|
||||||
<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)
|
||||||
{
|
{
|
||||||
var p = profile;
|
var p = profile;
|
||||||
if (p.Category != category)
|
if (p.Category != _category)
|
||||||
{
|
{
|
||||||
<div class="col text-center pb-2">
|
<div class="col text-center pb-2">
|
||||||
<strong>@p.Category</strong>
|
<strong>@p.Category</strong>
|
||||||
</div>
|
</div>
|
||||||
category = p.Category;
|
_category = p.Category;
|
||||||
}
|
}
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
|
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
|
||||||
@@ -125,13 +134,13 @@ else
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@if (p.IsRequired)
|
@if (p.Rows == 1)
|
||||||
{
|
{
|
||||||
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" />
|
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" @attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
|
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" @attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -139,96 +148,102 @@ else
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabStrip>
|
</TabStrip>
|
||||||
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
<br />
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) && PageState.Runtime != Shared.Runtime.Hybrid && !_ishost)
|
||||||
<br />
|
{
|
||||||
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon" DeletedBy="@deletedby" DeletedOn="@deletedon"></AuditInfo>
|
<button type="button" class="btn btn-primary ms-1" @onclick="ImpersonateUser">@Localizer["Impersonate"]</button>
|
||||||
|
}
|
||||||
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && _isdeleted == "True")
|
||||||
|
{
|
||||||
|
<ActionDialog Header="Delete User" Message="Are You Sure You Wish To Permanently Delete This User?" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteUser())" ResourceKey="DeleteUser" />
|
||||||
|
}
|
||||||
|
<br /><br />
|
||||||
|
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
|
||||||
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
private List<Models.TimeZone> _timezones;
|
||||||
|
private bool _initialized = false;
|
||||||
private string _passwordrequirements;
|
private string _passwordrequirements;
|
||||||
private int userid;
|
private int _userid;
|
||||||
private string username = string.Empty;
|
private string _username = string.Empty;
|
||||||
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 string confirm = string.Empty;
|
private string _confirm = string.Empty;
|
||||||
private string email = string.Empty;
|
private string _email = string.Empty;
|
||||||
private string displayname = string.Empty;
|
private string _confirmed = string.Empty;
|
||||||
private FileManager filemanager;
|
private string _displayname = string.Empty;
|
||||||
private int photofileid = -1;
|
private string _timezoneid = string.Empty;
|
||||||
private File photo = null;
|
private string _isdeleted;
|
||||||
private string isdeleted;
|
private string _lastlogin;
|
||||||
private string lastlogin;
|
private string _lastipaddress;
|
||||||
private string lastipaddress;
|
private bool _ishost = false;
|
||||||
|
|
||||||
private List<Profile> profiles;
|
private List<Profile> _profiles;
|
||||||
private Dictionary<string, string> settings;
|
private Dictionary<string, string> _settings;
|
||||||
private string category = string.Empty;
|
private string _category = string.Empty;
|
||||||
|
|
||||||
private string createdby;
|
private string _createdby;
|
||||||
private DateTime createdon;
|
private DateTime _createdon;
|
||||||
private string modifiedby;
|
private string _modifiedby;
|
||||||
private DateTime modifiedon;
|
private DateTime _modifiedon;
|
||||||
private string deletedby;
|
private string _deletedby;
|
||||||
private DateTime? deletedon;
|
private DateTime? _deletedon;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
|
||||||
if (PageState.QueryString.ContainsKey("id"))
|
|
||||||
{
|
{
|
||||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
|
_profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
|
||||||
userid = Int32.Parse(PageState.QueryString["id"]);
|
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
||||||
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
|
|
||||||
|
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)
|
if (user != null)
|
||||||
{
|
{
|
||||||
username = user.Username;
|
_username = user.Username;
|
||||||
email = user.Email;
|
_email = user.Email;
|
||||||
displayname = user.DisplayName;
|
_confirmed = user.EmailConfirmed.ToString();
|
||||||
if (user.PhotoFileId != null)
|
_displayname = user.DisplayName;
|
||||||
{
|
_timezoneid = PageState.User.TimeZoneId;
|
||||||
photofileid = user.PhotoFileId.Value;
|
_isdeleted = user.IsDeleted.ToString();
|
||||||
photo = await FileService.GetFileAsync(photofileid);
|
_lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", UtcToLocal(user.LastLoginOn));
|
||||||
}
|
_lastipaddress = user.LastIPAddress;
|
||||||
else
|
_ishost = UserSecurity.ContainsRole(user.Roles, RoleNames.Host);
|
||||||
{
|
|
||||||
photofileid = -1;
|
|
||||||
photo = null;
|
|
||||||
}
|
|
||||||
isdeleted = user.IsDeleted.ToString();
|
|
||||||
lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn);
|
|
||||||
lastipaddress = user.LastIPAddress;
|
|
||||||
|
|
||||||
settings = await SettingService.GetUserSettingsAsync(user.UserId);
|
_settings = user.Settings;
|
||||||
createdby = user.CreatedBy;
|
_createdby = user.CreatedBy;
|
||||||
createdon = user.CreatedOn;
|
_createdon = user.CreatedOn;
|
||||||
modifiedby = user.ModifiedBy;
|
_modifiedby = user.ModifiedBy;
|
||||||
modifiedon = user.ModifiedOn;
|
_modifiedon = user.ModifiedOn;
|
||||||
deletedby = user.DeletedBy;
|
_deletedby = user.DeletedBy;
|
||||||
deletedon = user.DeletedOn;
|
_deletedon = user.DeletedOn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_initialized = true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Loading User {UserId} {Error}", userid, ex.Message);
|
await logger.LogError(ex, "Error Loading User {UserId} {Error}", _userid, ex.Message);
|
||||||
AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetProfileValue(string SettingName, string DefaultValue)
|
private string GetProfileValue(string SettingName, string DefaultValue)
|
||||||
{
|
{
|
||||||
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
|
string value = SettingService.GetSetting(_settings, SettingName, DefaultValue);
|
||||||
if (value.Contains("]"))
|
if (value.Contains("]"))
|
||||||
{
|
{
|
||||||
value = value.Substring(value.IndexOf("]") + 1);
|
value = value.Substring(value.IndexOf("]") + 1);
|
||||||
@@ -240,29 +255,29 @@ else
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (username != string.Empty && email != string.Empty && ValidateProfiles())
|
if (_username != string.Empty && _email != string.Empty)
|
||||||
{
|
{
|
||||||
if (_password == confirm)
|
if (_password == _confirm)
|
||||||
{
|
{
|
||||||
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
|
if (ValidateProfiles())
|
||||||
|
{
|
||||||
|
var user = await UserService.GetUserAsync(_userid, PageState.Site.SiteId);
|
||||||
user.SiteId = PageState.Site.SiteId;
|
user.SiteId = PageState.Site.SiteId;
|
||||||
user.Username = username;
|
user.Username = _username;
|
||||||
user.Password = _password;
|
user.Password = _password;
|
||||||
user.Email = email;
|
user.Email = _email;
|
||||||
user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname;
|
user.EmailConfirmed = bool.Parse(_confirmed);
|
||||||
user.PhotoFileId = null;
|
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
||||||
user.PhotoFileId = filemanager.GetFileId();
|
user.TimeZoneId = _timezoneid;
|
||||||
if (user.PhotoFileId == -1)
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
user.PhotoFileId = null;
|
user.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
|
||||||
}
|
}
|
||||||
|
|
||||||
user.IsDeleted = (isdeleted == null ? true : Boolean.Parse(isdeleted));
|
|
||||||
|
|
||||||
user = await UserService.UpdateUserAsync(user);
|
user = await UserService.UpdateUserAsync(user);
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
|
await SettingService.UpdateUserSettingsAsync(_settings, user.UserId);
|
||||||
await logger.LogInformation("User Saved {User}", user);
|
await logger.LogInformation("User Saved {User}", user);
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
}
|
}
|
||||||
@@ -271,6 +286,7 @@ else
|
|||||||
AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error);
|
AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Message.Password.NoMatch"], MessageType.Warning);
|
AddModuleMessage(Localizer["Message.Password.NoMatch"], MessageType.Warning);
|
||||||
@@ -283,40 +299,84 @@ else
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Saving User {Username} {Email} {Error}", username, email, ex.Message);
|
await logger.LogError(ex, "Error Saving User {Username} {Email} {Error}", _username, _email, ex.Message);
|
||||||
AddModuleMessage(Localizer["Error.User.Save"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.User.Save"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ImpersonateUser()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await logger.LogInformation(LogFunction.Security, "User {Username} Impersonated By Administrator {Administrator}", _username, PageState.User.Username);
|
||||||
|
|
||||||
|
// post back to the server so that the cookies are set correctly
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, returnurl = PageState.Alias.Path };
|
||||||
|
string url = Utilities.TenantUrl(PageState.Alias, "/pages/impersonate/");
|
||||||
|
await interop.SubmitForm(url, fields);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Impersonating User {Username} {Error}", _username, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.User.Impersonate"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DeleteUser()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && _userid != PageState.User.UserId)
|
||||||
|
{
|
||||||
|
var user = await UserService.GetUserAsync(_userid, PageState.Site.SiteId);
|
||||||
|
await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId);
|
||||||
|
await logger.LogInformation("User Permanently Deleted {User}", user);
|
||||||
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Permanently Deleting User {UserId} {Error}", _userid, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.DeleteUser"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private bool ValidateProfiles()
|
private bool ValidateProfiles()
|
||||||
{
|
{
|
||||||
bool valid = true;
|
foreach (Profile profile in _profiles)
|
||||||
foreach (Profile profile in profiles)
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)) && !string.IsNullOrEmpty(profile.DefaultValue))
|
var value = GetProfileValue(profile.Name, string.Empty);
|
||||||
|
if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue))
|
||||||
{
|
{
|
||||||
settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue);
|
_settings = SettingService.SetSetting(_settings, profile.Name, profile.DefaultValue);
|
||||||
}
|
}
|
||||||
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
{
|
{
|
||||||
if (valid == true && profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)))
|
if (profile.IsRequired && string.IsNullOrEmpty(value))
|
||||||
{
|
{
|
||||||
valid = false;
|
AddModuleMessage(string.Format(SharedLocalizer["ProfileRequired"], profile.Title), MessageType.Warning);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
if (valid == true && !string.IsNullOrEmpty(profile.Validation))
|
if (!string.IsNullOrEmpty(profile.Validation))
|
||||||
{
|
{
|
||||||
Regex regex = new Regex(profile.Validation);
|
Regex regex = new Regex(profile.Validation);
|
||||||
valid = regex.Match(SettingService.GetSetting(settings, profile.Name, string.Empty)).Success;
|
bool valid = regex.Match(value).Success;
|
||||||
|
if (!valid)
|
||||||
|
{
|
||||||
|
AddModuleMessage(string.Format(SharedLocalizer["ProfileInvalid"], profile.Title), MessageType.Warning);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return valid;
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProfileChanged(ChangeEventArgs e, string SettingName)
|
private void ProfileChanged(ChangeEventArgs e, string SettingName)
|
||||||
{
|
{
|
||||||
var value = (string)e.Value;
|
var value = (string)e.Value;
|
||||||
settings = SettingService.SetSetting(settings, SettingName, value);
|
_settings = SettingService.SetSetting(_settings, SettingName, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TogglePassword()
|
private void TogglePassword()
|
||||||
|
|||||||
@@ -17,21 +17,10 @@ else
|
|||||||
{
|
{
|
||||||
<TabStrip>
|
<TabStrip>
|
||||||
<TabPanel Name="Users" Heading="Users" ResourceKey="Users">
|
<TabPanel Name="Users" Heading="Users" ResourceKey="Users">
|
||||||
<div class="container">
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<div class="col-sm-4">
|
|
||||||
<ActionLink Action="Add" Text="Add User" Security="SecurityAccessLevel.Edit" ResourceKey="AddUser" />
|
<ActionLink Action="Add" Text="Add User" Security="SecurityAccessLevel.Edit" ResourceKey="AddUser" />
|
||||||
<ActionLink Text="Import Users" Class="btn btn-secondary" Action="Users" Security="SecurityAccessLevel.Admin" ResourceKey="ImportUsers"/>
|
<ActionLink Text="Import Users" Class="btn btn-secondary ms-2" Action="Users" Security="SecurityAccessLevel.Admin" ResourceKey="ImportUsers"/>
|
||||||
</div>
|
|
||||||
<div class="col-sm-4">
|
<Pager Items="@users" RowClass="align-middle" SearchProperties="User.Username,User.Email,User.DisplayName">
|
||||||
<input class="form-control" @bind="@_search" />
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-4">
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="OnSearch">@SharedLocalizer["Search"]</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Pager Items="@users" RowClass="align-middle">
|
|
||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
@@ -43,18 +32,18 @@ else
|
|||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td>
|
<td>
|
||||||
<ActionLink Action="Edit" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="EditUser" />
|
<ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="EditUser" />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<ActionDialog Header="Delete User" Message="@string.Format(Localizer["Confirm.User.Delete"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUser(context))" Disabled="@(context.UserId == PageState.User.UserId)" ResourceKey="DeleteUser" />
|
<ActionDialog Header="Delete User" Message="@string.Format(Localizer["Confirm.User.Delete"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUser(context))" Disabled="@(context.UserId == PageState.User.UserId || context.User.IsDeleted)" ResourceKey="DeleteUser" />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<ActionLink Action="Roles" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="Roles" />
|
<ActionLink Action="Roles" Text="Roles" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="Roles" />
|
||||||
</td>
|
</td>
|
||||||
<td>@context.User.Username</td>
|
<td>@context.User.Username</td>
|
||||||
<td>@context.User.DisplayName</td>
|
<td>@context.User.DisplayName</td>
|
||||||
<td>@((MarkupString)string.Format("<a href=\"mailto:{0}\">{1}</a>", @context.User.Email, @context.User.Email))</td>
|
<td>@((MarkupString)string.Format("<a href=\"mailto:{0}\">{1}</a>", @context.User.Email, @context.User.Email))</td>
|
||||||
<td>@((context.User.LastLoginOn != DateTime.MinValue) ? string.Format("{0:dd-MMM-yyyy HH:mm:ss}", context.User.LastLoginOn) : "")</td>
|
<td>@((context.User.LastLoginOn != DateTime.MinValue) ? string.Format("{0:dd-MMM-yyyy HH:mm:ss}", UtcToLocal(context.User.LastLoginOn)) : "")</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
@@ -65,32 +54,26 @@ else
|
|||||||
<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">
|
||||||
<select id="allowregistration" class="form-select" @bind="@_allowregistration">
|
<select id="allowregistration" class="form-select" @bind="@_allowregistration">
|
||||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
|
||||||
<option value="False">@SharedLocalizer["No"]</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@if (_providertype != "")
|
|
||||||
{
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="allowsitelogin" HelpText="Do you want to allow users to sign in using a username and password that is managed locally on this site? Note that you should only disable this option if you have already sucessfully configured an external login provider, or else you may lock yourself out of the site." ResourceKey="AllowSiteLogin">Allow Login?</Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<select id="allowsitelogin" class="form-select" @bind="@_allowsitelogin">
|
|
||||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
<option value="false">@SharedLocalizer["No"]</option>
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
@if (_allowregistration == "true")
|
||||||
else
|
|
||||||
{
|
{
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="allowsitelogin" HelpText="Do you want to allow users to sign in using a username and password that is managed locally on this site? Note that you should only disable this option if you have already sucessfully configured an external login provider, or else you may lock yourself out of the site." ResourceKey="AllowSiteLogin">Allow Login?</Label>
|
<Label Class="col-sm-3" For="registerurl" HelpText="Optionally provide a custom registration url" ResourceKey="RegisterUrl">Register Url:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="allowsitelogin" class="form-control" value="@SharedLocalizer["Yes"]" readonly />
|
<input id="registerurl" class="form-control" @bind="@_registerurl" />
|
||||||
</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>
|
||||||
@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">
|
||||||
@@ -109,6 +92,30 @@ else
|
|||||||
<input id="cookiename" class="form-control" @bind="@_cookiename" />
|
<input id="cookiename" class="form-control" @bind="@_cookiename" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="cookieexpiration" HelpText="You can choose to use a custom authentication cookie expiration timespan for each site (e.g. '08:00:00' for 8 hours). The default is 14 days if not specified." ResourceKey="CookieExpiration">Cookie Expiration Timespan:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="cookieexpiration" class="form-control" @bind="@_cookieexpiration" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="alwaysremember" HelpText="Enabling this option will set a permanent cookie in conjunction with the Cookie Expiration Timespan, which will automatically sign in users the next time they visit the site. By default the site will use session cookies." ResourceKey="AlwaysRemember">Always Remember User?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="alwaysremember" class="form-select" @bind="@_alwaysremember">
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="logouteverywhere" HelpText="Do you want users to be logged out of every active session on any device, or only their current session?" ResourceKey="LogoutEverywhere">Logout Everywhere?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="logouteverywhere" class="form-select" @bind="@_logouteverywhere">
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</Section>
|
</Section>
|
||||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
@@ -178,20 +185,38 @@ else
|
|||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
<Section Name="ExternalLogin" Heading="External Login Settings" ResourceKey="ExternalLoginSettings">
|
<Section Name="ExternalLogin" Heading="External Login Settings" ResourceKey="ExternalLoginSettings">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="provider" HelpText="Select the external login provider" ResourceKey="Provider">Provider:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<select id="provider" class="form-select" value="@_provider" @onchange="(e => ProviderChanged(e))">
|
||||||
|
@foreach (var provider in Shared.ExternalLoginProviders.Providers)
|
||||||
|
{
|
||||||
|
<option value="@provider.Name">@Localizer[provider.Name]</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
@if (!string.IsNullOrEmpty(_providerurl))
|
||||||
|
{
|
||||||
|
<a href="@_providerurl" class="btn btn-secondary" target="_new">@Localizer["Info"]</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="providertype" HelpText="Select the external login provider type" ResourceKey="ProviderType">Provider Type:</Label>
|
<Label Class="col-sm-3" For="providertype" HelpText="Select the external login provider type" ResourceKey="ProviderType">Provider Type:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="providertype" class="form-select" value="@_providertype" @onchange="(e => ProviderTypeChanged(e))">
|
<select id="providertype" class="form-select" value="@_providertype" @onchange="(e => ProviderTypeChanged(e))">
|
||||||
<option value="" selected>@Localizer["Not Specified"]</option>
|
<option value="" selected><@Localizer["Not Specified"]></option>
|
||||||
<option value="@AuthenticationProviderTypes.OpenIDConnect">@Localizer["OpenID Connect"]</option>
|
<option value="@AuthenticationProviderTypes.OpenIDConnect">@Localizer["OIDC"]</option>
|
||||||
<option value="@AuthenticationProviderTypes.OAuth2">@Localizer["OAuth 2.0"]</option>
|
<option value="@AuthenticationProviderTypes.OAuth2">@Localizer["OAuth2"]</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (_providertype != "")
|
@if (_providertype != "")
|
||||||
{
|
{
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="providername" HelpText="The external login provider name which will be displayed on the login page" ResourceKey="ProviderName">Provider Name:</Label>
|
<Label Class="col-sm-3" For="providername" HelpText="Specify a friendly name for the external login provider which will be displayed on the Login page" ResourceKey="ProviderName">Provider Name:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="providername" class="form-control" @bind="@_providername" />
|
<input id="providername" class="form-control" @bind="@_providername" />
|
||||||
</div>
|
</div>
|
||||||
@@ -250,6 +275,24 @@ else
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
|
||||||
|
{
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="authresponsetype" HelpText="Specify the authorization response type. The default is Authorization Code which is considered to be the most secure option based on the latest OAuth specification." ResourceKey="AuthResponseType">Authorization Response Type:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="authresponsetype" class="form-select" @bind="@_authresponsetype" required>
|
||||||
|
<option value="code">@Localizer["AuthFlow.Code"]</option>
|
||||||
|
<option value="code id_token">@Localizer["AuthFlow.CodeIdToken"]</option>
|
||||||
|
<option value="code id_token token">@Localizer["AuthFlow.CodeIdTokenToken"]</option>
|
||||||
|
<option value="code token">@Localizer["AuthFlow.CodeToken"]</option>
|
||||||
|
<option value="id_token">@Localizer["AuthFlow.IdToken"]</option>
|
||||||
|
<option value="id_token token">@Localizer["AuthFlow.IdTokenToken"]</option>
|
||||||
|
<option value="token">@Localizer["AuthFlow.Token"]</option>
|
||||||
|
<option value="none">@Localizer["AuthFlow.None"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="scopes" HelpText="A list of Scopes to request from the provider (separated by commas). If none are specified, standard Scopes will be used by default." ResourceKey="Scopes">Scopes:</Label>
|
<Label Class="col-sm-3" For="scopes" HelpText="A list of Scopes to request from the provider (separated by commas). If none are specified, standard Scopes will be used by default." ResourceKey="Scopes">Scopes:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -278,32 +321,76 @@ 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="identifierclaimtype" HelpText="The name of the unique user identifier claim provided by the provider" ResourceKey="IdentifierClaimType">Identifier Claim:</Label>
|
<Label Class="col-sm-3" For="reviewclaims" HelpText="This option will record the full list of Claims returned by the Provider in the Event Log. It should only be used for testing purposes. External Login will be restricted when this option is enabled." ResourceKey="ReviewClaims">Review Claims?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<select id="reviewclaims" class="form-select" @bind="@_reviewclaims" required>
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
@if (_reviewclaims == "true")
|
||||||
|
{
|
||||||
|
<a href="@_externalloginurl" target="_blank" class="btn btn-secondary">@SharedLocalizer["Test"]</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="identifierclaimtype" HelpText="Specify the type name of the unique user identifier claim provided by the provider. The default value is 'sub'." ResourceKey="IdentifierClaimType">Identifier Claim:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="identifierclaimtype" class="form-control" @bind="@_identifierclaimtype" />
|
<input id="identifierclaimtype" class="form-control" @bind="@_identifierclaimtype" />
|
||||||
</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="emailclaimtype" HelpText="The name of the email address claim provided by the provider" ResourceKey="EmailClaimType">Email Claim:</Label>
|
<Label Class="col-sm-3" For="nameclaimtype" HelpText="Optionally specify the type name of the user's name claim provided by the provider. The typical value is 'name'." ResourceKey="NameClaimType">Name Claim:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="nameclaimtype" class="form-control" @bind="@_nameclaimtype" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="emailclaimtype" HelpText="Optionally specify the type name of the email address claim provided by the provider. The typical value is 'email'," ResourceKey="EmailClaimType">Email Claim:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="emailclaimtype" class="form-control" @bind="@_emailclaimtype" />
|
<input id="emailclaimtype" class="form-control" @bind="@_emailclaimtype" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
|
|
||||||
{
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="roleclaimtype" HelpText="The name of the role claim provided by the provider" ResourceKey="RoleClaimType">Role Claim:</Label>
|
<Label Class="col-sm-3" For="roleclaimtype" HelpText="The name of the roles claim provided by the provider" ResourceKey="RoleClaimType">Roles Claim:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="roleclaimtype" class="form-control" @bind="@_roleclaimtype" />
|
<input id="roleclaimtype" class="form-control" @bind="@_roleclaimtype" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="roleclaimmappings" HelpText="Optionally provide a comma delimited list of role names provided by the identity provider, as well as mappings to your site roles." ResourceKey="RoleClaimMappings">Role Claim Mappings:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="roleclaimmappings" class="form-control" @bind="@_roleclaimmappings" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="synchronizeroles" HelpText="This option will add or remove role assignments so that the site roles exactly match the roles provided by the identity provider" ResourceKey="SynchronizeRoles">Synchronize Roles?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<select id="synchronizeroles" class="form-select" @bind="@_synchronizeroles" required>
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="profileclaimtypes" HelpText="A comma delimited list of user profile claims provided by the provider, as well as mappings to your user profile definition. For example if the provider includes a 'given_name' claim and you have a 'FirstName' user profile definition you should specify 'given_name:FirstName'." ResourceKey="ProfileClaimTypes">User Profile Claims:</Label>
|
<Label Class="col-sm-3" For="profileclaimtypes" HelpText="A comma delimited list of user profile claims provided by the provider, as well as mappings to your user profile definition. For example if the provider includes a 'given_name' claim and you have a 'FirstName' user profile definition you should specify 'given_name:FirstName'." ResourceKey="ProfileClaimTypes">User Profile Claims:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="profileclaimtypes" class="form-control" @bind="@_profileclaimtypes" />
|
<input id="profileclaimtypes" class="form-control" @bind="@_profileclaimtypes" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="savetokens" HelpText="Specify whether access and refresh tokens should be saved after a successful login. The default is false to reduce the size of the authentication cookie." ResourceKey="SaveTokens">Save Tokens?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="savetokens" class="form-select" @bind="@_savetokens" required>
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="domainfilter" HelpText="Provide any email domain filter criteria (separated by commas). Domains to exclude should be prefixed with an exclamation point (!). For example 'microsoft.com,!hotmail.com' would include microsoft.com email addresses but not hotmail.com email addresses." ResourceKey="DomainFilter">Domain Filter:</Label>
|
<Label Class="col-sm-3" For="domainfilter" HelpText="Provide any email domain filter criteria (separated by commas). Domains to exclude should be prefixed with an exclamation point (!). For example 'microsoft.com,!hotmail.com' would include microsoft.com email addresses but not hotmail.com email addresses." ResourceKey="DomainFilter">Domain Filter:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -319,14 +406,44 @@ else
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="verifyusers" HelpText="Do you want existing users to perform an additional email verification step to link their external login? If you disable this option, existing users will be linked automatically." ResourceKey="VerifyUsers">Verify Existing Users?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="verifyusers" class="form-select" @bind="@_verifyusers">
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
|
{
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="allowhostrole" HelpText="Indicate if host roles are supported from the identity provider. Please use caution with this option as it allows the host user to administrate every site within your installation." ResourceKey="AllowHostRole">Allow Host Role?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="allowhostrole" class="form-select" @bind="@_allowhostrole" required>
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="allowsitelogin" HelpText="Do you want to allow users to sign in using a username and password that is managed locally on this site? Note that you should only disable this option if you have already sucessfully configured an external login provider, or else you may lock yourself out of the site." ResourceKey="AllowSiteLogin">Allow Local Login?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="allowsitelogin" class="form-select" @bind="@_allowsitelogin">
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</Section>
|
</Section>
|
||||||
<Section Name="Token" Heading="Token Settings" ResourceKey="TokenSettings">
|
<Section Name="Token" Heading="Token Settings" ResourceKey="TokenSettings">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="secret" HelpText="If you want to want to provide API access, please specify a secret which will be used to encrypt your tokens. The secret should be 16 characters or more to ensure optimal security. Please note that if you change this secret, all existing tokens will become invalid and will need to be regenerated." ResourceKey="Secret">Secret:</Label>
|
<Label Class="col-sm-3" For="jwtsecret" HelpText="If you want to want to provide API access, please specify a secret which will be used to encrypt your tokens. The secret should be 16 characters or more to ensure optimal security. Please note that if you change this secret, all existing tokens will become invalid and will need to be regenerated." ResourceKey="Secret">Secret:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="@_secrettype" id="secret" class="form-control" @bind="@_secret" />
|
<input type="@_secrettype" id="jwtsecret" class="form-control" @bind="@_secret" />
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@ToggleSecret">@_togglesecret</button>
|
<button type="button" class="btn btn-secondary" @onclick="@ToggleSecret">@_togglesecret</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -368,14 +485,16 @@ else
|
|||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private List<UserRole> allusers;
|
|
||||||
private List<UserRole> users;
|
private List<UserRole> users;
|
||||||
private string _search = "";
|
|
||||||
|
|
||||||
private string _allowregistration;
|
private string _allowregistration;
|
||||||
private string _allowsitelogin;
|
private string _registerurl;
|
||||||
|
private string _profileurl;
|
||||||
private string _twofactor;
|
private string _twofactor;
|
||||||
private string _cookiename;
|
private string _cookiename;
|
||||||
|
private string _cookieexpiration;
|
||||||
|
private string _alwaysremember;
|
||||||
|
private string _logouteverywhere;
|
||||||
|
|
||||||
private string _minimumlength;
|
private string _minimumlength;
|
||||||
private string _uniquecharacters;
|
private string _uniquecharacters;
|
||||||
@@ -386,6 +505,8 @@ else
|
|||||||
private string _maximumfailures;
|
private string _maximumfailures;
|
||||||
private string _lockoutduration;
|
private string _lockoutduration;
|
||||||
|
|
||||||
|
private string _provider;
|
||||||
|
private string _providerurl;
|
||||||
private string _providertype;
|
private string _providertype;
|
||||||
private string _providername;
|
private string _providername;
|
||||||
private string _authority;
|
private string _authority;
|
||||||
@@ -397,16 +518,26 @@ else
|
|||||||
private string _clientsecret;
|
private string _clientsecret;
|
||||||
private string _clientsecrettype = "password";
|
private string _clientsecrettype = "password";
|
||||||
private string _toggleclientsecret = string.Empty;
|
private string _toggleclientsecret = string.Empty;
|
||||||
|
private string _authresponsetype;
|
||||||
private string _scopes;
|
private string _scopes;
|
||||||
private string _parameters;
|
private string _parameters;
|
||||||
private string _pkce;
|
private string _pkce;
|
||||||
private string _redirecturl;
|
private string _redirecturl;
|
||||||
|
private string _reviewclaims;
|
||||||
|
private string _externalloginurl;
|
||||||
private string _identifierclaimtype;
|
private string _identifierclaimtype;
|
||||||
|
private string _nameclaimtype;
|
||||||
private string _emailclaimtype;
|
private string _emailclaimtype;
|
||||||
private string _roleclaimtype;
|
private string _roleclaimtype;
|
||||||
|
private string _roleclaimmappings;
|
||||||
|
private string _synchronizeroles;
|
||||||
private string _profileclaimtypes;
|
private string _profileclaimtypes;
|
||||||
|
private string _savetokens;
|
||||||
private string _domainfilter;
|
private string _domainfilter;
|
||||||
private string _createusers;
|
private string _createusers;
|
||||||
|
private string _verifyusers;
|
||||||
|
private string _allowhostrole;
|
||||||
|
private string _allowsitelogin;
|
||||||
|
|
||||||
private string _secret;
|
private string _secret;
|
||||||
private string _secrettype = "password";
|
private string _secrettype = "password";
|
||||||
@@ -423,17 +554,20 @@ else
|
|||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
await LoadUserSettingsAsync();
|
|
||||||
await LoadUsersAsync(true);
|
await LoadUsersAsync(true);
|
||||||
|
|
||||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
_allowregistration = PageState.Site.AllowRegistration.ToString();
|
_allowregistration = PageState.Site.AllowRegistration.ToString().ToLower();
|
||||||
_allowsitelogin = SettingService.GetSetting(settings, "LoginOptions:AllowSiteLogin", "true");
|
_registerurl = SettingService.GetSetting(settings, "LoginOptions:RegisterUrl", "");
|
||||||
|
_profileurl = SettingService.GetSetting(settings, "LoginOptions:ProfileUrl", "");
|
||||||
|
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
_twofactor = SettingService.GetSetting(settings, "LoginOptions:TwoFactor", "false");
|
_twofactor = SettingService.GetSetting(settings, "LoginOptions:TwoFactor", "false");
|
||||||
_cookiename = SettingService.GetSetting(settings, "LoginOptions:CookieName", ".AspNetCore.Identity.Application");
|
_cookiename = SettingService.GetSetting(settings, "LoginOptions:CookieName", ".AspNetCore.Identity.Application");
|
||||||
|
_cookieexpiration = SettingService.GetSetting(settings, "LoginOptions:CookieExpiration", "");
|
||||||
|
_alwaysremember = SettingService.GetSetting(settings, "LoginOptions:AlwaysRemember", "false");
|
||||||
|
_logouteverywhere = SettingService.GetSetting(settings, "LoginOptions:LogoutEverywhere", "false");
|
||||||
|
|
||||||
_minimumlength = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredLength", "6");
|
_minimumlength = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredLength", "6");
|
||||||
_uniquecharacters = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", "1");
|
_uniquecharacters = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", "1");
|
||||||
@@ -445,6 +579,20 @@ else
|
|||||||
_maximumfailures = SettingService.GetSetting(settings, "IdentityOptions:Lockout:MaxFailedAccessAttempts", "5");
|
_maximumfailures = SettingService.GetSetting(settings, "IdentityOptions:Lockout:MaxFailedAccessAttempts", "5");
|
||||||
_lockoutduration = TimeSpan.Parse(SettingService.GetSetting(settings, "IdentityOptions:Lockout:DefaultLockoutTimeSpan", "00:05:00")).TotalMinutes.ToString();
|
_lockoutduration = TimeSpan.Parse(SettingService.GetSetting(settings, "IdentityOptions:Lockout:DefaultLockoutTimeSpan", "00:05:00")).TotalMinutes.ToString();
|
||||||
|
|
||||||
|
LoadExternalLoginSettings(settings);
|
||||||
|
|
||||||
|
_secret = SettingService.GetSetting(settings, "JwtOptions:Secret", "");
|
||||||
|
_togglesecret = SharedLocalizer["ShowPassword"];
|
||||||
|
_issuer = SettingService.GetSetting(settings, "JwtOptions:Issuer", PageState.Uri.Scheme + "://" + PageState.Alias.Name);
|
||||||
|
_audience = SettingService.GetSetting(settings, "JwtOptions:Audience", "");
|
||||||
|
_lifetime = SettingService.GetSetting(settings, "JwtOptions:Lifetime", "20");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadExternalLoginSettings(Dictionary<string, string> settings)
|
||||||
|
{
|
||||||
|
_provider = SettingService.GetSetting(settings, "ExternalLogin:Provider", "<Custom>");
|
||||||
|
_providerurl = SettingService.GetSetting(settings, "ExternalLogin:ProviderUrl", "");
|
||||||
_providertype = SettingService.GetSetting(settings, "ExternalLogin:ProviderType", "");
|
_providertype = SettingService.GetSetting(settings, "ExternalLogin:ProviderType", "");
|
||||||
_providername = SettingService.GetSetting(settings, "ExternalLogin:ProviderName", "");
|
_providername = SettingService.GetSetting(settings, "ExternalLogin:ProviderName", "");
|
||||||
_authority = SettingService.GetSetting(settings, "ExternalLogin:Authority", "");
|
_authority = SettingService.GetSetting(settings, "ExternalLogin:Authority", "");
|
||||||
@@ -455,92 +603,74 @@ else
|
|||||||
_clientid = SettingService.GetSetting(settings, "ExternalLogin:ClientId", "");
|
_clientid = SettingService.GetSetting(settings, "ExternalLogin:ClientId", "");
|
||||||
_clientsecret = SettingService.GetSetting(settings, "ExternalLogin:ClientSecret", "");
|
_clientsecret = SettingService.GetSetting(settings, "ExternalLogin:ClientSecret", "");
|
||||||
_toggleclientsecret = SharedLocalizer["ShowPassword"];
|
_toggleclientsecret = SharedLocalizer["ShowPassword"];
|
||||||
|
_authresponsetype = SettingService.GetSetting(settings, "ExternalLogin:AuthResponseType", "code");
|
||||||
_scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", "");
|
_scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", "");
|
||||||
_parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", "");
|
_parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", "");
|
||||||
_pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false");
|
_pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false");
|
||||||
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;
|
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;
|
||||||
|
_reviewclaims = SettingService.GetSetting(settings, "ExternalLogin:ReviewClaims", "false");
|
||||||
|
_externalloginurl = Utilities.TenantUrl(PageState.Alias, "/pages/external");
|
||||||
_identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "sub");
|
_identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "sub");
|
||||||
|
_nameclaimtype = SettingService.GetSetting(settings, "ExternalLogin:NameClaimType", "name");
|
||||||
_emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "email");
|
_emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "email");
|
||||||
_roleclaimtype = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimType", "");
|
_roleclaimtype = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimType", "");
|
||||||
|
_roleclaimmappings = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimMappings", "");
|
||||||
|
_synchronizeroles = SettingService.GetSetting(settings, "ExternalLogin:SynchronizeRoles", "false");
|
||||||
_profileclaimtypes = SettingService.GetSetting(settings, "ExternalLogin:ProfileClaimTypes", "");
|
_profileclaimtypes = SettingService.GetSetting(settings, "ExternalLogin:ProfileClaimTypes", "");
|
||||||
|
_savetokens = SettingService.GetSetting(settings, "ExternalLogin:SaveTokens", "false");
|
||||||
_domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", "");
|
_domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", "");
|
||||||
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
|
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
|
||||||
|
_verifyusers = SettingService.GetSetting(settings, "ExternalLogin:VerifyUsers", "true");
|
||||||
_secret = SettingService.GetSetting(settings, "JwtOptions:Secret", "");
|
_allowhostrole = SettingService.GetSetting(settings, "ExternalLogin:AllowHostRole", "false");
|
||||||
_togglesecret = SharedLocalizer["ShowPassword"];
|
_allowsitelogin = SettingService.GetSetting(settings, "LoginOptions:AllowSiteLogin", "true");
|
||||||
_issuer = SettingService.GetSetting(settings, "JwtOptions:Issuer", PageState.Uri.Scheme + "://" + PageState.Alias.Name);
|
|
||||||
_audience = SettingService.GetSetting(settings, "JwtOptions:Audience", "");
|
|
||||||
_lifetime = SettingService.GetSetting(settings, "JwtOptions:Lifetime", "20");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadUsersAsync(bool load)
|
private async Task LoadUsersAsync(bool load)
|
||||||
{
|
{
|
||||||
if (load)
|
if (load)
|
||||||
{
|
{
|
||||||
allusers = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
|
users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
var hosts = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Host);
|
var hosts = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Host);
|
||||||
allusers.AddRange(hosts);
|
users.AddRange(hosts);
|
||||||
allusers = allusers.OrderBy(u => u.User.DisplayName).ToList();
|
users = users.OrderBy(u => u.User.DisplayName).ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
users = allusers;
|
|
||||||
if (!string.IsNullOrEmpty(_search))
|
|
||||||
{
|
|
||||||
users = users.Where(item =>
|
|
||||||
(
|
|
||||||
item.User.Username.Contains(_search, StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
item.User.Email.Contains(_search, StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
item.User.DisplayName.Contains(_search, StringComparison.OrdinalIgnoreCase)
|
|
||||||
)
|
|
||||||
).ToList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task OnSearch()
|
|
||||||
{
|
|
||||||
await UpdateUserSettingsAsync();
|
|
||||||
await LoadUsersAsync(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DeleteUser(UserRole UserRole)
|
private async Task DeleteUser(UserRole UserRole)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
var user = await UserService.GetUserAsync(UserRole.UserId, PageState.Site.SiteId);
|
var user = await UserService.GetUserAsync(UserRole.UserId, PageState.Site.SiteId);
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId);
|
user.IsDeleted = true;
|
||||||
await logger.LogInformation("User Deleted {User}", UserRole.User);
|
await UserService.UpdateUserAsync(user);
|
||||||
|
await logger.LogInformation("User Soft Deleted {User}", user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var userrole = await UserRoleService.GetUserRoleAsync(UserRole.UserRoleId);
|
||||||
|
userrole.ExpiryDate = DateTime.UtcNow;
|
||||||
|
await UserRoleService.UpdateUserRoleAsync(userrole);
|
||||||
|
await logger.LogInformation("User {Username} Expired From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
||||||
|
}
|
||||||
|
AddModuleMessage(Localizer["Success.DeleteUser"], MessageType.Success);
|
||||||
await LoadUsersAsync(true);
|
await LoadUsersAsync(true);
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Deleting User {User} {Error}", UserRole.User, ex.Message);
|
await logger.LogError(ex, "Error Deleting User {User} {Error}", UserRole.User, ex.Message);
|
||||||
AddModuleMessage(ex.Message, MessageType.Error);
|
AddModuleMessage(Localizer["Error.DeleteUser"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string settingSearch = "AU-search";
|
|
||||||
|
|
||||||
private async Task LoadUserSettingsAsync()
|
|
||||||
{
|
|
||||||
Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
|
|
||||||
_search = SettingService.GetSetting(settings, settingSearch, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task UpdateUserSettingsAsync()
|
|
||||||
{
|
|
||||||
Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
|
|
||||||
settings = SettingService.SetSetting(settings, settingSearch, _search);
|
|
||||||
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SaveSiteSettings()
|
private async Task SaveSiteSettings()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -550,12 +680,16 @@ else
|
|||||||
await SiteService.UpdateSiteAsync(site);
|
await SiteService.UpdateSiteAsync(site);
|
||||||
|
|
||||||
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
||||||
settings = SettingService.SetSetting(settings, "LoginOptions:AllowSiteLogin", _allowsitelogin, false);
|
|
||||||
|
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
|
settings = SettingService.SetSetting(settings, "LoginOptions:RegisterUrl", _registerurl, false);
|
||||||
|
settings = SettingService.SetSetting(settings, "LoginOptions:ProfileUrl", _profileurl, false);
|
||||||
settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false);
|
settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false);
|
||||||
settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true);
|
settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "LoginOptions:AlwaysRemember", _alwaysremember, false);
|
||||||
|
settings = SettingService.SetSetting(settings, "LoginOptions:LogoutEverywhere", _logouteverywhere, false);
|
||||||
|
|
||||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredLength", _minimumlength, true);
|
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredLength", _minimumlength, true);
|
||||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", _uniquecharacters, true);
|
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", _uniquecharacters, true);
|
||||||
@@ -567,6 +701,7 @@ else
|
|||||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Lockout:MaxFailedAccessAttempts", _maximumfailures, true);
|
settings = SettingService.SetSetting(settings, "IdentityOptions:Lockout:MaxFailedAccessAttempts", _maximumfailures, true);
|
||||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Lockout:DefaultLockoutTimeSpan", TimeSpan.FromMinutes(Convert.ToInt64(_lockoutduration)).ToString(), true);
|
settings = SettingService.SetSetting(settings, "IdentityOptions:Lockout:DefaultLockoutTimeSpan", TimeSpan.FromMinutes(Convert.ToInt64(_lockoutduration)).ToString(), true);
|
||||||
|
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:Provider", _provider, false);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ProviderType", _providertype, false);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:ProviderType", _providertype, false);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ProviderName", _providername, false);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:ProviderName", _providername, false);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:Authority", _authority, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:Authority", _authority, true);
|
||||||
@@ -576,17 +711,25 @@ else
|
|||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:UserInfoUrl", _userinfourl, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:UserInfoUrl", _userinfourl, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientId", _clientid, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientId", _clientid, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientSecret", _clientsecret, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientSecret", _clientsecret, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:AuthResponseType", _authresponsetype, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:Scopes", _scopes, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:Scopes", _scopes, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:Parameters", _parameters, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:Parameters", _parameters, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:ReviewClaims", _reviewclaims, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:IdentifierClaimType", _identifierclaimtype, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:IdentifierClaimType", _identifierclaimtype, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:NameClaimType", _nameclaimtype, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:EmailClaimType", _emailclaimtype, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:EmailClaimType", _emailclaimtype, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimType", _roleclaimtype, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimType", _roleclaimtype, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimMappings", _roleclaimmappings, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:SynchronizeRoles", _synchronizeroles, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ProfileClaimTypes", _profileclaimtypes, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:ProfileClaimTypes", _profileclaimtypes, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:SaveTokens", _savetokens, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true);
|
||||||
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:AllowHostRole", _allowhostrole, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "LoginOptions:AllowSiteLogin", _allowsitelogin, false);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(_secret) && _secret.Length < 16) _secret = (_secret + "????????????????").Substring(0, 16);
|
|
||||||
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);
|
||||||
@@ -608,6 +751,21 @@ else
|
|||||||
await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message);
|
await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message);
|
||||||
AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error);
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await ScrollToPageTop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProviderChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
_provider = (string)e.Value;
|
||||||
|
var provider = Shared.ExternalLoginProviders.Providers.FirstOrDefault(item => item.Name == _provider);
|
||||||
|
if (provider != null)
|
||||||
|
{
|
||||||
|
LoadExternalLoginSettings(provider.Settings);
|
||||||
|
}
|
||||||
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProviderTypeChanged(ChangeEventArgs e)
|
private void ProviderTypeChanged(ChangeEventArgs e)
|
||||||
|
|||||||
@@ -34,13 +34,13 @@ else
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this role assignment is active" ResourceKey="EffectiveDate">Effective Date: </Label>
|
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this role assignment is active" ResourceKey="EffectiveDate">Effective Date: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input type="date" id="effectiveDate" class="form-control" @bind="@effectivedate" />
|
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
|
||||||
</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="expiryDate" HelpText="The date that this role assignment expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
|
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this role assignment expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input type="date" id="expiryDate" class="form-control" @bind="@expirydate" />
|
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -60,10 +60,10 @@ else
|
|||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td>@context.Role.Name</td>
|
<td>@context.Role.Name</td>
|
||||||
<td>@context.EffectiveDate</td>
|
<td>@Utilities.UtcAsLocalDate(context.EffectiveDate)</td>
|
||||||
<td>@context.ExpiryDate</td>
|
<td>@Utilities.UtcAsLocalDate(context.ExpiryDate)</td>
|
||||||
<td>
|
<td>
|
||||||
<ActionDialog Header="Remove Role" Message="@string.Format(Localizer["Confirm.User.RemoveRole"], context.Role.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.IsAutoAssigned || (context.Role.Name == RoleNames.Host && userid == PageState.User.UserId))" ResourceKey="DeleteUserRole" />
|
<ActionDialog Header="Remove Role" Message="@string.Format(Localizer["Confirm.User.RemoveRole"], context.Role.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.Name == RoleNames.Host && userid == PageState.User.UserId)" ResourceKey="DeleteUserRole" />
|
||||||
</td>
|
</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
@@ -75,8 +75,8 @@ else
|
|||||||
private string name = string.Empty;
|
private string name = string.Empty;
|
||||||
private List<Role> roles;
|
private List<Role> roles;
|
||||||
private int roleid = -1;
|
private int roleid = -1;
|
||||||
private DateTime? effectivedate = null;
|
private DateTime? _effectivedate = null;
|
||||||
private DateTime? expirydate = null;
|
private DateTime? _expirydate = null;
|
||||||
private List<UserRole> userroles;
|
private List<UserRole> userroles;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||||
@@ -113,6 +113,7 @@ else
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
userroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, userid);
|
userroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, userid);
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -127,11 +128,16 @@ else
|
|||||||
{
|
{
|
||||||
if (roleid != -1)
|
if (roleid != -1)
|
||||||
{
|
{
|
||||||
|
if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate))
|
||||||
|
{
|
||||||
|
AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
var userrole = userroles.Where(item => item.UserId == userid && item.RoleId == roleid).FirstOrDefault();
|
var userrole = userroles.Where(item => item.UserId == userid && item.RoleId == roleid).FirstOrDefault();
|
||||||
if (userrole != null)
|
if (userrole != null)
|
||||||
{
|
{
|
||||||
userrole.EffectiveDate = effectivedate;
|
userrole.EffectiveDate = _effectivedate;
|
||||||
userrole.ExpiryDate = expirydate;
|
userrole.ExpiryDate = _expirydate;
|
||||||
await UserRoleService.UpdateUserRoleAsync(userrole);
|
await UserRoleService.UpdateUserRoleAsync(userrole);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -139,8 +145,8 @@ else
|
|||||||
userrole = new UserRole();
|
userrole = new UserRole();
|
||||||
userrole.UserId = userid;
|
userrole.UserId = userid;
|
||||||
userrole.RoleId = roleid;
|
userrole.RoleId = roleid;
|
||||||
userrole.EffectiveDate = effectivedate;
|
userrole.EffectiveDate = Utilities.UtcAsLocalDate(_effectivedate);
|
||||||
userrole.ExpiryDate = expirydate;
|
userrole.ExpiryDate = Utilities.UtcAsLocalDate(_expirydate);
|
||||||
await UserRoleService.AddUserRoleAsync(userrole);
|
await UserRoleService.AddUserRoleAsync(userrole);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,9 +170,19 @@ else
|
|||||||
private async Task DeleteUserRole(int UserRoleId)
|
private async Task DeleteUserRole(int UserRoleId)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
var userrole = await UserRoleService.GetUserRoleAsync(UserRoleId);
|
||||||
|
if (userrole.Role.Name == RoleNames.Registered)
|
||||||
|
{
|
||||||
|
userrole.ExpiryDate = DateTime.UtcNow;
|
||||||
|
await UserRoleService.UpdateUserRoleAsync(userrole);
|
||||||
|
await logger.LogInformation("User {Username} Expired From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
await UserRoleService.DeleteUserRoleAsync(UserRoleId);
|
await UserRoleService.DeleteUserRoleAsync(UserRoleId);
|
||||||
await logger.LogInformation("User Removed From Role {UserRoleId}", UserRoleId);
|
await logger.LogInformation("User {Username} Removed From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
||||||
|
}
|
||||||
AddModuleMessage(Localizer["Success.User.Remove"], MessageType.Success);
|
AddModuleMessage(Localizer["Success.User.Remove"], MessageType.Success);
|
||||||
await GetUserRoles();
|
await GetUserRoles();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
|
|||||||
@@ -101,8 +101,8 @@
|
|||||||
_url = visitor.Url;
|
_url = visitor.Url;
|
||||||
_referrer = visitor.Referrer;
|
_referrer = visitor.Referrer;
|
||||||
_visits = visitor.Visits.ToString();
|
_visits = visitor.Visits.ToString();
|
||||||
_visited = visitor.VisitedOn.ToString(CultureInfo.CurrentCulture);
|
_visited = UtcToLocal(visitor.VisitedOn).Value.ToString(CultureInfo.CurrentCulture);
|
||||||
_created = visitor.CreatedOn.ToString(CultureInfo.CurrentCulture);
|
_created = UtcToLocal(visitor.CreatedOn).Value.ToString(CultureInfo.CurrentCulture);
|
||||||
|
|
||||||
if (visitor.UserId != null)
|
if (visitor.UserId != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ else
|
|||||||
<th>@Localizer["Created"]</th>
|
<th>@Localizer["Created"]</th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><ActionLink Action="Detail" Parameters="@($"id={context.VisitorId}")" ReturnUrl="@(NavigateUrl(PageState.Page.Path, $"type={_type}&days={_days}&page={_page}"))" ResourceKey="Details" /></td>
|
<td><ActionLink Action="Detail" Text="Detail" Parameters="@($"id={context.VisitorId}")" ReturnUrl="@(NavigateUrl(PageState.Page.Path, $"type={_type}&days={_days}&page={_page}"))" ResourceKey="Details" /></td>
|
||||||
<td>@context.IPAddress</td>
|
<td>@context.IPAddress</td>
|
||||||
<td>
|
<td>
|
||||||
@if (context.UserId != null)
|
@if (context.UserId != null)
|
||||||
@@ -53,8 +53,8 @@ else
|
|||||||
</td>
|
</td>
|
||||||
<td>@context.Language</td>
|
<td>@context.Language</td>
|
||||||
<td>@context.Visits</td>
|
<td>@context.Visits</td>
|
||||||
<td>@context.VisitedOn</td>
|
<td>@UtcToLocal(context.VisitedOn)</td>
|
||||||
<td>@context.CreatedOn</td>
|
<td>@UtcToLocal(context.CreatedOn)</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
@@ -69,6 +69,12 @@ else
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="duration" HelpText="The duration of a browsing session considered to be a distinct visit (in minutes)" ResourceKey="Duration">Session Duration: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="duration" class="form-control" type="number" min="0" step="1" @bind="@_duration" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="filter" HelpText="Comma delimited list of terms which may exist in IP addresses, user agents, or languages identifying visitors which should not be tracked" ResourceKey="Filter">Filter: </Label>
|
<Label Class="col-sm-3" For="filter" HelpText="Comma delimited list of terms which may exist in IP addresses, user agents, or languages identifying visitors which should not be tracked" ResourceKey="Filter">Filter: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -76,9 +82,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="retention" HelpText="Number of days of visitor activity to retain" ResourceKey="Retention">Retention (Days): </Label>
|
<Label Class="col-sm-3" For="retention" HelpText="Number of days of visitor activity to retain" ResourceKey="Retention">Retention: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="retention" class="form-control" @bind="@_retention" />
|
<input id="retention" class="form-control" type="number" min="0" step="1" @bind="@_retention" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -94,6 +100,18 @@ else
|
|||||||
<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>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
<TabPanel Name="Robots" Heading="Robots.txt" ResourceKey="Robots">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="robots" HelpText="Specify your robots.txt instructions to provide bots with guidance on which parts of your site should be indexed" ResourceKey="Robots">Instructions: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea id="robots" class="form-control" @bind="@_robots" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||||
|
</TabPanel>
|
||||||
</TabStrip>
|
</TabStrip>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,9 +121,11 @@ else
|
|||||||
private int _page = 1;
|
private int _page = 1;
|
||||||
private List<Visitor> _visitors;
|
private List<Visitor> _visitors;
|
||||||
private string _tracking;
|
private string _tracking;
|
||||||
|
private int _duration = 5;
|
||||||
private string _filter = "";
|
private string _filter = "";
|
||||||
private string _retention = "";
|
private int _retention = 30;
|
||||||
private string _correlation = "true";
|
private string _correlation = "true";
|
||||||
|
private string _robots = "";
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
|
||||||
@@ -128,9 +148,11 @@ else
|
|||||||
|
|
||||||
_tracking = PageState.Site.VisitorTracking.ToString();
|
_tracking = PageState.Site.VisitorTracking.ToString();
|
||||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
_duration = int.Parse(SettingService.GetSetting(settings, "VisitorDuration", "5"));
|
||||||
_filter = SettingService.GetSetting(settings, "VisitorFilter", Constants.DefaultVisitorFilter);
|
_filter = SettingService.GetSetting(settings, "VisitorFilter", Constants.DefaultVisitorFilter);
|
||||||
_retention = SettingService.GetSetting(settings, "VisitorRetention", "30");
|
_retention = int.Parse(SettingService.GetSetting(settings, "VisitorRetention", "30"));
|
||||||
_correlation = SettingService.GetSetting(settings, "VisitorCorrelation", "true");
|
_correlation = SettingService.GetSetting(settings, "VisitorCorrelation", "true");
|
||||||
|
_robots = SettingService.GetSetting(settings, "Robots", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void TypeChanged(ChangeEventArgs e)
|
private async void TypeChanged(ChangeEventArgs e)
|
||||||
@@ -179,9 +201,11 @@ else
|
|||||||
await SiteService.UpdateSiteAsync(site);
|
await SiteService.UpdateSiteAsync(site);
|
||||||
|
|
||||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
settings = SettingService.SetSetting(settings, "VisitorDuration", _duration.ToString(), true);
|
||||||
settings = SettingService.SetSetting(settings, "VisitorFilter", _filter, true);
|
settings = SettingService.SetSetting(settings, "VisitorFilter", _filter, true);
|
||||||
settings = SettingService.SetSetting(settings, "VisitorRetention", _retention, true);
|
settings = SettingService.SetSetting(settings, "VisitorRetention", _retention.ToString(), true);
|
||||||
settings = SettingService.SetSetting(settings, "VisitorCorrelation", _correlation, true);
|
settings = SettingService.SetSetting(settings, "VisitorCorrelation", _correlation, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "Robots", _robots, true);
|
||||||
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||||
|
|
||||||
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
||||||
|
|||||||
@@ -2,9 +2,12 @@
|
|||||||
@using System.Text.Json
|
@using System.Text.Json
|
||||||
@inherits LocalizableComponent
|
@inherits LocalizableComponent
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
|
||||||
@if (_visible)
|
@if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Interactive)
|
||||||
{
|
{
|
||||||
|
@if (_visible)
|
||||||
|
{
|
||||||
<div class="app-actiondialog">
|
<div class="app-actiondialog">
|
||||||
<div class="modal" tabindex="-1" role="dialog">
|
<div class="modal" tabindex="-1" role="dialog">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
@@ -19,24 +22,76 @@
|
|||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
@if (!string.IsNullOrEmpty(Action))
|
@if (!string.IsNullOrEmpty(Action))
|
||||||
{
|
{
|
||||||
<button type="button" class="@Class" @onclick="Confirm">@((MarkupString)_iconSpan) @Text</button>
|
<button type="button" class="@ConfirmClass" @onclick="Confirm">@((MarkupString)_iconSpan) @Text</button>
|
||||||
}
|
}
|
||||||
<button type="button" class="btn btn-secondary" @onclick="DisplayModal">@SharedLocalizer["Cancel"]</button>
|
<button type="button" class="@CancelClass" @onclick="DisplayModal">@SharedLocalizer["Cancel"]</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (_authorized)
|
@if (_authorized)
|
||||||
{
|
{
|
||||||
if (Disabled)
|
if (Disabled)
|
||||||
{
|
{
|
||||||
<button type="button" class="@Class" disabled>@((MarkupString)_iconSpan) @Text</button>
|
<button type="button" class="@Class" disabled>@((MarkupString)_openIconSpan) @_openText</button>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<button type="button" class="@Class" @onclick="DisplayModal">@((MarkupString)_iconSpan) @Text</button>
|
<button type="button" class="@Class" @onclick="DisplayModal">@((MarkupString)_openIconSpan) @_openText</button>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@if (_visible)
|
||||||
|
{
|
||||||
|
<div class="app-actiondialog">
|
||||||
|
<div class="modal" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<form class="app-form-inline" method="post" @formname="@($"ActionDialogCloseForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="DisplayModal" data-enhance>
|
||||||
|
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">@Header</h5>
|
||||||
|
<button type="submit" class="btn-close" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>@Message</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
@if (!string.IsNullOrEmpty(Action))
|
||||||
|
{
|
||||||
|
<form method="post" @formname="@($"ActionDialogConfirmForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="Confirm" data-enhance>
|
||||||
|
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||||
|
<button type="submit" class="@ConfirmClass">@((MarkupString)_iconSpan) @Text</button>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
<form method="post" @formname="@($"ActionDialogCancelForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="DisplayModal" data-enhance>
|
||||||
|
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||||
|
<button type="submit" class="@CancelClass">@SharedLocalizer["Cancel"]</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (_authorized)
|
||||||
|
{
|
||||||
|
if (Disabled)
|
||||||
|
{
|
||||||
|
<button type="button" class="@Class" disabled>@((MarkupString)_openIconSpan) @_openText</button>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<form method="post" class="app-form-inline" @formname="@($"ActionDialogActionForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="DisplayModal" data-enhance>
|
||||||
|
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||||
|
<button type="submit" class="@Class">@((MarkupString)_openIconSpan) @_openText</button>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,6 +101,8 @@
|
|||||||
private bool _editmode = false;
|
private bool _editmode = false;
|
||||||
private bool _authorized = false;
|
private bool _authorized = false;
|
||||||
private string _iconSpan = string.Empty;
|
private string _iconSpan = string.Empty;
|
||||||
|
private string _openIconSpan = string.Empty;
|
||||||
|
private string _openText = string.Empty;
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Header { get; set; } // required
|
public string Header { get; set; } // required
|
||||||
@@ -71,6 +128,12 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public string Class { get; set; } // optional
|
public string Class { get; set; } // optional
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string ConfirmClass { get; set; } // optional - for Confirm modal button
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string CancelClass { get; set; } // optional - for Cancel modal button
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public bool Disabled { get; set; } // optional
|
public bool Disabled { get; set; } // optional
|
||||||
|
|
||||||
@@ -83,6 +146,12 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public string IconName { get; set; } // optional - specifies an icon for the link - default is no icon
|
public string IconName { get; set; } // optional - specifies an icon for the link - default is no icon
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public bool IconOnly { get; set; } // optional - specifies only icon in opening link
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string Id { get; set; } // optional - specifies a unique id for the compoment - required when there are multiple component instances on a page in static rendering
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(Permissions))
|
if (!string.IsNullOrEmpty(Permissions))
|
||||||
@@ -99,31 +168,60 @@
|
|||||||
{
|
{
|
||||||
Text = Action;
|
Text = Action;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(Class))
|
if (string.IsNullOrEmpty(Class))
|
||||||
{
|
{
|
||||||
Class = "btn btn-success";
|
Class = "btn btn-success";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(ConfirmClass))
|
||||||
|
{
|
||||||
|
ConfirmClass = Class;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(CancelClass))
|
||||||
|
{
|
||||||
|
CancelClass = "btn btn-secondary";
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(EditMode))
|
if (!string.IsNullOrEmpty(EditMode))
|
||||||
{
|
{
|
||||||
_editmode = bool.Parse(EditMode);
|
_editmode = bool.Parse(EditMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(IconName))
|
|
||||||
{
|
|
||||||
if (!IconName.Contains(" "))
|
|
||||||
{
|
|
||||||
IconName = "oi oi-" + IconName;
|
|
||||||
}
|
|
||||||
_iconSpan = $"<span class=\"{IconName}\"></span> ";
|
|
||||||
}
|
|
||||||
|
|
||||||
Text = Localize(nameof(Text), Text);
|
Text = Localize(nameof(Text), Text);
|
||||||
Header = Localize(nameof(Header), Header);
|
Header = Localize(nameof(Header), Header);
|
||||||
Message = Localize(nameof(Message), Message);
|
Message = Localize(nameof(Message), Message);
|
||||||
|
|
||||||
|
_openText = Text;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(IconName))
|
||||||
|
{
|
||||||
|
if (IconOnly)
|
||||||
|
{
|
||||||
|
_openText = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if IconName starts with "oi oi-"
|
||||||
|
bool startsWithOiOi = IconName.StartsWith("oi oi-");
|
||||||
|
|
||||||
|
if (!startsWithOiOi && !IconName.Contains(" "))
|
||||||
|
{
|
||||||
|
IconName = "oi oi-" + IconName;
|
||||||
|
}
|
||||||
|
_openIconSpan = $"<span class=\"{IconName}\"></span>{(IconOnly ? "" : " ")}";
|
||||||
|
_iconSpan = $"<span class=\"{IconName}\"></span> ";
|
||||||
|
}
|
||||||
|
|
||||||
_permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList;
|
_permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList;
|
||||||
_authorized = IsAuthorized();
|
_authorized = IsAuthorized();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(Id)) Id = "1";
|
||||||
|
|
||||||
|
if (PageState.QueryString.ContainsKey("dialog"))
|
||||||
|
{
|
||||||
|
_visible = (PageState.QueryString["dialog"] == Id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsAuthorized()
|
private bool IsAuthorized()
|
||||||
@@ -175,12 +273,22 @@
|
|||||||
private void DisplayModal()
|
private void DisplayModal()
|
||||||
{
|
{
|
||||||
_visible = !_visible;
|
_visible = !_visible;
|
||||||
|
if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Interactive)
|
||||||
|
{
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var parameters = new Dictionary<string, string>(PageState.QueryString);
|
||||||
|
if (parameters.ContainsKey("dialog")) parameters.Remove("dialog");
|
||||||
|
if (_visible) parameters.Add("dialog", Id);
|
||||||
|
NavigationManager.NavigateTo(PageState.Route.AbsolutePath + Utilities.CreateQueryString(parameters));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void Confirm()
|
private void Confirm()
|
||||||
{
|
{
|
||||||
DisplayModal();
|
|
||||||
OnClick();
|
OnClick();
|
||||||
|
DisplayModal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
{
|
{
|
||||||
if (Disabled)
|
if (Disabled)
|
||||||
{
|
{
|
||||||
<button type="button" class="@_classname" style="@_style" disabled>@((MarkupString)_iconSpan) @_text</button>
|
<NavLink class="@($"{_classname} disabled")" href="@_url" style="@_style">@((MarkupString)_iconSpan) @_text</NavLink>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -97,10 +97,13 @@
|
|||||||
{
|
{
|
||||||
base.OnParametersSet();
|
base.OnParametersSet();
|
||||||
|
|
||||||
_text = Action;
|
|
||||||
if (!string.IsNullOrEmpty(Text))
|
if (!string.IsNullOrEmpty(Text))
|
||||||
{
|
{
|
||||||
_text = Text;
|
_text = Localize(nameof(Text), Text);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_text = Localize(nameof(Action), Action);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IconOnly && !string.IsNullOrEmpty(IconName))
|
if (IconOnly && !string.IsNullOrEmpty(IconName))
|
||||||
@@ -142,7 +145,10 @@
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(IconName))
|
if (!string.IsNullOrEmpty(IconName))
|
||||||
{
|
{
|
||||||
if (!IconName.Contains(" "))
|
// Check if IconName starts with "oi oi-"
|
||||||
|
bool startsWithOiOi = IconName.StartsWith("oi oi-");
|
||||||
|
|
||||||
|
if (!startsWithOiOi && !IconName.Contains(" "))
|
||||||
{
|
{
|
||||||
IconName = "oi oi-" + IconName;
|
IconName = "oi oi-" + IconName;
|
||||||
}
|
}
|
||||||
@@ -150,7 +156,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
_permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList;
|
_permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList;
|
||||||
_text = Localize(nameof(Text), _text);
|
|
||||||
|
|
||||||
_url = EditUrl(_path, _moduleId, Action, _parameters);
|
_url = EditUrl(_path, _moduleId, Action, _parameters);
|
||||||
if (!string.IsNullOrEmpty(ReturnUrl))
|
if (!string.IsNullOrEmpty(ReturnUrl))
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
|
|
||||||
if (CreatedOn != null)
|
if (CreatedOn != null)
|
||||||
{
|
{
|
||||||
_text += $" {Localizer["On"]} <b>{CreatedOn.Value.ToString(DateTimeFormat)}</b>";
|
_text += $" {Localizer["On"]} <b>{UtcToLocal(CreatedOn).Value.ToString(DateTimeFormat)}</ b >";
|
||||||
}
|
}
|
||||||
|
|
||||||
_text += "</p>";
|
_text += "</p>";
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
|
|
||||||
if (ModifiedOn != null)
|
if (ModifiedOn != null)
|
||||||
{
|
{
|
||||||
_text += $" {Localizer["On"]} <b>{ModifiedOn.Value.ToString(DateTimeFormat)}</b>";
|
_text += $" {Localizer["On"]} <b>{UtcToLocal(ModifiedOn).Value.ToString(DateTimeFormat)}</ b >";
|
||||||
}
|
}
|
||||||
|
|
||||||
_text += "</p>";
|
_text += "</p>";
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
|
|
||||||
if (DeletedOn != null)
|
if (DeletedOn != null)
|
||||||
{
|
{
|
||||||
_text += $" {Localizer["On"]} <b>{DeletedOn.Value.ToString(DateTimeFormat)}</b>";
|
_text += $" {Localizer["On"]} <b>{UtcToLocal(DeletedOn).Value.ToString(DateTimeFormat)}</ b >";
|
||||||
}
|
}
|
||||||
|
|
||||||
_text += "</p>";
|
_text += "</p>";
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
@inherits ModuleControlBase
|
@inherits ModuleControlBase
|
||||||
@inject IFolderService FolderService
|
@inject IFolderService FolderService
|
||||||
@inject IFileService FileService
|
@inject IFileService FileService
|
||||||
|
@inject IUserService UserService
|
||||||
|
@inject ISettingService SettingService
|
||||||
@inject IStringLocalizer<FileManager> Localizer
|
@inject IStringLocalizer<FileManager> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@@ -40,10 +42,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (FileId != -1 && _file != null && !UploadMultiple)
|
||||||
|
{
|
||||||
|
<input class="form-control" @bind="@_file.Name" disabled />
|
||||||
|
}
|
||||||
|
}
|
||||||
@if (ShowUpload && _haseditpermission)
|
@if (ShowUpload && _haseditpermission)
|
||||||
{
|
{
|
||||||
<div class="row">
|
<div class="row mt-2">
|
||||||
<div class="col mt-2">
|
<div class="col">
|
||||||
@if (UploadMultiple)
|
@if (UploadMultiple)
|
||||||
{
|
{
|
||||||
<input type="file" id="@_fileinputid" name="file" accept="@_filter" multiple />
|
<input type="file" id="@_fileinputid" name="file" accept="@_filter" multiple />
|
||||||
@@ -53,9 +62,9 @@
|
|||||||
<input type="file" id="@_fileinputid" name="file" accept="@_filter" />
|
<input type="file" id="@_fileinputid" name="file" accept="@_filter" />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="col mt-2 text-end">
|
<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>
|
||||||
@if (GetFileId() != -1)
|
@if (FileId != -1 && !UploadMultiple)
|
||||||
{
|
{
|
||||||
<button type="button" class="btn btn-danger mx-1" @onclick="DeleteFile">@SharedLocalizer["Delete"]</button>
|
<button type="button" class="btn btn-danger mx-1" @onclick="DeleteFile">@SharedLocalizer["Delete"]</button>
|
||||||
}
|
}
|
||||||
@@ -76,6 +85,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@if (!string.IsNullOrEmpty(_message))
|
||||||
|
{
|
||||||
|
<div class="row mt-1">
|
||||||
|
<div class="col">
|
||||||
|
<ModuleMessage Message="@_message" Type="@_messagetype" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (_image != string.Empty)
|
@if (_image != string.Empty)
|
||||||
@@ -85,14 +102,6 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@if (!string.IsNullOrEmpty(_message))
|
|
||||||
{
|
|
||||||
<div class="row mt-1">
|
|
||||||
<div class="col">
|
|
||||||
<ModuleMessage Message="@_message" Type="@_messagetype" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,9 +157,19 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false
|
public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public int ChunkSize { get; set; } = 1; // optional - size of file chunks to upload 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
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public EventCallback<int> OnSelectFolder { get; set; } // optional - executes a method in the calling component when a folder is selected
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public EventCallback<int> OnSelectFile { get; set; } // optional - executes a method in the calling component when a file is selected
|
||||||
|
|
||||||
|
[Obsolete("Use OnSelectFile instead.")]
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public EventCallback<int> OnSelect { get; set; } // optional - executes a method in the calling component when a file is selected
|
public EventCallback<int> OnSelect { get; set; } // optional - executes a method in the calling component when a file is selected
|
||||||
|
|
||||||
@@ -187,7 +206,7 @@
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
FolderId = -1;
|
FolderId = -1;
|
||||||
_message = "Folder Path " + Folder + "Does Not Exist";
|
_message = "Folder Path " + Folder + " Does Not Exist";
|
||||||
_messagetype = MessageType.Error;
|
_messagetype = MessageType.Error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -217,9 +236,9 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
FileId = -1; // file does not exist
|
_message = "FileId " + FileId.ToString() + " Does Not Exist";
|
||||||
_message = "FileId " + FileId.ToString() + "Does Not Exist";
|
|
||||||
_messagetype = MessageType.Error;
|
_messagetype = MessageType.Error;
|
||||||
|
FileId = -1; // file does not exist
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,6 +307,8 @@
|
|||||||
FileId = -1;
|
FileId = -1;
|
||||||
_file = null;
|
_file = null;
|
||||||
_image = string.Empty;
|
_image = string.Empty;
|
||||||
|
|
||||||
|
await OnSelectFolder.InvokeAsync(FolderId);
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -303,9 +324,11 @@
|
|||||||
_message = string.Empty;
|
_message = string.Empty;
|
||||||
FileId = int.Parse((string)e.Value);
|
FileId = int.Parse((string)e.Value);
|
||||||
await SetImage();
|
await SetImage();
|
||||||
|
#pragma warning disable CS0618
|
||||||
await OnSelect.InvokeAsync(FileId);
|
await OnSelect.InvokeAsync(FileId);
|
||||||
|
#pragma warning restore CS0618
|
||||||
|
await OnSelectFile.InvokeAsync(FileId);
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SetImage()
|
private async Task SetImage()
|
||||||
@@ -336,6 +359,7 @@
|
|||||||
_message = string.Empty;
|
_message = string.Empty;
|
||||||
var interop = new Interop(JSRuntime);
|
var interop = new Interop(JSRuntime);
|
||||||
var uploads = await interop.GetFiles(_fileinputid);
|
var uploads = await interop.GetFiles(_fileinputid);
|
||||||
|
|
||||||
if (uploads.Length > 0)
|
if (uploads.Length > 0)
|
||||||
{
|
{
|
||||||
string restricted = "";
|
string restricted = "";
|
||||||
@@ -343,64 +367,48 @@
|
|||||||
{
|
{
|
||||||
var filename = upload.Split(':')[0];
|
var filename = upload.Split(':')[0];
|
||||||
var extension = (filename.LastIndexOf(".") != -1) ? filename.Substring(filename.LastIndexOf(".") + 1) : "";
|
var extension = (filename.LastIndexOf(".") != -1) ? filename.Substring(filename.LastIndexOf(".") + 1) : "";
|
||||||
if (!Constants.UploadableFiles.Split(',').Contains(extension.ToLower()))
|
if (!PageState.Site.UploadableFiles.Split(',').Contains(extension.ToLower()))
|
||||||
{
|
{
|
||||||
restricted += (restricted == "" ? "" : ",") + extension;
|
restricted += (restricted == "" ? "" : ",") + extension;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (restricted == "")
|
if (restricted == "")
|
||||||
{
|
{
|
||||||
if (!ShowProgress)
|
CancellationTokenSource tokenSource = new CancellationTokenSource();
|
||||||
{
|
|
||||||
_uploading = true;
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// upload the files
|
// upload the files
|
||||||
var posturl = Utilities.TenantUrl(PageState.Alias, "/api/file/upload");
|
var posturl = Utilities.TenantUrl(PageState.Alias, "/api/file/upload");
|
||||||
var folder = (Folder == Constants.PackagesFolder) ? Folder : FolderId.ToString();
|
var folder = (Folder == Constants.PackagesFolder) ? Folder : FolderId.ToString();
|
||||||
await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken);
|
var jwt = "";
|
||||||
|
if (PageState.Runtime == Shared.Runtime.Hybrid)
|
||||||
|
{
|
||||||
|
jwt = await UserService.GetTokenAsync();
|
||||||
|
if (string.IsNullOrEmpty(jwt))
|
||||||
|
{
|
||||||
|
await logger.LogInformation("File Upload Failed From .NET MAUI Due To Missing Security Token. Token Options Must Be Set In User Settings.");
|
||||||
|
_message = "Security Token Not Specified";
|
||||||
|
_messagetype = MessageType.Error;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// uploading is asynchronous so we need to poll to determine if uploads are completed
|
var chunksize = ChunkSize;
|
||||||
var success = true;
|
if (chunksize == 1)
|
||||||
int upload = 0;
|
|
||||||
while (upload < uploads.Length && success)
|
|
||||||
{
|
{
|
||||||
success = false;
|
// if ChunkSize parameter is not overridden use the site setting
|
||||||
var filename = uploads[upload].Split(':')[0];
|
chunksize = int.Parse(SettingService.GetSetting(PageState.Site.Settings, "MaxChunkSize", "1"));
|
||||||
var size = Int64.Parse(uploads[upload].Split(':')[1]);
|
}
|
||||||
var maxattempts = (int)Math.Ceiling(size / 500000.0) + 1; // 30 MB takes 1 minute at 5 Mbps
|
|
||||||
|
|
||||||
int attempts = 0;
|
if (!ShowProgress)
|
||||||
while (attempts < maxattempts && !success)
|
|
||||||
{
|
{
|
||||||
attempts += 1;
|
_uploading = true;
|
||||||
Thread.Sleep(1000);
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
if (Folder == Constants.PackagesFolder)
|
// upload files
|
||||||
{
|
var success = await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken, jwt, chunksize, tokenSource.Token);
|
||||||
var files = await FileService.GetFilesAsync(folder);
|
|
||||||
if (files != null && files.Any(item => item.Name == filename))
|
|
||||||
{
|
|
||||||
success = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var file = await FileService.GetFileAsync(int.Parse(folder), filename);
|
|
||||||
if (file != null)
|
|
||||||
{
|
|
||||||
success = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
upload++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset progress indicators
|
// reset progress indicators
|
||||||
if (ShowProgress)
|
if (ShowProgress)
|
||||||
@@ -425,7 +433,7 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await logger.LogInformation("File Upload Failed Or Is Still In Progress {Files}", uploads);
|
await logger.LogError("File Upload Failed {Files}", uploads);
|
||||||
_message = Localizer["Error.File.Upload"];
|
_message = Localizer["Error.File.Upload"];
|
||||||
_messagetype = MessageType.Error;
|
_messagetype = MessageType.Error;
|
||||||
}
|
}
|
||||||
@@ -442,7 +450,10 @@
|
|||||||
{
|
{
|
||||||
FileId = file.FileId;
|
FileId = file.FileId;
|
||||||
await SetImage();
|
await SetImage();
|
||||||
|
#pragma warning disable CS0618
|
||||||
await OnSelect.InvokeAsync(FileId);
|
await OnSelect.InvokeAsync(FileId);
|
||||||
|
#pragma warning restore CS0618
|
||||||
|
await OnSelectFile.InvokeAsync(FileId);
|
||||||
await OnUpload.InvokeAsync(FileId);
|
await OnUpload.InvokeAsync(FileId);
|
||||||
}
|
}
|
||||||
await GetFiles();
|
await GetFiles();
|
||||||
@@ -455,6 +466,10 @@
|
|||||||
_message = Localizer["Error.File.Upload"];
|
_message = Localizer["Error.File.Upload"];
|
||||||
_messagetype = MessageType.Error;
|
_messagetype = MessageType.Error;
|
||||||
_uploading = false;
|
_uploading = false;
|
||||||
|
await tokenSource.CancelAsync();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
tokenSource.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -489,7 +504,10 @@
|
|||||||
await GetFiles();
|
await GetFiles();
|
||||||
FileId = -1;
|
FileId = -1;
|
||||||
await SetImage();
|
await SetImage();
|
||||||
|
#pragma warning disable CS0618
|
||||||
await OnSelect.InvokeAsync(FileId);
|
await OnSelect.InvokeAsync(FileId);
|
||||||
|
#pragma warning restore CS0618
|
||||||
|
await OnSelectFile.InvokeAsync(FileId);
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -37,8 +37,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void OnChange(ChangeEventArgs e)
|
protected void OnChange(ChangeEventArgs e)
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(e.Value.ToString()))
|
|
||||||
{
|
{
|
||||||
Value = e.Value.ToString();
|
Value = e.Value.ToString();
|
||||||
if (ValueChanged.HasDelegate)
|
if (ValueChanged.HasDelegate)
|
||||||
@@ -46,5 +44,4 @@
|
|||||||
ValueChanged.InvokeAsync(Value);
|
ValueChanged.InvokeAsync(Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -2,15 +2,25 @@
|
|||||||
@inherits ModuleControlBase
|
@inherits ModuleControlBase
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
|
|
||||||
@if (!string.IsNullOrEmpty(_message))
|
@if (!string.IsNullOrEmpty(Message))
|
||||||
{
|
{
|
||||||
<div class="@_classname alert-dismissible fade show mb-3" role="alert">
|
<div class="@_classname alert-dismissible fade show mb-3" role="alert">
|
||||||
@((MarkupString)_message)
|
@((MarkupString)Message)
|
||||||
@if (Type == MessageType.Error && PageState != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
@if (Type == MessageType.Error && PageState != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
@((MarkupString)" ")<NavLink href="@NavigateUrl("admin/log")">View Details</NavLink>
|
<NavLink class="ms-2" href="@NavigateUrl("admin/log")">View Details</NavLink>
|
||||||
|
}
|
||||||
|
@if (ModuleState != null)
|
||||||
|
{
|
||||||
|
@if (ModuleState.RenderMode == RenderModes.Static)
|
||||||
|
{
|
||||||
|
<a href="@NavigationManager.Uri" class="btn-close" data-dismiss="alert" aria-label="close"></a>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<button type="button" class="btn-close" data-dismiss="alert" aria-label="close" @onclick="CloseMessage"></button>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
<button type="button" class="btn-close" aria-label="Close" @onclick="DismissModal"></button>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,6 +34,9 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public MessageType Type { get; set; }
|
public MessageType Type { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public RenderModeBoundary Parent { get; set; }
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
_message = Message;
|
_message = Message;
|
||||||
@@ -54,10 +67,15 @@
|
|||||||
|
|
||||||
return classname;
|
return classname;
|
||||||
}
|
}
|
||||||
|
private void CloseMessage(MouseEventArgs e)
|
||||||
private void DismissModal()
|
|
||||||
{
|
{
|
||||||
_message = "";
|
if(Parent != null)
|
||||||
StateHasChanged();
|
{
|
||||||
|
Parent.DismissMessage();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NavigationManager.NavigateTo(NavigationManager.Uri);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,38 @@
|
|||||||
@namespace Oqtane.Modules.Controls
|
@namespace Oqtane.Modules.Controls
|
||||||
@inherits ModuleControlBase
|
@inherits ModuleControlBase
|
||||||
@inject IStringLocalizerFactory LocalizerFactory
|
@inject IStringLocalizerFactory LocalizerFactory
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
@typeparam TableItem
|
@typeparam TableItem
|
||||||
|
|
||||||
@if (ItemList != null)
|
@if (ItemList != null)
|
||||||
{
|
{
|
||||||
|
@if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Interactive)
|
||||||
|
{
|
||||||
|
@if (!string.IsNullOrEmpty(SearchProperties))
|
||||||
|
{
|
||||||
|
<form autocomplete="off">
|
||||||
|
<div class="input-group my-3 @SearchBoxClass">
|
||||||
|
<input type="text" id="pagersearch" class="form-control" placeholder=@string.Format(Localizer["SearchPlaceholder"], FormatSearchProperties()) @bind="@_search" />
|
||||||
|
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
|
||||||
@if ((Toolbar == "Top" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
|
@if ((Toolbar == "Top" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
|
||||||
{
|
{
|
||||||
<ul class="pagination justify-content-center my-2">
|
<ul class="pagination justify-content-@PaginationAlignment.ToLower() my-2">
|
||||||
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
|
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
|
||||||
<a class="page-link" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
|
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
|
||||||
</li>
|
</li>
|
||||||
@if (_pages > _displayPages && _displayPages > 1)
|
@if (_pages > _displayPages && _displayPages > 1)
|
||||||
{
|
{
|
||||||
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
|
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
|
||||||
<a class="page-link" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
|
<a class="page-link shadow-none" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
|
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
|
||||||
<a class="page-link" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
|
<a class="page-link shadow-none" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
|
||||||
</li>
|
</li>
|
||||||
@for (int i = _startPage; i <= _endPage; i++)
|
@for (int i = _startPage; i <= _endPage; i++)
|
||||||
{
|
{
|
||||||
@@ -26,33 +40,98 @@
|
|||||||
if (pager == _page)
|
if (pager == _page)
|
||||||
{
|
{
|
||||||
<li class="page-item app-pager-pointer active">
|
<li class="page-item app-pager-pointer active">
|
||||||
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<li class="page-item app-pager-pointer">
|
<li class="page-item app-pager-pointer">
|
||||||
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
|
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
|
||||||
<a class="page-link" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
|
<a class="page-link shadow-none" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
|
||||||
</li>
|
</li>
|
||||||
@if (_pages > _displayPages && _displayPages > 1)
|
@if (_pages > _displayPages && _displayPages > 1)
|
||||||
{
|
{
|
||||||
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
|
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
|
||||||
<a class="page-link" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
|
<a class="page-link shadow-none" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
|
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
|
||||||
<a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
|
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
|
||||||
</li>
|
</li>
|
||||||
<li class="page-item disabled">
|
<li class="page-item disabled">
|
||||||
<a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
|
<a class="page-link shadow-none" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@if (!string.IsNullOrEmpty(SearchProperties))
|
||||||
|
{
|
||||||
|
<form method="post" autocomplete="off" @formname="PagerForm" @onsubmit="Search" data-enhance>
|
||||||
|
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||||
|
<div class="input-group my-3 @SearchBoxClass">
|
||||||
|
<input type="text" id="pagersearch" name="_search" class="form-control" placeholder=@string.Format(Localizer["SearchPlaceholder"], FormatSearchProperties()) @bind="@_search" />
|
||||||
|
<button type="submit" class="btn btn-primary">@SharedLocalizer["Search"]</button>
|
||||||
|
<a class="btn btn-secondary" href="@PageUrl(1, "")">@SharedLocalizer["Reset"]</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if ((Toolbar == "Top" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
|
||||||
|
{
|
||||||
|
<ul class="pagination justify-content-@PaginationAlignment.ToLower() my-2">
|
||||||
|
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(1, _search)"><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
|
||||||
|
</li>
|
||||||
|
@if (_pages > _displayPages && _displayPages > 1)
|
||||||
|
{
|
||||||
|
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(_startPage - 1, _search)"><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(((_page > 1) ? _page - 1 : _page), _search)"><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
|
||||||
|
</li>
|
||||||
|
@for (int i = _startPage; i <= _endPage; i++)
|
||||||
|
{
|
||||||
|
var pager = i;
|
||||||
|
if (pager == _page)
|
||||||
|
{
|
||||||
|
<li class="page-item app-pager-pointer active">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(pager, _search)">@pager</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<li class="page-item app-pager-pointer">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(pager, _search)">@pager</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(((_page < _pages) ? _page + 1 : _page), _search)"><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
|
||||||
|
</li>
|
||||||
|
@if (_pages > _displayPages && _displayPages > 1)
|
||||||
|
{
|
||||||
|
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(_endPage + 1, _search)"><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(_pages, _search)"><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
|
||||||
|
</li>
|
||||||
|
<li class="page-item disabled">
|
||||||
|
<a class="page-link shadow-none" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@if (Format == "Table" && Row != null)
|
@if (Format == "Table" && Row != null)
|
||||||
{
|
{
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
@@ -116,20 +195,23 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if ((Toolbar == "Bottom" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
|
@if ((Toolbar == "Bottom" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
|
||||||
{
|
{
|
||||||
<ul class="pagination justify-content-center my-2">
|
@if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Interactive)
|
||||||
|
{
|
||||||
|
<ul class="pagination justify-content-@PaginationAlignment.ToLower() my-2">
|
||||||
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
|
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
|
||||||
<a class="page-link" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
|
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
|
||||||
</li>
|
</li>
|
||||||
@if (_pages > _displayPages && _displayPages > 1)
|
@if (_pages > _displayPages && _displayPages > 1)
|
||||||
{
|
{
|
||||||
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
|
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
|
||||||
<a class="page-link" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
|
<a class="page-link shadow-none" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
|
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
|
||||||
<a class="page-link" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
|
<a class="page-link shadow-none" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
|
||||||
</li>
|
</li>
|
||||||
@for (int i = _startPage; i <= _endPage; i++)
|
@for (int i = _startPage; i <= _endPage; i++)
|
||||||
{
|
{
|
||||||
@@ -137,33 +219,83 @@
|
|||||||
if (pager == _page)
|
if (pager == _page)
|
||||||
{
|
{
|
||||||
<li class="page-item app-pager-pointer active">
|
<li class="page-item app-pager-pointer active">
|
||||||
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<li class="page-item app-pager-pointer">
|
<li class="page-item app-pager-pointer">
|
||||||
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
|
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
|
||||||
<a class="page-link" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
|
<a class="page-link shadow-none" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
|
||||||
</li>
|
</li>
|
||||||
@if (_pages > _displayPages && _displayPages > 1)
|
@if (_pages > _displayPages && _displayPages > 1)
|
||||||
{
|
{
|
||||||
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
|
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
|
||||||
<a class="page-link" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
|
<a class="page-link shadow-none" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
|
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
|
||||||
<a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
|
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
|
||||||
</li>
|
</li>
|
||||||
<li class="page-item disabled">
|
<li class="page-item disabled">
|
||||||
<a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
|
<a class="page-link shadow-none" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<ul class="pagination justify-content-@PaginationAlignment.ToLower() my-2">
|
||||||
|
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(1, _search)"><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
|
||||||
|
</li>
|
||||||
|
@if (_pages > _displayPages && _displayPages > 1)
|
||||||
|
{
|
||||||
|
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(_startPage - 1, _search)"><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(((_page > 1) ? _page - 1 : _page), _search)"><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
|
||||||
|
</li>
|
||||||
|
@for (int i = _startPage; i <= _endPage; i++)
|
||||||
|
{
|
||||||
|
var pager = i;
|
||||||
|
if (pager == _page)
|
||||||
|
{
|
||||||
|
<li class="page-item app-pager-pointer active">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(pager, _search)">@pager</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<li class="page-item app-pager-pointer">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(pager, _search)">@pager</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(((_page < _pages) ? _page + 1 : _page), _search)"><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
|
||||||
|
</li>
|
||||||
|
@if (_pages > _displayPages && _displayPages > 1)
|
||||||
|
{
|
||||||
|
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(_endPage + 1, _search)"><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(_pages, _search)"><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
|
||||||
|
</li>
|
||||||
|
<li class="page-item disabled">
|
||||||
|
<a class="page-link shadow-none" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@@ -175,6 +307,9 @@
|
|||||||
private int _startPage = 0;
|
private int _startPage = 0;
|
||||||
private int _endPage = 0;
|
private int _endPage = 0;
|
||||||
private int _columns = 0;
|
private int _columns = 0;
|
||||||
|
private string _search = "";
|
||||||
|
|
||||||
|
private IEnumerable<TableItem> AllItems;
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Format { get; set; } // Table or Grid
|
public string Format { get; set; } // Table or Grid
|
||||||
@@ -221,6 +356,24 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public Action<int> OnPageChange { get; set; } // a method to be executed in the calling component when the page changes
|
public Action<int> OnPageChange { get; set; } // a method to be executed in the calling component when the page changes
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string SearchProperties { get; set; } // comma delimited list of property names to include in search
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string SearchBoxClass { get; set; } // class for Search box
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string Parameters { get; set; } // optional - querystring parameters in the form of "id=x&name=y" used in static render mode
|
||||||
|
|
||||||
|
[SupplyParameterFromForm(FormName = "PagerForm")]
|
||||||
|
public string _Search { get => ""; set => _search = value; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Accepted values are Start or Center or End. The default value is Center
|
||||||
|
/// </summary>
|
||||||
|
[Parameter]
|
||||||
|
public string PaginationAlignment { get; set; } = "center"; // Alignment of the Page Numbering start, center, end
|
||||||
|
|
||||||
private IEnumerable<TableItem> ItemList { get; set; }
|
private IEnumerable<TableItem> ItemList { get; set; }
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
@@ -276,6 +429,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (PageState.QueryString.ContainsKey("search"))
|
||||||
|
{
|
||||||
|
_search = PageState.QueryString["search"];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(SearchProperties))
|
||||||
|
{
|
||||||
|
AllItems = Items; // only used in search
|
||||||
|
if (!string.IsNullOrEmpty(_search))
|
||||||
|
{
|
||||||
|
Search();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(PageSize))
|
if (!string.IsNullOrEmpty(PageSize))
|
||||||
{
|
{
|
||||||
_maxItems = int.Parse(PageSize);
|
_maxItems = int.Parse(PageSize);
|
||||||
@@ -291,6 +458,12 @@
|
|||||||
_displayPages = int.Parse(DisplayPages);
|
_displayPages = int.Parse(DisplayPages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (PageState.QueryString.ContainsKey("page") && int.TryParse(PageState.QueryString["page"], out int page))
|
||||||
|
{
|
||||||
|
_page = page;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
if (!string.IsNullOrEmpty(CurrentPage))
|
if (!string.IsNullOrEmpty(CurrentPage))
|
||||||
{
|
{
|
||||||
_page = int.Parse(CurrentPage);
|
_page = int.Parse(CurrentPage);
|
||||||
@@ -299,6 +472,7 @@
|
|||||||
{
|
{
|
||||||
_page = 1;
|
_page = 1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (_page < 1) _page = 1;
|
if (_page < 1) _page = 1;
|
||||||
|
|
||||||
_startPage = 0;
|
_startPage = 0;
|
||||||
@@ -369,4 +543,96 @@
|
|||||||
|
|
||||||
UpdateList(_page);
|
UpdateList(_page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Search()
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(_search))
|
||||||
|
{
|
||||||
|
Items = AllItems.Where(item =>
|
||||||
|
{
|
||||||
|
var values = SearchProperties.Split(',')
|
||||||
|
.Select(itemType => GetPropertyValue(item, itemType))
|
||||||
|
.Where(value => value != null)
|
||||||
|
.Select(value => value.ToString().ToLower());
|
||||||
|
|
||||||
|
return values.Any(value => value.Contains(_search.ToLower()));
|
||||||
|
}).ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Items = AllItems;
|
||||||
|
}
|
||||||
|
_pages = (int)Math.Ceiling(Items.Count() / (decimal)_maxItems);
|
||||||
|
UpdateList(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private object GetPropertyValue(object obj, string propertyName)
|
||||||
|
{
|
||||||
|
var index = propertyName.IndexOf(".");
|
||||||
|
if (index != -1)
|
||||||
|
{
|
||||||
|
var propertyInfo = obj.GetType().GetProperty(propertyName.Substring(0, index));
|
||||||
|
if (propertyInfo != null)
|
||||||
|
{
|
||||||
|
return GetPropertyValue(propertyInfo.GetValue(obj), propertyName.Substring(index + 1));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var propertyInfo = obj.GetType().GetProperty(propertyName);
|
||||||
|
if (propertyInfo != null)
|
||||||
|
{
|
||||||
|
return propertyInfo.GetValue(obj);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
_search = "";
|
||||||
|
Items = AllItems;
|
||||||
|
_pages = (int)Math.Ceiling(Items.Count() / (decimal)_maxItems);
|
||||||
|
UpdateList(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string FormatSearchProperties()
|
||||||
|
{
|
||||||
|
var properties = new List<string>();
|
||||||
|
foreach (var property in SearchProperties.Split(',', StringSplitOptions.RemoveEmptyEntries))
|
||||||
|
{
|
||||||
|
var index = property.LastIndexOf(".");
|
||||||
|
if (index != -1)
|
||||||
|
{
|
||||||
|
properties.Add(property.Substring(index + 1));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
properties.Add(property);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string.Join(",", properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string PageUrl(int page, string search)
|
||||||
|
{
|
||||||
|
var parameters = new Dictionary<string, string>(PageState.QueryString);
|
||||||
|
if (parameters.ContainsKey("page")) parameters.Remove("page");
|
||||||
|
parameters.Add("page", page.ToString());
|
||||||
|
if (parameters.ContainsKey("search")) parameters.Remove("search");
|
||||||
|
if (!string.IsNullOrEmpty(search))
|
||||||
|
{
|
||||||
|
parameters.Add("search", search);
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(Parameters))
|
||||||
|
{
|
||||||
|
foreach (var parameter in Utilities.ParseQueryString(Parameters))
|
||||||
|
{
|
||||||
|
if (parameters.ContainsKey(parameter.Key)) parameters.Remove(parameter.Key);
|
||||||
|
parameters.Add(parameter.Key, parameter.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return PageState.Route.AbsolutePath + Utilities.CreateQueryString(parameters);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
@foreach (User user in _users)
|
@foreach (User user in _users)
|
||||||
{
|
{
|
||||||
<tr>
|
<tr>
|
||||||
<td>@user.DisplayName</td>
|
<td>@user.DisplayName (@user.Username)</td>
|
||||||
@foreach (var permissionname in _permissionnames)
|
@foreach (var permissionname in _permissionnames)
|
||||||
{
|
{
|
||||||
<td style="text-align: center; width: 1px;">
|
<td style="text-align: center; width: 1px;">
|
||||||
@@ -270,8 +270,8 @@
|
|||||||
private async Task<Dictionary<string, string>> GetUsers(string filter)
|
private async Task<Dictionary<string, string>> GetUsers(string filter)
|
||||||
{
|
{
|
||||||
var users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
|
var users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
|
||||||
return users.Where(item => item.User.DisplayName.Contains(filter, StringComparison.OrdinalIgnoreCase))
|
return users.Where(item => item.User.DisplayName.Contains(filter, StringComparison.OrdinalIgnoreCase) || item.User.Username.Contains(filter, StringComparison.OrdinalIgnoreCase))
|
||||||
.ToDictionary(item => item.UserId.ToString(), item => item.User.DisplayName);
|
.ToDictionary(item => item.UserId.ToString(), item => item.User.DisplayName + " (" + item.User.Username + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task AddUser()
|
private async Task AddUser()
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Oqtane.Modules.Controls
|
namespace Oqtane.Modules.Controls
|
||||||
{
|
{
|
||||||
public class RichTextEditorInterop
|
public class QuillEditorInterop
|
||||||
{
|
{
|
||||||
private readonly IJSRuntime _jsRuntime;
|
private readonly IJSRuntime _jsRuntime;
|
||||||
|
|
||||||
public RichTextEditorInterop(IJSRuntime jsRuntime)
|
public QuillEditorInterop(IJSRuntime jsRuntime)
|
||||||
{
|
{
|
||||||
_jsRuntime = jsRuntime;
|
_jsRuntime = jsRuntime;
|
||||||
}
|
}
|
||||||
@@ -105,13 +105,25 @@ namespace Oqtane.Modules.Controls
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task InsertImage(ElementReference quillElement, string imageUrl, string altText)
|
public ValueTask<int> GetCurrentCursor(ElementReference quillElement)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _jsRuntime.InvokeAsync<int>("Oqtane.RichTextEditor.getCurrentCursor", quillElement);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return new ValueTask<int>(Task.FromResult(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task InsertImage(ElementReference quillElement, string imageUrl, string altText, int editorIndex)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_jsRuntime.InvokeAsync<object>(
|
_jsRuntime.InvokeAsync<object>(
|
||||||
"Oqtane.RichTextEditor.insertQuillImage",
|
"Oqtane.RichTextEditor.insertQuillImage",
|
||||||
quillElement, imageUrl, altText);
|
quillElement, imageUrl, altText, editorIndex);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
578
Oqtane.Client/Modules/Controls/QuillJSTextEditor.razor
Normal file
578
Oqtane.Client/Modules/Controls/QuillJSTextEditor.razor
Normal file
@@ -0,0 +1,578 @@
|
|||||||
|
@namespace Oqtane.Modules.Controls
|
||||||
|
@inherits ModuleControlBase
|
||||||
|
@implements ITextEditor
|
||||||
|
@inject ISettingService SettingService
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject IStringLocalizer<QuillJSTextEditor> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
<div class="quill-text-editor">
|
||||||
|
<TabStrip ActiveTab="@_activetab">
|
||||||
|
@if (_allowRichText)
|
||||||
|
{
|
||||||
|
<TabPanel Name="Rich" Heading="Rich Text Editor" ResourceKey="RichTextEditor">
|
||||||
|
@if (_richfilemanager)
|
||||||
|
{
|
||||||
|
<FileManager @ref="_fileManager" Filter="@PageState.Site.ImageFiles" />
|
||||||
|
<ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
|
||||||
|
<br />
|
||||||
|
}
|
||||||
|
<div class="d-flex justify-content-center mb-2">
|
||||||
|
@if (_allowFileManagement)
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-primary" @onclick="InsertRichImage">@Localizer["InsertImage"]</button>
|
||||||
|
}
|
||||||
|
@if (_richfilemanager)
|
||||||
|
{
|
||||||
|
@((MarkupString)" ")
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="CloseRichFileManager">@Localizer["Close"]</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div @ref="@_toolBar">
|
||||||
|
@if (!string.IsNullOrEmpty(_toolbarContent))
|
||||||
|
{
|
||||||
|
@((MarkupString)_toolbarContent)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<select class="ql-header">
|
||||||
|
<option selected=""></option>
|
||||||
|
<option value="1"></option>
|
||||||
|
<option value="2"></option>
|
||||||
|
<option value="3"></option>
|
||||||
|
<option value="4"></option>
|
||||||
|
<option value="5"></option>
|
||||||
|
</select>
|
||||||
|
<span class="ql-formats">
|
||||||
|
<button class="ql-bold"></button>
|
||||||
|
<button class="ql-italic"></button>
|
||||||
|
<button class="ql-underline"></button>
|
||||||
|
<button class="ql-strike"></button>
|
||||||
|
</span>
|
||||||
|
<span class="ql-formats">
|
||||||
|
<select class="ql-color"></select>
|
||||||
|
<select class="ql-background"></select>
|
||||||
|
</span>
|
||||||
|
<span class="ql-formats">
|
||||||
|
<button class="ql-list" value="ordered"></button>
|
||||||
|
<button class="ql-list" value="bullet"></button>
|
||||||
|
</span>
|
||||||
|
<span class="ql-formats">
|
||||||
|
<button class="ql-link"></button>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div @ref="@_editorElement"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabPanel>
|
||||||
|
}
|
||||||
|
@if (_allowRawHtml)
|
||||||
|
{
|
||||||
|
<TabPanel Name="Raw" Heading="Raw HTML Editor" ResourceKey="HtmlEditor">
|
||||||
|
@if (_rawfilemanager)
|
||||||
|
{
|
||||||
|
<FileManager @ref="_fileManager" Filter="@PageState.Site.ImageFiles" />
|
||||||
|
<ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
|
||||||
|
<br />
|
||||||
|
}
|
||||||
|
<div class="d-flex justify-content-center mb-2">
|
||||||
|
@if (_allowFileManagement)
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-primary" @onclick="InsertRawImage">@Localizer["InsertImage"]</button>
|
||||||
|
}
|
||||||
|
@if (_rawfilemanager)
|
||||||
|
{
|
||||||
|
@((MarkupString)" ")
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="CloseRawFileManager">@Localizer["Close"]</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
@if (ReadOnly)
|
||||||
|
{
|
||||||
|
<textarea id="@_rawhtmlid" class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10" readonly></textarea>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<textarea id="@_rawhtmlid" class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10"></textarea>
|
||||||
|
}
|
||||||
|
</TabPanel>
|
||||||
|
}
|
||||||
|
@if (_allowSettings)
|
||||||
|
{
|
||||||
|
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
|
||||||
|
<div class="quill-text-editor-settings">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="Scope" ResourceKey="Scope" ResourceType="@resourceType" HelpText="Specify if settings are scoped to the module or site">Scope: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="Scope" class="form-select" value="@_scopeSetting" @onchange="(e => ScopeChanged(e))">
|
||||||
|
<option value="Module">@SharedLocalizer["Module"]</option>
|
||||||
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
|
{
|
||||||
|
<option value="Site">@SharedLocalizer["Site"]</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="AllowRichText" ResourceKey="AllowRichText" ResourceType="@resourceType" HelpText="Specify if editors can use the Rich Text Editor">Rich Text Editor? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="AllowRichText" class="form-select" @bind="@_allowRichTextSetting" 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="AllowRawHtml" ResourceKey="AllowRawHtml" ResourceType="@resourceType" HelpText="Specify if editors can use the Raw HTML Editor">Raw HTML Editor? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="AllowRawHtml" class="form-select" @bind="@_allowRawHtmlSetting" 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="AllowFileManagement" ResourceKey="AllowFileManagement" ResourceType="@resourceType" HelpText="Specify if editors can upload and insert images">Insert Images? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="AllowFileManagement" class="form-select" @bind="@_allowFileManagementSetting" 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="Theme" ResourceKey="Theme" ResourceType="@resourceType" HelpText="Specify the Rich Text Editor's theme">Theme: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" id="Theme" class="form-control" @bind="_themeSetting" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="DebugLevel" ResourceKey="DebugLevel" ResourceType="@resourceType" HelpText="Specify the Debug Level">Debug Level: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="DebugLevel" class="form-select" @bind="_debugLevelSetting">
|
||||||
|
@foreach (var level in _debugLevels)
|
||||||
|
{
|
||||||
|
<option value="@level">@level</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="ToolbarContent" ResourceKey="ToolbarContent" ResourceType="@resourceType" HelpText="Specify any toolbar content to customize the Rich Text Editor">Toolbar Content: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea id="ToolbarContent" class="form-control" @bind="_toolbarContentSetting" rows="3" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<div class="col-sm-9 offset-sm-3">
|
||||||
|
<button type="button" class="btn btn-success" @onclick="@(async () => await UpdateSettings())">@Localizer["SaveSettings"]</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabPanel>
|
||||||
|
}
|
||||||
|
</TabStrip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
public string Name => "QuillJS";
|
||||||
|
|
||||||
|
private string resourceType = "Oqtane.Modules.Controls.QuillJSTextEditor, Oqtane.Client";
|
||||||
|
|
||||||
|
private bool _settingsLoaded;
|
||||||
|
private bool _initialized = false;
|
||||||
|
|
||||||
|
private QuillEditorInterop _interop;
|
||||||
|
private FileManager _fileManager;
|
||||||
|
private string _activetab = "Rich";
|
||||||
|
private bool _allowSettings = false;
|
||||||
|
|
||||||
|
private bool _allowFileManagement = false;
|
||||||
|
private bool _allowRawHtml = false;
|
||||||
|
private bool _allowRichText = false;
|
||||||
|
private string _theme = "snow";
|
||||||
|
private string _debugLevel = "info";
|
||||||
|
private string _toolbarContent = string.Empty;
|
||||||
|
|
||||||
|
private string _scopeSetting = "Module";
|
||||||
|
private string _allowFileManagementSetting = "False";
|
||||||
|
private string _allowRawHtmlSetting = "False";
|
||||||
|
private string _allowRichTextSetting = "False";
|
||||||
|
private string _themeSetting = "snow";
|
||||||
|
private string _debugLevelSetting = "info";
|
||||||
|
private string _toolbarContentSetting = string.Empty;
|
||||||
|
|
||||||
|
private ElementReference _editorElement;
|
||||||
|
private ElementReference _toolBar;
|
||||||
|
private bool _richfilemanager = false;
|
||||||
|
private string _richhtml = string.Empty;
|
||||||
|
private string _originalrichhtml = string.Empty;
|
||||||
|
|
||||||
|
private bool _rawfilemanager = false;
|
||||||
|
private string _rawhtmlid = "RawHtmlEditor_" + Guid.NewGuid().ToString("N");
|
||||||
|
private string _rawhtml = string.Empty;
|
||||||
|
private string _originalrawhtml = string.Empty;
|
||||||
|
|
||||||
|
private string _message = string.Empty;
|
||||||
|
private bool _contentchanged = false;
|
||||||
|
private int _editorIndex;
|
||||||
|
|
||||||
|
private List<string> _debugLevels = new List<string> { "info", "log", "warn", "error" };
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public bool ReadOnly { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string Placeholder { get; set; }
|
||||||
|
|
||||||
|
// the following parameters were supported by the original RichTextEditor and can be passed as optional static parameters
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public bool? AllowFileManagement { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public bool? AllowRichText { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public bool? AllowRawHtml { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string Theme { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string DebugLevel { get; set; }
|
||||||
|
|
||||||
|
public override List<Resource> Resources { get; set; } = new List<Resource>()
|
||||||
|
{
|
||||||
|
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill.min.js", Location = ResourceLocation.Body },
|
||||||
|
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-blot-formatter.min.js", Location = ResourceLocation.Body },
|
||||||
|
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-interop.js", Location = ResourceLocation.Body }
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
_interop = new QuillEditorInterop(JSRuntime);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(Placeholder))
|
||||||
|
{
|
||||||
|
Placeholder = Localizer["Placeholder"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
LoadSettings();
|
||||||
|
|
||||||
|
if (!_allowRichText)
|
||||||
|
{
|
||||||
|
_activetab = "Raw";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
// include CSS theme
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
await interop.IncludeLink("", "stylesheet", $"{PageState?.Alias.BaseUrl}/css/quill/quill.{_theme}.css", "text/css", "", "", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
await base.OnAfterRenderAsync(firstRender);
|
||||||
|
|
||||||
|
if (_allowRichText)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
await _interop.CreateEditor(
|
||||||
|
_editorElement,
|
||||||
|
_toolBar,
|
||||||
|
ReadOnly,
|
||||||
|
Placeholder,
|
||||||
|
_theme,
|
||||||
|
_debugLevel);
|
||||||
|
|
||||||
|
await _interop.LoadEditorContent(_editorElement, _richhtml);
|
||||||
|
|
||||||
|
// preserve a copy of the content (Quill sanitizes content so we need to retrieve it from the editor as it may have been modified)
|
||||||
|
_originalrichhtml = await _interop.GetHtml(_editorElement);
|
||||||
|
|
||||||
|
_initialized = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_initialized)
|
||||||
|
{
|
||||||
|
if (_contentchanged)
|
||||||
|
{
|
||||||
|
// reload editor if Content passed to component has changed
|
||||||
|
await _interop.LoadEditorContent(_editorElement, _richhtml);
|
||||||
|
_originalrichhtml = await _interop.GetHtml(_editorElement);
|
||||||
|
|
||||||
|
_contentchanged = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// preserve changed content on re-render event
|
||||||
|
var richhtml = await _interop.GetHtml(_editorElement);
|
||||||
|
if (richhtml != _richhtml)
|
||||||
|
{
|
||||||
|
_richhtml = richhtml;
|
||||||
|
await _interop.LoadEditorContent(_editorElement, _richhtml);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize(string content)
|
||||||
|
{
|
||||||
|
_richhtml = content;
|
||||||
|
_rawhtml = content;
|
||||||
|
_originalrichhtml = "";
|
||||||
|
_richhtml = content;
|
||||||
|
if (!_contentchanged)
|
||||||
|
{
|
||||||
|
_contentchanged = content != _originalrawhtml;
|
||||||
|
}
|
||||||
|
|
||||||
|
_originalrawhtml = _rawhtml; // preserve for comparison later
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetContent()
|
||||||
|
{
|
||||||
|
// evaluate raw html content as first priority
|
||||||
|
if (_rawhtml != _originalrawhtml)
|
||||||
|
{
|
||||||
|
return _rawhtml;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var richhtml = "";
|
||||||
|
|
||||||
|
if (_allowRichText)
|
||||||
|
{
|
||||||
|
richhtml = await _interop.GetHtml(_editorElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (richhtml != _originalrichhtml && !string.IsNullOrEmpty(richhtml))
|
||||||
|
{
|
||||||
|
// convert Quill's empty content to empty string
|
||||||
|
if (richhtml == "<p><br></p>")
|
||||||
|
{
|
||||||
|
richhtml = string.Empty;
|
||||||
|
}
|
||||||
|
return richhtml;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// return original raw html content
|
||||||
|
return _originalrawhtml;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CloseRichFileManager()
|
||||||
|
{
|
||||||
|
_richfilemanager = false;
|
||||||
|
_message = string.Empty;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CloseRawFileManager()
|
||||||
|
{
|
||||||
|
_rawfilemanager = false;
|
||||||
|
_message = string.Empty;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetHtml()
|
||||||
|
{
|
||||||
|
// evaluate raw html content as first priority
|
||||||
|
if (_rawhtml != _originalrawhtml)
|
||||||
|
{
|
||||||
|
return _rawhtml;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var richhtml = "";
|
||||||
|
|
||||||
|
if (_allowRichText)
|
||||||
|
{
|
||||||
|
richhtml = await _interop.GetHtml(_editorElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (richhtml != _originalrichhtml && !string.IsNullOrEmpty(richhtml))
|
||||||
|
{
|
||||||
|
// convert Quill's empty content to empty string
|
||||||
|
if (richhtml == "<p><br></p>")
|
||||||
|
{
|
||||||
|
richhtml = string.Empty;
|
||||||
|
}
|
||||||
|
return richhtml;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// return original raw html content
|
||||||
|
return _originalrawhtml;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InsertRichImage()
|
||||||
|
{
|
||||||
|
_message = string.Empty;
|
||||||
|
if (_richfilemanager)
|
||||||
|
{
|
||||||
|
var file = _fileManager.GetFile();
|
||||||
|
if (file != null)
|
||||||
|
{
|
||||||
|
await _interop.InsertImage(_editorElement, file.Url, ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name), _editorIndex);
|
||||||
|
_richhtml = await _interop.GetHtml(_editorElement);
|
||||||
|
_richfilemanager = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_message = Localizer["Message.Require.Image"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_editorIndex = await _interop.GetCurrentCursor(_editorElement);
|
||||||
|
_richfilemanager = true;
|
||||||
|
}
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InsertRawImage()
|
||||||
|
{
|
||||||
|
_message = string.Empty;
|
||||||
|
if (_rawfilemanager)
|
||||||
|
{
|
||||||
|
var file = _fileManager.GetFile();
|
||||||
|
if (file != null)
|
||||||
|
{
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
int pos = await interop.GetCaretPosition(_rawhtmlid);
|
||||||
|
var image = "<img src=\"" + file.Url + "\" alt=\"" + ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name) + "\" class=\"img-fluid\">";
|
||||||
|
_rawhtml = _rawhtml.Substring(0, pos) + image + _rawhtml.Substring(pos);
|
||||||
|
_rawfilemanager = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_message = Localizer["Message.Require.Image"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_rawfilemanager = true;
|
||||||
|
}
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ScopeChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
_scopeSetting = (string)e.Value;
|
||||||
|
LoadSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadSettings(bool reload = false)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!_settingsLoaded || reload)
|
||||||
|
{
|
||||||
|
_allowFileManagement = bool.Parse(GetSetting("Component", "QuillTextEditor_AllowFileManagement", "True"));
|
||||||
|
_allowRawHtml = bool.Parse(GetSetting("Component", "QuillTextEditor_AllowRawHtml", "True"));
|
||||||
|
_allowRichText = bool.Parse(GetSetting("Component", "QuillTextEditor_AllowRichText", "True"));
|
||||||
|
_theme = GetSetting("Component", "QuillTextEditor_Theme", "snow");
|
||||||
|
_debugLevel = GetSetting("Component", "QuillTextEditor_DebugLevel", "info");
|
||||||
|
_toolbarContent = GetSetting("Component", "QuillTextEditor_ToolbarContent", string.Empty);
|
||||||
|
|
||||||
|
// optional static parameter overrides
|
||||||
|
if (AllowFileManagement != null) _allowFileManagement = AllowFileManagement.Value;
|
||||||
|
if (AllowRichText != null) _allowRichText = AllowRichText.Value;
|
||||||
|
if (AllowRawHtml != null) _allowRawHtml = AllowRawHtml.Value;
|
||||||
|
if (!string.IsNullOrEmpty(Theme)) _theme = Theme;
|
||||||
|
if (!string.IsNullOrEmpty(DebugLevel)) _debugLevel = DebugLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
_allowSettings = PageState.EditMode && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList);
|
||||||
|
if (_allowSettings)
|
||||||
|
{
|
||||||
|
_allowFileManagementSetting = GetSetting(_scopeSetting, "QuillTextEditor_AllowFileManagement", "True");
|
||||||
|
_allowRawHtmlSetting = GetSetting(_scopeSetting, "QuillTextEditor_AllowRawHtml", "True");
|
||||||
|
_allowRichTextSetting = GetSetting(_scopeSetting, "QuillTextEditor_AllowRichText", "True");
|
||||||
|
_themeSetting = GetSetting(_scopeSetting, "QuillTextEditor_Theme", "snow");
|
||||||
|
_debugLevelSetting = GetSetting(_scopeSetting, "QuillTextEditor_DebugLevel", "info");
|
||||||
|
_toolbarContentSetting = GetSetting(_scopeSetting, "QuillTextEditor_ToolbarContent", string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
_settingsLoaded = true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AddModuleMessage(ex.Message, MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetSetting(string scope, string settingName, string defaultValue)
|
||||||
|
{
|
||||||
|
var settingValue = "";
|
||||||
|
switch (scope)
|
||||||
|
{
|
||||||
|
case "Component":
|
||||||
|
settingValue = SettingService.GetSetting(PageState.Site.Settings, settingName, defaultValue);
|
||||||
|
settingValue = SettingService.GetSetting(ModuleState.Settings, settingName, settingValue);
|
||||||
|
break;
|
||||||
|
case "Site":
|
||||||
|
settingValue = SettingService.GetSetting(PageState.Site.Settings, settingName, defaultValue);
|
||||||
|
break;
|
||||||
|
case "Module":
|
||||||
|
settingValue = SettingService.GetSetting(ModuleState.Settings, settingName, defaultValue);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return settingValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UpdateSettings()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_scopeSetting == "Site" && UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
|
{
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
settings = SettingService.SetSetting(settings, "QuillTextEditor_AllowFileManagement", _allowFileManagementSetting);
|
||||||
|
settings = SettingService.SetSetting(settings, "QuillTextEditor_AllowRawHtml", _allowRawHtmlSetting);
|
||||||
|
settings = SettingService.SetSetting(settings, "QuillTextEditor_AllowRichText", _allowRichTextSetting);
|
||||||
|
settings = SettingService.SetSetting(settings, "QuillTextEditor_Theme", _themeSetting);
|
||||||
|
settings = SettingService.SetSetting(settings, "QuillTextEditor_DebugLevel", _debugLevelSetting);
|
||||||
|
settings = SettingService.SetSetting(settings, "QuillTextEditor_ToolbarContent", _toolbarContentSetting);
|
||||||
|
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||||
|
}
|
||||||
|
else if (_scopeSetting == "Module")
|
||||||
|
{
|
||||||
|
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
|
||||||
|
settings = SettingService.SetSetting(settings, "QuillTextEditor_AllowFileManagement", _allowFileManagementSetting);
|
||||||
|
settings = SettingService.SetSetting(settings, "QuillTextEditor_AllowRawHtml", _allowRawHtmlSetting);
|
||||||
|
settings = SettingService.SetSetting(settings, "QuillTextEditor_AllowRichText", _allowRichTextSetting);
|
||||||
|
settings = SettingService.SetSetting(settings, "QuillTextEditor_Theme", _themeSetting);
|
||||||
|
settings = SettingService.SetSetting(settings, "QuillTextEditor_DebugLevel", _debugLevelSetting);
|
||||||
|
settings = SettingService.SetSetting(settings, "QuillTextEditor_ToolbarContent", _toolbarContentSetting);
|
||||||
|
await SettingService.UpdateModuleSettingsAsync(settings,ModuleState.ModuleId);
|
||||||
|
}
|
||||||
|
LoadSettings(true);
|
||||||
|
|
||||||
|
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AddModuleMessage(ex.Message, MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,119 +1,22 @@
|
|||||||
|
@using System.Text.RegularExpressions
|
||||||
|
@using Microsoft.AspNetCore.Components.Rendering
|
||||||
|
@using Microsoft.Extensions.DependencyInjection
|
||||||
@namespace Oqtane.Modules.Controls
|
@namespace Oqtane.Modules.Controls
|
||||||
@inherits ModuleControlBase
|
@inherits ModuleControlBase
|
||||||
|
@inject IServiceProvider ServiceProvider
|
||||||
|
@inject ISettingService SettingService
|
||||||
@inject IStringLocalizer<RichTextEditor> Localizer
|
@inject IStringLocalizer<RichTextEditor> Localizer
|
||||||
|
|
||||||
<div class="row" style="margin-bottom: 50px;">
|
<div class="row" style="margin-bottom: 50px;">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<TabStrip>
|
@_textEditorComponent
|
||||||
<TabPanel Name="Rich" Heading="Rich Text Editor" ResourceKey="RichTextEditor">
|
|
||||||
@if (_richfilemanager)
|
|
||||||
{
|
|
||||||
<FileManager @ref="_fileManager" Filter="@Constants.ImageFiles" />
|
|
||||||
<ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
|
|
||||||
<br />
|
|
||||||
}
|
|
||||||
<div class="d-flex justify-content-center mb-2">
|
|
||||||
@if (AllowRawHtml)
|
|
||||||
{
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="RefreshRichText">@Localizer["SynchronizeContent"]</button>@((MarkupString)" ")
|
|
||||||
}
|
|
||||||
@if (AllowFileManagement)
|
|
||||||
{
|
|
||||||
<button type="button" class="btn btn-primary" @onclick="InsertRichImage">@Localizer["InsertImage"]</button>
|
|
||||||
}
|
|
||||||
@if (_richfilemanager)
|
|
||||||
{
|
|
||||||
@((MarkupString)" ")
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="CloseRichFileManager">@Localizer["Close"]</button>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<div @ref="@_toolBar">
|
|
||||||
@if (ToolbarContent != null)
|
|
||||||
{
|
|
||||||
@ToolbarContent
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<select class="ql-header">
|
|
||||||
<option selected=""></option>
|
|
||||||
<option value="1"></option>
|
|
||||||
<option value="2"></option>
|
|
||||||
<option value="3"></option>
|
|
||||||
<option value="4"></option>
|
|
||||||
<option value="5"></option>
|
|
||||||
</select>
|
|
||||||
<span class="ql-formats">
|
|
||||||
<button class="ql-bold"></button>
|
|
||||||
<button class="ql-italic"></button>
|
|
||||||
<button class="ql-underline"></button>
|
|
||||||
<button class="ql-strike"></button>
|
|
||||||
</span>
|
|
||||||
<span class="ql-formats">
|
|
||||||
<select class="ql-color"></select>
|
|
||||||
<select class="ql-background"></select>
|
|
||||||
</span>
|
|
||||||
<span class="ql-formats">
|
|
||||||
<button class="ql-list" value="ordered"></button>
|
|
||||||
<button class="ql-list" value="bullet"></button>
|
|
||||||
</span>
|
|
||||||
<span class="ql-formats">
|
|
||||||
<button class="ql-link"></button>
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div @ref="@_editorElement">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</TabPanel>
|
|
||||||
@if (AllowRawHtml)
|
|
||||||
{
|
|
||||||
<TabPanel Name="Raw" Heading="Raw HTML Editor" ResourceKey="HtmlEditor">
|
|
||||||
@if (_rawfilemanager)
|
|
||||||
{
|
|
||||||
<FileManager @ref="_fileManager" Filter="@Constants.ImageFiles" />
|
|
||||||
<ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
|
|
||||||
<br />
|
|
||||||
}
|
|
||||||
<div class="d-flex justify-content-center mb-2">
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="RefreshRawHtml">@Localizer["SynchronizeContent"]</button>
|
|
||||||
@if (AllowFileManagement)
|
|
||||||
{
|
|
||||||
<button type="button" class="btn btn-primary" @onclick="InsertRawImage">@Localizer["InsertImage"]</button>
|
|
||||||
}
|
|
||||||
@if (_rawfilemanager)
|
|
||||||
{
|
|
||||||
@((MarkupString)" ")
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="CloseRawFileManager">@Localizer["Close"]</button>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
@if (ReadOnly)
|
|
||||||
{
|
|
||||||
<textarea id="rawhtmleditor" class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10" readonly></textarea>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<textarea id="rawhtmleditor" class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10"></textarea>
|
|
||||||
}
|
|
||||||
</TabPanel>
|
|
||||||
}
|
|
||||||
</TabStrip>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private ElementReference _editorElement;
|
private string _textEditorType;
|
||||||
private ElementReference _toolBar;
|
private RenderFragment _textEditorComponent;
|
||||||
private bool _richfilemanager = false;
|
private ITextEditor _textEditor;
|
||||||
private FileManager _fileManager;
|
|
||||||
private string _richhtml = string.Empty;
|
|
||||||
private string _originalrichhtml = string.Empty;
|
|
||||||
private bool _rawfilemanager = false;
|
|
||||||
private string _rawhtml = string.Empty;
|
|
||||||
private string _originalrawhtml = string.Empty;
|
|
||||||
private string _message = string.Empty;
|
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Content { get; set; }
|
public string Content { get; set; }
|
||||||
@@ -122,159 +25,103 @@
|
|||||||
public bool ReadOnly { get; set; } = false;
|
public bool ReadOnly { get; set; } = false;
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Placeholder { get; set; } = "Enter Your Content...";
|
public string Placeholder { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public bool AllowFileManagement { get; set; } = true;
|
public string Provider { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter(CaptureUnmatchedValues = true)]
|
||||||
public bool AllowRawHtml { get; set; } = true;
|
public Dictionary<string, object> AdditionalAttributes { get; set; } = new Dictionary<string, object>();
|
||||||
|
|
||||||
// parameters only applicable to rich text editor
|
protected override void OnInitialized()
|
||||||
[Parameter]
|
|
||||||
public RenderFragment ToolbarContent { get; set; }
|
|
||||||
|
|
||||||
[Parameter]
|
|
||||||
public string Theme { get; set; } = "snow";
|
|
||||||
|
|
||||||
[Parameter]
|
|
||||||
public string DebugLevel { get; set; } = "info";
|
|
||||||
|
|
||||||
public override List<Resource> Resources => new List<Resource>()
|
|
||||||
{
|
{
|
||||||
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill.min.js" },
|
_textEditorType = GetTextEditorType();
|
||||||
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-blot-formatter.min.js" },
|
}
|
||||||
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-interop.js" }
|
|
||||||
};
|
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
_richhtml = Content;
|
_textEditorComponent = (builder) =>
|
||||||
_rawhtml = Content;
|
{
|
||||||
_originalrawhtml = _rawhtml; // preserve for comparison later
|
CreateTextEditor(builder);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
{
|
{
|
||||||
|
if(_textEditor != null)
|
||||||
|
{
|
||||||
|
_textEditor.Initialize(Content);
|
||||||
|
}
|
||||||
|
|
||||||
await base.OnAfterRenderAsync(firstRender);
|
await base.OnAfterRenderAsync(firstRender);
|
||||||
|
|
||||||
var interop = new RichTextEditorInterop(JSRuntime);
|
|
||||||
|
|
||||||
if (firstRender)
|
|
||||||
{
|
|
||||||
await interop.CreateEditor(
|
|
||||||
_editorElement,
|
|
||||||
_toolBar,
|
|
||||||
ReadOnly,
|
|
||||||
Placeholder,
|
|
||||||
Theme,
|
|
||||||
DebugLevel);
|
|
||||||
|
|
||||||
await interop.LoadEditorContent(_editorElement, _richhtml);
|
|
||||||
|
|
||||||
// preserve a copy of the rich text content (Quill sanitizes content so we need to retrieve it from the editor)
|
|
||||||
_originalrichhtml = await interop.GetHtml(_editorElement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CloseRichFileManager()
|
|
||||||
{
|
|
||||||
_richfilemanager = false;
|
|
||||||
_message = string.Empty;
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CloseRawFileManager()
|
|
||||||
{
|
|
||||||
_rawfilemanager = false;
|
|
||||||
_message = string.Empty;
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RefreshRichText()
|
|
||||||
{
|
|
||||||
_richhtml = _rawhtml;
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task RefreshRawHtml()
|
|
||||||
{
|
|
||||||
var interop = new RichTextEditorInterop(JSRuntime);
|
|
||||||
_rawhtml = await interop.GetHtml(_editorElement);
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GetHtml()
|
public async Task<string> GetHtml()
|
||||||
{
|
{
|
||||||
// evaluate raw html content as first priority
|
return await _textEditor.GetContent();
|
||||||
if (_rawhtml != _originalrawhtml)
|
}
|
||||||
|
|
||||||
|
private void CreateTextEditor(RenderTreeBuilder builder)
|
||||||
{
|
{
|
||||||
return _rawhtml;
|
if(!string.IsNullOrEmpty(_textEditorType))
|
||||||
|
{
|
||||||
|
var editorType = Type.GetType(_textEditorType);
|
||||||
|
if (editorType != null)
|
||||||
|
{
|
||||||
|
builder.OpenComponent(0, editorType);
|
||||||
|
|
||||||
|
var attributes = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "Placeholder", Placeholder },
|
||||||
|
{ "ReadOnly", ReadOnly }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (AdditionalAttributes != null)
|
||||||
|
{
|
||||||
|
foreach(var key in AdditionalAttributes.Keys)
|
||||||
|
{
|
||||||
|
if(!attributes.ContainsKey(key))
|
||||||
|
{
|
||||||
|
attributes.Add(key, AdditionalAttributes[key]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// return rich text content if it has changed
|
attributes[key] = AdditionalAttributes[key];
|
||||||
var interop = new RichTextEditorInterop(JSRuntime);
|
|
||||||
var richhtml = await interop.GetHtml(_editorElement);
|
|
||||||
if (richhtml != _originalrichhtml)
|
|
||||||
{
|
|
||||||
return richhtml;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// return original raw html content
|
|
||||||
return _originalrawhtml;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task InsertRichImage()
|
var index = 1;
|
||||||
|
foreach(var name in attributes.Keys)
|
||||||
{
|
{
|
||||||
_message = string.Empty;
|
if (editorType.GetProperty(name) != null)
|
||||||
if (_richfilemanager)
|
|
||||||
{
|
{
|
||||||
var file = _fileManager.GetFile();
|
builder.AddAttribute(index++, name, attributes[name]);
|
||||||
if (file != null)
|
|
||||||
{
|
|
||||||
var interop = new RichTextEditorInterop(JSRuntime);
|
|
||||||
await interop.InsertImage(_editorElement, file.Url, ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name));
|
|
||||||
_richfilemanager = false;
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
_message = Localizer["Message.Require.Image"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_richfilemanager = true;
|
|
||||||
}
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task InsertRawImage()
|
builder.AddComponentReferenceCapture(index, (c) =>
|
||||||
{
|
{
|
||||||
_message = string.Empty;
|
_textEditor = (ITextEditor)c;
|
||||||
if (_rawfilemanager)
|
});
|
||||||
{
|
builder.CloseComponent();
|
||||||
var file = _fileManager.GetFile();
|
|
||||||
if (file != null)
|
|
||||||
{
|
|
||||||
var interop = new Interop(JSRuntime);
|
|
||||||
int pos = await interop.GetCaretPosition("rawhtmleditor");
|
|
||||||
var image = "<img src=\"" + file.Url + "\" alt=\"" + ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name) + "\" class=\"img-fluid\">";
|
|
||||||
_rawhtml = _rawhtml.Substring(0, pos) + image + _rawhtml.Substring(pos);
|
|
||||||
_rawfilemanager = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_message = Localizer["Message.Require.Image"];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
_rawfilemanager = true;
|
|
||||||
}
|
}
|
||||||
StateHasChanged();
|
|
||||||
|
private string GetTextEditorType()
|
||||||
|
{
|
||||||
|
const string EditorSettingName = "TextEditor";
|
||||||
|
|
||||||
|
if(!string.IsNullOrEmpty(Provider))
|
||||||
|
{
|
||||||
|
var provider = ServiceProvider.GetServices<ITextEditor>().FirstOrDefault(i => i.Name.Equals(Provider, StringComparison.OrdinalIgnoreCase));
|
||||||
|
if(provider != null)
|
||||||
|
{
|
||||||
|
return Utilities.GetFullTypeName(provider.GetType().AssemblyQualifiedName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SettingService.GetSetting(PageState.Site.Settings, EditorSettingName, Constants.DefaultTextEditor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
@namespace Oqtane.Modules.Controls
|
@namespace Oqtane.Modules.Controls
|
||||||
@inherits LocalizableComponent
|
@inherits LocalizableComponent
|
||||||
|
|
||||||
<div class="d-flex mt-2">
|
@if (IsVisible)
|
||||||
|
{
|
||||||
|
<div class="d-flex mt-2">
|
||||||
<div>
|
<div>
|
||||||
<a data-bs-toggle="collapse" class="app-link-unstyled" href="#@Name" aria-expanded="@_expanded" aria-controls="@Name" @onclick:preventDefault="true">
|
<a data-bs-toggle="collapse" class="app-link-unstyled" href="#@Name" aria-expanded="@_expanded" aria-controls="@Name" @onclick:preventDefault="true">
|
||||||
<h5>@_heading</h5>
|
<h5>@_heading</h5>
|
||||||
@@ -12,16 +14,17 @@
|
|||||||
<i class="oi oi-chevron-bottom"></i>
|
<i class="oi oi-chevron-bottom"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<hr class="app-rule" />
|
<hr class="app-rule" />
|
||||||
</div>
|
</div>
|
||||||
<div class="collapse @_show" id="@Name">
|
<div class="collapse @_show" id="@Name">
|
||||||
@if (ChildContent != null)
|
@if (ChildContent != null)
|
||||||
{
|
{
|
||||||
@ChildContent
|
@ChildContent
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private string _heading = string.Empty;
|
private string _heading = string.Empty;
|
||||||
@@ -40,6 +43,9 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public string Expanded { get; set; } // optional - will default to false if not provided
|
public string Expanded { get; set; } // optional - will default to false if not provided
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public bool IsVisible { get; set; } = true;
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
base.OnParametersSet(); // must be included to call method in LocalizableComponent
|
base.OnParametersSet(); // must be included to call method in LocalizableComponent
|
||||||
|
|||||||
@@ -36,14 +36,7 @@ else
|
|||||||
|
|
||||||
Parent.AddTabPanel((TabPanel)this);
|
Parent.AddTabPanel((TabPanel)this);
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(Heading))
|
Heading = string.IsNullOrEmpty(Heading) ? Localize(nameof(Name), Name) : Localize(nameof(Heading), Heading);
|
||||||
{
|
|
||||||
Heading = Localize(nameof(Name), Name);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Heading = Localize(nameof(Heading), Heading);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string DisplayHeading()
|
public string DisplayHeading()
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
<div class="tab-content">
|
<div class="tab-content @TabContentClass">
|
||||||
<br />
|
<br />
|
||||||
@ChildContent
|
@ChildContent
|
||||||
</div>
|
</div>
|
||||||
@@ -47,6 +47,9 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public string Id { get; set; } // optional - used to uniquely identify an instance of a tab strip component (will be set automatically if no value provided)
|
public string Id { get; set; } // optional - used to uniquely identify an instance of a tab strip component (will be set automatically if no value provided)
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string TabContentClass { get; set; } // optional - to extend the TabContent div.
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(Id))
|
if (string.IsNullOrEmpty(Id))
|
||||||
|
|||||||
33
Oqtane.Client/Modules/Controls/TextAreaTextEditor.razor
Normal file
33
Oqtane.Client/Modules/Controls/TextAreaTextEditor.razor
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
@namespace Oqtane.Modules.Controls
|
||||||
|
@inherits ModuleControlBase
|
||||||
|
@implements ITextEditor
|
||||||
|
|
||||||
|
<div class="text-area-editor">
|
||||||
|
<textarea @bind="_content" @ref="_editor" placeholder="@Placeholder" readonly="@ReadOnly" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
public string Name => "TextArea";
|
||||||
|
|
||||||
|
private ElementReference _editor;
|
||||||
|
private string _content;
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public bool ReadOnly { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string Placeholder { get; set; }
|
||||||
|
|
||||||
|
public void Initialize(string content)
|
||||||
|
{
|
||||||
|
_content = content;
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetContent()
|
||||||
|
{
|
||||||
|
await Task.CompletedTask;
|
||||||
|
return _content;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<TabPanel Name="Edit" Heading="Edit" ResourceKey="Edit">
|
<TabPanel Name="Edit" Heading="Edit" ResourceKey="Edit">
|
||||||
@if (_content != null)
|
@if (_content != null)
|
||||||
{
|
{
|
||||||
<RichTextEditor Content="@_content" AllowFileManagement="@_allowfilemanagement" AllowRawHtml="@_allowrawhtml" @ref="@RichTextEditorHtml"></RichTextEditor>
|
<RichTextEditor Content="@_content" @ref="@RichTextEditorHtml"></RichTextEditor>
|
||||||
<br />
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveContent">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveContent">@SharedLocalizer["Save"]</button>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
@@ -51,15 +51,7 @@
|
|||||||
|
|
||||||
public override string Title => "Edit Html/Text";
|
public override string Title => "Edit Html/Text";
|
||||||
|
|
||||||
public override List<Resource> Resources => new List<Resource>()
|
|
||||||
{
|
|
||||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill.bubble.css" },
|
|
||||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill.snow.css" }
|
|
||||||
};
|
|
||||||
|
|
||||||
private RichTextEditor RichTextEditorHtml;
|
private RichTextEditor RichTextEditorHtml;
|
||||||
private bool _allowfilemanagement;
|
|
||||||
private bool _allowrawhtml;
|
|
||||||
private string _content = null;
|
private string _content = null;
|
||||||
private string _createdby;
|
private string _createdby;
|
||||||
private DateTime _createdon;
|
private DateTime _createdon;
|
||||||
@@ -72,8 +64,6 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_allowfilemanagement = bool.Parse(SettingService.GetSetting(ModuleState.Settings, "AllowFileManagement", "true"));
|
|
||||||
_allowrawhtml = bool.Parse(SettingService.GetSetting(ModuleState.Settings, "AllowRawHtml", "true"));
|
|
||||||
await LoadContent();
|
await LoadContent();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -2,30 +2,50 @@
|
|||||||
@namespace Oqtane.Modules.HtmlText
|
@namespace Oqtane.Modules.HtmlText
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject IHtmlTextService HtmlTextService
|
@inject IHtmlTextService HtmlTextService
|
||||||
|
@inject ISettingService SettingService
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
|
|
||||||
@((MarkupString)content)
|
|
||||||
|
|
||||||
@if (PageState.EditMode)
|
@if (PageState.EditMode)
|
||||||
{
|
{
|
||||||
<br />
|
<div class="text-center mb-2">
|
||||||
<ActionLink Action="Edit" EditMode="true" ResourceKey="Edit" />
|
<ActionLink Action="Edit" EditMode="true" ResourceKey="Edit" />
|
||||||
<br />
|
</div>
|
||||||
<br />
|
}
|
||||||
|
|
||||||
|
@((MarkupString)content)
|
||||||
|
|
||||||
|
@if (PageState.EditMode && content.Length > 3000)
|
||||||
|
{
|
||||||
|
<div class="text-center mt-2">
|
||||||
|
<ActionLink Action="Edit" EditMode="true" ResourceKey="Edit" />
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private string content = "";
|
private string content = "";
|
||||||
|
|
||||||
|
public override string RenderMode => RenderModes.Static;
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
if (ShouldRender())
|
||||||
{
|
{
|
||||||
var htmltext = await HtmlTextService.GetHtmlTextAsync(ModuleState.ModuleId);
|
var htmltext = await HtmlTextService.GetHtmlTextAsync(ModuleState.ModuleId);
|
||||||
if (htmltext != null)
|
if (htmltext != null)
|
||||||
{
|
{
|
||||||
content = htmltext.Content;
|
content = htmltext.Content;
|
||||||
content = Utilities.FormatContent(content, PageState.Alias, "render");
|
content = Utilities.FormatContent(content, PageState.Alias, "render");
|
||||||
|
if (bool.Parse(SettingService.GetSetting(ModuleState.Settings, "DynamicTokens", "false")))
|
||||||
|
{
|
||||||
|
content = ReplaceTokens(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
content = "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Oqtane.Documentation;
|
using Oqtane.Documentation;
|
||||||
@@ -9,7 +8,7 @@ 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")]
|
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
|
||||||
public class HtmlTextService : ServiceBase, IHtmlTextService, IService
|
public class HtmlTextService : ServiceBase, IHtmlTextService, IClientService
|
||||||
{
|
{
|
||||||
public HtmlTextService(HttpClient http, SiteState siteState) : base(http, siteState) {}
|
public HtmlTextService(HttpClient http, SiteState siteState) : base(http, siteState) {}
|
||||||
|
|
||||||
@@ -30,9 +29,9 @@ namespace Oqtane.Modules.HtmlText.Services
|
|||||||
return await GetJsonAsync<Models.HtmlText>(CreateAuthorizationPolicyUrl($"{ApiUrl}/{htmlTextId}/{moduleId}", EntityNames.Module, moduleId));
|
return await GetJsonAsync<Models.HtmlText>(CreateAuthorizationPolicyUrl($"{ApiUrl}/{htmlTextId}/{moduleId}", EntityNames.Module, moduleId));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AddHtmlTextAsync(Models.HtmlText htmlText)
|
public async Task<Models.HtmlText> AddHtmlTextAsync(Models.HtmlText htmlText)
|
||||||
{
|
{
|
||||||
await PostJsonAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}", EntityNames.Module, htmlText.ModuleId), htmlText);
|
return await PostJsonAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}", EntityNames.Module, htmlText.ModuleId), htmlText);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteHtmlTextAsync(int htmlTextId, int moduleId)
|
public async Task DeleteHtmlTextAsync(int htmlTextId, int moduleId)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace Oqtane.Modules.HtmlText.Services
|
|||||||
|
|
||||||
Task<Models.HtmlText> GetHtmlTextAsync(int htmlTextId, int moduleId);
|
Task<Models.HtmlText> GetHtmlTextAsync(int htmlTextId, int moduleId);
|
||||||
|
|
||||||
Task AddHtmlTextAsync(Models.HtmlText htmltext);
|
Task<Models.HtmlText> AddHtmlTextAsync(Models.HtmlText htmltext);
|
||||||
|
|
||||||
Task DeleteHtmlTextAsync(int htmlTextId, int moduleId);
|
Task DeleteHtmlTextAsync(int htmlTextId, int moduleId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,42 +5,37 @@
|
|||||||
@inject IStringLocalizer<Settings> Localizer
|
@inject IStringLocalizer<Settings> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<div class="container">
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
|
<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="files" ResourceKey="AllowFileManagement" ResourceType="@resourceType" HelpText="Specify If Editors Can Upload and Select Files">Allow File Management: </Label>
|
<Label Class="col-sm-3" For="dynamictokens" ResourceKey="DynamicTokens" ResourceType="@resourceType" HelpText="Do you wish to allow tokens to be dynamically replaced? Please note that this will affect the performance of your site.">Dynamic Tokens? </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="files" class="form-select" @bind="@_allowfilemanagement">
|
<select id="dynamictokens" class="form-select" @bind="@_dynamictokens">
|
||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="files" ResourceKey="AllowRawHtml" ResourceType="@resourceType" HelpText="Specify If Editors Can Enter Raw HTML">Allow Raw HTML: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<select id="files" class="form-select" @bind="@_allowrawhtml">
|
|
||||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
|
||||||
<option value="false">@SharedLocalizer["No"]</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</div>
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private string resourceType = "Oqtane.Modules.HtmlText.Settings, Oqtane.Client"; // for localization
|
private string resourceType = "Oqtane.Modules.HtmlText.Settings, Oqtane.Client"; // for localization
|
||||||
private string _allowfilemanagement;
|
|
||||||
private string _allowrawhtml;
|
private ElementReference form;
|
||||||
|
private bool validated = false;
|
||||||
|
|
||||||
|
private string _dynamictokens;
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_allowfilemanagement = SettingService.GetSetting(ModuleState.Settings, "AllowFileManagement", "true");
|
_dynamictokens = SettingService.GetSetting(ModuleState.Settings, "DynamicTokens", "false");
|
||||||
_allowrawhtml = SettingService.GetSetting(ModuleState.Settings, "AllowRawHtml", "true");
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
ModuleInstance.AddModuleMessage(ex.Message, MessageType.Error);
|
AddModuleMessage(ex.Message, MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,13 +44,12 @@
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
|
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
|
||||||
settings = SettingService.SetSetting(settings, "AllowFileManagement", _allowfilemanagement);
|
settings = SettingService.SetSetting(settings, "DynamicTokens", _dynamictokens);
|
||||||
settings = SettingService.SetSetting(settings, "AllowRawHtml", _allowrawhtml);
|
|
||||||
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
|
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
ModuleInstance.AddModuleMessage(ex.Message, MessageType.Error);
|
AddModuleMessage(ex.Message, MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,25 @@
|
|||||||
using Microsoft.AspNetCore.Components;
|
|
||||||
using Oqtane.Shared;
|
|
||||||
using Oqtane.Models;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Oqtane.Services;
|
|
||||||
using System;
|
using System;
|
||||||
using Oqtane.Enums;
|
|
||||||
using Oqtane.UI;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.JSInterop;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Dynamic;
|
using System.Dynamic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
|
using Oqtane.Enums;
|
||||||
|
using Oqtane.Models;
|
||||||
|
using Oqtane.Services;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
using Oqtane.UI;
|
||||||
|
|
||||||
namespace Oqtane.Modules
|
namespace Oqtane.Modules
|
||||||
{
|
{
|
||||||
public abstract class ModuleBase : ComponentBase, IModuleControl
|
public abstract class ModuleBase : ComponentBase, IModuleControl
|
||||||
{
|
{
|
||||||
private Logger _logger;
|
private Logger _logger;
|
||||||
private string _urlparametersstate;
|
private string _urlparametersstate = string.Empty;
|
||||||
private Dictionary<string, string> _urlparameters;
|
private Dictionary<string, string> _urlparameters;
|
||||||
|
private bool _scriptsloaded = false;
|
||||||
|
|
||||||
protected Logger logger => _logger ?? (_logger = new Logger(this));
|
protected Logger logger => _logger ?? (_logger = new Logger(this));
|
||||||
|
|
||||||
@@ -34,10 +36,10 @@ namespace Oqtane.Modules
|
|||||||
protected PageState PageState { get; set; }
|
protected PageState PageState { get; set; }
|
||||||
|
|
||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
protected Module ModuleState { get; set; }
|
protected Models.Module ModuleState { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public ModuleInstance ModuleInstance { get; set; }
|
public RenderModeBoundary RenderModeBoundary { get; set; }
|
||||||
|
|
||||||
// optional interface properties
|
// optional interface properties
|
||||||
public virtual SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.View; } set { } } // default security
|
public virtual SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.View; } set { } } // default security
|
||||||
@@ -50,13 +52,17 @@ namespace Oqtane.Modules
|
|||||||
|
|
||||||
public virtual List<Resource> Resources { get; set; }
|
public virtual List<Resource> Resources { get; set; }
|
||||||
|
|
||||||
|
public virtual string RenderMode { get { return RenderModes.Interactive; } } // interactive by default
|
||||||
|
|
||||||
|
public virtual bool? Prerender { get { return null; } } // allows the Site Prerender property to be overridden
|
||||||
|
|
||||||
// url parameters
|
// url parameters
|
||||||
public virtual string UrlParametersTemplate { get; set; }
|
public virtual string UrlParametersTemplate { get; set; }
|
||||||
|
|
||||||
public Dictionary<string, string> UrlParameters {
|
public Dictionary<string, string> UrlParameters {
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (_urlparametersstate == null || _urlparametersstate != PageState.UrlParameters)
|
if (string.IsNullOrEmpty(_urlparametersstate) || _urlparametersstate != PageState.UrlParameters)
|
||||||
{
|
{
|
||||||
_urlparametersstate = PageState.UrlParameters;
|
_urlparametersstate = PageState.UrlParameters;
|
||||||
_urlparameters = GetUrlParameters(UrlParametersTemplate);
|
_urlparameters = GetUrlParameters(UrlParametersTemplate);
|
||||||
@@ -73,36 +79,42 @@ namespace Oqtane.Modules
|
|||||||
{
|
{
|
||||||
List<Resource> resources = null;
|
List<Resource> resources = null;
|
||||||
var type = GetType();
|
var type = GetType();
|
||||||
if (type.BaseType == typeof(ModuleBase))
|
if (type.IsSubclassOf(typeof(ModuleBase)))
|
||||||
{
|
{
|
||||||
if (PageState.Page.Resources != null)
|
if (type.IsSubclassOf(typeof(ModuleControlBase)))
|
||||||
{
|
|
||||||
resources = PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level != ResourceLevel.Site && item.Namespace == type.Namespace).ToList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else // modulecontrolbase
|
|
||||||
{
|
{
|
||||||
if (Resources != null)
|
if (Resources != null)
|
||||||
{
|
{
|
||||||
resources = Resources.Where(item => item.ResourceType == ResourceType.Script).ToList();
|
resources = Resources.Where(item => item.ResourceType == ResourceType.Script).ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (resources != null &&resources.Any())
|
else // ModuleBase
|
||||||
|
{
|
||||||
|
if (PageState.Page.Resources != null)
|
||||||
|
{
|
||||||
|
resources = PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level == ResourceLevel.Module && item.Namespace == type.Namespace).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (resources != null && resources.Any())
|
||||||
{
|
{
|
||||||
var interop = new Interop(JSRuntime);
|
var interop = new Interop(JSRuntime);
|
||||||
var scripts = new List<object>();
|
var scripts = new List<object>();
|
||||||
var inline = 0;
|
var inline = 0;
|
||||||
foreach (Resource resource in resources)
|
foreach (Resource resource in resources)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Interactive)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(resource.Url))
|
if (!string.IsNullOrEmpty(resource.Url))
|
||||||
{
|
{
|
||||||
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url;
|
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url;
|
||||||
scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module, location = resource.Location.ToString().ToLower() });
|
scripts.Add(new { href = url, type = resource.Type ?? "", bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", location = resource.Location.ToString().ToLower(), dataAttributes = resource.DataAttributes });
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
inline += 1;
|
inline += 1;
|
||||||
await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Content, resource.Location.ToString().ToLower());
|
await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Type ?? "", resource.Content, resource.Location.ToString().ToLower());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (scripts.Any())
|
if (scripts.Any())
|
||||||
@@ -110,6 +122,20 @@ namespace Oqtane.Modules
|
|||||||
await interop.IncludeScripts(scripts.ToArray());
|
await interop.IncludeScripts(scripts.ToArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_scriptsloaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool ShouldRender()
|
||||||
|
{
|
||||||
|
return PageState?.RenderId == ModuleState?.RenderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ScriptsLoaded
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _scriptsloaded;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,8 +146,18 @@ namespace Oqtane.Modules
|
|||||||
return PageState?.Alias.BaseUrl + "/Modules/" + GetType().Namespace + "/";
|
return PageState?.Alias.BaseUrl + "/Modules/" + GetType().Namespace + "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fingerprint hash code for static assets
|
||||||
|
public string Fingerprint
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return ModuleState.ModuleDefinition.Fingerprint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// url methods
|
// url methods
|
||||||
|
|
||||||
|
// navigate url
|
||||||
public string NavigateUrl()
|
public string NavigateUrl()
|
||||||
{
|
{
|
||||||
return NavigateUrl(PageState.Page.Path);
|
return NavigateUrl(PageState.Page.Path);
|
||||||
@@ -137,24 +173,65 @@ namespace Oqtane.Modules
|
|||||||
return NavigateUrl(PageState.Page.Path, refresh);
|
return NavigateUrl(PageState.Page.Path, refresh);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string NavigateUrl(string path, string parameters)
|
public string NavigateUrl(string path, string querystring)
|
||||||
{
|
{
|
||||||
return Utilities.NavigateUrl(PageState.Alias.Path, path, parameters);
|
return Utilities.NavigateUrl(PageState.Alias.Path, path, querystring);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string NavigateUrl(string path, Dictionary<string, string> querystring)
|
||||||
|
{
|
||||||
|
return NavigateUrl(path, Utilities.CreateQueryString(querystring));
|
||||||
}
|
}
|
||||||
|
|
||||||
public string NavigateUrl(string path, bool refresh)
|
public string NavigateUrl(string path, bool refresh)
|
||||||
{
|
{
|
||||||
return Utilities.NavigateUrl(PageState.Alias.Path, path, refresh ? "refresh" : "");
|
return NavigateUrl(path, refresh ? "refresh" : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string NavigateUrl(int moduleId, string action)
|
||||||
|
{
|
||||||
|
return EditUrl(PageState.Page.Path, moduleId, action, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public string NavigateUrl(int moduleId, string action, string querystring)
|
||||||
|
{
|
||||||
|
return EditUrl(PageState.Page.Path, moduleId, action, querystring);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string NavigateUrl(int moduleId, string action, Dictionary<string, string> querystring)
|
||||||
|
{
|
||||||
|
return EditUrl(PageState.Page.Path, moduleId, action, querystring);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string NavigateUrl(string path, int moduleId, string action)
|
||||||
|
{
|
||||||
|
return EditUrl(path, moduleId, action, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public string NavigateUrl(string path, int moduleId, string action, string querystring)
|
||||||
|
{
|
||||||
|
return EditUrl(path, moduleId, action, querystring);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string NavigateUrl(string path, int moduleId, string action, Dictionary<string, string> querystring)
|
||||||
|
{
|
||||||
|
return EditUrl(path, moduleId, action, querystring);
|
||||||
|
}
|
||||||
|
|
||||||
|
// edit url
|
||||||
public string EditUrl(string action)
|
public string EditUrl(string action)
|
||||||
{
|
{
|
||||||
return EditUrl(ModuleState.ModuleId, action);
|
return EditUrl(ModuleState.ModuleId, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string EditUrl(string action, string parameters)
|
public string EditUrl(string action, string querystring)
|
||||||
{
|
{
|
||||||
return EditUrl(ModuleState.ModuleId, action, parameters);
|
return EditUrl(ModuleState.ModuleId, action, querystring);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string EditUrl(string action, Dictionary<string, string> querystring)
|
||||||
|
{
|
||||||
|
return EditUrl(ModuleState.ModuleId, action, querystring);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string EditUrl(int moduleId, string action)
|
public string EditUrl(int moduleId, string action)
|
||||||
@@ -162,16 +239,27 @@ namespace Oqtane.Modules
|
|||||||
return EditUrl(moduleId, action, "");
|
return EditUrl(moduleId, action, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
public string EditUrl(int moduleId, string action, string parameters)
|
public string EditUrl(int moduleId, string action, string querystring)
|
||||||
{
|
{
|
||||||
return EditUrl(PageState.Page.Path, moduleId, action, parameters);
|
return EditUrl(PageState.Page.Path, moduleId, action, querystring);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string EditUrl(string path, int moduleid, string action, string parameters)
|
public string EditUrl(int moduleId, string action, Dictionary<string, string> querystring)
|
||||||
{
|
{
|
||||||
return Utilities.EditUrl(PageState.Alias.Path, path, moduleid, action, parameters);
|
return EditUrl(PageState.Page.Path, moduleId, action, querystring);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string EditUrl(string path, int moduleid, string action, string querystring)
|
||||||
|
{
|
||||||
|
return Utilities.EditUrl(PageState.Alias.Path, path, moduleid, action, querystring);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string EditUrl(string path, int moduleid, string action, Dictionary<string, string> querystring)
|
||||||
|
{
|
||||||
|
return EditUrl(path, moduleid, action, Utilities.CreateQueryString(querystring));
|
||||||
|
}
|
||||||
|
|
||||||
|
// file url
|
||||||
public string FileUrl(string folderpath, string filename)
|
public string FileUrl(string folderpath, string filename)
|
||||||
{
|
{
|
||||||
return FileUrl(folderpath, filename, false);
|
return FileUrl(folderpath, filename, false);
|
||||||
@@ -191,6 +279,8 @@ namespace Oqtane.Modules
|
|||||||
return Utilities.FileUrl(PageState.Alias, fileid, download);
|
return Utilities.FileUrl(PageState.Alias, fileid, download);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// image url
|
||||||
|
|
||||||
public string ImageUrl(int fileid, int width, int height)
|
public string ImageUrl(int fileid, int width, int height)
|
||||||
{
|
{
|
||||||
return ImageUrl(fileid, width, height, "");
|
return ImageUrl(fileid, width, height, "");
|
||||||
@@ -261,22 +351,27 @@ namespace Oqtane.Modules
|
|||||||
// UI methods
|
// UI methods
|
||||||
public void AddModuleMessage(string message, MessageType type)
|
public void AddModuleMessage(string message, MessageType type)
|
||||||
{
|
{
|
||||||
ModuleInstance.AddModuleMessage(message, type);
|
AddModuleMessage(message, type, "top");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddModuleMessage(string message, MessageType type, string position)
|
||||||
|
{
|
||||||
|
RenderModeBoundary.AddModuleMessage(message, type, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClearModuleMessage()
|
public void ClearModuleMessage()
|
||||||
{
|
{
|
||||||
ModuleInstance.AddModuleMessage("", MessageType.Undefined);
|
RenderModeBoundary.AddModuleMessage("", MessageType.Undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ShowProgressIndicator()
|
public void ShowProgressIndicator()
|
||||||
{
|
{
|
||||||
ModuleInstance.ShowProgressIndicator();
|
RenderModeBoundary.ShowProgressIndicator();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HideProgressIndicator()
|
public void HideProgressIndicator()
|
||||||
{
|
{
|
||||||
ModuleInstance.HideProgressIndicator();
|
RenderModeBoundary.HideProgressIndicator();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetModuleTitle(string title)
|
public void SetModuleTitle(string title)
|
||||||
@@ -322,6 +417,118 @@ namespace Oqtane.Modules
|
|||||||
await interop.ScrollTo(0, 0, "smooth");
|
await interop.ScrollTo(0, 0, "smooth");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string ReplaceTokens(string content)
|
||||||
|
{
|
||||||
|
return ReplaceTokens(content, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ReplaceTokens(string content, object obj)
|
||||||
|
{
|
||||||
|
// Using StringBuilder avoids the performance penalty of repeated string allocations
|
||||||
|
// that occur with string.Replace or string concatenation inside loops.
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
var cache = new Dictionary<string, string>(); // Cache to store resolved tokens
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
// Loop through content to find and replace all tokens
|
||||||
|
while (index < content.Length)
|
||||||
|
{
|
||||||
|
int start = content.IndexOf('[', index); // Find start of token
|
||||||
|
if (start == -1)
|
||||||
|
{
|
||||||
|
sb.Append(content, index, content.Length - index); // Append remaining content
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int end = content.IndexOf(']', start); // Find end of token
|
||||||
|
if (end == -1)
|
||||||
|
{
|
||||||
|
sb.Append(content, index, content.Length - index); // Append unmatched content
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.Append(content, index, start - index); // Append content before token
|
||||||
|
|
||||||
|
string token = content.Substring(start + 1, end - start - 1); // Extract token without brackets
|
||||||
|
string[] parts = token.Split('|', 2); // Separate default fallback if present
|
||||||
|
string key = parts[0];
|
||||||
|
string fallback = parts.Length == 2 ? parts[1] : null;
|
||||||
|
|
||||||
|
if (!cache.TryGetValue(token, out string replacement)) // Check cache first
|
||||||
|
{
|
||||||
|
replacement = "[" + token + "]"; // Default replacement is original token
|
||||||
|
string[] segments = key.Split(':');
|
||||||
|
|
||||||
|
if (segments.Length >= 2)
|
||||||
|
{
|
||||||
|
object current = GetTarget(segments[0], obj); // Start from root object
|
||||||
|
for (int i = 1; i < segments.Length && current != null; i++)
|
||||||
|
{
|
||||||
|
var type = current.GetType();
|
||||||
|
var prop = type.GetProperty(segments[i]);
|
||||||
|
current = prop?.GetValue(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current != null)
|
||||||
|
{
|
||||||
|
replacement = current.ToString();
|
||||||
|
}
|
||||||
|
else if (fallback != null)
|
||||||
|
{
|
||||||
|
replacement = fallback; // Use fallback if available
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cache[token] = replacement; // Store in cache
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.Append(replacement); // Append replacement value
|
||||||
|
index = end + 1; // Move index past token
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve the object instance for a given object name
|
||||||
|
// Easy to extend with additional object types
|
||||||
|
private object GetTarget(string name, object obj)
|
||||||
|
{
|
||||||
|
return name switch
|
||||||
|
{
|
||||||
|
"ModuleState" => ModuleState,
|
||||||
|
"PageState" => PageState,
|
||||||
|
_ => (obj != null && obj.GetType().Name == name) ? obj : null // Fallback to obj
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// date methods
|
||||||
|
public DateTime? UtcToLocal(DateTime? datetime)
|
||||||
|
{
|
||||||
|
TimeZoneInfo timezone = null;
|
||||||
|
if (PageState.User != null && !string.IsNullOrEmpty(PageState.User.TimeZoneId))
|
||||||
|
{
|
||||||
|
timezone = TimeZoneInfo.FindSystemTimeZoneById(PageState.User.TimeZoneId);
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(PageState.Site.TimeZoneId))
|
||||||
|
{
|
||||||
|
timezone = TimeZoneInfo.FindSystemTimeZoneById(PageState.Site.TimeZoneId);
|
||||||
|
}
|
||||||
|
return Utilities.UtcAsLocalDateTime(datetime, timezone);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime? LocalToUtc(DateTime? datetime)
|
||||||
|
{
|
||||||
|
TimeZoneInfo timezone = null;
|
||||||
|
if (PageState.User != null && !string.IsNullOrEmpty(PageState.User.TimeZoneId))
|
||||||
|
{
|
||||||
|
timezone = TimeZoneInfo.FindSystemTimeZoneById(PageState.User.TimeZoneId);
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(PageState.Site.TimeZoneId))
|
||||||
|
{
|
||||||
|
timezone = TimeZoneInfo.FindSystemTimeZoneById(PageState.Site.TimeZoneId);
|
||||||
|
}
|
||||||
|
return Utilities.LocalDateAndTimeAsUtc(datetime, timezone);
|
||||||
|
}
|
||||||
|
|
||||||
// logging methods
|
// logging methods
|
||||||
public async Task Log(Alias alias, LogLevel level, string function, Exception exception, string message, params object[] args)
|
public async Task Log(Alias alias, LogLevel level, string function, Exception exception, string message, params object[] args)
|
||||||
{
|
{
|
||||||
@@ -476,5 +683,8 @@ namespace Oqtane.Modules
|
|||||||
{
|
{
|
||||||
return Utilities.FileUrl(PageState.Alias, fileid, asAttachment);
|
return Utilities.FileUrl(PageState.Alias, fileid, asAttachment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Referencing ModuleInstance methods from ModuleBase is deprecated. Use the ModuleBase methods instead
|
||||||
|
public ModuleInstance ModuleInstance { get { return new ModuleInstance(); } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<Configurations>Debug;Release</Configurations>
|
<Configurations>Debug;Release</Configurations>
|
||||||
<Version>4.0.4</Version>
|
<Version>6.1.3</Version>
|
||||||
<Product>Oqtane</Product>
|
<Product>Oqtane</Product>
|
||||||
<Authors>Shaun Walker</Authors>
|
<Authors>Shaun Walker</Authors>
|
||||||
<Company>.NET Foundation</Company>
|
<Company>.NET Foundation</Company>
|
||||||
@@ -12,28 +12,32 @@
|
|||||||
<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/v4.0.4</PackageReleaseNotes>
|
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3</PackageReleaseNotes>
|
||||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||||
<RepositoryType>Git</RepositoryType>
|
<RepositoryType>Git</RepositoryType>
|
||||||
<RootNamespace>Oqtane</RootNamespace>
|
<RootNamespace>Oqtane</RootNamespace>
|
||||||
<IsPackable>true</IsPackable>
|
<IsPackable>true</IsPackable>
|
||||||
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
|
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
|
||||||
|
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.5" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.5" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.5" PrivateAssets="all" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.5" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="7.0.5" />
|
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.5" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="7.0.5" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.5" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
|
|
||||||
<PackageReference Include="System.Net.Http.Json" Version="7.0.1" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Oqtane.Shared\Oqtane.Shared.csproj" />
|
<ProjectReference Include="..\Oqtane.Shared\Oqtane.Shared.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Update="Installer\Controls\AzureSqlConfig.razor">
|
||||||
|
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<PublishTrimmed>false</PublishTrimmed>
|
<PublishTrimmed>false</PublishTrimmed>
|
||||||
<BlazorEnableCompression>false</BlazorEnableCompression>
|
<BlazorEnableCompression>false</BlazorEnableCompression>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
@@ -13,13 +12,13 @@ using System.Text.Json;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||||
using Microsoft.AspNetCore.Localization;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
using Oqtane.Documentation;
|
using Oqtane.Documentation;
|
||||||
using Oqtane.Models;
|
using Oqtane.Models;
|
||||||
using Oqtane.Modules;
|
using Oqtane.Modules;
|
||||||
using Oqtane.Services;
|
using Oqtane.Services;
|
||||||
|
using Oqtane.Shared;
|
||||||
using Oqtane.UI;
|
using Oqtane.UI;
|
||||||
|
|
||||||
namespace Oqtane.Client
|
namespace Oqtane.Client
|
||||||
@@ -41,10 +40,10 @@ namespace Oqtane.Client
|
|||||||
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
|
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
|
||||||
|
|
||||||
// register auth services
|
// register auth services
|
||||||
builder.Services.AddOqtaneAuthorization();
|
builder.Services.AddOqtaneAuthentication();
|
||||||
|
|
||||||
// register scoped core services
|
// register scoped core services
|
||||||
builder.Services.AddOqtaneScopedServices();
|
builder.Services.AddOqtaneClientScopedServices();
|
||||||
|
|
||||||
var serviceProvider = builder.Services.BuildServiceProvider();
|
var serviceProvider = builder.Services.BuildServiceProvider();
|
||||||
|
|
||||||
@@ -220,6 +219,16 @@ namespace Oqtane.Client
|
|||||||
services.AddScoped(serviceType ?? implementationType, implementationType);
|
services.AddScoped(serviceType ?? implementationType, implementationType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
implementationTypes = assembly.GetInterfaces<IClientService>();
|
||||||
|
foreach (var implementationType in implementationTypes)
|
||||||
|
{
|
||||||
|
if (implementationType.AssemblyQualifiedName != null)
|
||||||
|
{
|
||||||
|
var serviceType = Type.GetType(implementationType.AssemblyQualifiedName.Replace(implementationType.Name, $"I{implementationType.Name}"));
|
||||||
|
services.AddScoped(serviceType ?? implementationType, implementationType);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -248,7 +257,7 @@ namespace Oqtane.Client
|
|||||||
var jsRuntime = serviceProvider.GetRequiredService<IJSRuntime>();
|
var jsRuntime = serviceProvider.GetRequiredService<IJSRuntime>();
|
||||||
var interop = new Interop(jsRuntime);
|
var interop = new Interop(jsRuntime);
|
||||||
var localizationCookie = await interop.GetCookie(CookieRequestCultureProvider.DefaultCookieName);
|
var localizationCookie = await interop.GetCookie(CookieRequestCultureProvider.DefaultCookieName);
|
||||||
var culture = CookieRequestCultureProvider.ParseCookieValue(localizationCookie)?.UICultures?[0].Value;
|
var culture = CookieRequestCultureProvider.ParseCookieValue(localizationCookie)?.UICulture.Name;
|
||||||
var localizationService = serviceProvider.GetRequiredService<ILocalizationService>();
|
var localizationService = serviceProvider.GetRequiredService<ILocalizationService>();
|
||||||
var cultures = await localizationService.GetCulturesAsync(false);
|
var cultures = await localizationService.GetCulturesAsync(false);
|
||||||
|
|
||||||
|
|||||||
@@ -8,20 +8,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"profiles": {
|
"profiles": {
|
||||||
"IIS Express": {
|
"Oqtane": {
|
||||||
"commandName": "IISExpress",
|
|
||||||
"launchBrowser": true,
|
|
||||||
"environmentVariables": {
|
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Oqtane.Client": {
|
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
},
|
},
|
||||||
"applicationUrl": "http://localhost:44358/"
|
"applicationUrl": "http://localhost:44358/"
|
||||||
|
},
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Net;
|
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Components;
|
|
||||||
using Microsoft.AspNetCore.Components.Authorization;
|
using Microsoft.AspNetCore.Components.Authorization;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Oqtane.Models;
|
using Oqtane.Models;
|
||||||
@@ -16,12 +14,10 @@ namespace Oqtane.Providers
|
|||||||
public class IdentityAuthenticationStateProvider : AuthenticationStateProvider
|
public class IdentityAuthenticationStateProvider : AuthenticationStateProvider
|
||||||
{
|
{
|
||||||
private readonly IServiceProvider _serviceProvider;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
private readonly NavigationManager _navigationManager;
|
|
||||||
|
|
||||||
public IdentityAuthenticationStateProvider(IServiceProvider serviceProvider, NavigationManager navigationManager)
|
public IdentityAuthenticationStateProvider(IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
_serviceProvider = serviceProvider;
|
_serviceProvider = serviceProvider;
|
||||||
_navigationManager = navigationManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
|
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -121,7 +121,7 @@
|
|||||||
<value>Server:</value>
|
<value>Server:</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Server.HelpText" xml:space="preserve">
|
<data name="Server.HelpText" xml:space="preserve">
|
||||||
<value>Enter the database server name. This might include a port number as well if you are using a cloud service (ie. servername.database.windows.net,1433) </value>
|
<value>Enter the database server name. This might include a port number as well if you are using a cloud service.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Database.Text" xml:space="preserve">
|
<data name="Database.Text" xml:space="preserve">
|
||||||
<value>Database:</value>
|
<value>Database:</value>
|
||||||
@@ -153,7 +153,7 @@
|
|||||||
<data name="Integrated" xml:space="preserve">
|
<data name="Integrated" xml:space="preserve">
|
||||||
<value>Integrated</value>
|
<value>Integrated</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Encryption,Text" xml:space="preserve">
|
<data name="Encryption.Text" xml:space="preserve">
|
||||||
<value>Encryption:</value>
|
<value>Encryption:</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Encryption.HelpText" xml:space="preserve">
|
<data name="Encryption.HelpText" xml:space="preserve">
|
||||||
|
|||||||
@@ -183,4 +183,13 @@
|
|||||||
<data name="Template" xml:space="preserve">
|
<data name="Template" xml:space="preserve">
|
||||||
<value>Select a site template</value>
|
<value>Select a site template</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Message.Username.Invalid" xml:space="preserve">
|
||||||
|
<value>The Username Provided Does Not Meet The System Requirement, It Can Only Contains Letters Or Digits.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Name.Text" xml:space="preserve">
|
||||||
|
<value>Full Name:</value>
|
||||||
|
</data>
|
||||||
|
<data name="Name.HelpText" xml:space="preserve">
|
||||||
|
<value>Provide the full name of the host user</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -135,9 +135,6 @@
|
|||||||
<data name="Error.Folder.Save" xml:space="preserve">
|
<data name="Error.Folder.Save" xml:space="preserve">
|
||||||
<value>Error Saving Folder</value>
|
<value>Error Saving Folder</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Message.Folder.Files.InvalidDelete" xml:space="preserve">
|
|
||||||
<value>Folder Has Files And Cannot Be Deleted</value>
|
|
||||||
</data>
|
|
||||||
<data name="Message.Folder.Subfolders.InvalidDelete" xml:space="preserve">
|
<data name="Message.Folder.Subfolders.InvalidDelete" xml:space="preserve">
|
||||||
<value>Folder Has Subfolders And Cannot Be Deleted</value>
|
<value>Folder Has Subfolders And Cannot Be Deleted</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -150,14 +147,11 @@
|
|||||||
<data name="Name.HelpText" xml:space="preserve">
|
<data name="Name.HelpText" xml:space="preserve">
|
||||||
<value>Enter the folder name</value>
|
<value>Enter the folder name</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Permissions.HelpText" xml:space="preserve">
|
|
||||||
<value>Select the permissions you want for the folder</value>
|
|
||||||
</data>
|
|
||||||
<data name="Parent.Text" xml:space="preserve">
|
<data name="Parent.Text" xml:space="preserve">
|
||||||
<value>Parent: </value>
|
<value>Parent: </value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Permissions.Text" xml:space="preserve">
|
<data name="Permissions.Heading" xml:space="preserve">
|
||||||
<value>Permissions: </value>
|
<value>Permissions</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="DeleteFolder.Header" xml:space="preserve">
|
<data name="DeleteFolder.Header" xml:space="preserve">
|
||||||
<value>Delete Folder</value>
|
<value>Delete Folder</value>
|
||||||
@@ -178,7 +172,7 @@
|
|||||||
<value>Capacity:</value>
|
<value>Capacity:</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ImageSizes.HelpText" xml:space="preserve">
|
<data name="ImageSizes.HelpText" xml:space="preserve">
|
||||||
<value>Enter a list of image sizes which can be generated dynamically from uploaded images (ie. 200x200,400x400). Use * to indicate the folder supports all image sizes.</value>
|
<value>Optionally enter a list of image sizes which can be generated dynamically from uploaded images (ie. 200x200,400x400). Use * to indicate the folder supports all image sizes (not recommended).</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ImageSizes.Text" xml:space="preserve">
|
<data name="ImageSizes.Text" xml:space="preserve">
|
||||||
<value>Image Sizes:</value>
|
<value>Image Sizes:</value>
|
||||||
@@ -195,4 +189,16 @@
|
|||||||
<data name="Folder Management" xml:space="preserve">
|
<data name="Folder Management" xml:space="preserve">
|
||||||
<value>Folder Management</value>
|
<value>Folder Management</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Message.Folder.Duplicate" xml:space="preserve">
|
||||||
|
<value>Folder Name Specified Already Exists In Parent</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings.Heading" xml:space="preserve">
|
||||||
|
<value>Settings</value>
|
||||||
|
</data>
|
||||||
|
<data name="CacheControl.Text" xml:space="preserve">
|
||||||
|
<value>Caching:</value>
|
||||||
|
</data>
|
||||||
|
<data name="CacheControl.HelpText" xml:space="preserve">
|
||||||
|
<value>Optionally provide a Cache-Control directive for this folder. For example 'public, max-age=60' indicates that files in this folder should be cached for 60 seconds. Please note that when caching is enabled, changes to files will not be immediately reflected in the UI.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -165,4 +165,31 @@
|
|||||||
<data name="UploadFiles.Text" xml:space="preserve">
|
<data name="UploadFiles.Text" xml:space="preserve">
|
||||||
<value>Upload Files</value>
|
<value>Upload Files</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Files.Heading" xml:space="preserve">
|
||||||
|
<value>Files</value>
|
||||||
|
</data>
|
||||||
|
<data name="ImageExtensions.Text" xml:space="preserve">
|
||||||
|
<value>Image Extensions:</value>
|
||||||
|
</data>
|
||||||
|
<data name="ImageExtensions.HelpText" xml:space="preserve">
|
||||||
|
<value>Enter a comma separated list of image file extensions</value>
|
||||||
|
</data>
|
||||||
|
<data name="UploadableFileExtensions.Text" xml:space="preserve">
|
||||||
|
<value>Uploadable File Extensions:</value>
|
||||||
|
</data>
|
||||||
|
<data name="UploadableFileExtensions.HelpText" xml:space="preserve">
|
||||||
|
<value>Enter a comma separated list of uploadable file extensions</value>
|
||||||
|
</data>
|
||||||
|
<data name="MaxChunkSize.Text" xml:space="preserve">
|
||||||
|
<value>Max Upload Chunk Size (MB):</value>
|
||||||
|
</data>
|
||||||
|
<data name="MaxChunkSize.HelpText" xml:space="preserve">
|
||||||
|
<value>Files are split into chunks to streamline the upload process. Specify the maximum chunk size in MB (note that higher chunk sizes should only be used on faster networks).</value>
|
||||||
|
</data>
|
||||||
|
<data name="Success.SaveSiteSettings" xml:space="preserve">
|
||||||
|
<value>Settings Saved Successfully</value>
|
||||||
|
</data>
|
||||||
|
<data name="Error.SaveSiteSettings" xml:space="preserve">
|
||||||
|
<value>Error Saving Settings</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -192,4 +192,7 @@
|
|||||||
<data name="Once" xml:space="preserve">
|
<data name="Once" xml:space="preserve">
|
||||||
<value>Execute Once</value>
|
<value>Execute Once</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Message.StartEndDateError" xml:space="preserve">
|
||||||
|
<value>Start Date cannot be after End Date.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -144,17 +144,8 @@
|
|||||||
<data name="Month" xml:space="preserve">
|
<data name="Month" xml:space="preserve">
|
||||||
<value>Month(s)</value>
|
<value>Month(s)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Error.Job.Delete" xml:space="preserve">
|
<data name="ViewLogs.Text" xml:space="preserve">
|
||||||
<value>Error Deleting Job</value>
|
<value>View All Logs</value>
|
||||||
</data>
|
|
||||||
<data name="ViewJobs.Text" xml:space="preserve">
|
|
||||||
<value>View Logs</value>
|
|
||||||
</data>
|
|
||||||
<data name="DeleteJob.Header" xml:space="preserve">
|
|
||||||
<value>Delete Job</value>
|
|
||||||
</data>
|
|
||||||
<data name="DeleteJob.Message" xml:space="preserve">
|
|
||||||
<value>Are You Sure You Wish To Delete This Job?</value>
|
|
||||||
</data>
|
</data>
|
||||||
<data name="Frequency" xml:space="preserve">
|
<data name="Frequency" xml:space="preserve">
|
||||||
<value>Frequency</value>
|
<value>Frequency</value>
|
||||||
@@ -165,9 +156,6 @@
|
|||||||
<data name="Stop" xml:space="preserve">
|
<data name="Stop" xml:space="preserve">
|
||||||
<value>Stop</value>
|
<value>Stop</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="DeleteJob.Text" xml:space="preserve">
|
|
||||||
<value>Delete</value>
|
|
||||||
</data>
|
|
||||||
<data name="EditJob.Text" xml:space="preserve">
|
<data name="EditJob.Text" xml:space="preserve">
|
||||||
<value>Edit</value>
|
<value>Edit</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -192,9 +180,6 @@
|
|||||||
<data name="Once" xml:space="preserve">
|
<data name="Once" xml:space="preserve">
|
||||||
<value>Execute Once</value>
|
<value>Execute Once</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Message.NoJobs" xml:space="preserve">
|
|
||||||
<value>Please Note That After An Initial Installation You Must <a href={0}>Restart</a> The Application In Order To Activate The Default Scheduled Jobs.</value>
|
|
||||||
</data>
|
|
||||||
<data name="Refresh.Text" xml:space="preserve">
|
<data name="Refresh.Text" xml:space="preserve">
|
||||||
<value>Refresh</value>
|
<value>Refresh</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user